Repository: GoogleCloudPlatform/microservices-demo Branch: main Commit: 1725a8044b95 Files: 316 Total size: 1.4 MB Directory structure: gitextract_n0fmcr6r/ ├── .deploystack/ │ ├── deploystack.yaml │ ├── messages/ │ │ ├── description.txt │ │ └── success.txt │ ├── scripts/ │ │ └── preinit.sh │ ├── test │ └── test.yaml ├── .editorconfig ├── .gitattributes ├── .github/ │ ├── CODEOWNERS │ ├── CODE_OF_CONDUCT.md │ ├── CONTRIBUTING.md │ ├── ISSUE_TEMPLATE/ │ │ ├── bug-report.md │ │ ├── feature-request.md │ │ └── other.md │ ├── SECURITY.md │ ├── auto-approve.yml │ ├── header-checker-lint.yml │ ├── pull_request_template.md │ ├── release-cluster/ │ │ ├── README.md │ │ ├── backend-config.yaml │ │ ├── frontend-config.yaml │ │ ├── frontend-ingress.yaml │ │ ├── frontend-service.yaml │ │ └── managed-cert.yaml │ ├── renovate.json5 │ ├── snippet-bot.yml │ ├── terraform/ │ │ ├── README.md │ │ ├── main.tf │ │ ├── variables.tf │ │ └── versions.tf │ └── workflows/ │ ├── README.md │ ├── ci-main.yaml │ ├── ci-pr.yaml │ ├── cleanup.yaml │ ├── helm-chart-ci.yaml │ ├── install-dependencies.sh │ ├── kubevious-manifests-ci.yaml │ ├── kustomize-build-ci.yaml │ └── terraform-validate-ci.yaml ├── .gitignore ├── LICENSE ├── README.md ├── cloudbuild.yaml ├── docs/ │ ├── adding-new-microservice.md │ ├── cloudshell-tutorial.md │ ├── deploystack.md │ ├── development-guide.md │ ├── product-requirements.md │ ├── purpose.md │ └── releasing/ │ ├── README.md │ ├── license_header.txt │ ├── make-docker-images.sh │ ├── make-helm-chart.sh │ ├── make-release-artifacts.sh │ └── make-release.sh ├── helm-chart/ │ ├── Chart.yaml │ ├── README.md │ ├── templates/ │ │ ├── NOTES.txt │ │ ├── adservice.yaml │ │ ├── cartservice.yaml │ │ ├── checkoutservice.yaml │ │ ├── common.yaml │ │ ├── currencyservice.yaml │ │ ├── emailservice.yaml │ │ ├── frontend.yaml │ │ ├── loadgenerator.yaml │ │ ├── opentelemetry-collector.yaml │ │ ├── paymentservice.yaml │ │ ├── productcatalogservice.yaml │ │ ├── recommendationservice.yaml │ │ └── shippingservice.yaml │ └── values.yaml ├── istio-manifests/ │ ├── allow-egress-googleapis.yaml │ ├── frontend-gateway.yaml │ └── frontend.yaml ├── kubernetes-manifests/ │ ├── README.md │ ├── adservice.yaml │ ├── cartservice.yaml │ ├── checkoutservice.yaml │ ├── currencyservice.yaml │ ├── emailservice.yaml │ ├── frontend.yaml │ ├── kustomization.yaml │ ├── loadgenerator.yaml │ ├── paymentservice.yaml │ ├── productcatalogservice.yaml │ ├── recommendationservice.yaml │ └── shippingservice.yaml ├── kustomize/ │ ├── README.md │ ├── base/ │ │ ├── adservice.yaml │ │ ├── cartservice.yaml │ │ ├── checkoutservice.yaml │ │ ├── currencyservice.yaml │ │ ├── emailservice.yaml │ │ ├── frontend.yaml │ │ ├── kustomization.yaml │ │ ├── loadgenerator.yaml │ │ ├── paymentservice.yaml │ │ ├── productcatalogservice.yaml │ │ ├── recommendationservice.yaml │ │ └── shippingservice.yaml │ ├── components/ │ │ ├── alloydb/ │ │ │ ├── README.md │ │ │ └── kustomization.yaml │ │ ├── container-images-registry/ │ │ │ ├── README.md │ │ │ └── kustomization.yaml │ │ ├── container-images-tag/ │ │ │ ├── README.md │ │ │ └── kustomization.yaml │ │ ├── container-images-tag-suffix/ │ │ │ ├── README.md │ │ │ └── kustomization.yaml │ │ ├── custom-base-url/ │ │ │ ├── README.md │ │ │ └── kustomization.yaml │ │ ├── cymbal-branding/ │ │ │ ├── README.md │ │ │ └── kustomization.yaml │ │ ├── google-cloud-operations/ │ │ │ ├── README.md │ │ │ ├── kustomization.yaml │ │ │ └── otel-collector.yaml │ │ ├── memorystore/ │ │ │ ├── README.md │ │ │ └── kustomization.yaml │ │ ├── network-policies/ │ │ │ ├── README.md │ │ │ ├── kustomization.yaml │ │ │ ├── network-policy-adservice.yaml │ │ │ ├── network-policy-cartservice.yaml │ │ │ ├── network-policy-checkoutservice.yaml │ │ │ ├── network-policy-currencyservice.yaml │ │ │ ├── network-policy-deny-all.yaml │ │ │ ├── network-policy-emailservice.yaml │ │ │ ├── network-policy-frontend.yaml │ │ │ ├── network-policy-loadgenerator.yaml │ │ │ ├── network-policy-paymentservice.yaml │ │ │ ├── network-policy-productcatalogservice.yaml │ │ │ ├── network-policy-recommendationservice.yaml │ │ │ ├── network-policy-redis.yaml │ │ │ └── network-policy-shippingservice.yaml │ │ ├── non-public-frontend/ │ │ │ ├── README.md │ │ │ └── kustomization.yaml │ │ ├── service-mesh-istio/ │ │ │ ├── README.md │ │ │ ├── allow-egress-googleapis.yaml │ │ │ ├── frontend-gateway.yaml │ │ │ ├── frontend.yaml │ │ │ └── kustomization.yaml │ │ ├── shopping-assistant/ │ │ │ ├── README.md │ │ │ ├── kustomization.yaml │ │ │ ├── scripts/ │ │ │ │ ├── 1_deploy_alloydb_infra.sh │ │ │ │ ├── 2_create_populate_alloydb_tables.sh │ │ │ │ └── generate_sql_from_products.py │ │ │ └── shoppingassistantservice.yaml │ │ ├── single-shared-session/ │ │ │ ├── README.md │ │ │ └── kustomization.yaml │ │ ├── spanner/ │ │ │ ├── README.md │ │ │ └── kustomization.yaml │ │ └── without-loadgenerator/ │ │ ├── README.md │ │ ├── delete-loadgenerator.patch.yaml │ │ └── kustomization.yaml │ ├── kustomization.yaml │ └── tests/ │ ├── README.md │ ├── memorystore-with-all-components/ │ │ └── kustomization.yaml │ ├── service-mesh-istio-with-all-components/ │ │ └── kustomization.yaml │ └── spanner-with-all-components/ │ └── kustomization.yaml ├── protos/ │ ├── demo.proto │ └── grpc/ │ └── health/ │ └── v1/ │ └── health.proto ├── release/ │ ├── istio-manifests.yaml │ └── kubernetes-manifests.yaml ├── skaffold.yaml ├── src/ │ ├── adservice/ │ │ ├── Dockerfile │ │ ├── README.md │ │ ├── build.gradle │ │ ├── genproto.sh │ │ ├── gradle/ │ │ │ └── wrapper/ │ │ │ ├── gradle-wrapper.jar │ │ │ └── gradle-wrapper.properties │ │ ├── gradlew │ │ ├── gradlew.bat │ │ ├── settings.gradle │ │ └── src/ │ │ └── main/ │ │ ├── java/ │ │ │ └── hipstershop/ │ │ │ ├── AdService.java │ │ │ └── AdServiceClient.java │ │ ├── proto/ │ │ │ └── demo.proto │ │ └── resources/ │ │ └── log4j2.xml │ ├── cartservice/ │ │ ├── cartservice.sln │ │ ├── src/ │ │ │ ├── .dockerignore │ │ │ ├── Dockerfile │ │ │ ├── Dockerfile.debug │ │ │ ├── Program.cs │ │ │ ├── Startup.cs │ │ │ ├── appsettings.json │ │ │ ├── cartservice.csproj │ │ │ ├── cartstore/ │ │ │ │ ├── AlloyDBCartStore.cs │ │ │ │ ├── ICartStore.cs │ │ │ │ ├── RedisCartStore.cs │ │ │ │ └── SpannerCartStore.cs │ │ │ ├── protos/ │ │ │ │ └── Cart.proto │ │ │ └── services/ │ │ │ ├── CartService.cs │ │ │ └── HealthCheckService.cs │ │ └── tests/ │ │ ├── CartServiceTests.cs │ │ └── cartservice.tests.csproj │ ├── checkoutservice/ │ │ ├── .dockerignore │ │ ├── Dockerfile │ │ ├── README.md │ │ ├── genproto/ │ │ │ ├── demo.pb.go │ │ │ └── demo_grpc.pb.go │ │ ├── genproto.sh │ │ ├── go.mod │ │ ├── go.sum │ │ ├── main.go │ │ └── money/ │ │ ├── money.go │ │ └── money_test.go │ ├── currencyservice/ │ │ ├── .dockerignore │ │ ├── .gitignore │ │ ├── Dockerfile │ │ ├── client.js │ │ ├── data/ │ │ │ └── currency_conversion.json │ │ ├── genproto.sh │ │ ├── package.json │ │ ├── proto/ │ │ │ ├── demo.proto │ │ │ └── grpc/ │ │ │ └── health/ │ │ │ └── v1/ │ │ │ └── health.proto │ │ └── server.js │ ├── emailservice/ │ │ ├── Dockerfile │ │ ├── demo_pb2.py │ │ ├── demo_pb2_grpc.py │ │ ├── email_client.py │ │ ├── email_server.py │ │ ├── genproto.sh │ │ ├── logger.py │ │ ├── requirements.in │ │ ├── requirements.txt │ │ └── templates/ │ │ └── confirmation.html │ ├── frontend/ │ │ ├── .dockerignore │ │ ├── .gitkeep │ │ ├── Dockerfile │ │ ├── README.md │ │ ├── deployment_details.go │ │ ├── genproto/ │ │ │ ├── demo.pb.go │ │ │ └── demo_grpc.pb.go │ │ ├── genproto.sh │ │ ├── go.mod │ │ ├── go.sum │ │ ├── handlers.go │ │ ├── main.go │ │ ├── middleware.go │ │ ├── money/ │ │ │ ├── money.go │ │ │ └── money_test.go │ │ ├── packaging_info.go │ │ ├── rpc.go │ │ ├── static/ │ │ │ ├── images/ │ │ │ │ └── credits.txt │ │ │ └── styles/ │ │ │ ├── bot.css │ │ │ ├── cart.css │ │ │ ├── order.css │ │ │ └── styles.css │ │ ├── templates/ │ │ │ ├── ad.html │ │ │ ├── assistant.html │ │ │ ├── cart.html │ │ │ ├── error.html │ │ │ ├── footer.html │ │ │ ├── header.html │ │ │ ├── home.html │ │ │ ├── order.html │ │ │ ├── product.html │ │ │ └── recommendations.html │ │ └── validator/ │ │ ├── validator.go │ │ └── validator_test.go │ ├── loadgenerator/ │ │ ├── Dockerfile │ │ ├── locustfile.py │ │ ├── requirements.in │ │ └── requirements.txt │ ├── paymentservice/ │ │ ├── .dockerignore │ │ ├── Dockerfile │ │ ├── charge.js │ │ ├── genproto.sh │ │ ├── index.js │ │ ├── logger.js │ │ ├── package.json │ │ ├── proto/ │ │ │ ├── demo.proto │ │ │ └── grpc/ │ │ │ └── health/ │ │ │ └── v1/ │ │ │ └── health.proto │ │ └── server.js │ ├── productcatalogservice/ │ │ ├── .dockerignore │ │ ├── Dockerfile │ │ ├── README.md │ │ ├── catalog_loader.go │ │ ├── genproto/ │ │ │ ├── demo.pb.go │ │ │ └── demo_grpc.pb.go │ │ ├── genproto.sh │ │ ├── go.mod │ │ ├── go.sum │ │ ├── product_catalog.go │ │ ├── product_catalog_test.go │ │ ├── products.json │ │ └── server.go │ ├── recommendationservice/ │ │ ├── Dockerfile │ │ ├── client.py │ │ ├── demo_pb2.py │ │ ├── demo_pb2_grpc.py │ │ ├── genproto.sh │ │ ├── logger.py │ │ ├── recommendation_server.py │ │ ├── requirements.in │ │ └── requirements.txt │ ├── shippingservice/ │ │ ├── .dockerignore │ │ ├── Dockerfile │ │ ├── README.md │ │ ├── genproto/ │ │ │ ├── demo.pb.go │ │ │ └── demo_grpc.pb.go │ │ ├── genproto.sh │ │ ├── go.mod │ │ ├── go.sum │ │ ├── main.go │ │ ├── quote.go │ │ ├── shippingservice_test.go │ │ └── tracker.go │ └── shoppingassistantservice/ │ ├── Dockerfile │ ├── requirements.in │ ├── requirements.txt │ └── shoppingassistantservice.py └── terraform/ ├── README.md ├── main.tf ├── memorystore.tf ├── output.tf ├── providers.tf └── variables.tf ================================================ FILE CONTENTS ================================================ ================================================ FILE: .deploystack/deploystack.yaml ================================================ # The fields inside this deploystack.yaml file are documented in https://github.com/GoogleCloudPlatform/deploystack. title: Microservices Demo (Online Boutique) name: microservices-demo duration: 5 collect_project: true collect_region: true region_type: compute region_default: us-central1 hard_settings: filepath_manifest: ../kustomize/ memorystore: "false" name: online-boutique namespace: default documentation_link: https://cloud.google.com/shell/docs/cloud-shell-tutorials/deploystack/microservices-demo ================================================ FILE: .deploystack/messages/description.txt ================================================ Online Boutique is a cloud-first microservices demo application. Online Boutique consists of an 11-tier microservices application. The application is a web-based e-commerce app where users can browse items, add them to the cart, and purchase them. ================================================ FILE: .deploystack/messages/success.txt ================================================ Congrats! You have successfully provisioned a GKE (Google Kubernetes Engine) cluster and deployed Online Boutique's 11 microservices, which includes a load generator. ================================================ FILE: .deploystack/scripts/preinit.sh ================================================ # 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. ROOT=$(pwd) sed -i.tmp "s/project_id/gcp_project_id/" $ROOT/terraform/terraform.tfvars ================================================ FILE: .deploystack/test ================================================ #! /bin/bash # 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. # DEPLOYSTACK - this file is a test script that is used by DeployStack's # testing rig to make sure that the Terraform script installs and uninstalls # cleanly # DON'T REMOVE FROM test script. CYAN='\033[0;36m' BCYAN='\033[1;36m' NC='\033[0m' # No Color DIVIDER=$(printf %"$(tput cols)"s | tr " " "*") DIVIDER+="\n" function get_project_id() { local __resultvar=$1 VALUE=$(gcloud config get-value project | xargs) eval $__resultvar="'$VALUE'" } function get_project_number() { local __resultvar=$1 local PRO=$2 VALUE=$(gcloud projects list --filter="project_id=$PRO" --format="value(PROJECT_NUMBER)" | xargs) eval $__resultvar="'$VALUE'" } # DISPLAY HELPERS function section_open() { section_description=$1 printf "$DIVIDER" printf "${CYAN}$section_description${NC} \n" printf "$DIVIDER" } function section_close() { printf "$DIVIDER" printf "${CYAN}$section_description ${BCYAN}- done${NC}\n" printf "\n\n" } function evalTest() { local command=$1 local expected=$2 local ERR="" got=$(eval $command 2>errFile) ERR=$( /dev/null" "deployment.apps/adservice" evalTest "kubectl get deployment cartservice --no-headers -o=name 2> /dev/null" "deployment.apps/cartservice" evalTest "kubectl get deployment checkoutservice --no-headers -o=name 2> /dev/null" "deployment.apps/checkoutservice" evalTest "kubectl get deployment currencyservice --no-headers -o=name 2> /dev/null" "deployment.apps/currencyservice" evalTest "kubectl get deployment emailservice --no-headers -o=name 2> /dev/null" "deployment.apps/emailservice" evalTest "kubectl get deployment loadgenerator --no-headers -o=name 2> /dev/null" "deployment.apps/loadgenerator" evalTest "kubectl get deployment paymentservice --no-headers -o=name 2> /dev/null" "deployment.apps/paymentservice" evalTest "kubectl get deployment productcatalogservice --no-headers -o=name 2> /dev/null" "deployment.apps/productcatalogservice" evalTest "kubectl get deployment recommendationservice --no-headers -o=name 2> /dev/null" "deployment.apps/recommendationservice" evalTest "kubectl get deployment redis-cart --no-headers -o=name 2> /dev/null" "deployment.apps/redis-cart" evalTest "kubectl get deployment shippingservice --no-headers -o=name 2> /dev/null" "deployment.apps/shippingservice" section_close sleep 120 ENDPOINT=$( kubectl get service frontend-external --no-headers 2> /dev/null | awk '{print $4}') section_open "Testing Online Boutique's front-end is working" evalTest 'curl -s -o /dev/null -w "%{http_code}" $ENDPOINT' "200" section_close # Uncomment the line: "deletion_protection = false" sed -i "s/# deletion_protection/deletion_protection/g" ${DIR}/main.tf terraform -chdir="$DIR" apply -auto-approve \ -var gcp_project_id="${PROJECT}" \ -var name="${NAME}" \ -var region="${REGION}" \ -var namespace="${NAMESPACE}" \ -var filepath_manifest="${FILEPATH_MANIFEST}" \ -var memorystore="${MEMORYSTORE}" terraform -chdir="$DIR" destroy -auto-approve \ -var gcp_project_id="${PROJECT}" \ -var name="${NAME}" \ -var region="${REGION}" \ -var namespace="${NAMESPACE}" \ -var filepath_manifest="${FILEPATH_MANIFEST}" \ -var memorystore="${MEMORYSTORE}" section_open "Testing Google Kubernetes Engine cluster does NOT exist" evalTest 'gcloud container clusters describe online-boutique --format="value(name)" --region $REGION' "EXPECTERROR" section_close # This is only needed if you tests fail alot because of overlapping runs of the # same set of tests. Really don't do this if you don't want to severely irritate # @tpryan section_open "Delete Test Project" gcloud projects delete $PROJECT -q section_close printf "$DIVIDER" printf "CONGRATS!!!!!!! \n" printf "You got the end the of your test with everything working. \n" printf "$DIVIDER" ================================================ FILE: .deploystack/test.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. # DEPLOYSTACK - this file is the cloudbuild for running testing automatically # in the testing rig steps: - name: 'bash' id: "creds" args: ['-c','echo $$CREDS > .deploystack/creds.json'] secretEnv: ['CREDS'] - name: 'gcr.io/cloudshell-images/cloudshell:latest' entrypoint: bash args: [ '.deploystack/test' ] secretEnv: ['BA'] timeout: 4200s options: machineType: 'E2_HIGHCPU_8' availableSecrets: secretManager: - versionName: projects/$PROJECT_ID/secrets/creds/versions/latest env: 'CREDS' - versionName: projects/$PROJECT_ID/secrets/billing_account/versions/latest env: 'BA' ================================================ FILE: .editorconfig ================================================ # The .editorconfig is used to maintain consistent code style. # The .editorconfig file is supported by most text editors. # See https://editorconfig.org root = true [*] insert_final_newline = true trim_trailing_whitespace = true indent_style = space indent_size = 2 [*.cs] indent_size = 4 [Dockerfile*] indent_size = 4 [*.go] indent_style = tab [*.java] indent_size = 4 [*.py] indent_size = 4 ================================================ FILE: .gitattributes ================================================ # This file configures the git settings for this repository. # Converts "CR + LF" to "LF", for all "text" files — for local files on all OSes and files pushed to the remote repo. * text=auto eol=lf ================================================ FILE: .github/CODEOWNERS ================================================ # See https://help.github.com/en/articles/about-code-owners # for more info about CODEOWNERS file. # These owners will be the default owners for everything in # the repo. Unless a later match takes precedence. * @GoogleCloudPlatform/devrel-flagship-app-maintainers @yoshi-approver ================================================ FILE: .github/CODE_OF_CONDUCT.md ================================================ # Contributor Code of Conduct As contributors and maintainers of this project, and in the interest of fostering an open and welcoming community, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities. We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, religion, or nationality. Examples of unacceptable behavior by participants include: * The use of sexualized language or imagery * Personal attacks * Trolling or insulting/derogatory comments * Public or private harassment * Publishing other's private information, such as physical or electronic addresses, without explicit permission * Other unethical or unprofessional conduct. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. By adopting this Code of Conduct, project maintainers commit themselves to fairly and consistently applying these principles to every aspect of managing this project. Project maintainers who do not follow or enforce the Code of Conduct may be permanently removed from the project team. This code of conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers. This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.2.0, available at [http://contributor-covenant.org/version/1/2/0/](http://contributor-covenant.org/version/1/2/0/) ================================================ FILE: .github/CONTRIBUTING.md ================================================ # How to Contribute Thank you so much for your interest in contributing to Online Boutique. Before contributing, you must: * Sign the [Contributor License Agreement (CLA)](#contributor-license-agreement). * Follow the [Google Open Source Community Guidelines](https://opensource.google.com/conduct/). * Follow the [Contribution Process](#contribution-process). ## Contributor License Agreement Contributions to Online Boutique must be accompanied by a Contributor License Agreement (CLA). You (or your employer) retain the copyright to your contribution. The CLA gives us permission to use and redistribute your contributions as part of the project. Head over to 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. ## Contribution Process Here's the process for making a change to this repository: 1. Review Online Boutique's [purpose](/docs/purpose.md) and [product requirements](/docs/product-requirements.md). 1. If your proposed changes **do not align** with the purpose and product requirements of Online Boutique, you may be asked to instead maintain your own fork of this repository. 1. For **small changes** (such as a bug fixes or spelling corrections): 1. Fork this repository and submit a [pull request](https://help.github.com/articles/about-pull-requests/). 1. Wait for a maintainer of this repository to review your change. 1. For **bigger changes**: 1. Create a [GitHub issue](https://github.com/GoogleCloudPlatform/microservices-demo/issues/new/choose) describing the change **before** working on the implementation. This is important to avoid potentially having to discard your development efforts. 1. Wait for a maintainer of this repository to review your GitHub issue. For significantly complex proposals, you may be asked to start a Google Doc to discuss design decisions. If you have any questions, please [create a GitHub issue](https://github.com/GoogleCloudPlatform/microservices-demo/issues/new/choose). ================================================ FILE: .github/ISSUE_TEMPLATE/bug-report.md ================================================ --- name: Bug report about: Create a report to help us improve title: '' labels: '' assignees: '' --- ### Describe the bug ### To Reproduce ### Logs ### Screenshots ### Environment ### Additional context ### Exposure ================================================ FILE: .github/ISSUE_TEMPLATE/feature-request.md ================================================ --- name: Feature request about: Suggest an idea for this project title: '' labels: '' assignees: '' --- ### Describe request or inquiry ### What purpose/environment will this feature serve? ================================================ FILE: .github/ISSUE_TEMPLATE/other.md ================================================ --- name: Other about: Have a question or need clarification? title: '' labels: '' assignees: '' --- ### Write down your inquiry ================================================ FILE: .github/SECURITY.md ================================================ # Security Policy To report a security issue, please use [g.co/vulnz](https://g.co/vulnz). The Google Security Team will respond within 5 working days of your report on g.co/vulnz. We use g.co/vulnz for our intake, and do coordination and disclosure here using GitHub Security Advisory to privately discuss and fix the issue. ================================================ FILE: .github/auto-approve.yml ================================================ # 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 # # 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. # https://github.com/googleapis/repo-automation-bots/tree/main/packages/auto-approve processes: - "PythonDependency" - "PythonSampleAppDependency" - "JavaDependency" - "JavaSampleAppDependency" - "GoDependency" - "NodeDependency" - "DockerDependency" ================================================ FILE: .github/header-checker-lint.yml ================================================ # 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 # # 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 file configures a GitHub Bot called "License Header Lint GCF": https://github.com/apps/license-header-lint-gcf # The bot runs a GitHub check called "header-check" (inside pull-requests) that warns us about invalid/missing license headers. # The schema for this configutation file is documented at https://github.com/googleapis/repo-automation-bots/tree/main/packages/header-checker-lint#header-checker-lint. allowedCopyrightHolders: - 'Google LLC' allowedLicenses: - 'Apache-2.0' # If you want to ignore certain files/folders, use ignoreFiles. # ignoreFiles: # - '**/requirements.txt' # If you want to ignore checking the license year, use ignoreLicenseYear. # ignoreLicenseYear: true # Useful when migrating in code licensed at previous years. sourceFileExtensions: - 'cs' - 'css' - 'Dockerfile' - 'dockerignore' - 'gitignore' - 'go' - 'html' - 'java' - 'js' - 'proto' - 'py' - 'sh' - 'tf' - 'yaml' - 'yml' ================================================ FILE: .github/pull_request_template.md ================================================ ### Background ### Fixes ### Change Summary ### Additional Notes ### Testing Procedure ### Related PRs or Issues ================================================ FILE: .github/release-cluster/README.md ================================================ # cymbal-shops.retail.cymbal.dev manifests This directory contains extra deploy manifests for configuring Online Boutique solution on GKE for cymbal-shops.retail.cymbal.dev. _Note: before moving forward, the Online Boutique apps should already be deployed [on the online-boutique-release GKE cluster](/docs/releasing#10-deploy-releasekubernetes-manifestsyaml-to-our-online-boutique-release-gke-cluster)._ ## Public static IP address Create the static public IP address: ``` STATIC_IP_NAME=online-boutique-ip # name hard-coded in: frontend-ingress.yaml gcloud compute addresses create $STATIC_IP_NAME --global ``` When ready to do so, you could grab this public IP address and update your DNS: ``` gcloud compute addresses describe $STATIC_IP_NAME \ --global \ --format "value(address)" ``` ## Cloud Armor Set up Cloud Armor: ``` SECURITY_POLICY_NAME=online-boutique-security-policy # Name hard-coded in: backendconfig.yaml gcloud compute security-policies create $SECURITY_POLICY_NAME \ --description "Block various attacks" gcloud compute security-policies rules create 1000 \ --security-policy $SECURITY_POLICY_NAME \ --expression "evaluatePreconfiguredExpr('xss-stable')" \ --action "deny-403" \ --description "XSS attack filtering" gcloud compute security-policies rules create 12345 \ --security-policy $SECURITY_POLICY_NAME \ --expression "evaluatePreconfiguredExpr('cve-canary')" \ --action "deny-403" \ --description "CVE-2021-44228 and CVE-2021-45046" gcloud compute security-policies update $SECURITY_POLICY_NAME \ --enable-layer7-ddos-defense gcloud compute security-policies update $SECURITY_POLICY_NAME \ --log-level=VERBOSE ``` ## SSL Policy Set up an SSL policy in order to later set up a redirect from HTTP to HTTPs: ``` SSL_POLICY_NAME=online-boutique-ssl-policy # Name hard-coded in: frontendconfig.yaml gcloud compute ssl-policies create $SSL_POLICY_NAME \ --profile COMPATIBLE \ --min-tls-version 1.0 ``` ## Deploy Kubernetes manifests Deploy the Kubernetes manifests in this current folder: ``` kubectl apply -f . ``` Wait for the `ManagedCertificate` to be provisioned. This usually takes about 30 minutes. ``` kubectl get managedcertificates ``` Remove the default `LoadBalancer` `Service` not used at this point: ``` kubectl delete service frontend-external ``` Remove the `loadgenerator` `Deployment` not used at this point: ``` kubectl delete deployment loadgenerator ``` ================================================ FILE: .github/release-cluster/backend-config.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 # # 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. apiVersion: cloud.google.com/v1 kind: BackendConfig metadata: name: frontend-backend-config spec: securityPolicy: name: online-boutique-security-policy ================================================ FILE: .github/release-cluster/frontend-config.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 # # 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. apiVersion: networking.gke.io/v1beta1 kind: FrontendConfig metadata: name: frontend-frontend-config spec: sslPolicy: online-boutique-ssl-policy redirectToHttps: enabled: true responseCodeName: MOVED_PERMANENTLY_DEFAULT ================================================ FILE: .github/release-cluster/frontend-ingress.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 # # 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. apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: frontend-ingress annotations: kubernetes.io/ingress.global-static-ip-name: online-boutique-ip networking.gke.io/managed-certificates: online-boutique-certificate networking.gke.io/v1beta1.FrontendConfig: frontend-frontend-config spec: defaultBackend: service: name: frontend port: number: 80 rules: - http: paths: - path: /* pathType: ImplementationSpecific backend: service: name: frontend port: number: 80 ================================================ FILE: .github/release-cluster/frontend-service.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 # # 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. apiVersion: v1 kind: Service metadata: name: frontend annotations: cloud.google.com/neg: '{"ingress": true}' cloud.google.com/backend-config: '{"default": "frontend-backend-config"}' spec: type: ClusterIP selector: app: frontend ports: - name: http port: 80 targetPort: 8080 ================================================ FILE: .github/release-cluster/managed-cert.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 # # 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. apiVersion: networking.gke.io/v1 kind: ManagedCertificate metadata: name: online-boutique-certificate spec: domains: - cymbal-shops.retail.cymbal.dev ================================================ FILE: .github/renovate.json5 ================================================ { extends: [ 'github>GoogleCloudPlatform/kubernetes-engine-samples//.github/renovate-configs/dee-platform-ops.json5', 'schedule:earlyMondays', ], 'pip-compile': { enabled: true, managerFilePatterns: [ '/(^|/)requirements\\.txt$/', ], }, pip_requirements: { enabled: false, }, constraints: { python: '~=3.11.0', }, kubernetes: { managerFilePatterns: [ '/\\.yaml$/', ], ignorePaths: [ 'release/**', 'kustomize/base/**', ], }, } ================================================ FILE: .github/snippet-bot.yml ================================================ # 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. ================================================ FILE: .github/terraform/README.md ================================================ This folder contains the Terraform for some of the infrastructure used by the CICD (continuous integration and continuous delivery/continuous deployment) of this repository. ## Update this Terraform To make changes to this Terraform, follow these steps: 1. Make sure you have access to the `online-boutique-ci` Google Cloud project. 1. Move into this folder: `cd .github/terraform` 1. Set the PROJECT_ID environment variable: `export PROJECT_ID=online-boutique-ci` 1. Prepare Terraform and download the necessary Terraform dependencies (such as the "hashicorp/google" Terraform provider): `terraform init` 1. Apply the Terraform: `terraform apply -var project_id=${PROJECT_ID}` * Ideally, you would see `Apply complete! Resources: 0 added, 0 changed, 0 destroyed.` in the output. 1. Make your desired changes to the Terraform code. 1. Apply the Terraform: `terraform apply -var project_id=${PROJECT_ID}` * This time, Terraform will prompt you confirm your changes before applying them. ================================================ FILE: .github/terraform/main.tf ================================================ /** * 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. */ # Set defaults for the google Terraform provider. provider "google" { project = var.project_id region = "us-central1" zone = "us-central1-a" } terraform { # Store the state inside a Google Cloud Storage bucket. backend "gcs" { bucket = "cicd-terraform-state" prefix = "terraform-state" } } # Enable Google Cloud APIs. module "enable_google_apis" { source = "terraform-google-modules/project-factory/google//modules/project_services" version = "~> 18.0" disable_services_on_destroy = false activate_apis = [ "cloudresourcemanager.googleapis.com", "container.googleapis.com", "iam.googleapis.com", "storage.googleapis.com", ] project_id = var.project_id } # Google Cloud Storage for storing Terraform state (.tfstate). resource "google_storage_bucket" "terraform_state_storage_bucket" { name = "cicd-terraform-state" location = "us" storage_class = "STANDARD" force_destroy = false public_access_prevention = "enforced" uniform_bucket_level_access = true versioning { enabled = true } } # Google Cloud IAM service account for GKE clusters. # We avoid using the Compute Engine default service account because it's too permissive. resource "google_service_account" "gke_clusters_service_account" { account_id = "gke-clusters-service-account" display_name = "My Service Account" depends_on = [ module.enable_google_apis ] } # See https://cloud.google.com/kubernetes-engine/docs/how-to/hardening-your-cluster#use_least_privilege_sa resource "google_project_iam_member" "gke_clusters_service_account_role_metric_writer" { project = var.project_id role = "roles/monitoring.metricWriter" member = "serviceAccount:${google_service_account.gke_clusters_service_account.email}" } # See https://cloud.google.com/kubernetes-engine/docs/how-to/hardening-your-cluster#use_least_privilege_sa resource "google_project_iam_member" "gke_clusters_service_account_role_logging_writer" { project = var.project_id role = "roles/logging.logWriter" member = "serviceAccount:${google_service_account.gke_clusters_service_account.email}" } # See https://cloud.google.com/kubernetes-engine/docs/how-to/hardening-your-cluster#use_least_privilege_sa resource "google_project_iam_member" "gke_clusters_service_account_role_monitoring_viewer" { project = var.project_id role = "roles/monitoring.viewer" member = "serviceAccount:${google_service_account.gke_clusters_service_account.email}" } # See https://cloud.google.com/kubernetes-engine/docs/how-to/hardening-your-cluster#use_least_privilege_sa resource "google_project_iam_member" "gke_clusters_service_account_role_stackdriver_writer" { project = var.project_id role = "roles/stackdriver.resourceMetadata.writer" member = "serviceAccount:${google_service_account.gke_clusters_service_account.email}" } # The GKE cluster used for pull-request (PR) staging deployments. resource "google_container_cluster" "prs_gke_cluster" { name = "prs-gke-cluster" location = "us-central1" enable_autopilot = true project = var.project_id deletion_protection = true depends_on = [ module.enable_google_apis ] cluster_autoscaling { auto_provisioning_defaults { service_account = google_service_account.gke_clusters_service_account.email } } # Need an empty ip_allocation_policy to overcome an error related to autopilot node pool constraints. # Workaround from https://github.com/hashicorp/terraform-provider-google/issues/10782#issuecomment-1024488630 ip_allocation_policy { } } ================================================ FILE: .github/terraform/variables.tf ================================================ /** * 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. */ # This file lists variables that you can set using the -var flag during "terraform apply". # Example: terraform apply -var project_id="${PROJECT_ID}" variable "project_id" { type = string description = "The Google Cloud project ID." } ================================================ FILE: .github/terraform/versions.tf ================================================ /** * 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. */ terraform { required_version = ">= 0.13" required_providers { google = { source = "hashicorp/google" version = "~> 7.0" } } } ================================================ FILE: .github/workflows/README.md ================================================ # GitHub Actions Workflows This page describes the CI/CD workflows for the Online Boutique app, which run in [Github Actions](https://github.com/GoogleCloudPlatform/microservices-demo/actions). ## Infrastructure The CI/CD pipelines for Online Boutique run in Github Actions, using a pool of two [self-hosted runners]((https://help.github.com/en/actions/automating-your-workflow-with-github-actions/about-self-hosted-runners)). These runners are GCE instances (virtual machines) that, for every open Pull Request in the repo, run the code test pipeline, deploy test pipeline, and (on main) deploy the latest version of the app to [cymbal-shops.retail.cymbal.dev](https://cymbal-shops.retail.cymbal.dev) We also host a test GKE cluster, which is where the deploy tests run. Every PR has its own namespace in the cluster. ## Workflows **Note**: In order for the current CI/CD setup to work on your pull request, you must branch directly off the repo (no forks). This is because the Github secrets necessary for these tests aren't copied over when you fork. ### Code Tests - [ci-pr.yaml](ci-pr.yaml) These tests run on every commit for every open PR, as well as any commit to main / any release branch. Currently, this workflow runs only Go unit tests. ### Deploy Tests- [ci-pr.yaml](ci-pr.yaml) These tests run on every commit for every open PR, as well as any commit to main / any release branch. This workflow: 1. Creates a dedicated GKE namespace for that PR, if it doesn't already exist, in the PR GKE cluster. 2. Uses `skaffold run` to build and push the images specific to that PR commit. Then skaffold deploys those images, via `kubernetes-manifests`, to the PR namespace in the test cluster. 3. Tests to make sure all the pods start up and become ready. 4. Gets the LoadBalancer IP for the frontend service. 5. Comments that IP in the pull request, for staging. ### Push and Deploy Latest - [push-deploy](push-deploy.yml) This is the Continuous Deployment workflow, and it runs on every commit to the main branch. This workflow: 1. Builds the container images for every service, tagging as `latest`. 2. Pushes those images to Google Container Registry. Note that this workflow does not update the image tags used in `release/kubernetes-manifests.yaml` - these release manifests are tied to a stable `v0.x.x` release. ### Cleanup - [cleanup.yaml](cleanup.yaml) This workflow runs when a PR closes, regardless of whether it was merged into main. This workflow deletes the PR-specific GKE namespace in the test cluster. ## Appendix - Creating a new Actions runner Should one of the two self-hosted Github Actions runners (GCE instances) fail, or you want to add more runner capacity, this is how to provision a new runner. Note that you need IAM access to the admin Online Boutique GCP project in order to do this. 1. Create a GCE instance. - VM should be at least n1-standard-4 with 50GB persistent disk - VM should use custom service account with permissions to: access a GKE cluster, create GCS storage buckets, and push to GCR. 2. SSH into new VM through the Google Cloud Console. 3. Install project-specific dependencies, including go, docker, skaffold, and kubectl: ``` wget -O - https://raw.githubusercontent.com/GoogleCloudPlatform/microservices-demo/main/.github/workflows/install-dependencies.sh | bash ``` The instance will restart when the script completes in order to finish the Docker install. 4. SSH back into the VM. 5. Follow the instructions to add a new runner on the [Actions Settings page](https://github.com/GoogleCloudPlatform/microservices-demo/settings/actions) to authenticate the new runner 6. Start GitHub Actions as a background service: ``` sudo ~/actions-runner/svc.sh install ; sudo ~/actions-runner/svc.sh start ``` ================================================ FILE: .github/workflows/ci-main.yaml ================================================ # Copyright 2020 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. name: "Continuous Integration - Main/Release" on: push: # run on pushes to main or release/* branches: - main - release/* paths-ignore: - '**/README.md' - 'kustomize/**' - '.github/workflows/kustomize-build-ci.yaml' - 'terraform/**' - '.github/workflows/terraform-validate-ci.yaml' - 'helm-chart/**' - '.github/workflows/helm-chart-ci.yaml' jobs: code-tests: runs-on: [self-hosted, is-enabled] steps: - uses: actions/checkout@v6 - uses: actions/setup-dotnet@v5 env: DOTNET_INSTALL_DIR: "./.dotnet" with: dotnet-version: '10.0' - uses: actions/setup-go@v6 with: go-version: '1.26' - name: Go Unit Tests timeout-minutes: 10 run: | for SERVICE in "shippingservice" "productcatalogservice"; do echo "testing $SERVICE..." pushd src/$SERVICE go test popd done - name: C# Unit Tests timeout-minutes: 10 run: | dotnet test src/cartservice/ deployment-tests: runs-on: [self-hosted, is-enabled] needs: code-tests strategy: matrix: profile: ["local-code"] fail-fast: true steps: - uses: actions/checkout@v6 - name: Build + Deploy PR images to GKE timeout-minutes: 20 run: | PR_NUMBER=$(echo $GITHUB_REF | awk 'BEGIN { FS = "/" } ; { print $3 }') NAMESPACE="pr${PR_NUMBER}" echo "::set-env name=NAMESPACE::$NAMESPACE" echo "::set-env name=PR_NUMBER::$PR_NUMBER" yes | gcloud auth configure-docker us-docker.pkg.dev gcloud container clusters get-credentials $PR_CLUSTER --region $REGION --project $PROJECT_ID cat < helm-template.yaml cat helm-template.yaml kustomize create --resources helm-template.yaml kustomize build . - name: helm template grpc health probes run: | # Test related to https://medium.com/google-cloud/b5bd26253a4c cd helm-chart/ SPANNER_CONNECTION_STRING=projects/PROJECT_ID/instances/SPANNER_INSTANCE_NAME/databases/SPANNER_DATABASE_NAME helm template . \ --set nativeGrpcHealthCheck=true \ -n onlineboutique \ > helm-template.yaml cat helm-template.yaml kustomize build . - name: helm template spanner run: | # Test related to https://medium.com/google-cloud/f7248e077339 cd helm-chart/ SPANNER_CONNECTION_STRING=projects/PROJECT_ID/instances/SPANNER_INSTANCE_NAME/databases/SPANNER_DATABASE_NAME SPANNER_DB_USER_GSA_ID=spanner-db-user@my-project.iam.gserviceaccount.com helm template . \ --set cartDatabase.inClusterRedis.create=false \ --set cartDatabase.type=spanner \ --set cartDatabase.connectionString=${SPANNER_CONNECTION_STRING} \ --set serviceAccounts.create=true \ --set serviceAccounts.annotationsOnlyForCartservice=true \ --set "serviceAccounts.annotations.iam\.gke\.io/gcp-service-account=${SPANNER_DB_USER_GSA_ID}" \ -n onlineboutique \ > helm-template.yaml cat helm-template.yaml kustomize build . - name: helm template asm run: | # Test related to https://medium.com/google-cloud/246119e46d53 cd helm-chart/ helm template . \ --set networkPolicies.create=true \ --set sidecars.create=true \ --set serviceAccounts.create=true \ --set authorizationPolicies.create=true \ --set frontend.externalService=false \ --set frontend.virtualService.create=true \ --set frontend.virtualService.gateway.name=asm-ingressgateway \ --set frontend.virtualService.gateway.namespace=asm-ingress \ --set frontend.virtualService.gateway.labelKey=asm \ --set frontend.virtualService.gateway.labelValue=ingressgateway \ -n onlineboutique \ > helm-template.yaml cat helm-template.yaml kustomize build . - name: helm template memorystore istio tls origination run: | # Test related to https://medium.com/google-cloud/64b71969318d cd helm-chart/ REDIS_IP=0.0.0.0 REDIS_PORT=7378 REDIS_CERT=dsjfgkldsjflkdsjflksdajfkldsjkfljsdaklfjaskjfakdsjfaklsdjflskadjfklasjfkls helm template . \ --set cartDatabase.inClusterRedis.create=false \ --set cartDatabase.connectionString=${REDIS_IP}:${REDIS_PORT} \ --set cartDatabase.externalRedisTlsOrigination.enable=true \ --set cartDatabase.externalRedisTlsOrigination.certificate="${REDIS_CERT}" \ --set cartDatabase.externalRedisTlsOrigination.endpointAddress=${REDIS_IP} \ --set cartDatabase.externalRedisTlsOrigination.endpointPort=${REDIS_PORT} \ -n onlineboutique \ > helm-template.yaml cat helm-template.yaml kustomize build . ================================================ FILE: .github/workflows/install-dependencies.sh ================================================ #!/bin/bash # Copyright 2020 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. set -euo pipefail # install wget sudo apt install -y wget # install dotnet CLI sudo apt-get update sudo apt-get install wget wget -O - https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor > microsoft.asc.gpg sudo mv microsoft.asc.gpg /etc/apt/trusted.gpg.d/ wget https://packages.microsoft.com/config/debian/9/prod.list sudo mv prod.list /etc/apt/sources.list.d/microsoft-prod.list sudo chown root:root /etc/apt/trusted.gpg.d/microsoft.asc.gpg sudo chown root:root /etc/apt/sources.list.d/microsoft-prod.list sudo apt-get install -y apt-transport-https && \ sudo apt-get update && \ sudo apt-get install -y dotnet-sdk-10.0 echo "✅ dotnet installed" # install kubectl sudo apt-get install -yqq kubectl git echo "✅ kubectl installed" # install go wget https://golang.org/dl/go1.25.linux-amd64.tar.gz sudo tar -C /usr/local -xzf go1.25.linux-amd64.tar.gz echo 'export GOPATH=$HOME/go' >> ~/.profile echo 'export PATH=$PATH:/usr/local/go/bin:$GOPATH/bin' >> ~/.profile source ~/.profile echo "✅ golang installed" # install build-essential (gcc, used for go test) sudo apt install -y build-essential # install addlicense go install github.com/google/addlicense@latest sudo ln -s $HOME/go/bin/addlicense /bin # install build-essential (gcc, used for go test) sudo apt install -y build-essential # install skaffold curl -Lo skaffold https://storage.googleapis.com/skaffold/releases/latest/skaffold-linux-amd64 && \ chmod +x skaffold && \ sudo mv skaffold /usr/local/bin echo "✅ skaffold installed" # install docker sudo apt install -yqq apt-transport-https ca-certificates curl gnupg2 software-properties-common && \ curl -fsSL https://download.docker.com/linux/debian/gpg | sudo apt-key add - && \ sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/debian $(lsb_release -cs) stable" && \ sudo apt-get update && \ sudo apt-get install -yqq docker-ce && \ sudo usermod -aG docker ${USER} echo "✅ docker installed, rebooting..." # reboot for docker setup sudo reboot ================================================ FILE: .github/workflows/kubevious-manifests-ci.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 # # 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. name: kubevious-manifests-ci on: push: branches: - main paths: - 'helm-chart/**' - 'kustomize/**' - '.github/workflows/kubevious-manifests-ci.yaml' pull_request: paths: - 'helm-chart/**' - 'kustomize/**' - '.github/workflows/kubevious-manifests-ci.yaml' permissions: contents: read jobs: kubevious-manifests-ci: runs-on: ubuntu-24.04 timeout-minutes: 1 steps: - uses: actions/checkout@v6 - name: Validate kubernetes-manifests id: kubernetes-manifests-validation uses: kubevious/cli@v1.0.64 with: manifests: kubernetes-manifests skip_rules: container-latest-image - name: Validate helm-chart id: helm-chart-validation uses: kubevious/cli@v1.0.64 with: manifests: helm-chart - name: Validate kustomize id: kustomize-validation uses: kubevious/cli@v1.0.64 with: manifests: kustomize skip_rules: container-latest-image ================================================ FILE: .github/workflows/kustomize-build-ci.yaml ================================================ # Copyright 2020 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. name: kustomize-build-ci on: push: branches: - main paths: - 'kustomize/**' - '.github/workflows/kustomize-build-ci.yaml' pull_request: paths: - 'kustomize/**' - '.github/workflows/kustomize-build-ci.yaml' jobs: kustomize-build-ci: runs-on: ubuntu-24.04 steps: - uses: actions/checkout@v6 - name: kustomize build base run: | cd kustomize/ kubectl kustomize . # Build the different combinations of Kustomize components found in kustomize/tests. - name: kustomize build tests run: | cd kustomize/tests KUSTOMIZE_TESTS_SUBFOLDERS=$(ls -d */) for test in $KUSTOMIZE_TESTS_SUBFOLDERS; do echo "## kustomize build for " + $test kustomize build $test done ================================================ FILE: .github/workflows/terraform-validate-ci.yaml ================================================ # Copyright 2020 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. name: terraform-validate-ci on: push: branches: - main paths: - 'terraform/**' - '.github/workflows/terraform-validate-ci.yaml' pull_request: paths: - 'terraform/**' - '.github/workflows/terraform-validate-ci.yaml' jobs: terraform-validate-ci: runs-on: ubuntu-24.04 steps: - uses: actions/checkout@v6 - uses: hashicorp/setup-terraform@v4 - name: terraform init & validate run: | cd terraform/ terraform init -backend=false terraform validate ================================================ FILE: .gitignore ================================================ .DS_Store .eclipse.buildship.core.prefs .gradle/ .idea/ .kubernetes-manifests-*/ .project .skaffold-*.yaml .terraform.lock.hcl .terraform/* .venv/ .vs/ .vscode/ *.iml *.ipr *.iws *.pyc *.swp *.tfstate* *.tfvars *~ bin/ build/ Dockerfile.pip node_modules/ obj/ pkg/ release/wi-kubernetes-manifests.yaml vendor/ ================================================ 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 ================================================ ![Continuous Integration](https://github.com/GoogleCloudPlatform/microservices-demo/workflows/Continuous%20Integration%20-%20Main/Release/badge.svg) **Online Boutique** is a cloud-first microservices demo application. The application is a web-based e-commerce app where users can browse items, add them to the cart, and purchase them. Google uses this application to demonstrate how developers can modernize enterprise applications using Google Cloud products, including: [Google Kubernetes Engine (GKE)](https://cloud.google.com/kubernetes-engine), [Cloud Service Mesh (CSM)](https://cloud.google.com/service-mesh), [gRPC](https://grpc.io/), [Cloud Operations](https://cloud.google.com/products/operations), [Spanner](https://cloud.google.com/spanner), [Memorystore](https://cloud.google.com/memorystore), [AlloyDB](https://cloud.google.com/alloydb), and [Gemini](https://ai.google.dev/). This application works on any Kubernetes cluster. If you’re using this demo, please **★Star** this repository to show your interest! **Note to Googlers:** Please fill out the form at [go/microservices-demo](http://go/microservices-demo). ## Architecture **Online Boutique** is composed of 11 microservices written in different languages that talk to each other over gRPC. [![Architecture of microservices](/docs/img/architecture-diagram.png)](/docs/img/architecture-diagram.png) Find **Protocol Buffers Descriptions** at the [`./protos` directory](/protos). | Service | Language | Description | | ---------------------------------------------------- | ------------- | --------------------------------------------------------------------------------------------------------------------------------- | | [frontend](/src/frontend) | Go | Exposes an HTTP server to serve the website. Does not require signup/login and generates session IDs for all users automatically. | | [cartservice](/src/cartservice) | C# | Stores the items in the user's shopping cart in Redis and retrieves it. | | [productcatalogservice](/src/productcatalogservice) | Go | Provides the list of products from a JSON file and ability to search products and get individual products. | | [currencyservice](/src/currencyservice) | Node.js | Converts one money amount to another currency. Uses real values fetched from European Central Bank. It's the highest QPS service. | | [paymentservice](/src/paymentservice) | Node.js | Charges the given credit card info (mock) with the given amount and returns a transaction ID. | | [shippingservice](/src/shippingservice) | Go | Gives shipping cost estimates based on the shopping cart. Ships items to the given address (mock) | | [emailservice](/src/emailservice) | Python | Sends users an order confirmation email (mock). | | [checkoutservice](/src/checkoutservice) | Go | Retrieves user cart, prepares order and orchestrates the payment, shipping and the email notification. | | [recommendationservice](/src/recommendationservice) | Python | Recommends other products based on what's given in the cart. | | [adservice](/src/adservice) | Java | Provides text ads based on given context words. | | [loadgenerator](/src/loadgenerator) | Python/Locust | Continuously sends requests imitating realistic user shopping flows to the frontend. | ## Screenshots | Home Page | Checkout Screen | | ----------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ | | [![Screenshot of store homepage](/docs/img/online-boutique-frontend-1.png)](/docs/img/online-boutique-frontend-1.png) | [![Screenshot of checkout screen](/docs/img/online-boutique-frontend-2.png)](/docs/img/online-boutique-frontend-2.png) | ## Quickstart (GKE) 1. Ensure you have the following requirements: - [Google Cloud project](https://cloud.google.com/resource-manager/docs/creating-managing-projects#creating_a_project). - Shell environment with `gcloud`, `git`, and `kubectl`. 2. Clone the latest major version. ```sh git clone --depth 1 --branch v0 https://github.com/GoogleCloudPlatform/microservices-demo.git cd microservices-demo/ ``` The `--depth 1` argument skips downloading git history. 3. Set the Google Cloud project and region and ensure the Google Kubernetes Engine API is enabled. ```sh export PROJECT_ID= export REGION=us-central1 gcloud services enable container.googleapis.com \ --project=${PROJECT_ID} ``` Substitute `` with the ID of your Google Cloud project. 4. Create a GKE cluster and get the credentials for it. ```sh gcloud container clusters create-auto online-boutique \ --project=${PROJECT_ID} --region=${REGION} ``` Creating the cluster may take a few minutes. 5. Deploy Online Boutique to the cluster. ```sh kubectl apply -f ./release/kubernetes-manifests.yaml ``` 6. Wait for the pods to be ready. ```sh kubectl get pods ``` After a few minutes, you should see the Pods in a `Running` state: ``` NAME READY STATUS RESTARTS AGE adservice-76bdd69666-ckc5j 1/1 Running 0 2m58s cartservice-66d497c6b7-dp5jr 1/1 Running 0 2m59s checkoutservice-666c784bd6-4jd22 1/1 Running 0 3m1s currencyservice-5d5d496984-4jmd7 1/1 Running 0 2m59s emailservice-667457d9d6-75jcq 1/1 Running 0 3m2s frontend-6b8d69b9fb-wjqdg 1/1 Running 0 3m1s loadgenerator-665b5cd444-gwqdq 1/1 Running 0 3m paymentservice-68596d6dd6-bf6bv 1/1 Running 0 3m productcatalogservice-557d474574-888kr 1/1 Running 0 3m recommendationservice-69c56b74d4-7z8r5 1/1 Running 0 3m1s redis-cart-5f59546cdd-5jnqf 1/1 Running 0 2m58s shippingservice-6ccc89f8fd-v686r 1/1 Running 0 2m58s ``` 7. Access the web frontend in a browser using the frontend's external IP. ```sh kubectl get service frontend-external | awk '{print $4}' ``` Visit `http://EXTERNAL_IP` in a web browser to access your instance of Online Boutique. 8. Congrats! You've deployed the default Online Boutique. To deploy a different variation of Online Boutique (e.g., with Google Cloud Operations tracing, Istio, etc.), see [Deploy Online Boutique variations with Kustomize](#deploy-online-boutique-variations-with-kustomize). 9. Once you are done with it, delete the GKE cluster. ```sh gcloud container clusters delete online-boutique \ --project=${PROJECT_ID} --region=${REGION} ``` Deleting the cluster may take a few minutes. ## Additional deployment options - **Terraform**: [See these instructions](/terraform) to learn how to deploy Online Boutique using [Terraform](https://www.terraform.io/intro). - **Istio / Cloud Service Mesh**: [See these instructions](/kustomize/components/service-mesh-istio/README.md) to deploy Online Boutique alongside an Istio-backed service mesh. - **Non-GKE clusters (Minikube, Kind, etc)**: See the [Development guide](/docs/development-guide.md) to learn how you can deploy Online Boutique on non-GKE clusters. - **AI assistant using Gemini**: [See these instructions](/kustomize/components/shopping-assistant/README.md) to deploy a Gemini-powered AI assistant that suggests products to purchase based on an image. - **And more**: The [`/kustomize` directory](/kustomize) contains instructions for customizing the deployment of Online Boutique with other variations. ## Documentation - [Development](/docs/development-guide.md) to learn how to run and develop this app locally. ## Demos featuring Online Boutique - [Security hardening of the OnlineBoutique sample apps with the Docker Hardened Images (DHI)](https://medium.com/google-cloud/security-hardening-of-the-onlineboutique-sample-apps-with-docker-hardened-images-dhi-ca1fad348343) - [alpine, distroless or scratch?](https://medium.com/google-cloud/alpine-distroless-or-scratch-caac35250e0b) - [Platform Engineering in action: Deploy the Online Boutique sample apps with Score and Humanitec](https://medium.com/p/d99101001e69) - [The new Kubernetes Gateway API with Istio and Anthos Service Mesh (ASM)](https://medium.com/p/9d64c7009cd) - [Use Azure Redis Cache with the Online Boutique sample on AKS](https://medium.com/p/981bd98b53f8) - [Sail Sharp, 8 tips to optimize and secure your .NET containers for Kubernetes](https://medium.com/p/c68ba253844a) - [Deploy multi-region application with Anthos and Google cloud Spanner](https://medium.com/google-cloud/a2ea3493ed0) - [Use Google Cloud Memorystore (Redis) with the Online Boutique sample on GKE](https://medium.com/p/82f7879a900d) - [Use Helm to simplify the deployment of Online Boutique, with a Service Mesh, GitOps, and more!](https://medium.com/p/246119e46d53) - [How to reduce microservices complexity with Apigee and Anthos Service Mesh](https://cloud.google.com/blog/products/application-modernization/api-management-and-service-mesh-go-together) - [gRPC health probes with Kubernetes 1.24+](https://medium.com/p/b5bd26253a4c) - [Use Google Cloud Spanner with the Online Boutique sample](https://medium.com/p/f7248e077339) - [Seamlessly encrypt traffic from any apps in your Mesh to Memorystore (redis)](https://medium.com/google-cloud/64b71969318d) - [Strengthen your app's security with Cloud Service Mesh and Anthos Config Management](https://cloud.google.com/service-mesh/docs/strengthen-app-security) - [From edge to mesh: Exposing service mesh applications through GKE Ingress](https://cloud.google.com/architecture/exposing-service-mesh-apps-through-gke-ingress) - [Take the first step toward SRE with Cloud Operations Sandbox](https://cloud.google.com/blog/products/operations/on-the-road-to-sre-with-cloud-operations-sandbox) - [Deploying the Online Boutique sample application on Cloud Service Mesh](https://cloud.google.com/service-mesh/docs/onlineboutique-install-kpt) - [Anthos Service Mesh Workshop: Lab Guide](https://codelabs.developers.google.com/codelabs/anthos-service-mesh-workshop) - [KubeCon EU 2019 - Reinventing Networking: A Deep Dive into Istio's Multicluster Gateways - Steve Dake, Independent](https://youtu.be/-t2BfT59zJA?t=982) - Google Cloud Next'18 SF - [Day 1 Keynote](https://youtu.be/vJ9OaAqfxo4?t=2416) showing GKE On-Prem - [Day 3 Keynote](https://youtu.be/JQPOPV_VH5w?t=815) showing Stackdriver APM (Tracing, Code Search, Profiler, Google Cloud Build) - [Introduction to Service Management with Istio](https://www.youtube.com/watch?v=wCJrdKdD6UM&feature=youtu.be&t=586) - [Google Cloud Next'18 London – Keynote](https://youtu.be/nIq2pkNcfEI?t=3071) showing Stackdriver Incident Response Management - [Microservices demo showcasing Go Micro](https://github.com/go-micro/demo) ================================================ FILE: cloudbuild.yaml ================================================ # Copyright 2020 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 cloudbuild_microservice_demo_cloudbuild] # This configuration file is used to build and deploy the app into a # GKE cluster using Google Cloud Build. # # PREREQUISITES: # - Cloud Build service account must have role: "Kubernetes Engine Developer" # USAGE: # GCP zone and GKE target cluster must be specified as substitutions # Example invocation: # `gcloud builds submit --config=cloudbuild.yaml --substitutions=_ZONE=us-central1-b,_CLUSTER=demo-app-staging .` steps: - id: 'Deploy application to cluster' name: 'gcr.io/k8s-skaffold/skaffold:v2.16.1' entrypoint: 'bash' args: - '-c' - > gcloud container clusters get-credentials --zone=$_ZONE $_CLUSTER; skaffold run -f=skaffold.yaml --default-repo=gcr.io/$PROJECT_ID; # Add more power, and more time, for heavy Skaffold build timeout: '3600s' options: machineType: 'N1_HIGHCPU_8' # [END cloudbuild_microservice_demo_cloudbuild] ================================================ FILE: docs/adding-new-microservice.md ================================================ # Adding a new microservice This document outlines the steps required to add a new microservice to the Online Boutique application. ## 1. Create a new directory Create a new directory for your microservice within the `src/` directory. The directory name should be the name of your microservice. ## 2. Add source code Place your microservice's source code inside the newly created directory. The structure of this directory should follow the conventions of the existing microservices. For example, a Python-based service would include at minimum the following files: - `README.md`: The service's description and documentation. - `main.py`: The application's entry point. - `requirements.in`: A list of Python dependencies. - `Dockerfile`: To containerize the application. Take a look at existing microservices for inspiration. ## 3. Create a Dockerfile Create a `Dockerfile` in your microservice's directory. This file will define the steps to build a container image for your service. Refer to this example and tweak based on your new service's needs: https://github.com/GoogleCloudPlatform/microservices-demo/blob/main/src/frontend/Dockerfile ## 4. Create Kubernetes manifests Create a new directory under `kustomize/components/` in the root of the repository for your microservice. Inside this directory, add the necessary Kubernetes YAML files for your new microservice. This typically includes: - A **Deployment** to manage your service's pods. - A **Service** to expose your microservice to other services within the cluster. Ensure you follow the existing naming conventions and that the container image specified in the Deployment matches the one built by your `cloudbuild.yaml` and `skaffold.yaml` files. Refer to this example and tweak based on your new service's needs: https://github.com/GoogleCloudPlatform/microservices-demo/tree/main/kustomize/components/shopping-assistant ## 5. Update the root `kustomization.yaml` file Add your newly created component to the root kustomization file so it gets picked up by the deployment cycle. The file is available here: https://github.com/GoogleCloudPlatform/microservices-demo/blob/main/kustomize/kustomization.yaml ## 6. Update the root `skaffold.yaml` Add your newly created service to the root skaffold file so the images build correctly. The file is available here: https://github.com/GoogleCloudPlatform/microservices-demo/blob/main/skaffold.yaml ## 7. Update the Helm chart Add your newly created service to the Helm chart templates and default values. The chart is available here: https://github.com/GoogleCloudPlatform/microservices-demo/tree/main/helm-chart ## 8. Update the documentation Finally, update the project's documentation to reflect the addition of your new microservice. This may include: - Adding a section to the main `README.md` if the service introduces significant new functionality. - Updating the architecture diagrams in the `docs/img` directory. - Adding a new document in the `docs` directory if the service requires detailed explanation. ================================================ FILE: docs/cloudshell-tutorial.md ================================================ # Online Boutique quickstart This tutorial shows you how to deploy **[Online Boutique](https://github.com/GoogleCloudPlatform/microservices-demo)** to a Kubernetes cluster. You'll be able to run Online Boutique on: - a local **[minikube](https://minikube.sigs.k8s.io/docs/)** cluster, which comes built in to the Cloud Shell instance - a **[Google Kubernetes Engine](https://cloud.google.com/kubernetes-engine)** cluster using a new or existing [Google Cloud project](https://cloud.google.com/resource-manager/docs/creating-managing-projects#creating_a_project) Let's get started! ## Kubernetes cluster setup Set up a Kubernetes cluster using the instructions below for either **minikube** or **GKE**. ### Minikube instructions Minikube creates a local Kubernetes cluster on Cloud Shell. 1. Click minikube on the status bar located at the bottom of the editor window. 2. The command palette will prompt you to choose which minikube cluster to control. Select **minikube** and, in the next prompt, click **Start** if the cluster has not already been started. 3. If prompted, authorize Cloud Shell to make a GCP API call with your credentials. *It may take a few minutes for minikube to finish starting.* Once minikube has started, you're ready to move on to the next step. ### GKE instructions In order to create a GKE cluster, you'll need to **[create a Google Cloud project](https://cloud.google.com/resource-manager/docs/creating-managing-projects#creating_a_project)** or use an existing project. 1. Access the command palette by going to **View > Find Command**. 2. Run the command **"Cloud Code: Create GKE cluster"**. 3. Select your GCP project. 4. Apply the following configurations in the GKE wizard: > - Zone: us-central1-b > - Cluster name: onlineboutique > - Node count: 4 > - Machine type: e2-standard-2 5. Click **Create Cluster**. Once your cluster has been created successfully, you can move on to the next step. ## Run on Kubernetes Now you can run Online Boutique on your Kubernetes cluster! 1. Launch the Cloud Code menu from the status bar and select Run on Kubernetes. 2. If prompted to select a Skaffold Profile, select **[default]**. 3. Select **Yes** to confirm your current context. 4. If you're using a GKE cluster, you'll need to confirm your container image registry. 5. If prompted, authorize Cloud Shell to make a GCP API call with your credentials. Cloud Code uses configurations defined in skaffold.yaml to build and deploy the app. *It may take a few minutes for the deploy to complete.* 6. Once the app is running, the local URLs will be displayed in the Output terminal. 7. To access your Online Boutique frontend service, click on the Web Preview button in the upper right of the editor window. 8. Select **Change Port** and enter '4503' as the port, then click **Change and Preview**. Your app will open in a new window. ## Stop the app To stop running the app: 1. Go to the Debug view 2. Click the **Stop** icon. 3. Select **Yes** to clean up deployed resources. You can start, stop, and debug apps from the Debug view. ### Clean up If you've deployed your app to a GKE cluster in your Google Cloud project, you'll want to delete the cluster to avoid incurring charges. 1. Navigate to the Cloud Code - Kubernetes view in the Activity bar. 2. Under the Google Kubernetes Engine Explorer tab, right-click on your cluster and select **Delete Cluster**. ## Conclusion Congratulations! You've successfully deployed Online Boutique using Cloud Shell. ##### What's next? Try other deployment options for Online Boutique: - **Istio/Cloud Service Mesh**: See these instructions. Learn more about the [Cloud Shell](https://cloud.google.com/shell) IDE environment and the [Cloud Code](https://cloud.google.com/code) extension. ================================================ FILE: docs/deploystack.md ================================================ ## Deploy Online Boutique with DeployStack The "Open in Google Cloud Shell" button below will use [DeployStack](https://cloud.google.com/shell/docs/cloud-shell-tutorials/deploystack/overview) to deploy Online Boutique to a new Google Kubernetes Engine (GKE) cluster. Open in Cloud Shell The button will open up a [Cloud Shell](https://cloud.google.com/shell) session where you will select your Google Cloud project. After project selection, the following will happen automatically: 1. a GKE cluster will be created inside the select project 2. Online Boutique (and its load generator) will be deployed to that cluster ================================================ FILE: docs/development-guide.md ================================================ # Development Guide This doc explains how to build and run the Online Boutique source code locally using the `skaffold` command-line tool. ## Prerequisites - [Docker for Desktop](https://www.docker.com/products/docker-desktop) - [kubectl](https://kubernetes.io/docs/tasks/tools/) (can be installed via `gcloud components install kubectl` for Option 1 - GKE) - [skaffold **2.0.2+**](https://skaffold.dev/docs/install/) (latest version recommended), a tool that builds and deploys Docker images in bulk. - Clone the repository. ```sh git clone https://github.com/GoogleCloudPlatform/microservices-demo cd microservices-demo/ ``` - A Google Cloud project with Google Container Registry enabled. (for Option 1 - GKE) - [Minikube](https://minikube.sigs.k8s.io/docs/start/) (optional for Option 2 - Local Cluster) - [Kind](https://kind.sigs.k8s.io/) (optional for Option 2 - Local Cluster) ## Option 1: Google Kubernetes Engine (GKE) > 💡 Recommended if you're using Google Cloud and want to try it on > a realistic cluster. **Note**: If your cluster has Workload Identity enabled, > [see these instructions](https://cloud.google.com/kubernetes-engine/docs/how-to/workload-identity#enable) 1. Create a Google Kubernetes Engine cluster and make sure `kubectl` is pointing to the cluster. ```sh gcloud services enable container.googleapis.com ``` ```sh gcloud container clusters create-auto demo --region=us-central1 ``` ``` kubectl get nodes ``` 2. Enable Artifact Registry (AR) on your GCP project and configure the `docker` CLI to authenticate to AR: ```sh gcloud services enable artifactregistry.googleapis.com ``` ```sh gcloud artifacts repositories create microservices-demo \ --repository-format=docker \ --location=us \ ``` ```sh gcloud auth configure-docker -q ``` 3. In the root of this repository, run: ``` skaffold run --default-repo=us-docker.pkg.dev/PROJECT_ID/microservices-demo ``` Where `PROJECT_ID` is replaced by your Google Cloud project ID. This command: - Builds the container images. - Pushes them to AR. - Applies the `./kubernetes-manifests` deploying the application to Kubernetes. **Troubleshooting:** If you get "No space left on device" error on Google Cloud Shell, you can build the images on Google Cloud Build: [Enable the Cloud Build API](https://console.cloud.google.com/flows/enableapi?apiid=cloudbuild.googleapis.com), then run `skaffold run -p gcb --default-repo=us-docker.pkg.dev/[PROJECT_ID]/microservices-demo` instead. 4. Find the IP address of your application, then visit the application on your browser to confirm installation. kubectl get service frontend-external 5. Navigate to `http://EXTERNAL-IP` to access the web frontend. ## Option 2 - Local Cluster 1. Launch a local Kubernetes cluster with one of the following tools: - To launch **Minikube** (tested with Ubuntu Linux). Please, ensure that the local Kubernetes cluster has at least: - 4 CPUs - 4.0 GiB memory - 32 GB disk space ```shell minikube start --cpus=4 --memory 4096 --disk-size 32g ``` - To launch **Docker for Desktop** (tested with Mac/Windows). Go to Preferences: - choose “Enable Kubernetes”, - set CPUs to at least 3, and Memory to at least 6.0 GiB - on the "Disk" tab, set at least 32 GB disk space - To launch a **Kind** cluster: ```shell kind create cluster ``` 2. Run `kubectl get nodes` to verify you're connected to the respective control plane. 3. Run `skaffold run` (first time will be slow, it can take ~20 minutes). This will build and deploy the application. If you need to rebuild the images automatically as you refactor the code, run `skaffold dev` command. 4. Run `kubectl get pods` to verify the Pods are ready and running. 5. Run `kubectl port-forward deployment/frontend 8080:8080` to forward a port to the frontend service. 6. Navigate to `localhost:8080` to access the web frontend. ## Adding a new microservice In general, the set of core microservices for Online Boutique is fairly complete and unlikely to change in the future, but it can be useful to add an additional optional microservice that can be deployed to complement the core services. See the [Adding a new microservice](adding-new-microservice.md) guide for instructions on how to add a new microservice. ## Cleanup If you've deployed the application with `skaffold run` command, you can run `skaffold delete` to clean up the deployed resources. ================================================ FILE: docs/product-requirements.md ================================================ ## Product Requirements This document contains a list of requirements that every change made to this repository should meet. Every change must: 1. Preserve the golden user journey taken by Kubernetes beginners. 1. Preserve the simplicity of demos. 1. Preserve the simplicity of the GKE quickstart. These requirements are about the default deployment (default configuration) of Online Boutique. Changes that will violate any of these rules should not be built into the default configuration of Online Boutique. Such changes should be opt-in only — ideally, as a [Kustomize Component](https://github.com/GoogleCloudPlatform/microservices-demo/tree/main/kustomize) if they align with the [purpose of Online Boutique](/docs/purpose.md). ### 1. Preserve the golden user journey taken by Kubernetes beginners The following statement about Online Boutique should always be true: > A user outside of Google can deploy Online Boutique's default configuration on a [_kind_ Kubernetes cluster](https://kind.sigs.k8s.io/). This statement describes the golden user journey that we expect new Kubernetes users to take while onboarding to Online Boutique. Being able to run Online Boutique on a _kind_ cluster ensures that Online Boutique is free and cloud-agnostic. This is aligned with [Google's mission](https://about.google/) of making information universally accessible and useful. To be specific, Online Boutique should be useful and accessible to developers that are new to Kubernetes. ### 2. Preserve the simplicity of demos New changes should not complicate the primary user journey showcased in live demos and tutorials. Today, the primary user journey is as follows: 1. Visit Online Boutique on a web browser. 2. Select an item from the homepage and add the item to the cart. 3. The checkout form is pre-populated with placeholder data (e.g. the shipping address). 4. The user checks out and completes the order. ### 3. Preserve the simplicity of the GKE quickstart New changes should not add additional complexity in the [main Online Boutique quickstart](https://github.com/GoogleCloudPlatform/microservices-demo#quickstart-gke). In particular, new changes should not add extra required steps or additional required tools in that quickstart. Ideally, extensions to Online Boutique's default functionality (such as a new microservice or a new cloud service integration) should be added as a [Kustomize Component](https://github.com/GoogleCloudPlatform/microservices-demo/tree/main/kustomize/components) which users can optionally opt into. ================================================ FILE: docs/purpose.md ================================================ ## Purpose Today, the primary purpose of Online Boutique is to demonstrate: * [Google Kubernetes Engine (GKE)](https://cloud.google.com/kubernetes-engine) * [Anthos](https://cloud.google.com/anthos) * [Google Cloud Operations](https://cloud.google.com/products/operations) * tools and technologies commonly used alongside the above products while being accessible and useful to all new Kubernetes users. ### Why does the purpose matter? We filter and prioritize the work to be done in this repository based on the purpose defined above. If you wish to make changes to this repository that do not align with the above purpose, we encourage you to maintain your own fork of Online Boutique. ================================================ FILE: docs/releasing/README.md ================================================ # Releasing Online Boutique This document walks through the process of creating a new release of Online Boutique. ## Prerequisites for tagging a release 1. Choose the logical [next release tag](https://github.com/GoogleCloudPlatform/bank-of-anthos/releases), using [semantic versioning](https://semver.org/): `vX.Y.Z`. If this release includes significant feature changes, update the minor version (`Y`). Otherwise, for bug-fix releases or standard quarterly release, update the patch version `Z`). 2. Ensure that the following commands are in your `PATH`: - `gsed` (found in the `gnu-sed` Brew package for macOS, or by symlinking `sed` for Linux) - `gcloud` - `helm` 3. Make sure that your `gcloud` is authenticated: ```sh gcloud auth login gcloud auth configure-docker us-central1-docker.pkg.dev ``` ## Create and tag the new release Run the `make-release.sh` script found inside the `docs/releasing/` directory: ```sh # assuming you are inside the root path of the bank-of-anthos repository export TAG=vX.Y.Z # This is the new version (e.g. `v0.3.5`) export REPO_PREFIX=us-central1-docker.pkg.dev/google-samples/microservices-demo # This is the Docker repository for tagged images export PROJECT_ID=google-samples # This is the Google Cloud project for the release CI ./docs/releasing/make-release.sh ``` This script does the following: 1. Uses `make-docker-images.sh` to build and push a Docker image for each microservice to the previously specified repository. 2. Uses `make-release-artifacts.sh` to regenerates (and update the image $TAGS) YAML file at `./release/kubernetes-manifests.yaml` and `./kustomize/base/`. 3. Runs `git tag` and pushes a new branch (e.g., `release/v0.3.5`) with the changes to `./release/kubernetes-manifests.yaml`. You can then browse the [Container Registry repository](https://pantheon.corp.google.com/gcr/images/google-samples/global/microservices-demo?project=google-samples) to make sure a Docker image was created for each microservice (with the new version tag). ## Create the PR Now that the release branch has been created, you can find it in the [list of branches](https://github.com/GoogleCloudPlatform/microservices-demo/branches) and create a pull request targeting `main` (the default branch). This process is going to trigger multiple CI checks as well as stage the release onto a temporary cluster. Once the PR has been approved and all checks are successfully passing, you can then merge the branch. Make sure to include the release draft (see next section) in the pull-request description for reviewers to see. Once reviewed and you're ready to merge, make sure to not delete the release branch or the tags during that process. ## Add notes to the release Once the PR has been fully merged, you are ready to create a new release for the newly created [tag](https://github.com/GoogleCloudPlatform/microservices-demo/tags). - Click the breadcrumbs on the row of the latest tag that was created in the [tags](https://github.com/GoogleCloudPlatform/microservices-demo/tags) page - Select the `Create release` option The release notes should contain a brief description of the changes since the previous release (like bug fixed and new features). For inspiration, you can look at the list of [releases](https://github.com/GoogleCloudPlatform/microservices-demo/releases). > ***Note:*** No assets need to be uploaded. They are picked up automatically from the tagged revision ## Deploy on the production environment Once the release notes are published, you should then replace the version of the production environment to the newly published version. 1. Connect to the [online-boutique-release GKE cluster](https://pantheon.corp.google.com/kubernetes/clusters/details/us-central1-c/online-boutique-release/details?project=online-boutique-ci): ```sh gcloud container clusters get-credentials online-boutique-release \ --zone us-central1-c --project online-boutique-ci ``` 2. Deploy `release/kubernetes-manifests.yaml` to it: ```sh kubectl apply -f ./release/kubernetes-manifests.yaml ``` 3. Remove unnecessary objects: ```sh kubectl delete service frontend-external kubectl delete deployment loadgenerator ``` 3. Make sure [cymbal-shops.retail.cymbal.dev](https://cymbal-shops.retail.cymbal.dev) works. ## Update major tags 1. Update the relevant major tag (for example, `v1`): ```sh export MAJOR_TAG=v0 # Edit this as needed (to v1/v2/v3/etc) git checkout release/${TAG} git pull git push --delete origin ${MAJOR_TAG} # Delete the remote tag (if it exists) git tag --delete ${MAJOR_TAG} # Delete the local tag (if it exists) git tag -a ${MAJOR_TAG} -m "Updating ${MAJOR_TAG} to its most recent release: ${TAG}" git push origin ${MAJOR_TAG} # Push the new tag to origin ``` ## Announce the new release internally Once the new release is out, you can now announce it via [g/online-boutique-announce](https://groups.google.com/a/google.com/g/online-boutique-announce). ================================================ FILE: docs/releasing/license_header.txt ================================================ # Copyright 2025 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. ================================================ FILE: docs/releasing/make-docker-images.sh ================================================ #!/usr/bin/env bash # Copyright 2019 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. # Builds and pushes docker image for each demo microservice. set -euo pipefail SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" REPO_ROOT=$SCRIPT_DIR/../.. log() { echo "$1" >&2; } TAG="${TAG:?TAG env variable must be specified}" REPO_PREFIX="${REPO_PREFIX:?REPO_PREFIX env variable must be specified}" PROJECT_ID="${PROJECT_ID:?PROJECT_ID env variable must be specified e.g. google-samples}" while IFS= read -d $'\0' -r dir; do # build image svcname="$(basename "${dir}")" builddir="${dir}" #PR 516 moved cartservice build artifacts one level down to src if [ $svcname == "cartservice" ] then builddir="${dir}/src" fi image="${REPO_PREFIX}/$svcname:$TAG" image_with_sample_public_image_tag="${REPO_PREFIX}/$svcname:sample-public-image-$TAG" ( cd "${builddir}" log "Building (and pushing) image on Google Cloud Build: ${image}" gcloud builds submit --project=${PROJECT_ID} --tag=${image} gcloud artifacts docker tags add ${image} ${image_with_sample_public_image_tag} ) done < <(find "${REPO_ROOT}/src" -mindepth 1 -maxdepth 1 -type d -print0) log "Successfully built and pushed all images." ================================================ FILE: docs/releasing/make-helm-chart.sh ================================================ #!/usr/bin/env bash # Copyright 2019 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. # Packages and pushes Online Boutique's Helm chart in public Artifact Registry. set -euo pipefail SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" REPO_ROOT=$SCRIPT_DIR/../.. log() { echo "$1" >&2; } TAG="${TAG:?TAG env variable must be specified}" HELM_CHART_REPO="us-docker.pkg.dev/online-boutique-ci/charts" cd ${REPO_ROOT}/helm-chart gsed -i "s/^appVersion:.*/appVersion: \"${TAG}\"/" Chart.yaml gsed -i "s/^version:.*/version: ${TAG:1}/" Chart.yaml helm package . helm push onlineboutique-${TAG:1}.tgz oci://$HELM_CHART_REPO rm ./onlineboutique-${TAG:1}.tgz log "Successfully built and pushed the Helm chart." ================================================ FILE: docs/releasing/make-release-artifacts.sh ================================================ #!/usr/bin/env bash # Copyright 2019 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 script compiles manifest files with the image tags and places them in # /release/... set -euo pipefail SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" REPO_ROOT=$SCRIPT_DIR/../.. [[ -n "${DEBUG:-}" ]] && set -x log() { echo "$1" >&2; } TAG="${TAG:?TAG env variable must be specified}" REPO_PREFIX="${REPO_PREFIX:?REPO_PREFIX env variable must be specified}" OUT_DIR="${OUT_DIR:-${REPO_ROOT}/release}" print_license_header() { cat "${SCRIPT_DIR}/license_header.txt" echo } print_autogenerated_warning() { cat< "${k8s_manifests_file}" log "Written ${k8s_manifests_file}" istio_manifests_file="${OUT_DIR}/istio-manifests.yaml" mk_istio_manifests > "${istio_manifests_file}" log "Written ${istio_manifests_file}" mk_kustomize_base log "Written Kustomize base" } main ================================================ FILE: docs/releasing/make-release.sh ================================================ #!/usr/bin/env bash # Copyright 2019 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 script creates a new release by: # - 1. building/pushing images # - 2. injecting tags into YAML manifests # - 3. creating a new git tag # - 4. pushing the tag/commit to main. set -euo pipefail SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" REPO_ROOT=$SCRIPT_DIR/../.. [[ -n "${DEBUG:-}" ]] && set -x log() { echo "$1" >&2; } fail() { log "$1"; exit 1; } TAG="${TAG:?TAG env variable must be specified}" REPO_PREFIX="${REPO_PREFIX:?REPO_PREFIX env variable must be specified e.g. us-central1-docker.pkg.dev\/google-samples\/microservices-demo}" PROJECT_ID="${PROJECT_ID:?PROJECT_ID env variable must be specified e.g. google-samples}" if [[ "$TAG" != v* ]]; then fail "\$TAG must start with 'v', e.g. v0.1.0 (got: $TAG)" fi # ensure there are no uncommitted changes if [[ $(git status -s | wc -l) -gt 0 ]]; then echo "error: can't have uncommitted changes" exit 1 fi # make sure local source is up to date git checkout main git pull # build and push images "${SCRIPT_DIR}"/make-docker-images.sh # update yaml "${SCRIPT_DIR}"/make-release-artifacts.sh # build and push images "${SCRIPT_DIR}"/make-helm-chart.sh # create git release / push to new branch git checkout -b "release/${TAG}" git add "${REPO_ROOT}/release/" git add "${REPO_ROOT}/kustomize/base/" git add "${REPO_ROOT}/helm-chart/" git commit --allow-empty -m "Release $TAG" log "Pushing k8s manifests to release/${TAG}..." git tag "$TAG" git push --set-upstream origin "release/${TAG}" git push --tags log "Successfully tagged release $TAG." ================================================ FILE: helm-chart/Chart.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 # # 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. apiVersion: v2 name: onlineboutique description: A Helm chart for Kubernetes for Online Boutique # A chart can be either an 'application' or a 'library' chart. # # Application charts are a collection of templates that can be packaged into versioned archives # to be deployed. # # Library charts provide useful utilities or functions for the chart developer. They're included as # a dependency of application charts to inject those utilities and functions into the rendering # pipeline. Library charts do not define any templates and therefore cannot be deployed. type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) version: 0.10.5 # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to # follow Semantic Versioning. They should reflect the version the application is using. # It is recommended to use it with quotes. appVersion: "v0.10.5" ================================================ FILE: helm-chart/README.md ================================================ # Helm chart for Online Boutique If you'd like to deploy Online Boutique via its Helm chart, you could leverage the following instructions. **Warning:** Online Boutique's Helm chart is currently experimental. If you have feedback or run into issues, let us know inside [GitHub Issue #1319](https://github.com/GoogleCloudPlatform/microservices-demo/issues/1319) or by creating a [new GitHub Issue](https://github.com/GoogleCloudPlatform/microservices-demo/issues/new/choose). Deploy the default setup of Online Boutique: ```sh helm upgrade onlineboutique oci://us-docker.pkg.dev/online-boutique-ci/charts/onlineboutique \ --install ``` Deploy advanced scenario of Online Boutique: ```sh helm upgrade onlineboutique oci://us-docker.pkg.dev/online-boutique-ci/charts/onlineboutique \ --install \ --create-namespace \ --set images.repository=us-docker.pkg.dev/my-project/microservices-demo \ --set frontend.externalService=false \ --set redis.create=false \ --set cartservice.database.type=spanner \ --set cartservice.database.connectionString=projects/my-project/instances/onlineboutique/databases/carts \ --set serviceAccounts.create=true \ --set authorizationPolicies.create=true \ --set networkPolicies.create=true \ --set sidecars.create=true \ --set frontend.virtualService.create=true \ --set 'serviceAccounts.annotations.iam\.gke\.io/gcp-service-account=spanner-db-user@my-project.iam.gserviceaccount.com' \ --set serviceAccounts.annotationsOnlyForCartservice=true \ -n onlineboutique ``` For the full list of configurations, see [values.yaml](./values.yaml). You could also find advanced scenarios with these blogs below: - [Online Boutique sample’s Helm chart, to simplify the setup of advanced and secured scenarios with Service Mesh and GitOps](https://medium.com/google-cloud/246119e46d53) - [gRPC health probes with Kubernetes 1.24+](https://medium.com/google-cloud/b5bd26253a4c) - [Use Google Cloud Spanner with the Online Boutique sample](https://medium.com/google-cloud/f7248e077339) ================================================ FILE: helm-chart/templates/NOTES.txt ================================================ {{- if and .Values.frontend.create .Values.frontend.externalService }} Note: It may take a few minutes for the LoadBalancer IP to be available. Watch the status of the frontend IP address with: kubectl get --namespace {{ .Release.Namespace }} svc -w {{ .Values.frontend.name }}-external Get the external IP address of the frontend: export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ .Values.frontend.name }}-external --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}") echo http://$SERVICE_IP {{- end }} {{- if .Values.frontend.virtualService.create }} Get the external IP address of the ingress gateway: export SERVICE_IP=$(kubectl get svc --namespace {{ .Values.frontend.virtualService.gateway.namespace }} {{ .Values.frontend.virtualService.gateway.name }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}") echo http://$SERVICE_IP {{- end }} ================================================ FILE: helm-chart/templates/adservice.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 # # 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. {{- if .Values.adService.create }} {{- if .Values.serviceAccounts.create }} apiVersion: v1 kind: ServiceAccount metadata: name: {{ .Values.adService.name }} namespace: {{.Release.Namespace}} {{- if not .Values.serviceAccounts.annotationsOnlyForCartservice }} {{- with .Values.serviceAccounts.annotations }} annotations: {{- toYaml . | nindent 4 }} {{- end }} {{- end }} --- {{- end }} apiVersion: apps/v1 kind: Deployment metadata: name: {{ .Values.adService.name }} namespace: {{ .Release.Namespace }} labels: app: {{ .Values.adService.name }} spec: selector: matchLabels: app: {{ .Values.adService.name }} template: metadata: labels: app: {{ .Values.adService.name }} spec: {{- if .Values.serviceAccounts.create }} serviceAccountName: {{ .Values.adService.name }} {{- else }} serviceAccountName: default {{- end }} terminationGracePeriodSeconds: 5 {{- if .Values.securityContext.enable }} securityContext: fsGroup: 1000 runAsGroup: 1000 runAsNonRoot: true runAsUser: 1000 {{- if .Values.seccompProfile.enable }} seccompProfile: type: {{ .Values.seccompProfile.type }} {{- end }} {{- end }} containers: - name: server securityContext: allowPrivilegeEscalation: false capabilities: drop: - ALL privileged: false readOnlyRootFilesystem: true image: {{ .Values.images.repository }}/{{ .Values.adService.name }}:{{ .Values.images.tag | default .Chart.AppVersion }} ports: - containerPort: 9555 env: - name: PORT value: "9555" resources: {{- toYaml .Values.adService.resources | nindent 10 }} readinessProbe: initialDelaySeconds: 20 periodSeconds: 15 grpc: port: 9555 livenessProbe: initialDelaySeconds: 20 periodSeconds: 15 grpc: port: 9555 --- apiVersion: v1 kind: Service metadata: name: {{ .Values.adService.name }} namespace: {{ .Release.Namespace }} labels: app: {{ .Values.adService.name }} spec: type: ClusterIP selector: app: {{ .Values.adService.name }} ports: - name: grpc port: 9555 targetPort: 9555 {{- if .Values.networkPolicies.create }} --- apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: {{ .Values.adService.name }} namespace: {{ .Release.Namespace }} spec: podSelector: matchLabels: app: {{ .Values.adService.name }} policyTypes: - Ingress - Egress ingress: - from: - podSelector: matchLabels: app: {{ .Values.frontend.name }} ports: - port: 9555 protocol: TCP egress: - {} {{- end }} {{- if .Values.sidecars.create }} --- apiVersion: networking.istio.io/v1beta1 kind: Sidecar metadata: name: {{ .Values.adService.name }} namespace: {{ .Release.Namespace }} spec: workloadSelector: labels: app: {{ .Values.adService.name }} egress: - hosts: - istio-system/* {{- if .Values.opentelemetryCollector.create }} - ./{{ .Values.opentelemetryCollector.name }}.{{ .Release.Namespace }}.svc.cluster.local {{- end }} {{- end }} {{- if .Values.authorizationPolicies.create }} --- apiVersion: security.istio.io/v1beta1 kind: AuthorizationPolicy metadata: name: {{ .Values.adService.name }} namespace: {{ .Release.Namespace }} spec: selector: matchLabels: app: {{ .Values.adService.name }} rules: - from: - source: principals: {{- if .Values.serviceAccounts.create }} - cluster.local/ns/{{ .Release.Namespace }}/sa/{{ .Values.frontend.name }} {{- else }} - cluster.local/ns/{{ .Release.Namespace }}/sa/default {{- end }} to: - operation: paths: - /hipstershop.AdService/GetAds methods: - POST ports: - "9555" {{- end }} {{- end }} ================================================ FILE: helm-chart/templates/cartservice.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 # # 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. {{- if .Values.cartService.create }} {{- if .Values.serviceAccounts.create }} apiVersion: v1 kind: ServiceAccount metadata: name: {{ .Values.cartService.name }} namespace: {{.Release.Namespace}} {{- with .Values.serviceAccounts.annotations }} annotations: {{- toYaml . | nindent 4 }} {{- end }} --- {{- end }} apiVersion: apps/v1 kind: Deployment metadata: name: {{ .Values.cartService.name }} namespace: {{ .Release.Namespace }} labels: app: {{ .Values.cartService.name }} spec: selector: matchLabels: app: {{ .Values.cartService.name }} template: metadata: {{- if .Values.cartDatabase.externalRedisTlsOrigination.enable }} annotations: sidecar.istio.io/userVolumeMount: '[{"name": "{{ .Values.cartDatabase.externalRedisTlsOrigination.name }}", "mountPath": "/etc/certs", "readonly": true}]' sidecar.istio.io/userVolume: '[{"name": "{{ .Values.cartDatabase.externalRedisTlsOrigination.name }}", "secret": {"secretName": "{{ .Values.cartDatabase.externalRedisTlsOrigination.name }}"}}]' proxy.istio.io/config: '{"holdApplicationUntilProxyStarts": true}' {{- end }} labels: app: {{ .Values.cartService.name }} spec: {{- if .Values.serviceAccounts.create }} serviceAccountName: {{ .Values.cartService.name }} {{- else }} serviceAccountName: default {{- end }} terminationGracePeriodSeconds: 5 {{- if .Values.securityContext.enable }} securityContext: fsGroup: 1000 runAsGroup: 1000 runAsNonRoot: true runAsUser: 1000 {{- end }} {{- if .Values.seccompProfile.enable }} seccompProfile: type: {{ .Values.seccompProfile.type }} {{- end }} containers: - name: server securityContext: allowPrivilegeEscalation: false capabilities: drop: - ALL privileged: false readOnlyRootFilesystem: true image: {{ .Values.images.repository }}/{{ .Values.cartService.name }}:{{ .Values.images.tag | default .Chart.AppVersion }} ports: - containerPort: 7070 env: {{- if eq .Values.cartDatabase.type "spanner" }} - name: SPANNER_CONNECTION_STRING {{- else }} - name: REDIS_ADDR {{- end }} value: {{ .Values.cartDatabase.connectionString | quote }} resources: {{- toYaml .Values.cartService.resources | nindent 10 }} readinessProbe: initialDelaySeconds: 15 grpc: port: 7070 livenessProbe: initialDelaySeconds: 15 periodSeconds: 10 grpc: port: 7070 --- apiVersion: v1 kind: Service metadata: name: {{ .Values.cartService.name }} namespace: {{ .Release.Namespace }} labels: app: {{ .Values.cartService.name }} spec: type: ClusterIP selector: app: {{ .Values.cartService.name }} ports: - name: grpc port: 7070 targetPort: 7070 {{- if .Values.networkPolicies.create }} --- apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: {{ .Values.cartService.name }} namespace: {{ .Release.Namespace }} spec: podSelector: matchLabels: app: {{ .Values.cartService.name }} policyTypes: - Ingress - Egress ingress: - from: - podSelector: matchLabels: app: {{ .Values.frontend.name }} - podSelector: matchLabels: app: {{ .Values.checkoutService.name }} ports: - port: 7070 protocol: TCP egress: - {} {{- end }} {{- if .Values.sidecars.create }} --- apiVersion: networking.istio.io/v1beta1 kind: Sidecar metadata: name: {{ .Values.cartService.name }} namespace: {{ .Release.Namespace }} spec: workloadSelector: labels: app: {{ .Values.cartService.name }} egress: - hosts: - istio-system/* {{- if eq .Values.cartDatabase.type "redis" }} {{- if .Values.cartDatabase.externalRedisTlsOrigination.enable }} - ./{{ .Values.cartDatabase.externalRedisTlsOrigination.name }}.{{ .Release.Namespace }} {{- else }} - ./{{ .Values.cartDatabase.inClusterRedis.name }}.{{ .Release.Namespace }}.svc.cluster.local {{- end }} {{- end }} {{- if .Values.opentelemetryCollector.create }} - ./{{ .Values.opentelemetryCollector.name }}.{{ .Release.Namespace }}.svc.cluster.local {{- end }} {{- end }} {{- if .Values.authorizationPolicies.create }} --- apiVersion: security.istio.io/v1beta1 kind: AuthorizationPolicy metadata: name: {{ .Values.cartService.name }} namespace: {{ .Release.Namespace }} spec: selector: matchLabels: app: {{ .Values.cartService.name }} rules: - from: - source: principals: {{- if .Values.serviceAccounts.create }} - cluster.local/ns/{{ .Release.Namespace }}/sa/{{ .Values.frontend.name }} - cluster.local/ns/{{ .Release.Namespace }}/sa/{{ .Values.checkoutService.name }} {{- else }} - cluster.local/ns/{{ .Release.Namespace }}/sa/default {{- end }} to: - operation: paths: - /hipstershop.CartService/AddItem - /hipstershop.CartService/GetCart - /hipstershop.CartService/EmptyCart methods: - POST ports: - "7070" {{- end }} {{- if .Values.cartDatabase.inClusterRedis.create }} --- {{- if .Values.serviceAccounts.create }} apiVersion: v1 kind: ServiceAccount metadata: name: {{ .Values.cartDatabase.inClusterRedis.name }} namespace: {{.Release.Namespace}} {{- if not .Values.serviceAccounts.annotationsOnlyForCartservice }} {{- with .Values.serviceAccounts.annotations }} annotations: {{- toYaml . | nindent 4 }} {{- end }} {{- end }} --- {{- end }} apiVersion: apps/v1 kind: Deployment metadata: name: {{ .Values.cartDatabase.inClusterRedis.name }} namespace: {{ .Release.Namespace }} spec: selector: matchLabels: app: {{ .Values.cartDatabase.inClusterRedis.name }} template: metadata: labels: app: {{ .Values.cartDatabase.inClusterRedis.name }} spec: {{- if .Values.serviceAccounts.create }} serviceAccountName: {{ .Values.cartDatabase.inClusterRedis.name }} {{- else }} serviceAccountName: default {{- end }} {{- if .Values.securityContext.enable }} securityContext: fsGroup: 1000 runAsGroup: 1000 runAsNonRoot: true runAsUser: 1000 {{- if .Values.seccompProfile.enable }} seccompProfile: type: {{ .Values.seccompProfile.type }} {{- end }} {{- end }} containers: - name: redis securityContext: allowPrivilegeEscalation: false capabilities: drop: - ALL privileged: false readOnlyRootFilesystem: true {{- if .Values.cartDatabase.inClusterRedis.publicRepository }} image: redis:alpine@sha256:2afba59292f25f5d1af200496db41bea2c6c816b059f57ae74703a50a03a27d0 {{- else }} image: {{ .Values.images.repository }}/redis:alpine {{- end }} ports: - containerPort: 6379 readinessProbe: periodSeconds: 5 tcpSocket: port: 6379 livenessProbe: periodSeconds: 5 tcpSocket: port: 6379 volumeMounts: - mountPath: /data name: redis-data resources: limits: memory: 256Mi cpu: 125m requests: cpu: 70m memory: 200Mi volumes: - name: redis-data emptyDir: {} --- apiVersion: v1 kind: Service metadata: name: {{ .Values.cartDatabase.inClusterRedis.name }} namespace: {{ .Release.Namespace }} spec: type: ClusterIP selector: app: {{ .Values.cartDatabase.inClusterRedis.name }} ports: - name: tcp-redis port: 6379 targetPort: 6379 {{- if .Values.networkPolicies.create }} --- apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: {{ .Values.cartDatabase.inClusterRedis.name }} namespace: {{ .Release.Namespace }} spec: podSelector: matchLabels: app: {{ .Values.cartDatabase.inClusterRedis.name }} policyTypes: - Ingress - Egress ingress: - from: - podSelector: matchLabels: app: {{ .Values.cartService.name }} ports: - port: 6379 protocol: TCP egress: - {} {{- end }} {{- if .Values.sidecars.create }} --- apiVersion: networking.istio.io/v1beta1 kind: Sidecar metadata: name: {{ .Values.cartDatabase.inClusterRedis.name }} namespace: {{ .Release.Namespace }} spec: workloadSelector: labels: app: {{ .Values.cartDatabase.inClusterRedis.name }} egress: - hosts: - istio-system/* {{- end }} {{- if .Values.authorizationPolicies.create }} --- apiVersion: security.istio.io/v1beta1 kind: AuthorizationPolicy metadata: name: {{ .Values.cartDatabase.inClusterRedis.name }} namespace: {{ .Release.Namespace }} spec: selector: matchLabels: app: {{ .Values.cartDatabase.inClusterRedis.name }} rules: - from: - source: principals: {{- if .Values.serviceAccounts.create }} - cluster.local/ns/{{ .Release.Namespace }}/sa/{{ .Values.cartService.name }} {{- else }} - cluster.local/ns/{{ .Release.Namespace }}/sa/default {{- end }} to: - operation: ports: - "6379" {{- end }} {{- end }} {{- if .Values.cartDatabase.externalRedisTlsOrigination.enable }} --- apiVersion: v1 data: {{ .Values.cartDatabase.externalRedisTlsOrigination.name }}.pem: {{ .Values.cartDatabase.externalRedisTlsOrigination.certificate | b64enc | quote }} kind: Secret metadata: name: {{ .Values.cartDatabase.externalRedisTlsOrigination.name }} namespace: {{ .Release.Namespace }} --- apiVersion: networking.istio.io/v1beta1 kind: DestinationRule metadata: name: {{ .Values.cartDatabase.externalRedisTlsOrigination.name }} namespace: {{ .Release.Namespace }} spec: exportTo: - '.' host: {{ .Values.cartDatabase.externalRedisTlsOrigination.name }}.{{ .Release.Namespace }} trafficPolicy: tls: mode: SIMPLE caCertificates: /etc/certs/{{ .Values.cartDatabase.externalRedisTlsOrigination.name }}.pem --- apiVersion: networking.istio.io/v1beta1 kind: ServiceEntry metadata: name: {{ .Values.cartDatabase.externalRedisTlsOrigination.name }} namespace: {{ .Release.Namespace }} spec: hosts: - {{ .Values.cartDatabase.externalRedisTlsOrigination.name }}.{{ .Release.Namespace }} addresses: - {{ .Values.cartDatabase.externalRedisTlsOrigination.endpointAddress }}/32 endpoints: - address: {{ .Values.cartDatabase.externalRedisTlsOrigination.endpointAddress }} location: MESH_EXTERNAL resolution: STATIC ports: - number: {{ .Values.cartDatabase.externalRedisTlsOrigination.endpointPort }} name: tcp-redis protocol: TCP {{- end }} {{- end }} ================================================ FILE: helm-chart/templates/checkoutservice.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 # # 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. {{- if .Values.checkoutService.create }} {{- if .Values.serviceAccounts.create }} apiVersion: v1 kind: ServiceAccount metadata: name: {{ .Values.checkoutService.name }} namespace: {{.Release.Namespace}} {{- if not .Values.serviceAccounts.annotationsOnlyForCartservice }} {{- with .Values.serviceAccounts.annotations }} annotations: {{- toYaml . | nindent 4 }} {{- end }} {{- end }} --- {{- end }} apiVersion: apps/v1 kind: Deployment metadata: name: {{ .Values.checkoutService.name }} namespace: {{ .Release.Namespace }} labels: app: {{ .Values.checkoutService.name }} spec: selector: matchLabels: app: {{ .Values.checkoutService.name }} template: metadata: labels: app: {{ .Values.checkoutService.name }} spec: {{- if .Values.serviceAccounts.create }} serviceAccountName: {{ .Values.checkoutService.name }} {{- else }} serviceAccountName: default {{- end }} {{- if .Values.securityContext.enable }} securityContext: fsGroup: 1000 runAsGroup: 1000 runAsNonRoot: true runAsUser: 1000 {{- if .Values.seccompProfile.enable }} seccompProfile: type: {{ .Values.seccompProfile.type }} {{- end }} {{- end }} containers: - name: server securityContext: allowPrivilegeEscalation: false capabilities: drop: - ALL privileged: false readOnlyRootFilesystem: true image: {{ .Values.images.repository }}/{{ .Values.checkoutService.name }}:{{ .Values.images.tag | default .Chart.AppVersion }} ports: - containerPort: 5050 readinessProbe: grpc: port: 5050 livenessProbe: grpc: port: 5050 env: - name: PORT value: "5050" - name: PRODUCT_CATALOG_SERVICE_ADDR value: "{{ .Values.productCatalogService.name }}:3550" - name: SHIPPING_SERVICE_ADDR value: "{{ .Values.shippingService.name }}:50051" - name: PAYMENT_SERVICE_ADDR value: "{{ .Values.paymentService.name }}:50051" - name: EMAIL_SERVICE_ADDR value: "{{ .Values.emailService.name }}:5000" - name: CURRENCY_SERVICE_ADDR value: "{{ .Values.currencyService.name }}:7000" - name: CART_SERVICE_ADDR value: "{{ .Values.cartService.name }}:7070" {{- if .Values.opentelemetryCollector.create }} - name: COLLECTOR_SERVICE_ADDR value: "{{ .Values.opentelemetryCollector.name }}:4317" - name: OTEL_SERVICE_NAME value: "{{ .Values.checkoutService.name }}" {{- end }} {{- if .Values.googleCloudOperations.tracing }} - name: ENABLE_TRACING value: "1" {{- end }} {{- if .Values.googleCloudOperations.profiler }} - name: ENABLE_PROFILER value: "1" {{- end }} resources: {{- toYaml .Values.checkoutService.resources | nindent 10 }} --- apiVersion: v1 kind: Service metadata: name: {{ .Values.checkoutService.name }} namespace: {{ .Release.Namespace }} labels: app: {{ .Values.checkoutService.name }} spec: type: ClusterIP selector: app: {{ .Values.checkoutService.name }} ports: - name: grpc port: 5050 targetPort: 5050 {{- if .Values.networkPolicies.create }} --- apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: {{ .Values.checkoutService.name }} namespace: {{ .Release.Namespace }} spec: podSelector: matchLabels: app: {{ .Values.checkoutService.name }} policyTypes: - Ingress - Egress ingress: - from: - podSelector: matchLabels: app: {{ .Values.frontend.name }} ports: - port: 5050 protocol: TCP egress: - {} {{- end }} {{- if .Values.sidecars.create }} --- apiVersion: networking.istio.io/v1beta1 kind: Sidecar metadata: name: {{ .Values.checkoutService.name }} namespace: {{ .Release.Namespace }} spec: workloadSelector: labels: app: {{ .Values.checkoutService.name }} egress: - hosts: - istio-system/* - ./{{ .Values.cartService.name }}.{{ .Release.Namespace }}.svc.cluster.local - ./{{ .Values.currencyService.name }}.{{ .Release.Namespace }}.svc.cluster.local - ./{{ .Values.emailService.name }}.{{ .Release.Namespace }}.svc.cluster.local - ./{{ .Values.paymentService.name }}.{{ .Release.Namespace }}.svc.cluster.local - ./{{ .Values.productCatalogService.name }}.{{ .Release.Namespace }}.svc.cluster.local - ./{{ .Values.shippingService.name }}.{{ .Release.Namespace }}.svc.cluster.local {{- if .Values.opentelemetryCollector.create }} - ./{{ .Values.opentelemetryCollector.name }}.{{ .Release.Namespace }}.svc.cluster.local {{- end }} {{- end }} {{- if .Values.authorizationPolicies.create }} --- apiVersion: security.istio.io/v1beta1 kind: AuthorizationPolicy metadata: name: {{ .Values.checkoutService.name }} namespace: {{ .Release.Namespace }} spec: selector: matchLabels: app: {{ .Values.checkoutService.name }} rules: - from: - source: principals: {{- if .Values.serviceAccounts.create }} - cluster.local/ns/{{ .Release.Namespace }}/sa/{{ .Values.frontend.name }} {{- else }} - cluster.local/ns/{{ .Release.Namespace }}/sa/default {{- end }} to: - operation: paths: - /hipstershop.CheckoutService/PlaceOrder methods: - POST ports: - "5050" {{- end }} {{- end }} ================================================ FILE: helm-chart/templates/common.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 # # 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. {{- if .Values.networkPolicies.create }} apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: deny-all namespace: {{ .Release.Namespace }} spec: podSelector: {} policyTypes: - Ingress - Egress {{- end }} {{- if .Values.authorizationPolicies.create }} --- apiVersion: security.istio.io/v1beta1 kind: AuthorizationPolicy metadata: name: deny-all namespace: {{ .Release.Namespace }} spec: {} {{- end }} ================================================ FILE: helm-chart/templates/currencyservice.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 # # 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. {{- if .Values.currencyService.create }} {{- if .Values.serviceAccounts.create }} apiVersion: v1 kind: ServiceAccount metadata: name: {{ .Values.currencyService.name }} namespace: {{.Release.Namespace}} {{- if not .Values.serviceAccounts.annotationsOnlyForCartservice }} {{- with .Values.serviceAccounts.annotations }} annotations: {{- toYaml . | nindent 4 }} {{- end }} {{- end }} --- {{- end }} apiVersion: apps/v1 kind: Deployment metadata: name: {{ .Values.currencyService.name }} namespace: {{ .Release.Namespace }} labels: app: {{ .Values.currencyService.name }} spec: selector: matchLabels: app: {{ .Values.currencyService.name }} template: metadata: labels: app: {{ .Values.currencyService.name }} spec: {{- if .Values.serviceAccounts.create }} serviceAccountName: {{ .Values.currencyService.name }} {{- else }} serviceAccountName: default {{- end }} terminationGracePeriodSeconds: 5 {{- if .Values.securityContext.enable }} securityContext: fsGroup: 1000 runAsGroup: 1000 runAsNonRoot: true runAsUser: 1000 {{- if .Values.seccompProfile.enable }} seccompProfile: type: {{ .Values.seccompProfile.type }} {{- end }} {{- end }} containers: - name: server securityContext: allowPrivilegeEscalation: false capabilities: drop: - ALL privileged: false readOnlyRootFilesystem: true image: {{ .Values.images.repository }}/{{ .Values.currencyService.name }}:{{ .Values.images.tag | default .Chart.AppVersion }} ports: - name: grpc containerPort: 7000 env: - name: PORT value: "7000" {{- if .Values.opentelemetryCollector.create }} - name: COLLECTOR_SERVICE_ADDR value: "{{ .Values.opentelemetryCollector.name }}:4317" - name: OTEL_SERVICE_NAME value: "{{ .Values.currencyService.name }}" {{- end }} {{- if .Values.googleCloudOperations.tracing }} - name: ENABLE_TRACING value: "1" {{- end }} {{- if not .Values.googleCloudOperations.profiler }} - name: DISABLE_PROFILER value: "1" {{- end }} readinessProbe: grpc: port: 7000 livenessProbe: grpc: port: 7000 resources: {{- toYaml .Values.currencyService.resources | nindent 10 }} --- apiVersion: v1 kind: Service metadata: name: {{ .Values.currencyService.name }} namespace: {{ .Release.Namespace }} labels: app: {{ .Values.currencyService.name }} spec: type: ClusterIP selector: app: {{ .Values.currencyService.name }} ports: - name: grpc port: 7000 targetPort: 7000 {{- if .Values.networkPolicies.create }} --- apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: {{ .Values.currencyService.name }} namespace: {{ .Release.Namespace }} spec: podSelector: matchLabels: app: {{ .Values.currencyService.name }} policyTypes: - Ingress - Egress ingress: - from: - podSelector: matchLabels: app: {{ .Values.frontend.name }} - podSelector: matchLabels: app: {{ .Values.checkoutService.name }} ports: - port: 7000 protocol: TCP egress: - {} {{- end }} {{- if .Values.sidecars.create }} --- apiVersion: networking.istio.io/v1beta1 kind: Sidecar metadata: name: {{ .Values.currencyService.name }} namespace: {{ .Release.Namespace }} spec: workloadSelector: labels: app: {{ .Values.currencyService.name }} egress: - hosts: - istio-system/* {{- if .Values.opentelemetryCollector.create }} - ./{{ .Values.opentelemetryCollector.name }}.{{ .Release.Namespace }}.svc.cluster.local {{- end }} {{- end }} {{- if .Values.authorizationPolicies.create }} --- apiVersion: security.istio.io/v1beta1 kind: AuthorizationPolicy metadata: name: {{ .Values.currencyService.name }} namespace: {{ .Release.Namespace }} spec: selector: matchLabels: app: {{ .Values.currencyService.name }} rules: - from: - source: principals: {{- if .Values.serviceAccounts.create }} - cluster.local/ns/{{ .Release.Namespace }}/sa/{{ .Values.frontend.name }} - cluster.local/ns/{{ .Release.Namespace }}/sa/{{ .Values.checkoutService.name }} {{- else }} - cluster.local/ns/{{ .Release.Namespace }}/sa/default {{- end }} to: - operation: paths: - /hipstershop.CurrencyService/Convert - /hipstershop.CurrencyService/GetSupportedCurrencies methods: - POST ports: - "7000" {{- end }} {{- end }} ================================================ FILE: helm-chart/templates/emailservice.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 # # 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. {{- if .Values.emailService.create }} {{- if .Values.serviceAccounts.create }} apiVersion: v1 kind: ServiceAccount metadata: name: {{ .Values.emailService.name }} namespace: {{.Release.Namespace}} {{- if not .Values.serviceAccounts.annotationsOnlyForCartservice }} {{- with .Values.serviceAccounts.annotations }} annotations: {{- toYaml . | nindent 4 }} {{- end }} {{- end }} --- {{- end }} apiVersion: apps/v1 kind: Deployment metadata: name: {{ .Values.emailService.name }} namespace: {{ .Release.Namespace }} labels: app: {{ .Values.emailService.name }} spec: selector: matchLabels: app: {{ .Values.emailService.name }} template: metadata: labels: app: {{ .Values.emailService.name }} spec: {{- if .Values.serviceAccounts.create }} serviceAccountName: {{ .Values.emailService.name }} {{- else }} serviceAccountName: default {{- end }} terminationGracePeriodSeconds: 5 {{- if .Values.securityContext.enable }} securityContext: fsGroup: 1000 runAsGroup: 1000 runAsNonRoot: true runAsUser: 1000 {{- if .Values.seccompProfile.enable }} seccompProfile: type: {{ .Values.seccompProfile.type }} {{- end }} {{- end }} containers: - name: server securityContext: allowPrivilegeEscalation: false capabilities: drop: - ALL privileged: false readOnlyRootFilesystem: true image: {{ .Values.images.repository }}/{{ .Values.emailService.name }}:{{ .Values.images.tag | default .Chart.AppVersion }} ports: - containerPort: 8080 env: - name: PORT value: "8080" {{- if .Values.opentelemetryCollector.create }} - name: COLLECTOR_SERVICE_ADDR value: "{{ .Values.opentelemetryCollector.name }}:4317" - name: OTEL_SERVICE_NAME value: "{{ .Values.emailService.name }}" {{- end }} {{- if .Values.googleCloudOperations.tracing }} - name: ENABLE_TRACING value: "1" {{- end }} {{- if not .Values.googleCloudOperations.profiler }} - name: DISABLE_PROFILER value: "1" {{- end }} readinessProbe: periodSeconds: 5 grpc: port: 8080 livenessProbe: periodSeconds: 5 grpc: port: 8080 resources: {{- toYaml .Values.emailService.resources | nindent 10 }} --- apiVersion: v1 kind: Service metadata: name: {{ .Values.emailService.name }} namespace: {{ .Release.Namespace }} labels: app: {{ .Values.emailService.name }} spec: type: ClusterIP selector: app: {{ .Values.emailService.name }} ports: - name: grpc port: 5000 targetPort: 8080 {{- if .Values.networkPolicies.create }} --- apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: {{ .Values.emailService.name }} namespace: {{ .Release.Namespace }} spec: podSelector: matchLabels: app: {{ .Values.emailService.name }} policyTypes: - Ingress - Egress ingress: - from: - podSelector: matchLabels: app: {{ .Values.checkoutService.name }} ports: - port: 8080 protocol: TCP egress: - {} {{- end }} {{- if .Values.sidecars.create }} --- apiVersion: networking.istio.io/v1beta1 kind: Sidecar metadata: name: {{ .Values.emailService.name }} namespace: {{ .Release.Namespace }} spec: workloadSelector: labels: app: {{ .Values.emailService.name }} egress: - hosts: - istio-system/* {{- if .Values.opentelemetryCollector.create }} - ./{{ .Values.opentelemetryCollector.name }}.{{ .Release.Namespace }}.svc.cluster.local {{- end }} {{- end }} {{- if .Values.authorizationPolicies.create }} --- apiVersion: security.istio.io/v1beta1 kind: AuthorizationPolicy metadata: name: {{ .Values.emailService.name }} namespace: {{ .Release.Namespace }} spec: selector: matchLabels: app: {{ .Values.emailService.name }} rules: - from: - source: principals: {{- if .Values.serviceAccounts.create }} - cluster.local/ns/{{ .Release.Namespace }}/sa/{{ .Values.checkoutService.name }} {{- else }} - cluster.local/ns/{{ .Release.Namespace }}/sa/default {{- end }} to: - operation: paths: - /hipstershop.EmailService/SendOrderConfirmation methods: - POST ports: - "8080" {{- end }} {{- end }} ================================================ FILE: helm-chart/templates/frontend.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 # # 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. {{- if .Values.frontend.create }} {{- if .Values.serviceAccounts.create }} apiVersion: v1 kind: ServiceAccount metadata: name: {{ .Values.frontend.name }} namespace: {{.Release.Namespace}} {{- if not .Values.serviceAccounts.annotationsOnlyForCartservice }} {{- with .Values.serviceAccounts.annotations }} annotations: {{- toYaml . | nindent 4 }} {{- end }} {{- end }} --- {{- end }} apiVersion: apps/v1 kind: Deployment metadata: name: {{ .Values.frontend.name }} namespace: {{ .Release.Namespace }} labels: app: {{ .Values.frontend.name }} spec: selector: matchLabels: app: {{ .Values.frontend.name }} template: metadata: labels: app: {{ .Values.frontend.name }} annotations: sidecar.istio.io/rewriteAppHTTPProbers: "true" spec: {{- if .Values.serviceAccounts.create }} serviceAccountName: {{ .Values.frontend.name }} {{- else }} serviceAccountName: default {{- end }} {{- if .Values.securityContext.enable }} securityContext: fsGroup: 1000 runAsGroup: 1000 runAsNonRoot: true runAsUser: 1000 {{- if .Values.seccompProfile.enable }} seccompProfile: type: {{ .Values.seccompProfile.type }} {{- end }} {{- end }} containers: - name: server securityContext: allowPrivilegeEscalation: false capabilities: drop: - ALL privileged: false readOnlyRootFilesystem: true image: {{ .Values.images.repository }}/{{ .Values.frontend.name }}:{{ .Values.images.tag | default .Chart.AppVersion }} ports: - containerPort: 8080 readinessProbe: initialDelaySeconds: 10 httpGet: path: "/_healthz" port: 8080 httpHeaders: - name: "Cookie" value: "shop_session-id=x-readiness-probe" livenessProbe: initialDelaySeconds: 10 httpGet: path: "/_healthz" port: 8080 httpHeaders: - name: "Cookie" value: "shop_session-id=x-liveness-probe" env: - name: PORT value: "8080" - name: PRODUCT_CATALOG_SERVICE_ADDR value: "{{ .Values.productCatalogService.name }}:3550" - name: CURRENCY_SERVICE_ADDR value: "{{ .Values.currencyService.name }}:7000" - name: CART_SERVICE_ADDR value: "{{ .Values.cartService.name }}:7070" - name: RECOMMENDATION_SERVICE_ADDR value: "{{ .Values.recommendationService.name }}:8080" - name: SHIPPING_SERVICE_ADDR value: "{{ .Values.shippingService.name }}:50051" - name: CHECKOUT_SERVICE_ADDR value: "{{ .Values.checkoutService.name }}:5050" - name: AD_SERVICE_ADDR value: "{{ .Values.adService.name }}:9555" - name: SHOPPING_ASSISTANT_SERVICE_ADDR value: "{{ .Values.shoppingAssistantService.name }}:80" - name: ENV_PLATFORM value: {{ .Values.frontend.platform | quote }} {{- if .Values.opentelemetryCollector.create }} - name: COLLECTOR_SERVICE_ADDR value: "{{ .Values.opentelemetryCollector.name }}:4317" - name: OTEL_SERVICE_NAME value: "{{ .Values.frontend.name }}" {{- end }} {{- if .Values.googleCloudOperations.tracing }} - name: ENABLE_TRACING value: "1" {{- end }} {{- if .Values.googleCloudOperations.profiler }} - name: ENABLE_PROFILER value: "1" {{- end }} - name: CYMBAL_BRANDING value: {{ .Values.frontend.cymbalBranding | quote }} - name: ENABLE_ASSISTANT value: {{ .Values.shoppingAssistantService.create | quote }} - name: ENABLE_SINGLE_SHARED_SESSION value: {{ .Values.frontend.singleSharedSession | quote }} resources: {{- toYaml .Values.frontend.resources | nindent 12 }} --- apiVersion: v1 kind: Service metadata: name: {{ .Values.frontend.name }} namespace: {{ .Release.Namespace }} labels: app: {{ .Values.frontend.name }} spec: type: ClusterIP selector: app: {{ .Values.frontend.name }} ports: - name: http port: 80 targetPort: 8080 {{- if .Values.frontend.externalService }} --- apiVersion: v1 kind: Service metadata: name: {{ .Values.frontend.name }}-external namespace: {{ .Release.Namespace }} spec: type: LoadBalancer selector: app: {{ .Values.frontend.name }} ports: - name: http port: 80 targetPort: 8080 {{- end }} {{- if .Values.networkPolicies.create }} --- apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: {{ .Values.frontend.name }} namespace: {{ .Release.Namespace }} spec: podSelector: matchLabels: app: {{ .Values.frontend.name }} policyTypes: - Ingress - Egress ingress: {{- if .Values.frontend.externalService }} - {} {{- else }} - from: - podSelector: matchLabels: app: {{ .Values.loadGenerator.name }} {{- if .Values.frontend.virtualService.create }} - namespaceSelector: matchLabels: kubernetes.io/metadata.name: {{ .Values.frontend.virtualService.gateway.namespace }} podSelector: matchLabels: {{ .Values.frontend.virtualService.gateway.labelKey }}: {{ .Values.frontend.virtualService.gateway.labelValue }} {{- end }} ports: - port: 8080 protocol: TCP {{- end }} egress: - {} {{- end }} {{- if .Values.sidecars.create }} --- apiVersion: networking.istio.io/v1beta1 kind: Sidecar metadata: name: {{ .Values.frontend.name }} namespace: {{ .Release.Namespace }} spec: workloadSelector: labels: app: {{ .Values.frontend.name }} egress: - hosts: - istio-system/* - ./{{ .Values.adService.name }}.{{ .Release.Namespace }}.svc.cluster.local - ./{{ .Values.cartService.name }}.{{ .Release.Namespace }}.svc.cluster.local - ./{{ .Values.checkoutService.name }}.{{ .Release.Namespace }}.svc.cluster.local - ./{{ .Values.currencyService.name }}.{{ .Release.Namespace }}.svc.cluster.local - ./{{ .Values.productCatalogService.name }}.{{ .Release.Namespace }}.svc.cluster.local - ./{{ .Values.recommendationService.name }}.{{ .Release.Namespace }}.svc.cluster.local - ./{{ .Values.shippingService.name }}.{{ .Release.Namespace }}.svc.cluster.local {{- if .Values.opentelemetryCollector.create }} - ./{{ .Values.opentelemetryCollector.name }}.{{ .Release.Namespace }}.svc.cluster.local {{- end }} {{- end }} {{- if .Values.authorizationPolicies.create }} --- apiVersion: security.istio.io/v1beta1 kind: AuthorizationPolicy metadata: name: {{ .Values.frontend.name }} namespace: {{ .Release.Namespace }} spec: selector: matchLabels: app: {{ .Values.frontend.name }} rules: {{- if .Values.frontend.externalService }} - to: {{- else }} - from: - source: principals: {{- if .Values.serviceAccounts.create }} - cluster.local/ns/{{ .Release.Namespace }}/sa/{{ .Values.loadGenerator.name }} {{- else }} - cluster.local/ns/{{ .Release.Namespace }}/sa/default {{- end }} {{- if .Values.frontend.virtualService.create }} - cluster.local/ns/{{ .Values.frontend.virtualService.gateway.namespace }}/sa/{{ .Values.frontend.virtualService.gateway.name }} {{- end }} to: {{- end }} - operation: methods: - GET - POST ports: - "8080" {{- end }} {{- if .Values.frontend.virtualService.create }} --- apiVersion: networking.istio.io/v1alpha3 kind: VirtualService metadata: name: {{ .Values.frontend.name }} namespace: {{ .Release.Namespace }} spec: {{- with .Values.frontend.virtualService.hosts }} hosts: {{- toYaml . | nindent 2 }} {{- end }} gateways: - {{ .Values.frontend.virtualService.gateway.namespace }}/{{ .Values.frontend.virtualService.gateway.name }} http: - route: - destination: host: {{ .Values.frontend.name }} port: number: 80 {{- end }} {{- end }} ================================================ FILE: helm-chart/templates/loadgenerator.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. {{- if .Values.loadGenerator.create }} {{- if .Values.serviceAccounts.create }} apiVersion: v1 kind: ServiceAccount metadata: name: {{ .Values.loadGenerator.name }} namespace: {{.Release.Namespace}} {{- if not .Values.serviceAccounts.annotationsOnlyForCartservice }} {{- with .Values.serviceAccounts.annotations }} annotations: {{- toYaml . | nindent 4 }} {{- end }} {{- end }} --- {{- end }} apiVersion: apps/v1 kind: Deployment metadata: name: {{ .Values.loadGenerator.name }} namespace: {{ .Release.Namespace }} labels: app: {{ .Values.loadGenerator.name }} spec: selector: matchLabels: app: {{ .Values.loadGenerator.name }} replicas: 1 template: metadata: labels: app: {{ .Values.loadGenerator.name }} annotations: sidecar.istio.io/rewriteAppHTTPProbers: "true" spec: {{- if .Values.serviceAccounts.create }} serviceAccountName: {{ .Values.loadGenerator.name }} {{- else }} serviceAccountName: default {{- end }} terminationGracePeriodSeconds: 5 restartPolicy: Always {{- if .Values.securityContext.enable }} securityContext: fsGroup: 1000 runAsGroup: 1000 runAsNonRoot: true runAsUser: 1000 {{- if .Values.seccompProfile.enable }} seccompProfile: type: {{ .Values.seccompProfile.type }} {{- end }} {{- end }} {{- if .Values.loadGenerator.checkFrontendInitContainer }} initContainers: - command: - /bin/sh - -exc - | MAX_RETRIES=12 RETRY_INTERVAL=10 for i in $(seq 1 $MAX_RETRIES); do echo "Attempt $i: Pinging frontend: ${FRONTEND_ADDR}..." STATUSCODE=$(wget --server-response http://${FRONTEND_ADDR} 2>&1 | awk '/^ HTTP/{print $2}') if [ $STATUSCODE -eq 200 ]; then echo "Frontend is reachable." exit 0 fi echo "Error: Could not reach frontend - Status code: ${STATUSCODE}" sleep $RETRY_INTERVAL done echo "Failed to reach frontend after $MAX_RETRIES attempts." exit 1 name: frontend-check securityContext: allowPrivilegeEscalation: false capabilities: drop: - ALL privileged: false readOnlyRootFilesystem: true image: busybox:latest@sha256:b3255e7dfbcd10cb367af0d409747d511aeb66dfac98cf30e97e87e4207dd76f env: - name: FRONTEND_ADDR value: "{{ .Values.frontend.name }}:80" {{- end }} containers: - name: main securityContext: allowPrivilegeEscalation: false capabilities: drop: - ALL privileged: false readOnlyRootFilesystem: true image: {{ .Values.images.repository }}/{{ .Values.loadGenerator.name }}:{{ .Values.images.tag | default .Chart.AppVersion }} env: - name: FRONTEND_ADDR value: "{{ .Values.frontend.name }}:80" - name: USERS value: "10" - name: RATE value: "1" resources: {{- toYaml .Values.loadGenerator.resources | nindent 10 }} {{- if .Values.networkPolicies.create }} --- apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: {{ .Values.loadGenerator.name }} namespace: {{ .Release.Namespace }} labels: app: {{ .Values.loadGenerator.name }} spec: podSelector: matchLabels: app: {{ .Values.loadGenerator.name }} policyTypes: - Egress egress: - {} {{- end }} {{- if .Values.sidecars.create }} --- apiVersion: networking.istio.io/v1beta1 kind: Sidecar metadata: name: {{ .Values.loadGenerator.name }} namespace: {{ .Release.Namespace }} spec: workloadSelector: labels: app: {{ .Values.loadGenerator.name }} egress: - hosts: - istio-system/* - ./{{ .Values.frontend.name }}.{{ .Release.Namespace }}.svc.cluster.local {{- if .Values.opentelemetryCollector.create }} - ./{{ .Values.opentelemetryCollector.name }}.{{ .Release.Namespace }}.svc.cluster.local {{- end }} {{- end }} {{- end }} ================================================ FILE: helm-chart/templates/opentelemetry-collector.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. {{- if .Values.opentelemetryCollector.create }} {{- if .Values.serviceAccounts.create }} apiVersion: v1 kind: ServiceAccount metadata: name: {{ .Values.opentelemetryCollector.name }} namespace: {{.Release.Namespace}} {{- if not .Values.serviceAccounts.annotationsOnlyForCartservice }} {{- with .Values.serviceAccounts.annotations }} annotations: {{- toYaml . | nindent 4 }} {{- end }} {{- end }} --- {{- end }} apiVersion: apps/v1 kind: Deployment metadata: name: {{ .Values.opentelemetryCollector.name }} namespace: {{ .Release.Namespace }} spec: replicas: 1 selector: matchLabels: app: {{ .Values.opentelemetryCollector.name }} template: metadata: labels: app: {{ .Values.opentelemetryCollector.name }} spec: {{- if .Values.serviceAccounts.create }} serviceAccountName: {{ .Values.opentelemetryCollector.name }} {{- else }} serviceAccountName: default {{- end }} {{- if .Values.securityContext.enable }} securityContext: fsGroup: 1000 runAsGroup: 1000 runAsNonRoot: true runAsUser: 1000 {{- if .Values.seccompProfile.enable }} seccompProfile: type: {{ .Values.seccompProfile.type }} {{- end }} {{- end }} {{- if eq .Values.opentelemetryCollector.projectId "PROJECT_ID" }} initContainers: # Init container retrieves the current cloud project id from the metadata server # and inserts it into the collector config template # https://cloud.google.com/compute/docs/storing-retrieving-metadata - name: otel-gateway-init securityContext: allowPrivilegeEscalation: false capabilities: drop: - ALL privileged: false readOnlyRootFilesystem: true image: busybox:latest@sha256:b3255e7dfbcd10cb367af0d409747d511aeb66dfac98cf30e97e87e4207dd76f command: - '/bin/sh' - '-c' - | sed "s/PROJECT_ID/$(curl -H 'Metadata-Flavor: Google' http://metadata.google.internal/computeMetadata/v1/project/project-id)/" /template/collector-gateway-config-template.yaml >> /conf/collector-gateway-config.yaml volumeMounts: - name: collector-gateway-config-template mountPath: /template - name: collector-gateway-config mountPath: /conf {{- end }} containers: # This gateway container will receive traces and metrics from each microservice # and forward it to GCP - name: otel-gateway securityContext: allowPrivilegeEscalation: false capabilities: drop: - ALL privileged: false readOnlyRootFilesystem: true args: - --config=/conf/collector-gateway-config.yaml image: otel/opentelemetry-collector-contrib:0.147.0@sha256:e7c92c715f28ff142f3bcaccd4fc5603cf4c71276ef09954a38eb4038500a5a5 volumeMounts: - name: collector-gateway-config mountPath: /conf volumes: # Simple ConfigMap volume with template file - name: collector-gateway-config-template configMap: items: - key: collector-gateway-config-template.yaml path: collector-gateway-config-template.yaml name: collector-gateway-config-template # Create a volume to store the expanded template (with correct cloud project ID) - name: collector-gateway-config emptyDir: {} --- apiVersion: v1 kind: Service metadata: name: {{ .Values.opentelemetryCollector.name }} namespace: {{ .Release.Namespace }} spec: ports: - name: grpc-otlp port: 4317 protocol: TCP targetPort: 4317 selector: app: {{ .Values.opentelemetryCollector.name }} type: ClusterIP --- apiVersion: v1 kind: ConfigMap metadata: name: collector-gateway-config-template namespace: {{ .Release.Namespace }} # Open Telemetry Collector config # https://opentelemetry.io/docs/collector/configuration/ data: collector-gateway-config-template.yaml: | receivers: otlp: protocols: grpc: processors: exporters: googlecloud: project: {{ .Values.opentelemetryCollector.projectId | quote }} service: pipelines: traces: receivers: [otlp] # Receive otlp-formatted data from other collector instances processors: [] exporters: [googlecloud] # Export traces directly to Google Cloud metrics: receivers: [otlp] processors: [] exporters: [googlecloud] # Export metrics to Google Cloud {{- if .Values.networkPolicies.create }} --- apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: {{ .Values.opentelemetryCollector.name }} namespace: {{ .Release.Namespace }} spec: podSelector: matchLabels: app: {{ .Values.opentelemetryCollector.name }} policyTypes: - Ingress - Egress ingress: - from: - podSelector: matchLabels: app: {{ .Values.adService.name }} - podSelector: matchLabels: app: {{ .Values.cartService.name }} - podSelector: matchLabels: app: {{ .Values.checkoutService.name }} - podSelector: matchLabels: app: {{ .Values.currencyService.name }} - podSelector: matchLabels: app: {{ .Values.emailService.name }} - podSelector: matchLabels: app: {{ .Values.frontend.name }} - podSelector: matchLabels: app: {{ .Values.loadGenerator.name }} - podSelector: matchLabels: app: {{ .Values.paymentService.name }} - podSelector: matchLabels: app: {{ .Values.productCatalogService.name }} - podSelector: matchLabels: app: {{ .Values.recommendationService.name }} - podSelector: matchLabels: app: {{ .Values.shippingService.name }} ports: - port: 4317 protocol: TCP egress: - {} {{- end }} {{- if .Values.sidecars.create }} --- apiVersion: networking.istio.io/v1beta1 kind: Sidecar metadata: name: {{ .Values.opentelemetryCollector.name }} namespace: {{ .Release.Namespace }} spec: workloadSelector: labels: app: {{ .Values.opentelemetryCollector.name }} egress: - hosts: - istio-system/* {{- end }} {{- if .Values.authorizationPolicies.create }} --- apiVersion: security.istio.io/v1beta1 kind: AuthorizationPolicy metadata: name: {{ .Values.opentelemetryCollector.name }} namespace: {{ .Release.Namespace }} spec: selector: matchLabels: app: {{ .Values.opentelemetryCollector.name }} rules: - from: - source: principals: {{- if .Values.serviceAccounts.create }} - cluster.local/ns/{{ .Release.Namespace }}/sa/{{ .Values.adService.name }} - cluster.local/ns/{{ .Release.Namespace }}/sa/{{ .Values.cartService.name }} - cluster.local/ns/{{ .Release.Namespace }}/sa/{{ .Values.checkoutService.name }} - cluster.local/ns/{{ .Release.Namespace }}/sa/{{ .Values.currencyService.name }} - cluster.local/ns/{{ .Release.Namespace }}/sa/{{ .Values.emailService.name }} - cluster.local/ns/{{ .Release.Namespace }}/sa/{{ .Values.frontend.name }} - cluster.local/ns/{{ .Release.Namespace }}/sa/{{ .Values.loadGenerator.name }} - cluster.local/ns/{{ .Release.Namespace }}/sa/{{ .Values.paymentService.name }} - cluster.local/ns/{{ .Release.Namespace }}/sa/{{ .Values.productCatalogService.name }} - cluster.local/ns/{{ .Release.Namespace }}/sa/{{ .Values.recommendationService.name }} - cluster.local/ns/{{ .Release.Namespace }}/sa/{{ .Values.shippingService.name }} {{- else }} - cluster.local/ns/{{ .Release.Namespace }}/sa/default {{- end }} to: - operation: ports: - "4317" {{- end }} {{- end }} ================================================ FILE: helm-chart/templates/paymentservice.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 # # 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. {{- if .Values.paymentService.create }} {{- if .Values.serviceAccounts.create }} apiVersion: v1 kind: ServiceAccount metadata: name: {{ .Values.paymentService.name }} namespace: {{.Release.Namespace}} {{- if not .Values.serviceAccounts.annotationsOnlyForCartservice }} {{- with .Values.serviceAccounts.annotations }} annotations: {{- toYaml . | nindent 4 }} {{- end }} {{- end }} --- {{- end }} apiVersion: apps/v1 kind: Deployment metadata: name: {{ .Values.paymentService.name }} namespace: {{ .Release.Namespace }} labels: app: {{ .Values.paymentService.name }} spec: selector: matchLabels: app: {{ .Values.paymentService.name }} template: metadata: labels: app: {{ .Values.paymentService.name }} spec: {{- if .Values.serviceAccounts.create }} serviceAccountName: {{ .Values.paymentService.name }} {{- else }} serviceAccountName: default {{- end }} terminationGracePeriodSeconds: 5 {{- if .Values.securityContext.enable }} securityContext: fsGroup: 1000 runAsGroup: 1000 runAsNonRoot: true runAsUser: 1000 {{- if .Values.seccompProfile.enable }} seccompProfile: type: {{ .Values.seccompProfile.type }} {{- end }} {{- end }} containers: - name: server securityContext: allowPrivilegeEscalation: false capabilities: drop: - ALL privileged: false readOnlyRootFilesystem: true image: {{ .Values.images.repository }}/{{ .Values.paymentService.name }}:{{ .Values.images.tag | default .Chart.AppVersion }} ports: - containerPort: 50051 env: - name: PORT value: "50051" {{- if .Values.opentelemetryCollector.create }} - name: COLLECTOR_SERVICE_ADDR value: "{{ .Values.opentelemetryCollector.name }}:4317" - name: OTEL_SERVICE_NAME value: "{{ .Values.paymentService.name }}" {{- end }} {{- if .Values.googleCloudOperations.tracing }} - name: ENABLE_TRACING value: "1" {{- end }} {{- if not .Values.googleCloudOperations.profiler }} - name: DISABLE_PROFILER value: "1" {{- end }} readinessProbe: grpc: port: 50051 livenessProbe: grpc: port: 50051 resources: {{- toYaml .Values.paymentService.resources | nindent 10 }} --- apiVersion: v1 kind: Service metadata: name: {{ .Values.paymentService.name }} namespace: {{ .Release.Namespace }} labels: app: {{ .Values.paymentService.name }} spec: type: ClusterIP selector: app: {{ .Values.paymentService.name }} ports: - name: grpc port: 50051 targetPort: 50051 {{- if .Values.networkPolicies.create }} --- apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: {{ .Values.paymentService.name }} namespace: {{ .Release.Namespace }} spec: podSelector: matchLabels: app: {{ .Values.paymentService.name }} policyTypes: - Ingress - Egress ingress: - from: - podSelector: matchLabels: app: {{ .Values.checkoutService.name }} ports: - port: 50051 protocol: TCP egress: - {} {{- end }} {{- if .Values.sidecars.create }} --- apiVersion: networking.istio.io/v1beta1 kind: Sidecar metadata: name: {{ .Values.paymentService.name }} namespace: {{ .Release.Namespace }} spec: workloadSelector: labels: app: {{ .Values.paymentService.name }} egress: - hosts: - istio-system/* {{- if .Values.opentelemetryCollector.create }} - ./{{ .Values.opentelemetryCollector.name }}.{{ .Release.Namespace }}.svc.cluster.local {{- end }} {{- end }} {{- if .Values.authorizationPolicies.create }} --- apiVersion: security.istio.io/v1beta1 kind: AuthorizationPolicy metadata: name: {{ .Values.paymentService.name }} namespace: {{ .Release.Namespace }} spec: selector: matchLabels: app: {{ .Values.paymentService.name }} rules: - from: - source: principals: {{- if .Values.serviceAccounts.create }} - cluster.local/ns/{{ .Release.Namespace }}/sa/{{ .Values.checkoutService.name }} {{- else }} - cluster.local/ns/{{ .Release.Namespace }}/sa/default {{- end }} to: - operation: paths: - /hipstershop.PaymentService/Charge methods: - POST ports: - "50051" {{- end }} {{- end }} ================================================ FILE: helm-chart/templates/productcatalogservice.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 # # 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. {{- if .Values.productCatalogService.create }} {{- if .Values.serviceAccounts.create }} apiVersion: v1 kind: ServiceAccount metadata: name: {{ .Values.productCatalogService.name }} namespace: {{.Release.Namespace}} {{- if not .Values.serviceAccounts.annotationsOnlyForCartservice }} {{- with .Values.serviceAccounts.annotations }} annotations: {{- toYaml . | nindent 4 }} {{- end }} {{- end }} --- {{- end }} apiVersion: apps/v1 kind: Deployment metadata: name: {{ .Values.productCatalogService.name }} namespace: {{ .Release.Namespace }} labels: app: {{ .Values.productCatalogService.name }} spec: selector: matchLabels: app: {{ .Values.productCatalogService.name }} template: metadata: labels: app: {{ .Values.productCatalogService.name }} spec: {{- if .Values.serviceAccounts.create }} serviceAccountName: {{ .Values.productCatalogService.name }} {{- else }} serviceAccountName: default {{- end }} terminationGracePeriodSeconds: 5 {{- if .Values.securityContext.enable }} securityContext: fsGroup: 1000 runAsGroup: 1000 runAsNonRoot: true runAsUser: 1000 {{- if .Values.seccompProfile.enable }} seccompProfile: type: {{ .Values.seccompProfile.type }} {{- end }} {{- end }} containers: - name: server securityContext: allowPrivilegeEscalation: false capabilities: drop: - ALL privileged: false readOnlyRootFilesystem: true image: {{ .Values.images.repository }}/{{ .Values.productCatalogService.name }}:{{ .Values.images.tag | default .Chart.AppVersion }} ports: - containerPort: 3550 env: - name: PORT value: "3550" {{- if .Values.opentelemetryCollector.create }} - name: COLLECTOR_SERVICE_ADDR value: "{{ .Values.opentelemetryCollector.name }}:4317" - name: OTEL_SERVICE_NAME value: "{{ .Values.productCatalogService.name }}" {{- end }} {{- if .Values.googleCloudOperations.tracing }} - name: ENABLE_TRACING value: "1" {{- end }} {{- if not .Values.googleCloudOperations.profiler }} - name: DISABLE_PROFILER value: "1" {{- end }} - name: EXTRA_LATENCY value: {{ .Values.productCatalogService.extraLatency }} readinessProbe: grpc: port: 3550 livenessProbe: grpc: port: 3550 resources: {{- toYaml .Values.productCatalogService.resources | nindent 10 }} --- apiVersion: v1 kind: Service metadata: name: {{ .Values.productCatalogService.name }} namespace: {{ .Release.Namespace }} labels: app: {{ .Values.productCatalogService.name }} spec: type: ClusterIP selector: app: {{ .Values.productCatalogService.name }} ports: - name: grpc port: 3550 targetPort: 3550 {{- if .Values.networkPolicies.create }} --- apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: {{ .Values.productCatalogService.name }} namespace: {{ .Release.Namespace }} spec: podSelector: matchLabels: app: {{ .Values.productCatalogService.name }} policyTypes: - Ingress - Egress ingress: - from: - podSelector: matchLabels: app: {{ .Values.frontend.name }} - podSelector: matchLabels: app: {{ .Values.checkoutService.name }} - podSelector: matchLabels: app: {{ .Values.recommendationService.name }} ports: - port: 3550 protocol: TCP egress: - {} {{- end }} {{- if .Values.sidecars.create }} --- apiVersion: networking.istio.io/v1beta1 kind: Sidecar metadata: name: {{ .Values.productCatalogService.name }} namespace: {{ .Release.Namespace }} spec: workloadSelector: labels: app: {{ .Values.productCatalogService.name }} egress: - hosts: - istio-system/* {{- if .Values.opentelemetryCollector.create }} - ./{{ .Values.opentelemetryCollector.name }}.{{ .Release.Namespace }}.svc.cluster.local {{- end }} {{- end }} {{- if .Values.authorizationPolicies.create }} --- apiVersion: security.istio.io/v1beta1 kind: AuthorizationPolicy metadata: name: {{ .Values.productCatalogService.name }} namespace: {{ .Release.Namespace }} spec: selector: matchLabels: app: {{ .Values.productCatalogService.name }} rules: - from: - source: principals: {{- if .Values.serviceAccounts.create }} - cluster.local/ns/{{ .Release.Namespace }}/sa/{{ .Values.frontend.name }} - cluster.local/ns/{{ .Release.Namespace }}/sa/{{ .Values.checkoutService.name }} - cluster.local/ns/{{ .Release.Namespace }}/sa/{{ .Values.recommendationService.name }} {{- else }} - cluster.local/ns/{{ .Release.Namespace }}/sa/default {{- end }} to: - operation: paths: - /hipstershop.ProductCatalogService/GetProduct - /hipstershop.ProductCatalogService/ListProducts methods: - POST ports: - "3550" {{- end }} {{- end }} ================================================ FILE: helm-chart/templates/recommendationservice.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 # # 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. {{- if .Values.recommendationService.create }} {{- if .Values.serviceAccounts.create }} apiVersion: v1 kind: ServiceAccount metadata: name: {{ .Values.recommendationService.name }} namespace: {{.Release.Namespace}} {{- if not .Values.serviceAccounts.annotationsOnlyForCartservice }} {{- with .Values.serviceAccounts.annotations }} annotations: {{- toYaml . | nindent 4 }} {{- end }} {{- end }} --- {{- end }} apiVersion: apps/v1 kind: Deployment metadata: name: {{ .Values.recommendationService.name }} namespace: {{ .Release.Namespace }} labels: app: {{ .Values.recommendationService.name }} spec: selector: matchLabels: app: {{ .Values.recommendationService.name }} template: metadata: labels: app: {{ .Values.recommendationService.name }} spec: {{- if .Values.serviceAccounts.create }} serviceAccountName: {{ .Values.recommendationService.name }} {{- else }} serviceAccountName: default {{- end }} terminationGracePeriodSeconds: 5 {{- if .Values.securityContext.enable }} securityContext: fsGroup: 1000 runAsGroup: 1000 runAsNonRoot: true runAsUser: 1000 {{- if .Values.seccompProfile.enable }} seccompProfile: type: {{ .Values.seccompProfile.type }} {{- end }} {{- end }} containers: - name: server securityContext: allowPrivilegeEscalation: false capabilities: drop: - ALL privileged: false readOnlyRootFilesystem: true image: {{ .Values.images.repository }}/{{ .Values.recommendationService.name }}:{{ .Values.images.tag | default .Chart.AppVersion }} ports: - containerPort: 8080 readinessProbe: periodSeconds: 5 grpc: port: 8080 livenessProbe: periodSeconds: 5 grpc: port: 8080 env: - name: PORT value: "8080" - name: PRODUCT_CATALOG_SERVICE_ADDR value: "{{ .Values.productCatalogService.name }}:3550" {{- if .Values.opentelemetryCollector.create }} - name: COLLECTOR_SERVICE_ADDR value: "{{ .Values.opentelemetryCollector.name }}:4317" - name: OTEL_SERVICE_NAME value: "{{ .Values.recommendationService.name }}" {{- end }} {{- if .Values.googleCloudOperations.tracing }} - name: ENABLE_TRACING value: "1" {{- end }} {{- if not .Values.googleCloudOperations.profiler }} - name: DISABLE_PROFILER value: "1" {{- end }} resources: {{- toYaml .Values.recommendationService.resources | nindent 10 }} --- apiVersion: v1 kind: Service metadata: name: {{ .Values.recommendationService.name }} namespace: {{ .Release.Namespace }} labels: app: {{ .Values.recommendationService.name }} spec: type: ClusterIP selector: app: {{ .Values.recommendationService.name }} ports: - name: grpc port: 8080 targetPort: 8080 {{- if .Values.networkPolicies.create }} --- apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: {{ .Values.recommendationService.name }} namespace: {{ .Release.Namespace }} spec: podSelector: matchLabels: app: {{ .Values.recommendationService.name }} policyTypes: - Ingress - Egress ingress: - from: - podSelector: matchLabels: app: {{ .Values.frontend.name }} ports: - port: 8080 protocol: TCP egress: - {} {{- end }} {{- if .Values.sidecars.create }} --- apiVersion: networking.istio.io/v1beta1 kind: Sidecar metadata: name: {{ .Values.recommendationService.name }} namespace: {{ .Release.Namespace }} spec: workloadSelector: labels: app: {{ .Values.recommendationService.name }} egress: - hosts: - istio-system/* - ./{{ .Values.productCatalogService.name }}.{{ .Release.Namespace }}.svc.cluster.local {{- if .Values.opentelemetryCollector.create }} - ./{{ .Values.opentelemetryCollector.name }}.{{ .Release.Namespace }}.svc.cluster.local {{- end }} {{- end }} {{- if .Values.authorizationPolicies.create }} --- apiVersion: security.istio.io/v1beta1 kind: AuthorizationPolicy metadata: name: {{ .Values.recommendationService.name }} namespace: {{ .Release.Namespace }} spec: selector: matchLabels: app: {{ .Values.recommendationService.name }} rules: - from: - source: principals: {{- if .Values.serviceAccounts.create }} - cluster.local/ns/{{ .Release.Namespace }}/sa/{{ .Values.frontend.name }} {{- else }} - cluster.local/ns/{{ .Release.Namespace }}/sa/default {{- end }} to: - operation: paths: - /hipstershop.RecommendationService/ListRecommendations methods: - POST ports: - "8080" {{- end }} {{- end }} ================================================ FILE: helm-chart/templates/shippingservice.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 # # 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. {{- if .Values.shippingService.create }} {{- if .Values.serviceAccounts.create }} apiVersion: v1 kind: ServiceAccount metadata: name: {{ .Values.shippingService.name }} namespace: {{.Release.Namespace}} {{- if not .Values.serviceAccounts.annotationsOnlyForCartservice }} {{- with .Values.serviceAccounts.annotations }} annotations: {{- toYaml . | nindent 4 }} {{- end }} {{- end }} --- {{- end }} apiVersion: apps/v1 kind: Deployment metadata: name: {{ .Values.shippingService.name }} namespace: {{ .Release.Namespace }} labels: app: {{ .Values.shippingService.name }} spec: selector: matchLabels: app: {{ .Values.shippingService.name }} template: metadata: labels: app: {{ .Values.shippingService.name }} spec: {{- if .Values.serviceAccounts.create }} serviceAccountName: {{ .Values.shippingService.name }} {{- else }} serviceAccountName: default {{- end }} {{- if .Values.securityContext.enable }} securityContext: fsGroup: 1000 runAsGroup: 1000 runAsNonRoot: true runAsUser: 1000 {{- if .Values.seccompProfile.enable }} seccompProfile: type: {{ .Values.seccompProfile.type }} {{- end }} {{- end }} containers: - name: server securityContext: allowPrivilegeEscalation: false capabilities: drop: - ALL privileged: false readOnlyRootFilesystem: true image: {{ .Values.images.repository }}/{{ .Values.shippingService.name }}:{{ .Values.images.tag | default .Chart.AppVersion }} ports: - containerPort: 50051 env: - name: PORT value: "50051" {{- if not .Values.googleCloudOperations.profiler }} - name: DISABLE_PROFILER value: "1" {{- end }} readinessProbe: periodSeconds: 5 grpc: port: 50051 livenessProbe: grpc: port: 50051 resources: {{- toYaml .Values.shippingService.resources | nindent 10 }} --- apiVersion: v1 kind: Service metadata: name: {{ .Values.shippingService.name }} namespace: {{ .Release.Namespace }} labels: app: {{ .Values.shippingService.name }} spec: type: ClusterIP selector: app: {{ .Values.shippingService.name }} ports: - name: grpc port: 50051 targetPort: 50051 {{- if .Values.networkPolicies.create }} --- apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: {{ .Values.shippingService.name }} namespace: {{ .Release.Namespace }} spec: podSelector: matchLabels: app: {{ .Values.shippingService.name }} policyTypes: - Ingress - Egress ingress: - from: - podSelector: matchLabels: app: {{ .Values.frontend.name }} - podSelector: matchLabels: app: {{ .Values.checkoutService.name }} ports: - port: 50051 protocol: TCP egress: - {} {{- end }} {{- if .Values.sidecars.create }} --- apiVersion: networking.istio.io/v1beta1 kind: Sidecar metadata: name: {{ .Values.shippingService.name }} namespace: {{ .Release.Namespace }} spec: workloadSelector: labels: app: {{ .Values.shippingService.name }} egress: - hosts: - istio-system/* {{- if .Values.opentelemetryCollector.create }} - ./{{ .Values.opentelemetryCollector.name }}.{{ .Release.Namespace }}.svc.cluster.local {{- end }} {{- end }} {{- if .Values.authorizationPolicies.create }} --- apiVersion: security.istio.io/v1beta1 kind: AuthorizationPolicy metadata: name: {{ .Values.shippingService.name }} namespace: {{ .Release.Namespace }} spec: selector: matchLabels: app: {{ .Values.shippingService.name }} rules: - from: - source: principals: {{- if .Values.serviceAccounts.create }} - cluster.local/ns/{{ .Release.Namespace }}/sa/{{ .Values.frontend.name }} - cluster.local/ns/{{ .Release.Namespace }}/sa/{{ .Values.checkoutService.name }} {{- else }} - cluster.local/ns/{{ .Release.Namespace }}/sa/default {{- end }} to: - operation: paths: - /hipstershop.ShippingService/GetQuote - /hipstershop.ShippingService/ShipOrder methods: - POST ports: - "50051" {{- end }} {{- end }} ================================================ FILE: helm-chart/values.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 # # 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. # Default values for onlineboutique. # This is a YAML-formatted file. # Declare variables to be passed into your templates. images: repository: us-central1-docker.pkg.dev/google-samples/microservices-demo # Overrides the image tag whose default is the chart appVersion. tag: "" serviceAccounts: # Specifies whether service accounts should be created. create: true # Annotations to add to the service accounts. annotations: {} # Annotations to add only for the cartservice app. This allows to follow the least privilege principle where only cartservice needs to connect to external database for example via Workload Identity. annotationsOnlyForCartservice: false networkPolicies: # Specifies if the NetworkPolicies are created or not. If true, one fine granular NetworkPolicy per app is created. create: false sidecars: # Specifies if the Sidecars are created or not. If true, one fine granular Sidecar per app is created. create: false authorizationPolicies: # Specifies if the AuthorizationPolicies are created or not. If true, one fine granular AuthorizationPolicy per app is created. create: false opentelemetryCollector: create: false name: opentelemetrycollector # Specifies the project id for the otel collector. If set as "PROJECT_ID" (default value), an initContainer will automatically retrieve the project id value from the metadata server. projectId: "PROJECT_ID" googleCloudOperations: profiler: false tracing: false metrics: false seccompProfile: enable: false type: RuntimeDefault securityContext: enable: true adService: create: true name: adservice resources: requests: cpu: 200m memory: 180Mi limits: cpu: 300m memory: 300Mi cartService: create: true name: cartservice resources: requests: cpu: 200m memory: 128Mi limits: cpu: 300m memory: 256Mi checkoutService: create: true name: checkoutservice resources: requests: cpu: 100m memory: 64Mi limits: cpu: 200m memory: 128Mi currencyService: create: true name: currencyservice resources: requests: cpu: 100m memory: 128Mi limits: cpu: 200m memory: 256Mi emailService: create: true name: emailservice resources: requests: cpu: 100m memory: 64Mi limits: cpu: 200m memory: 128Mi frontend: create: true name: frontend externalService: true cymbalBranding: false # One of: local, gcp, aws, azure, onprem, alibaba. When not set, defaults to "local" unless running in GKE, otherwise auto-sets to gcp. platform: local singleSharedSession: false virtualService: create: false hosts: - "*" gateway: name: asm-ingressgateway namespace: asm-ingress labelKey: asm labelValue: ingressgateway resources: requests: cpu: 100m memory: 64Mi limits: cpu: 200m memory: 128Mi loadGenerator: create: true name: loadgenerator checkFrontendInitContainer: true resources: requests: cpu: 300m memory: 256Mi limits: cpu: 500m memory: 512Mi paymentService: create: true name: paymentservice resources: requests: cpu: 100m memory: 128Mi limits: cpu: 200m memory: 256Mi productCatalogService: create: true name: productcatalogservice # Specifies an extra latency to any request on productcatalogservice, by default no extra latency. extraLatency: "" resources: requests: cpu: 100m memory: 64Mi limits: cpu: 200m memory: 128Mi recommendationService: create: true name: recommendationservice resources: requests: cpu: 100m memory: 220Mi limits: cpu: 200m memory: 450Mi shippingService: create: true name: shippingservice resources: requests: cpu: 100m memory: 64Mi limits: cpu: 200m memory: 128Mi cartDatabase: # Specifies the type of the cartservice's database, could be either redis or spanner. type: redis connectionString: "redis-cart:6379" inClusterRedis: create: true name: redis-cart # Uses the public redis image from Docker Hub, otherwise will use the images.repository. publicRepository: true externalRedisTlsOrigination: enable: false name: exernal-redis-tls-origination endpointAddress: "" endpointPort: "" certificate: "" # @TODO: This service is not currently available in Helm. # https://github.com/GoogleCloudPlatform/microservices-demo/tree/main/kustomize/components/shopping-assistant shoppingAssistantService: create: false name: shoppingassistantservice ================================================ FILE: istio-manifests/allow-egress-googleapis.yaml ================================================ # Copyright 2018 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. apiVersion: networking.istio.io/v1alpha3 kind: ServiceEntry metadata: name: allow-egress-googleapis spec: hosts: - "accounts.google.com" # Used to get token - "*.googleapis.com" ports: - number: 80 protocol: HTTP name: http - number: 443 protocol: HTTPS name: https --- apiVersion: networking.istio.io/v1alpha3 kind: ServiceEntry metadata: name: allow-egress-google-metadata spec: hosts: - metadata.google.internal addresses: - 169.254.169.254 # GCE metadata server ports: - number: 80 name: http protocol: HTTP - number: 443 name: https protocol: HTTPS ================================================ FILE: istio-manifests/frontend-gateway.yaml ================================================ # Copyright 2018 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. apiVersion: networking.istio.io/v1alpha3 kind: Gateway metadata: name: frontend-gateway spec: selector: istio: ingressgateway # use Istio default gateway implementation servers: - port: number: 80 name: http protocol: HTTP hosts: - "*" --- apiVersion: networking.istio.io/v1alpha3 kind: VirtualService metadata: name: frontend-ingress spec: hosts: - "*" gateways: - frontend-gateway http: - route: - destination: host: frontend port: number: 80 ================================================ FILE: istio-manifests/frontend.yaml ================================================ # Copyright 2018 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. apiVersion: networking.istio.io/v1alpha3 kind: VirtualService metadata: name: frontend spec: hosts: - "frontend.default.svc.cluster.local" http: - route: - destination: host: frontend port: number: 80 ================================================ FILE: kubernetes-manifests/README.md ================================================ # ./kubernetes-manifests :warning: Kubernetes manifests provided in this directory are not directly deployable to a cluster. They are meant to be used with `skaffold` command to insert the correct `image:` tags. Use the manifests in [/release](/release) directory which are configured with pre-built public images. ================================================ FILE: kubernetes-manifests/adservice.yaml ================================================ # Copyright 2018 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. apiVersion: apps/v1 kind: Deployment metadata: name: adservice labels: app: adservice spec: selector: matchLabels: app: adservice template: metadata: labels: app: adservice spec: serviceAccountName: adservice terminationGracePeriodSeconds: 5 securityContext: fsGroup: 1000 runAsGroup: 1000 runAsNonRoot: true runAsUser: 1000 containers: - name: server securityContext: allowPrivilegeEscalation: false capabilities: drop: - ALL privileged: false readOnlyRootFilesystem: true image: adservice ports: - containerPort: 9555 env: - name: PORT value: "9555" resources: requests: cpu: 200m memory: 180Mi limits: cpu: 300m memory: 300Mi readinessProbe: initialDelaySeconds: 20 periodSeconds: 15 grpc: port: 9555 livenessProbe: initialDelaySeconds: 20 periodSeconds: 15 grpc: port: 9555 --- apiVersion: v1 kind: Service metadata: name: adservice labels: app: adservice spec: type: ClusterIP selector: app: adservice ports: - name: grpc port: 9555 targetPort: 9555 --- apiVersion: v1 kind: ServiceAccount metadata: name: adservice ================================================ FILE: kubernetes-manifests/cartservice.yaml ================================================ # Copyright 2018 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. apiVersion: apps/v1 kind: Deployment metadata: name: cartservice labels: app: cartservice spec: selector: matchLabels: app: cartservice template: metadata: labels: app: cartservice spec: serviceAccountName: cartservice terminationGracePeriodSeconds: 5 securityContext: fsGroup: 1000 runAsGroup: 1000 runAsNonRoot: true runAsUser: 1000 containers: - name: server securityContext: allowPrivilegeEscalation: false capabilities: drop: - ALL privileged: false readOnlyRootFilesystem: true image: cartservice ports: - containerPort: 7070 env: - name: REDIS_ADDR value: "redis-cart:6379" resources: requests: cpu: 200m memory: 64Mi limits: cpu: 300m memory: 128Mi readinessProbe: initialDelaySeconds: 15 grpc: port: 7070 livenessProbe: initialDelaySeconds: 15 periodSeconds: 10 grpc: port: 7070 --- apiVersion: v1 kind: Service metadata: name: cartservice labels: app: cartservice spec: type: ClusterIP selector: app: cartservice ports: - name: grpc port: 7070 targetPort: 7070 --- apiVersion: v1 kind: ServiceAccount metadata: name: cartservice --- apiVersion: apps/v1 kind: Deployment metadata: name: redis-cart labels: app: redis-cart spec: selector: matchLabels: app: redis-cart template: metadata: labels: app: redis-cart spec: securityContext: fsGroup: 1000 runAsGroup: 1000 runAsNonRoot: true runAsUser: 1000 containers: - name: redis securityContext: allowPrivilegeEscalation: false capabilities: drop: - ALL privileged: false readOnlyRootFilesystem: true image: redis:alpine ports: - containerPort: 6379 readinessProbe: periodSeconds: 5 tcpSocket: port: 6379 livenessProbe: periodSeconds: 5 tcpSocket: port: 6379 volumeMounts: - mountPath: /data name: redis-data resources: limits: memory: 256Mi cpu: 125m requests: cpu: 70m memory: 200Mi volumes: - name: redis-data emptyDir: {} --- apiVersion: v1 kind: Service metadata: name: redis-cart labels: app: redis-cart spec: type: ClusterIP selector: app: redis-cart ports: - name: tcp-redis port: 6379 targetPort: 6379 ================================================ FILE: kubernetes-manifests/checkoutservice.yaml ================================================ # Copyright 2018 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. apiVersion: apps/v1 kind: Deployment metadata: name: checkoutservice labels: app: checkoutservice spec: selector: matchLabels: app: checkoutservice template: metadata: labels: app: checkoutservice spec: serviceAccountName: checkoutservice securityContext: fsGroup: 1000 runAsGroup: 1000 runAsNonRoot: true runAsUser: 1000 containers: - name: server securityContext: allowPrivilegeEscalation: false capabilities: drop: - ALL privileged: false readOnlyRootFilesystem: true image: checkoutservice ports: - containerPort: 5050 readinessProbe: grpc: port: 5050 livenessProbe: grpc: port: 5050 env: - name: PORT value: "5050" - name: PRODUCT_CATALOG_SERVICE_ADDR value: "productcatalogservice:3550" - name: SHIPPING_SERVICE_ADDR value: "shippingservice:50051" - name: PAYMENT_SERVICE_ADDR value: "paymentservice:50051" - name: EMAIL_SERVICE_ADDR value: "emailservice:5000" - name: CURRENCY_SERVICE_ADDR value: "currencyservice:7000" - name: CART_SERVICE_ADDR value: "cartservice:7070" resources: requests: cpu: 100m memory: 64Mi limits: cpu: 200m memory: 128Mi --- apiVersion: v1 kind: Service metadata: name: checkoutservice labels: app: checkoutservice spec: type: ClusterIP selector: app: checkoutservice ports: - name: grpc port: 5050 targetPort: 5050 --- apiVersion: v1 kind: ServiceAccount metadata: name: checkoutservice ================================================ FILE: kubernetes-manifests/currencyservice.yaml ================================================ # Copyright 2018 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. apiVersion: apps/v1 kind: Deployment metadata: name: currencyservice labels: app: currencyservice spec: selector: matchLabels: app: currencyservice template: metadata: labels: app: currencyservice spec: serviceAccountName: currencyservice terminationGracePeriodSeconds: 5 securityContext: fsGroup: 1000 runAsGroup: 1000 runAsNonRoot: true runAsUser: 1000 containers: - name: server securityContext: allowPrivilegeEscalation: false capabilities: drop: - ALL privileged: false readOnlyRootFilesystem: true image: currencyservice ports: - name: grpc containerPort: 7000 env: - name: PORT value: "7000" - name: DISABLE_PROFILER value: "1" readinessProbe: grpc: port: 7000 livenessProbe: grpc: port: 7000 resources: requests: cpu: 100m memory: 64Mi limits: cpu: 200m memory: 128Mi --- apiVersion: v1 kind: Service metadata: name: currencyservice labels: app: currencyservice spec: type: ClusterIP selector: app: currencyservice ports: - name: grpc port: 7000 targetPort: 7000 --- apiVersion: v1 kind: ServiceAccount metadata: name: currencyservice ================================================ FILE: kubernetes-manifests/emailservice.yaml ================================================ # Copyright 2018 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. apiVersion: apps/v1 kind: Deployment metadata: name: emailservice labels: app: emailservice spec: selector: matchLabels: app: emailservice template: metadata: labels: app: emailservice spec: serviceAccountName: emailservice terminationGracePeriodSeconds: 5 securityContext: fsGroup: 1000 runAsGroup: 1000 runAsNonRoot: true runAsUser: 1000 containers: - name: server securityContext: allowPrivilegeEscalation: false capabilities: drop: - ALL privileged: false readOnlyRootFilesystem: true image: emailservice ports: - containerPort: 8080 env: - name: PORT value: "8080" - name: DISABLE_PROFILER value: "1" readinessProbe: periodSeconds: 5 grpc: port: 8080 livenessProbe: periodSeconds: 5 grpc: port: 8080 resources: requests: cpu: 100m memory: 64Mi limits: cpu: 200m memory: 128Mi --- apiVersion: v1 kind: Service metadata: name: emailservice labels: app: emailservice spec: type: ClusterIP selector: app: emailservice ports: - name: grpc port: 5000 targetPort: 8080 --- apiVersion: v1 kind: ServiceAccount metadata: name: emailservice ================================================ FILE: kubernetes-manifests/frontend.yaml ================================================ # Copyright 2018 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. apiVersion: apps/v1 kind: Deployment metadata: name: frontend labels: app: frontend spec: selector: matchLabels: app: frontend template: metadata: labels: app: frontend annotations: sidecar.istio.io/rewriteAppHTTPProbers: "true" spec: serviceAccountName: frontend securityContext: fsGroup: 1000 runAsGroup: 1000 runAsNonRoot: true runAsUser: 1000 containers: - name: server securityContext: allowPrivilegeEscalation: false capabilities: drop: - ALL privileged: false readOnlyRootFilesystem: true image: frontend ports: - containerPort: 8080 readinessProbe: initialDelaySeconds: 10 httpGet: path: "/_healthz" port: 8080 httpHeaders: - name: "Cookie" value: "shop_session-id=x-readiness-probe" livenessProbe: initialDelaySeconds: 10 httpGet: path: "/_healthz" port: 8080 httpHeaders: - name: "Cookie" value: "shop_session-id=x-liveness-probe" env: - name: PORT value: "8080" - name: PRODUCT_CATALOG_SERVICE_ADDR value: "productcatalogservice:3550" - name: CURRENCY_SERVICE_ADDR value: "currencyservice:7000" - name: CART_SERVICE_ADDR value: "cartservice:7070" - name: RECOMMENDATION_SERVICE_ADDR value: "recommendationservice:8080" - name: SHIPPING_SERVICE_ADDR value: "shippingservice:50051" - name: CHECKOUT_SERVICE_ADDR value: "checkoutservice:5050" - name: AD_SERVICE_ADDR value: "adservice:9555" - name: SHOPPING_ASSISTANT_SERVICE_ADDR value: "shoppingassistantservice:80" # # ENV_PLATFORM: One of: local, gcp, aws, azure, onprem, alibaba # # When not set, defaults to "local" unless running in GKE, otherwies auto-sets to gcp # - name: ENV_PLATFORM # value: "aws" - name: ENABLE_PROFILER value: "0" # - name: CYMBAL_BRANDING # value: "true" # - name: ENABLE_ASSISTANT # value: "true" # - name: FRONTEND_MESSAGE # value: "Replace this with a message you want to display on all pages." # As part of an optional Google Cloud demo, you can run an optional microservice called the "packaging service". # - name: PACKAGING_SERVICE_URL # value: "" # This value would look like "http://123.123.123" resources: requests: cpu: 100m memory: 64Mi limits: cpu: 200m memory: 128Mi --- apiVersion: v1 kind: Service metadata: name: frontend labels: app: frontend spec: type: ClusterIP selector: app: frontend ports: - name: http port: 80 targetPort: 8080 --- apiVersion: v1 kind: Service metadata: name: frontend-external labels: app: frontend spec: type: LoadBalancer selector: app: frontend ports: - name: http port: 80 targetPort: 8080 --- apiVersion: v1 kind: ServiceAccount metadata: name: frontend ================================================ FILE: kubernetes-manifests/kustomization.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. apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization resources: - adservice.yaml - cartservice.yaml - checkoutservice.yaml - currencyservice.yaml - emailservice.yaml - frontend.yaml # - loadgenerator.yaml # During development, the loadgenerator module inside skaffold.yaml will be used. - paymentservice.yaml - productcatalogservice.yaml - recommendationservice.yaml - shippingservice.yaml # components: # - ../kustomize/components/cymbal-branding # - ../kustomize/components/google-cloud-operations # - ../kustomize/components/memorystore # - ../kustomize/components/network-policies # - ../kustomize/components/alloydb # - ../kustomize/components/shopping-assistant # - ../kustomize/components/spanner # - ../kustomize/components/container-images-tag # - ../kustomize/components/container-images-tag-suffix # - ../kustomize/components/container-images-registry ================================================ FILE: kubernetes-manifests/loadgenerator.yaml ================================================ # Copyright 2018 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. apiVersion: apps/v1 kind: Deployment metadata: name: loadgenerator labels: app: loadgenerator spec: selector: matchLabels: app: loadgenerator replicas: 1 template: metadata: labels: app: loadgenerator annotations: sidecar.istio.io/rewriteAppHTTPProbers: "true" spec: serviceAccountName: loadgenerator terminationGracePeriodSeconds: 5 restartPolicy: Always securityContext: fsGroup: 1000 runAsGroup: 1000 runAsNonRoot: true runAsUser: 1000 initContainers: - command: - /bin/sh - -exc - | MAX_RETRIES=12 RETRY_INTERVAL=10 for i in $(seq 1 $MAX_RETRIES); do echo "Attempt $i: Pinging frontend: ${FRONTEND_ADDR}..." STATUSCODE=$(wget --server-response http://${FRONTEND_ADDR} 2>&1 | awk '/^ HTTP/{print $2}') if [ $STATUSCODE -eq 200 ]; then echo "Frontend is reachable." exit 0 fi echo "Error: Could not reach frontend - Status code: ${STATUSCODE}" sleep $RETRY_INTERVAL done echo "Failed to reach frontend after $MAX_RETRIES attempts." exit 1 name: frontend-check securityContext: allowPrivilegeEscalation: false capabilities: drop: - ALL privileged: false readOnlyRootFilesystem: true image: busybox:latest env: - name: FRONTEND_ADDR value: "frontend:80" containers: - name: main securityContext: allowPrivilegeEscalation: false capabilities: drop: - ALL privileged: false readOnlyRootFilesystem: true image: loadgenerator env: - name: FRONTEND_ADDR value: "frontend:80" - name: USERS value: "10" - name: RATE value: "1" resources: requests: cpu: 300m memory: 256Mi limits: cpu: 500m memory: 512Mi --- apiVersion: v1 kind: ServiceAccount metadata: name: loadgenerator ================================================ FILE: kubernetes-manifests/paymentservice.yaml ================================================ # Copyright 2018 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. apiVersion: apps/v1 kind: Deployment metadata: name: paymentservice labels: app: paymentservice spec: selector: matchLabels: app: paymentservice template: metadata: labels: app: paymentservice spec: serviceAccountName: paymentservice terminationGracePeriodSeconds: 5 securityContext: fsGroup: 1000 runAsGroup: 1000 runAsNonRoot: true runAsUser: 1000 containers: - name: server securityContext: allowPrivilegeEscalation: false capabilities: drop: - ALL privileged: false readOnlyRootFilesystem: true image: paymentservice ports: - containerPort: 50051 env: - name: PORT value: "50051" - name: DISABLE_PROFILER value: "1" readinessProbe: grpc: port: 50051 livenessProbe: grpc: port: 50051 resources: requests: cpu: 100m memory: 64Mi limits: cpu: 200m memory: 128Mi --- apiVersion: v1 kind: Service metadata: name: paymentservice labels: app: paymentservice spec: type: ClusterIP selector: app: paymentservice ports: - name: grpc port: 50051 targetPort: 50051 --- apiVersion: v1 kind: ServiceAccount metadata: name: paymentservice ================================================ FILE: kubernetes-manifests/productcatalogservice.yaml ================================================ # Copyright 2018 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. apiVersion: apps/v1 kind: Deployment metadata: name: productcatalogservice labels: app: productcatalogservice spec: selector: matchLabels: app: productcatalogservice template: metadata: labels: app: productcatalogservice spec: serviceAccountName: productcatalogservice terminationGracePeriodSeconds: 5 securityContext: fsGroup: 1000 runAsGroup: 1000 runAsNonRoot: true runAsUser: 1000 containers: - name: server securityContext: allowPrivilegeEscalation: false capabilities: drop: - ALL privileged: false readOnlyRootFilesystem: true image: productcatalogservice ports: - containerPort: 3550 env: - name: PORT value: "3550" - name: DISABLE_PROFILER value: "1" readinessProbe: grpc: port: 3550 livenessProbe: grpc: port: 3550 resources: requests: cpu: 100m memory: 64Mi limits: cpu: 200m memory: 128Mi --- apiVersion: v1 kind: Service metadata: name: productcatalogservice labels: app: productcatalogservice spec: type: ClusterIP selector: app: productcatalogservice ports: - name: grpc port: 3550 targetPort: 3550 --- apiVersion: v1 kind: ServiceAccount metadata: name: productcatalogservice ================================================ FILE: kubernetes-manifests/recommendationservice.yaml ================================================ # Copyright 2018 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. apiVersion: apps/v1 kind: Deployment metadata: name: recommendationservice labels: app: recommendationservice spec: selector: matchLabels: app: recommendationservice template: metadata: labels: app: recommendationservice spec: serviceAccountName: recommendationservice terminationGracePeriodSeconds: 5 securityContext: fsGroup: 1000 runAsGroup: 1000 runAsNonRoot: true runAsUser: 1000 containers: - name: server securityContext: allowPrivilegeEscalation: false capabilities: drop: - ALL privileged: false readOnlyRootFilesystem: true image: recommendationservice ports: - containerPort: 8080 readinessProbe: periodSeconds: 5 grpc: port: 8080 livenessProbe: periodSeconds: 5 grpc: port: 8080 env: - name: PORT value: "8080" - name: PRODUCT_CATALOG_SERVICE_ADDR value: "productcatalogservice:3550" - name: DISABLE_PROFILER value: "1" resources: requests: cpu: 100m memory: 220Mi limits: cpu: 200m memory: 450Mi --- apiVersion: v1 kind: Service metadata: name: recommendationservice labels: app: recommendationservice spec: type: ClusterIP selector: app: recommendationservice ports: - name: grpc port: 8080 targetPort: 8080 --- apiVersion: v1 kind: ServiceAccount metadata: name: recommendationservice ================================================ FILE: kubernetes-manifests/shippingservice.yaml ================================================ # Copyright 2018 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. apiVersion: apps/v1 kind: Deployment metadata: name: shippingservice labels: app: shippingservice spec: selector: matchLabels: app: shippingservice template: metadata: labels: app: shippingservice spec: serviceAccountName: shippingservice securityContext: fsGroup: 1000 runAsGroup: 1000 runAsNonRoot: true runAsUser: 1000 containers: - name: server securityContext: allowPrivilegeEscalation: false capabilities: drop: - ALL privileged: false readOnlyRootFilesystem: true image: shippingservice ports: - containerPort: 50051 env: - name: PORT value: "50051" - name: DISABLE_PROFILER value: "1" readinessProbe: periodSeconds: 5 grpc: port: 50051 livenessProbe: grpc: port: 50051 resources: requests: cpu: 100m memory: 64Mi limits: cpu: 200m memory: 128Mi --- apiVersion: v1 kind: Service metadata: name: shippingservice labels: app: shippingservice spec: type: ClusterIP selector: app: shippingservice ports: - name: grpc port: 50051 targetPort: 50051 --- apiVersion: v1 kind: ServiceAccount metadata: name: shippingservice ================================================ FILE: kustomize/README.md ================================================ # Use Online Boutique with Kustomize This page contains instructions on deploying variations of the [Online Boutique](https://github.com/GoogleCloudPlatform/microservices-demo) sample application using [Kustomize](https://kustomize.io/). Each variations is designed as a [**Kustomize component**](https://github.com/kubernetes-sigs/kustomize/blob/master/examples/components.md), so multiple variations can be composed together in the deployment. ## What is Kustomize? Kustomize is a Kubernetes configuration management tool that allows users to customize their manifest configurations without duplication. Its commands are built into `kubectl` as `apply -k`. More information on Kustomize can be found on the [official Kustomize website](https://kustomize.io/). ## Prerequisites Optionally, [install the `kustomize` binary](https://kubectl.docs.kubernetes.io/installation/) to avoid manually editing a `kustomization.yaml` file. Online Boutique's instructions will often use `kustomize edit` (like `kustomize edit add component components/some-component`), but you can skip these commands and instead add components manually to the [`/kustomize/kustomization.yaml` file](/kustomize/kustomization.yaml). You need to have a Kubernetes cluster where you will deploy the Online Boutique's Kubernetes manifests. To set up a GKE (Google Kubernetes Engine) cluster, you can follow the instruction in the [root `/README.md`](/). ## Deploy Online Boutique with Kustomize 1. From the root folder of this repository, navigate to the `kustomize/` directory. ```bash cd kustomize/ ``` 1. See what the default Kustomize configuration defined by `kustomize/kustomization.yaml` will generate (without actually deploying them yet). ```bash kubectl kustomize . ``` 1. Apply the default Kustomize configuration (`kustomize/kustomization.yaml`). ```bash kubectl apply -k . ``` 1. Wait for all Pods to show `STATUS` of `Running`. ```bash kubectl get pods ``` The output should be similar to the following: ```terminal NAME READY STATUS RESTARTS AGE adservice-76bdd69666-ckc5j 1/1 Running 0 2m58s cartservice-66d497c6b7-dp5jr 1/1 Running 0 2m59s checkoutservice-666c784bd6-4jd22 1/1 Running 0 3m1s currencyservice-5d5d496984-4jmd7 1/1 Running 0 2m59s emailservice-667457d9d6-75jcq 1/1 Running 0 3m2s frontend-6b8d69b9fb-wjqdg 1/1 Running 0 3m1s loadgenerator-665b5cd444-gwqdq 1/1 Running 0 3m paymentservice-68596d6dd6-bf6bv 1/1 Running 0 3m productcatalogservice-557d474574-888kr 1/1 Running 0 3m recommendationservice-69c56b74d4-7z8r5 1/1 Running 0 3m1s shippingservice-6ccc89f8fd-v686r 1/1 Running 0 2m58s ``` _Note: It may take 2-3 minutes before the changes are reflected on the deployment._ 1. Access the web frontend in a browser using the frontend's `EXTERNAL_IP`. ```bash kubectl get service frontend-external | awk '{print $4}' ``` Note: you may see `` while GCP provisions the load balancer. If this happens, wait a few minutes and re-run the command. ## Deploy Online Boutique variations with Kustomize Here is the list of the variations available as Kustomize components that you could leverage: - [**Change to the Cymbal Shops Branding**](components/cymbal-branding) - Changes all Online Boutique-related branding to Google Cloud's fictitious company — Cymbal Shops. The code adds/enables an environment variable `CYMBAL_BRANDING` in the `frontend` service. - [**Integrate with Google Cloud Operations**](components/google-cloud-operations) - Enables Monitoring (Stats), Tracing, and Profiler for various services within Online Boutique. The code adds the appropriare environment variables (`ENABLE_STATS`, `ENABLE_TRACING`, `DISABLE_PROFILER`) for each YAML config file. - [**Integrate with Memorystore (Redis)**](components/memorystore) - The default Online Boutique deployment uses the in-cluster `redis` database for storing the contents of its shopping cart. The Memorystore deployment variation overrides the default database with its own Memorystore (Redis) database. These changes directly affect `cartservice`. - [**Integrate with Spanner**](components/spanner) - The default Online Boutique deployment uses the in-cluster `redis` database for storing the contents of its shopping cart. The Spanner deployment variation overrides the default database with its own Spanner database. These changes directly affect `cartservice`. - [**Integrate with AlloyDB**](components/alloydb) - The default Online Boutique deployment uses the in-cluster `redis` database for storing the contents of its shopping cart. The AlloyDB deployment variation overrides the default database with its own AlloyDB database. These changes directly affect `cartservice`. - [**Secure with Network Policies**](components/network-policies) - Deploy fine granular `NetworkPolicies` for Online Boutique. - [**Update the registry name of the container images**](components/container-images-registry) - [**Update the image tag of the container images**](components/container-images-tag) - [**Add an image tag suffix to the container images**](components/container-images-tag-suffix) - [**Do not expose the `frontend` publicly**](components/non-public-frontend) - [**Set the `frontend` to manage only one single shared session**](components/single-shared-session) - [**Configure `Istio` service mesh resources**](components/service-mesh-istio) ### Select variations To customize Online Boutique with its variations, you need to update the default `kustomize/kustomization.yaml` file. You could do that manually, use `sed`, or use the `kustomize edit` command like illustrated below. #### Use `kustomize edit` to select variations Here is an example with the [**Cymbal Shops Branding**](components/cymbal-branding) variation, from the `kustomize/` folder, run the command below: ```bash kustomize edit add component components/cymbal-branding ``` You could now combine it with other variations, like for example with the [**Google Cloud Operations**](components/google-cloud-operations) variation: ```bash kustomize edit add component components/google-cloud-operations ``` ### Deploy selected variations Like explained earlier, you can locally render these manifests by running `kubectl kustomize .` as well as deploying them by running `kubectl apply -k .`. So for example, the associated `kustomization.yaml` could look like: ```yaml apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization resources: - base components: - components/cymbal-branding - components/google-cloud-operations ``` ### Use remote Kustomize targets Kustomize allows you to reference public remote resources so the `kustomization.yaml` could look like: ```yaml apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization resources: - github.com/GoogleCloudPlatform/microservices-demo/kustomize/base components: - github.com/GoogleCloudPlatform/microservices-demo/kustomize/components/cymbal-branding - github.com/GoogleCloudPlatform/microservices-demo/kustomize/components/google-cloud-operations ``` Learn more about [Kustomize remote targets](https://github.com/kubernetes-sigs/kustomize/blob/master/examples/remoteBuild.md). ================================================ FILE: kustomize/base/adservice.yaml ================================================ # Copyright 2018 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. apiVersion: apps/v1 kind: Deployment metadata: name: adservice labels: app: adservice spec: selector: matchLabels: app: adservice template: metadata: labels: app: adservice spec: serviceAccountName: adservice terminationGracePeriodSeconds: 5 securityContext: fsGroup: 1000 runAsGroup: 1000 runAsNonRoot: true runAsUser: 1000 containers: - name: server securityContext: allowPrivilegeEscalation: false capabilities: drop: - ALL privileged: false readOnlyRootFilesystem: true image: us-central1-docker.pkg.dev/google-samples/microservices-demo/adservice:v0.10.5 ports: - containerPort: 9555 env: - name: PORT value: "9555" resources: requests: cpu: 200m memory: 180Mi limits: cpu: 300m memory: 300Mi readinessProbe: initialDelaySeconds: 20 periodSeconds: 15 grpc: port: 9555 livenessProbe: initialDelaySeconds: 20 periodSeconds: 15 grpc: port: 9555 --- apiVersion: v1 kind: Service metadata: name: adservice labels: app: adservice spec: type: ClusterIP selector: app: adservice ports: - name: grpc port: 9555 targetPort: 9555 --- apiVersion: v1 kind: ServiceAccount metadata: name: adservice ================================================ FILE: kustomize/base/cartservice.yaml ================================================ # Copyright 2018 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. apiVersion: apps/v1 kind: Deployment metadata: name: cartservice labels: app: cartservice spec: selector: matchLabels: app: cartservice template: metadata: labels: app: cartservice spec: serviceAccountName: cartservice terminationGracePeriodSeconds: 5 securityContext: fsGroup: 1000 runAsGroup: 1000 runAsNonRoot: true runAsUser: 1000 containers: - name: server securityContext: allowPrivilegeEscalation: false capabilities: drop: - ALL privileged: false readOnlyRootFilesystem: true image: us-central1-docker.pkg.dev/google-samples/microservices-demo/cartservice:v0.10.5 ports: - containerPort: 7070 env: - name: REDIS_ADDR value: "redis-cart:6379" resources: requests: cpu: 200m memory: 64Mi limits: cpu: 300m memory: 128Mi readinessProbe: initialDelaySeconds: 15 grpc: port: 7070 livenessProbe: initialDelaySeconds: 15 periodSeconds: 10 grpc: port: 7070 --- apiVersion: v1 kind: Service metadata: name: cartservice labels: app: cartservice spec: type: ClusterIP selector: app: cartservice ports: - name: grpc port: 7070 targetPort: 7070 --- apiVersion: v1 kind: ServiceAccount metadata: name: cartservice --- apiVersion: apps/v1 kind: Deployment metadata: name: redis-cart labels: app: redis-cart spec: selector: matchLabels: app: redis-cart template: metadata: labels: app: redis-cart spec: securityContext: fsGroup: 1000 runAsGroup: 1000 runAsNonRoot: true runAsUser: 1000 containers: - name: redis securityContext: allowPrivilegeEscalation: false capabilities: drop: - ALL privileged: false readOnlyRootFilesystem: true image: redis:alpine ports: - containerPort: 6379 readinessProbe: periodSeconds: 5 tcpSocket: port: 6379 livenessProbe: periodSeconds: 5 tcpSocket: port: 6379 volumeMounts: - mountPath: /data name: redis-data resources: limits: memory: 256Mi cpu: 125m requests: cpu: 70m memory: 200Mi volumes: - name: redis-data emptyDir: {} --- apiVersion: v1 kind: Service metadata: name: redis-cart labels: app: redis-cart spec: type: ClusterIP selector: app: redis-cart ports: - name: tcp-redis port: 6379 targetPort: 6379 ================================================ FILE: kustomize/base/checkoutservice.yaml ================================================ # Copyright 2018 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. apiVersion: apps/v1 kind: Deployment metadata: name: checkoutservice labels: app: checkoutservice spec: selector: matchLabels: app: checkoutservice template: metadata: labels: app: checkoutservice spec: serviceAccountName: checkoutservice securityContext: fsGroup: 1000 runAsGroup: 1000 runAsNonRoot: true runAsUser: 1000 containers: - name: server securityContext: allowPrivilegeEscalation: false capabilities: drop: - ALL privileged: false readOnlyRootFilesystem: true image: us-central1-docker.pkg.dev/google-samples/microservices-demo/checkoutservice:v0.10.5 ports: - containerPort: 5050 readinessProbe: grpc: port: 5050 livenessProbe: grpc: port: 5050 env: - name: PORT value: "5050" - name: PRODUCT_CATALOG_SERVICE_ADDR value: "productcatalogservice:3550" - name: SHIPPING_SERVICE_ADDR value: "shippingservice:50051" - name: PAYMENT_SERVICE_ADDR value: "paymentservice:50051" - name: EMAIL_SERVICE_ADDR value: "emailservice:5000" - name: CURRENCY_SERVICE_ADDR value: "currencyservice:7000" - name: CART_SERVICE_ADDR value: "cartservice:7070" resources: requests: cpu: 100m memory: 64Mi limits: cpu: 200m memory: 128Mi --- apiVersion: v1 kind: Service metadata: name: checkoutservice labels: app: checkoutservice spec: type: ClusterIP selector: app: checkoutservice ports: - name: grpc port: 5050 targetPort: 5050 --- apiVersion: v1 kind: ServiceAccount metadata: name: checkoutservice ================================================ FILE: kustomize/base/currencyservice.yaml ================================================ # Copyright 2018 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. apiVersion: apps/v1 kind: Deployment metadata: name: currencyservice labels: app: currencyservice spec: selector: matchLabels: app: currencyservice template: metadata: labels: app: currencyservice spec: serviceAccountName: currencyservice terminationGracePeriodSeconds: 5 securityContext: fsGroup: 1000 runAsGroup: 1000 runAsNonRoot: true runAsUser: 1000 containers: - name: server securityContext: allowPrivilegeEscalation: false capabilities: drop: - ALL privileged: false readOnlyRootFilesystem: true image: us-central1-docker.pkg.dev/google-samples/microservices-demo/currencyservice:v0.10.5 ports: - name: grpc containerPort: 7000 env: - name: PORT value: "7000" - name: DISABLE_PROFILER value: "1" readinessProbe: grpc: port: 7000 livenessProbe: grpc: port: 7000 resources: requests: cpu: 100m memory: 64Mi limits: cpu: 200m memory: 128Mi --- apiVersion: v1 kind: Service metadata: name: currencyservice labels: app: currencyservice spec: type: ClusterIP selector: app: currencyservice ports: - name: grpc port: 7000 targetPort: 7000 --- apiVersion: v1 kind: ServiceAccount metadata: name: currencyservice ================================================ FILE: kustomize/base/emailservice.yaml ================================================ # Copyright 2018 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. apiVersion: apps/v1 kind: Deployment metadata: name: emailservice labels: app: emailservice spec: selector: matchLabels: app: emailservice template: metadata: labels: app: emailservice spec: serviceAccountName: emailservice terminationGracePeriodSeconds: 5 securityContext: fsGroup: 1000 runAsGroup: 1000 runAsNonRoot: true runAsUser: 1000 containers: - name: server securityContext: allowPrivilegeEscalation: false capabilities: drop: - ALL privileged: false readOnlyRootFilesystem: true image: us-central1-docker.pkg.dev/google-samples/microservices-demo/emailservice:v0.10.5 ports: - containerPort: 8080 env: - name: PORT value: "8080" - name: DISABLE_PROFILER value: "1" readinessProbe: periodSeconds: 5 grpc: port: 8080 livenessProbe: periodSeconds: 5 grpc: port: 8080 resources: requests: cpu: 100m memory: 64Mi limits: cpu: 200m memory: 128Mi --- apiVersion: v1 kind: Service metadata: name: emailservice labels: app: emailservice spec: type: ClusterIP selector: app: emailservice ports: - name: grpc port: 5000 targetPort: 8080 --- apiVersion: v1 kind: ServiceAccount metadata: name: emailservice ================================================ FILE: kustomize/base/frontend.yaml ================================================ # Copyright 2018 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. apiVersion: apps/v1 kind: Deployment metadata: name: frontend labels: app: frontend spec: selector: matchLabels: app: frontend template: metadata: labels: app: frontend annotations: sidecar.istio.io/rewriteAppHTTPProbers: "true" spec: serviceAccountName: frontend securityContext: fsGroup: 1000 runAsGroup: 1000 runAsNonRoot: true runAsUser: 1000 containers: - name: server securityContext: allowPrivilegeEscalation: false capabilities: drop: - ALL privileged: false readOnlyRootFilesystem: true image: us-central1-docker.pkg.dev/google-samples/microservices-demo/frontend:v0.10.5 ports: - containerPort: 8080 readinessProbe: initialDelaySeconds: 10 httpGet: path: "/_healthz" port: 8080 httpHeaders: - name: "Cookie" value: "shop_session-id=x-readiness-probe" livenessProbe: initialDelaySeconds: 10 httpGet: path: "/_healthz" port: 8080 httpHeaders: - name: "Cookie" value: "shop_session-id=x-liveness-probe" env: - name: PORT value: "8080" - name: PRODUCT_CATALOG_SERVICE_ADDR value: "productcatalogservice:3550" - name: CURRENCY_SERVICE_ADDR value: "currencyservice:7000" - name: CART_SERVICE_ADDR value: "cartservice:7070" - name: RECOMMENDATION_SERVICE_ADDR value: "recommendationservice:8080" - name: SHIPPING_SERVICE_ADDR value: "shippingservice:50051" - name: CHECKOUT_SERVICE_ADDR value: "checkoutservice:5050" - name: AD_SERVICE_ADDR value: "adservice:9555" - name: SHOPPING_ASSISTANT_SERVICE_ADDR value: "shoppingassistantservice:80" # # ENV_PLATFORM: One of: local, gcp, aws, azure, onprem, alibaba # # When not set, defaults to "local" unless running in GKE, otherwies auto-sets to gcp # - name: ENV_PLATFORM # value: "aws" - name: ENABLE_PROFILER value: "0" # - name: CYMBAL_BRANDING # value: "true" # - name: ENABLE_ASSISTANT # value: "true" # - name: FRONTEND_MESSAGE # value: "Replace this with a message you want to display on all pages." # As part of an optional Google Cloud demo, you can run an optional microservice called the "packaging service". # - name: PACKAGING_SERVICE_URL # value: "" # This value would look like "http://123.123.123" resources: requests: cpu: 100m memory: 64Mi limits: cpu: 200m memory: 128Mi --- apiVersion: v1 kind: Service metadata: name: frontend labels: app: frontend spec: type: ClusterIP selector: app: frontend ports: - name: http port: 80 targetPort: 8080 --- apiVersion: v1 kind: Service metadata: name: frontend-external labels: app: frontend spec: type: LoadBalancer selector: app: frontend ports: - name: http port: 80 targetPort: 8080 --- apiVersion: v1 kind: ServiceAccount metadata: name: frontend ================================================ FILE: kustomize/base/kustomization.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. apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization resources: - adservice.yaml - cartservice.yaml - checkoutservice.yaml - currencyservice.yaml - emailservice.yaml - frontend.yaml - loadgenerator.yaml - paymentservice.yaml - productcatalogservice.yaml - recommendationservice.yaml - shippingservice.yaml ================================================ FILE: kustomize/base/loadgenerator.yaml ================================================ # Copyright 2018 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. apiVersion: apps/v1 kind: Deployment metadata: name: loadgenerator labels: app: loadgenerator spec: selector: matchLabels: app: loadgenerator replicas: 1 template: metadata: labels: app: loadgenerator annotations: sidecar.istio.io/rewriteAppHTTPProbers: "true" spec: serviceAccountName: loadgenerator terminationGracePeriodSeconds: 5 restartPolicy: Always securityContext: fsGroup: 1000 runAsGroup: 1000 runAsNonRoot: true runAsUser: 1000 initContainers: - command: - /bin/sh - -exc - | MAX_RETRIES=12 RETRY_INTERVAL=10 for i in $(seq 1 $MAX_RETRIES); do echo "Attempt $i: Pinging frontend: ${FRONTEND_ADDR}..." STATUSCODE=$(wget --server-response http://${FRONTEND_ADDR} 2>&1 | awk '/^ HTTP/{print $2}') if [ $STATUSCODE -eq 200 ]; then echo "Frontend is reachable." exit 0 fi echo "Error: Could not reach frontend - Status code: ${STATUSCODE}" sleep $RETRY_INTERVAL done echo "Failed to reach frontend after $MAX_RETRIES attempts." exit 1 name: frontend-check securityContext: allowPrivilegeEscalation: false capabilities: drop: - ALL privileged: false readOnlyRootFilesystem: true image: busybox:latest env: - name: FRONTEND_ADDR value: "frontend:80" containers: - name: main securityContext: allowPrivilegeEscalation: false capabilities: drop: - ALL privileged: false readOnlyRootFilesystem: true image: us-central1-docker.pkg.dev/google-samples/microservices-demo/loadgenerator:v0.10.5 env: - name: FRONTEND_ADDR value: "frontend:80" - name: USERS value: "10" - name: RATE value: "1" resources: requests: cpu: 300m memory: 256Mi limits: cpu: 500m memory: 512Mi --- apiVersion: v1 kind: ServiceAccount metadata: name: loadgenerator ================================================ FILE: kustomize/base/paymentservice.yaml ================================================ # Copyright 2018 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. apiVersion: apps/v1 kind: Deployment metadata: name: paymentservice labels: app: paymentservice spec: selector: matchLabels: app: paymentservice template: metadata: labels: app: paymentservice spec: serviceAccountName: paymentservice terminationGracePeriodSeconds: 5 securityContext: fsGroup: 1000 runAsGroup: 1000 runAsNonRoot: true runAsUser: 1000 containers: - name: server securityContext: allowPrivilegeEscalation: false capabilities: drop: - ALL privileged: false readOnlyRootFilesystem: true image: us-central1-docker.pkg.dev/google-samples/microservices-demo/paymentservice:v0.10.5 ports: - containerPort: 50051 env: - name: PORT value: "50051" - name: DISABLE_PROFILER value: "1" readinessProbe: grpc: port: 50051 livenessProbe: grpc: port: 50051 resources: requests: cpu: 100m memory: 64Mi limits: cpu: 200m memory: 128Mi --- apiVersion: v1 kind: Service metadata: name: paymentservice labels: app: paymentservice spec: type: ClusterIP selector: app: paymentservice ports: - name: grpc port: 50051 targetPort: 50051 --- apiVersion: v1 kind: ServiceAccount metadata: name: paymentservice ================================================ FILE: kustomize/base/productcatalogservice.yaml ================================================ # Copyright 2018 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. apiVersion: apps/v1 kind: Deployment metadata: name: productcatalogservice labels: app: productcatalogservice spec: selector: matchLabels: app: productcatalogservice template: metadata: labels: app: productcatalogservice spec: serviceAccountName: productcatalogservice terminationGracePeriodSeconds: 5 securityContext: fsGroup: 1000 runAsGroup: 1000 runAsNonRoot: true runAsUser: 1000 containers: - name: server securityContext: allowPrivilegeEscalation: false capabilities: drop: - ALL privileged: false readOnlyRootFilesystem: true image: us-central1-docker.pkg.dev/google-samples/microservices-demo/productcatalogservice:v0.10.5 ports: - containerPort: 3550 env: - name: PORT value: "3550" - name: DISABLE_PROFILER value: "1" readinessProbe: grpc: port: 3550 livenessProbe: grpc: port: 3550 resources: requests: cpu: 100m memory: 64Mi limits: cpu: 200m memory: 128Mi --- apiVersion: v1 kind: Service metadata: name: productcatalogservice labels: app: productcatalogservice spec: type: ClusterIP selector: app: productcatalogservice ports: - name: grpc port: 3550 targetPort: 3550 --- apiVersion: v1 kind: ServiceAccount metadata: name: productcatalogservice ================================================ FILE: kustomize/base/recommendationservice.yaml ================================================ # Copyright 2018 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. apiVersion: apps/v1 kind: Deployment metadata: name: recommendationservice labels: app: recommendationservice spec: selector: matchLabels: app: recommendationservice template: metadata: labels: app: recommendationservice spec: serviceAccountName: recommendationservice terminationGracePeriodSeconds: 5 securityContext: fsGroup: 1000 runAsGroup: 1000 runAsNonRoot: true runAsUser: 1000 containers: - name: server securityContext: allowPrivilegeEscalation: false capabilities: drop: - ALL privileged: false readOnlyRootFilesystem: true image: us-central1-docker.pkg.dev/google-samples/microservices-demo/recommendationservice:v0.10.5 ports: - containerPort: 8080 readinessProbe: periodSeconds: 5 grpc: port: 8080 livenessProbe: periodSeconds: 5 grpc: port: 8080 env: - name: PORT value: "8080" - name: PRODUCT_CATALOG_SERVICE_ADDR value: "productcatalogservice:3550" - name: DISABLE_PROFILER value: "1" resources: requests: cpu: 100m memory: 220Mi limits: cpu: 200m memory: 450Mi --- apiVersion: v1 kind: Service metadata: name: recommendationservice labels: app: recommendationservice spec: type: ClusterIP selector: app: recommendationservice ports: - name: grpc port: 8080 targetPort: 8080 --- apiVersion: v1 kind: ServiceAccount metadata: name: recommendationservice ================================================ FILE: kustomize/base/shippingservice.yaml ================================================ # Copyright 2018 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. apiVersion: apps/v1 kind: Deployment metadata: name: shippingservice labels: app: shippingservice spec: selector: matchLabels: app: shippingservice template: metadata: labels: app: shippingservice spec: serviceAccountName: shippingservice securityContext: fsGroup: 1000 runAsGroup: 1000 runAsNonRoot: true runAsUser: 1000 containers: - name: server securityContext: allowPrivilegeEscalation: false capabilities: drop: - ALL privileged: false readOnlyRootFilesystem: true image: us-central1-docker.pkg.dev/google-samples/microservices-demo/shippingservice:v0.10.5 ports: - containerPort: 50051 env: - name: PORT value: "50051" - name: DISABLE_PROFILER value: "1" readinessProbe: periodSeconds: 5 grpc: port: 50051 livenessProbe: grpc: port: 50051 resources: requests: cpu: 100m memory: 64Mi limits: cpu: 200m memory: 128Mi --- apiVersion: v1 kind: Service metadata: name: shippingservice labels: app: shippingservice spec: type: ClusterIP selector: app: shippingservice ports: - name: grpc port: 50051 targetPort: 50051 --- apiVersion: v1 kind: ServiceAccount metadata: name: shippingservice ================================================ FILE: kustomize/components/alloydb/README.md ================================================ # Integrate Online Boutique with AlloyDB By default the `cartservice` stores its data in an in-cluster Redis database. Using a fully managed database service outside your GKE cluster (such as [AlloyDB](https://cloud.google.com/alloydb)) could bring more resiliency and more security. Note that because of AlloyDB's current connectivity, you'll need to run all this from a VM with VPC access to the network you want to use for everything (out of the box this should just use the default network). The Cloud Shell doesn't work because of transitive VPC peering not working. ## Provision an AlloyDB database and the supporting infrastructure Environmental variables needed for setup. These should be set in a .bashrc or similar as some of the variables are used in the application itself. Default values are supplied in this readme, but any of them can be changed. Anything in <> needs to be replaced. ```bash # PROJECT_ID should be set to the project ID that was created to hold the demo PROJECT_ID= #Pick a region near you that also has AlloyDB available. See available regions: https://cloud.google.com/alloydb/docs/locations REGION= USE_GKE_GCLOUD_AUTH_PLUGIN=True ALLOYDB_NETWORK=default ALLOYDB_SERVICE_NAME=onlineboutique-network-range ALLOYDB_CLUSTER_NAME=onlineboutique-cluster ALLOYDB_INSTANCE_NAME=onlineboutique-instance # **Note:** Primary and Read IP will need to be set after you create the instance. The command to set this in the shell is included below, but it would also be a good idea to run the command, and manually set the IP address in the .bashrc ALLOYDB_PRIMARY_IP= ALLOYDB_READ_IP= ALLOYDB_DATABASE_NAME=carts ALLOYDB_TABLE_NAME=cart_items ALLOYDB_USER_GSA_NAME=alloydb-user-sa ALLOYDB_USER_GSA_ID=${ALLOYDB_USER_GSA_NAME}@${PROJECT_ID}.iam.gserviceaccount.com CARTSERVICE_KSA_NAME=cartservice ALLOYDB_SECRET_NAME=alloydb-secret # PGPASSWORD needs to be set in order to run the psql from the CLI easily. The value for this # needs to be set behind the Secret mentioned above PGPASSWORD= ``` To provision an AlloyDB instance you can follow the following instructions: ```bash gcloud services enable alloydb.googleapis.com gcloud services enable servicenetworking.googleapis.com gcloud services enable secretmanager.googleapis.com # Set our DB credentials behind the secret. Replace with whatever you want # to use as the credentials for the database. Don't use $ in the password. echo | gcloud secrets create ${ALLOYDB_SECRET_NAME} --data-file=- # Setting up needed service connection gcloud compute addresses create ${ALLOYDB_SERVICE_NAME} \ --global \ --purpose=VPC_PEERING \ --prefix-length=16 \ --description="Online Boutique Private Services" \ --network=${ALLOYDB_NETWORK} gcloud services vpc-peerings connect \ --service=servicenetworking.googleapis.com \ --ranges=${ALLOYDB_SERVICE_NAME} \ --network=${ALLOYDB_NETWORK} gcloud alloydb clusters create ${ALLOYDB_CLUSTER_NAME} \ --region=${REGION} \ --password=${PGPASSWORD} \ --disable-automated-backup \ --network=${ALLOYDB_NETWORK} gcloud alloydb instances create ${ALLOYDB_INSTANCE_NAME} \ --cluster=${ALLOYDB_CLUSTER_NAME} \ --region=${REGION} \ --cpu-count=4 \ --instance-type=PRIMARY gcloud alloydb instances create ${ALLOYDB_INSTANCE_NAME}-replica \ --cluster=${ALLOYDB_CLUSTER_NAME} \ --region=${REGION} \ --cpu-count=4 \ --instance-type=READ_POOL \ --read-pool-node-count=2 # Need to grab and store the IP addresses for our primary and read replicas # Don't forget to set these two values in the environment for later use. ALLOYDB_PRIMARY_IP=gcloud alloydb instances list --region=${REGION} --cluster=${ALLOYDB_CLUSTER_NAME} --filter="INSTANCE_TYPE:PRIMARY" --format=flattened | sed -nE "s/ipAddress:\s*(.*)/\1/p" ALLOYDB_READ_IP=gcloud alloydb instances list --region=${REGION} --cluster=${ALLOYDB_CLUSTER_NAME} --filter="INSTANCE_TYPE:READ_POOL" --format=flattened | sed -nE "s/ipAddress:\s*(.*)/\1/p" psql -h ${ALLOYDB_PRIMARY_IP} -U postgres -c "CREATE DATABASE ${ALLOYDB_DATABASE_NAME}" psql -h ${ALLOYDB_PRIMARY_IP} -U postgres -d ${ALLOYDB_DATABASE_NAME} -c "CREATE TABLE ${ALLOYDB_TABLE_NAME} (userId text, productId text, quantity int, PRIMARY KEY(userId, productId))" psql -h ${ALLOYDB_PRIMARY_IP} -U postgres -d ${ALLOYDB_DATABASE_NAME} -c "CREATE INDEX cartItemsByUserId ON ${ALLOYDB_TABLE_NAME}(userId)" ``` _Note: It can take more than 20 minutes for the AlloyDB instances to be created._ ## Grant the `cartservice`'s service account access to the AlloyDB database **Important note:** Your GKE cluster should have [Workload Identity enabled](https://cloud.google.com/kubernetes-engine/docs/how-to/workload-identity#enable). As a good practice, let's create a dedicated least privilege Google Service Account to allow the `cartservice` to communicate with the AlloyDB database and grab the database password from the Secret manager.: ```bash gcloud iam service-accounts create ${ALLOYDB_USER_GSA_NAME} \ --display-name=${ALLOYDB_USER_GSA_NAME} gcloud projects add-iam-policy-binding ${PROJECT_ID} --member=serviceAccount:${ALLOYDB_USER_GSA_ID} --role=roles/alloydb.client gcloud projects add-iam-policy-binding ${PROJECT_ID} --member=serviceAccount:${ALLOYDB_USER_GSA_ID} --role=roles/secretmanager.secretAccessor gcloud iam service-accounts add-iam-policy-binding ${ALLOYDB_USER_GSA_ID} \ --member "serviceAccount:${PROJECT_ID}.svc.id.goog[default/${CARTSERVICE_KSA_NAME}]" \ --role roles/iam.workloadIdentityUser ``` ## Deploy Online Boutique connected to an AlloyDB database To automate the deployment of Online Boutique integrated with AlloyDB you can leverage the following variation with [Kustomize](../..). From the `kustomize/` folder at the root level of this repository, execute these commands: ```bash kustomize edit add component components/alloydb ``` _**Note:** this Kustomize component will also remove the `redis-cart` `Deployment` and `Service` not used anymore._ This will update the `kustomize/kustomization.yaml` file which could be similar to: ```yaml apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization resources: - base components: - components/alloydb ``` Update current Kustomize manifest to target this AlloyDB database. ```bash sed -i "s/PROJECT_ID_VAL/${PROJECT_ID}/g" components/alloydb/kustomization.yaml sed -i "s/ALLOYDB_PRIMARY_IP_VAL/${ALLOYDB_PRIMARY_IP}/g" components/alloydb/kustomization.yaml sed -i "s/ALLOYDB_USER_GSA_ID/${ALLOYDB_USER_GSA_ID}/g" components/alloydb/kustomization.yaml sed -i "s/ALLOYDB_DATABASE_NAME_VAL/${ALLOYDB_DATABASE_NAME}/g" components/alloydb/kustomization.yaml sed -i "s/ALLOYDB_TABLE_NAME_VAL/${ALLOYDB_TABLE_NAME}/g" components/alloydb/kustomization.yaml sed -i "s/ALLOYDB_SECRET_NAME_VAL/${ALLOYDB_SECRET_NAME}/g" components/alloydb/kustomization.yaml ``` You can locally render these manifests by running `kubectl kustomize .` as well as deploying them by running `kubectl apply -k .`. ## Extra cleanup steps ```bash gcloud compute addresses delete ${ALLOYDB_SERVICE_NAME} --global # Force takes care of cleaning up the instances inside the cluster automatically gcloud alloydb clusters delete ${ALLOYDB_CLUSTER_NAME} --force --region ${REGION} gcloud iam service-accounts delete ${ALLOYDB_USER_GSA_ID} gcloud secrets delete ${ALLOYDB_SECRET_NAME} ``` ================================================ FILE: kustomize/components/alloydb/kustomization.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. apiVersion: kustomize.config.k8s.io/v1alpha1 kind: Component patches: # cartservice - replace REDIS_ADDR by ALLOYDB_PRIMARY_IP for the cartservice Deployment # Potentially later we'll factor in splitting traffic to primary/read pool, but for now # we'll just manage the primary instance - patch: |- apiVersion: apps/v1 kind: Deployment metadata: name: cartservice spec: template: spec: containers: - name: server env: - name: REDIS_ADDR $patch: delete - name: ALLOYDB_PRIMARY_IP value: ALLOYDB_PRIMARY_IP_VAL - name: ALLOYDB_DATABASE_NAME value: ALLOYDB_CARTS_DATABASE_NAME_VAL - name: ALLOYDB_TABLE_NAME value: ALLOYDB_CARTS_TABLE_NAME_VAL - name: ALLOYDB_SECRET_NAME value: ALLOYDB_SECRET_NAME_VAL - name: PROJECT_ID value: PROJECT_ID_VAL # cartservice - add the GSA annotation for the cartservice KSA - patch: |- apiVersion: v1 kind: ServiceAccount metadata: name: cartservice annotations: iam.gke.io/gcp-service-account: ALLOYDB_USER_GSA_ID # productcatalogservice - replace ALLOYDB environments - patch: |- apiVersion: apps/v1 kind: Deployment metadata: name: productcatalogservice spec: template: spec: containers: - name: server env: - name: ALLOYDB_CLUSTER_NAME value: ALLOYDB_CLUSTER_NAME_VAL - name: ALLOYDB_INSTANCE_NAME value: ALLOYDB_INSTANCE_NAME_VAL - name: ALLOYDB_DATABASE_NAME value: ALLOYDB_PRODUCTS_DATABASE_NAME_VAL - name: ALLOYDB_TABLE_NAME value: ALLOYDB_PRODUCTS_TABLE_NAME_VAL - name: ALLOYDB_SECRET_NAME value: ALLOYDB_SECRET_NAME_VAL - name: PROJECT_ID value: PROJECT_ID_VAL - name: REGION value: REGION_VAL # productcatalogservice - add the GSA annotation for the productcatalogservice KSA - patch: |- apiVersion: v1 kind: ServiceAccount metadata: name: productcatalogservice annotations: iam.gke.io/gcp-service-account: ALLOYDB_USER_GSA_ID # redis - remove the redis-cart Deployment - patch: |- apiVersion: apps/v1 kind: Deployment metadata: name: redis-cart $patch: delete # redis - remove the redis-cart Service - patch: |- apiVersion: v1 kind: Service metadata: name: redis-cart $patch: delete ================================================ FILE: kustomize/components/container-images-registry/README.md ================================================ # Update the container registry of the Online Boutique apps By default, Online Boutique's services' container images are pulled from a public container registry (`us-central1-docker.pkg.dev/google-samples/microservices-demo`). One best practice is to have these container images in your own private container registry. The Kustomize variation in this folder can help with using your own private container registry. ## Change the default container registry via Kustomize To automate the deployment of Online Boutique integrated with your own container registry, you can leverage the following variation with [Kustomize](../..). From the `kustomize/` folder at the root level of this repository, execute this command: ```bash REGISTRY=my-registry # Example: us-central1-docker.pkg.dev/my-project/my-directory sed -i "s|CONTAINER_IMAGES_REGISTRY|${REGISTRY}|g" components/container-images-registry/kustomization.yaml kustomize edit add component components/container-images-registry ``` _Note: this Kustomize component will update the container registry in the `image:` field in all `Deployments`._ This will update the `kustomize/kustomization.yaml` file which could be similar to: ```yaml apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization resources: - base components: - components/container-images-registry ``` You can (optionally) locally render these manifests by running `kubectl kustomize .`. You can deploy them by running `kubectl apply -k .`. ================================================ FILE: kustomize/components/container-images-registry/kustomization.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. apiVersion: kustomize.config.k8s.io/v1alpha1 kind: Component images: - name: us-central1-docker.pkg.dev/google-samples/microservices-demo/adservice newName: CONTAINER_IMAGES_REGISTRY/adservice - name: us-central1-docker.pkg.dev/google-samples/microservices-demo/cartservice newName: CONTAINER_IMAGES_REGISTRY/cartservice - name: us-central1-docker.pkg.dev/google-samples/microservices-demo/checkoutservice newName: CONTAINER_IMAGES_REGISTRY/checkoutservice - name: us-central1-docker.pkg.dev/google-samples/microservices-demo/currencyservice newName: CONTAINER_IMAGES_REGISTRY/currencyservice - name: us-central1-docker.pkg.dev/google-samples/microservices-demo/emailservice newName: CONTAINER_IMAGES_REGISTRY/emailservice - name: us-central1-docker.pkg.dev/google-samples/microservices-demo/frontend newName: CONTAINER_IMAGES_REGISTRY/frontend - name: us-central1-docker.pkg.dev/google-samples/microservices-demo/loadgenerator newName: CONTAINER_IMAGES_REGISTRY/loadgenerator - name: us-central1-docker.pkg.dev/google-samples/microservices-demo/paymentservice newName: CONTAINER_IMAGES_REGISTRY/paymentservice - name: us-central1-docker.pkg.dev/google-samples/microservices-demo/productcatalogservice newName: CONTAINER_IMAGES_REGISTRY/productcatalogservice - name: us-central1-docker.pkg.dev/google-samples/microservices-demo/recommendationservice newName: CONTAINER_IMAGES_REGISTRY/recommendationservice - name: us-central1-docker.pkg.dev/google-samples/microservices-demo/shippingservice newName: CONTAINER_IMAGES_REGISTRY/shippingservice - name: redis newName: CONTAINER_IMAGES_REGISTRY/redis ================================================ FILE: kustomize/components/container-images-tag/README.md ================================================ # Update the container image tag of the Online Boutique apps By default, the Online Boutique apps are targeting the latest release version (see the list of versions [here](https://github.com/GoogleCloudPlatform/microservices-demo/releases)). You may need to change this image tag to target a specific version, this Kustomize variation will help you setting this up. ## Change the default container image tag via Kustomize To automate the deployment of the Online Boutique apps with a specific container imag tag, you can leverage the following variation with [Kustomize](../..). From the `kustomize/` folder at the root level of this repository, execute this command: ```bash TAG=v1.0.0 sed -i "s/CONTAINER_IMAGES_TAG/$TAG/g" components/container-images-tag/kustomization.yaml kustomize edit add component components/container-images-tag ``` _Note: this Kustomize component will update the container image tag of the `image:` field in all `Deployments`._ This will update the `kustomize/kustomization.yaml` file which could be similar to: ```yaml apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization resources: - base components: - components/container-images-tag ``` You can locally render these manifests by running `kubectl kustomize .` as well as deploying them by running `kubectl apply -k .`. **Important notes:** if combining with the other variations, here are some considerations: - should be placed before `components/container-images-registry` So for example here is the order respected: ```yaml apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization resources: - base components: - components/container-images-tag - components/container-images-registry ``` ================================================ FILE: kustomize/components/container-images-tag/kustomization.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. apiVersion: kustomize.config.k8s.io/v1alpha1 kind: Component images: - name: us-central1-docker.pkg.dev/google-samples/microservices-demo/adservice newTag: CONTAINER_IMAGES_TAG - name: us-central1-docker.pkg.dev/google-samples/microservices-demo/cartservice newTag: CONTAINER_IMAGES_TAG - name: us-central1-docker.pkg.dev/google-samples/microservices-demo/checkoutservice newTag: CONTAINER_IMAGES_TAG - name: us-central1-docker.pkg.dev/google-samples/microservices-demo/currencyservice newTag: CONTAINER_IMAGES_TAG - name: us-central1-docker.pkg.dev/google-samples/microservices-demo/emailservice newTag: CONTAINER_IMAGES_TAG - name: us-central1-docker.pkg.dev/google-samples/microservices-demo/frontend newTag: CONTAINER_IMAGES_TAG - name: us-central1-docker.pkg.dev/google-samples/microservices-demo/loadgenerator newTag: CONTAINER_IMAGES_TAG - name: us-central1-docker.pkg.dev/google-samples/microservices-demo/paymentservice newTag: CONTAINER_IMAGES_TAG - name: us-central1-docker.pkg.dev/google-samples/microservices-demo/productcatalogservice newTag: CONTAINER_IMAGES_TAG - name: us-central1-docker.pkg.dev/google-samples/microservices-demo/recommendationservice newTag: CONTAINER_IMAGES_TAG - name: us-central1-docker.pkg.dev/google-samples/microservices-demo/shippingservice newTag: CONTAINER_IMAGES_TAG ================================================ FILE: kustomize/components/container-images-tag-suffix/README.md ================================================ # Add a suffix to the image tag of the Online Boutique container images You may want to add a suffix to the Online Boutique container image tag to target a specific version. The Kustomize Component inside this folder can help. ## Add a suffix to the container image tag via Kustomize To automate the deployment of the Online Boutique apps with a suffix added to the container imag tag, you can leverage the following variation with [Kustomize](../..). From the `kustomize/` folder at the root level of this repository, execute this command: ```bash SUFFIX=-my-suffix sed -i "s/CONTAINER_IMAGES_TAG_SUFFIX/$SUFFIX/g" components/container-images-tag-suffix/kustomization.yaml kustomize edit add component components/container-images-tag-suffix ``` _Note: this Kustomize component will add a suffix to the container image tag of the `image:` field in all `Deployments`._ This will update the `kustomize/kustomization.yaml` file which could be similar to: ```yaml apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization resources: - base components: - components/container-images-tag-suffix ``` You can locally render these manifests by running `kubectl kustomize . | sed "s/$SUFFIX$SUFFIX/$SUFFIX/g"` as well as deploying them by running `kubectl kustomize . | sed "s/$SUFFIX$SUFFIX/$SUFFIX/g" | kubectl apply -f`. _Note: for this variation, `kubectl apply -k .` alone won't work because there is a [known issue currently in Kustomize](https://github.com/kubernetes-sigs/kustomize/issues/4814) where the `tagSuffix` is duplicated. The `sed "s/$SUFFIX$SUFFIX/$SUFFIX/g"` commands above are a temporary workaround._ ## Combine with other Kustomize Components If you're combining this Kustomize Component with other variations, here are some considerations: - `components/container-images-tag-suffix` should be placed before `components/container-images-registry` - `components/container-images-tag-suffix` should be placed after `components/container-images-tag` So for example here is the order respected: ```yaml apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization resources: - base components: - components/container-images-tag - components/container-images-tag-suffix - components/container-images-registry ``` ================================================ FILE: kustomize/components/container-images-tag-suffix/kustomization.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. apiVersion: kustomize.config.k8s.io/v1alpha1 kind: Component images: - name: us-central1-docker.pkg.dev/google-samples/microservices-demo/adservice tagSuffix: CONTAINER_IMAGES_TAG_SUFFIX - name: us-central1-docker.pkg.dev/google-samples/microservices-demo/cartservice tagSuffix: CONTAINER_IMAGES_TAG_SUFFIX - name: us-central1-docker.pkg.dev/google-samples/microservices-demo/checkoutservice tagSuffix: CONTAINER_IMAGES_TAG_SUFFIX - name: us-central1-docker.pkg.dev/google-samples/microservices-demo/currencyservice tagSuffix: CONTAINER_IMAGES_TAG_SUFFIX - name: us-central1-docker.pkg.dev/google-samples/microservices-demo/emailservice tagSuffix: CONTAINER_IMAGES_TAG_SUFFIX - name: us-central1-docker.pkg.dev/google-samples/microservices-demo/frontend tagSuffix: CONTAINER_IMAGES_TAG_SUFFIX - name: us-central1-docker.pkg.dev/google-samples/microservices-demo/loadgenerator tagSuffix: CONTAINER_IMAGES_TAG_SUFFIX - name: us-central1-docker.pkg.dev/google-samples/microservices-demo/paymentservice tagSuffix: CONTAINER_IMAGES_TAG_SUFFIX - name: us-central1-docker.pkg.dev/google-samples/microservices-demo/productcatalogservice tagSuffix: CONTAINER_IMAGES_TAG_SUFFIX - name: us-central1-docker.pkg.dev/google-samples/microservices-demo/recommendationservice tagSuffix: CONTAINER_IMAGES_TAG_SUFFIX - name: us-central1-docker.pkg.dev/google-samples/microservices-demo/shippingservice tagSuffix: CONTAINER_IMAGES_TAG_SUFFIX ================================================ FILE: kustomize/components/custom-base-url/README.md ================================================ # Customize the Base URL for Online Boutique This component allows you to change the base URL for the Online Boutique application. By default, the application uses the root path ("/") as its base URL. This customization sets the base URL to "/online-boutique" and updates the health check paths accordingly. ## What it does 1. Sets the `BASE_URL` environment variable to "/online-boutique" for the frontend deployment. 2. Updates the liveness probe path to "/online-boutique/_healthz". 3. Updates the readiness probe path to "/online-boutique/_healthz". ## How to use To apply this customization, you can use Kustomize to include this component in your deployment. From the `kustomize/` folder at the root level of this repository, execute this command: ```bash kustomize edit add component components/custom-base-url ``` This will update the `kustomize/kustomization.yaml` file, which could look similar to: ```yaml apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization resources: - base components: - components/custom-base-url ``` ## Render and Deploy You can locally render these manifests by running: ```bash kubectl kustomize . ``` To deploy the customized application, run: ```bash kubectl apply -k . ``` ## Customizing the Base URL If you want to use a different base URL, you can modify the `value` fields in the kustomization.yaml file. Make sure to update all three occurrences: 1. The `BASE_URL` environment variable 2. The liveness probe path 3. The readiness probe path For example, to change the base URL to "/shop", you would modify the values as follows: ```yaml value: /shop value: /shop/_healthz value: /shop/_healthz ``` Note: After changing the base URL, make sure to update any internal links or references within your application to use the new base URL. ================================================ FILE: kustomize/components/custom-base-url/kustomization.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. apiVersion: kustomize.config.k8s.io/v1alpha1 kind: Component patches: - target: kind: Deployment name: frontend patch: |- - op: add path: /spec/template/spec/containers/0/env/- value: name: BASE_URL value: /online-boutique - op: replace path: /spec/template/spec/containers/0/livenessProbe/httpGet/path value: /online-boutique/_healthz - op: replace path: /spec/template/spec/containers/0/readinessProbe/httpGet/path value: /online-boutique/_healthz ================================================ FILE: kustomize/components/cymbal-branding/README.md ================================================ # Change the Online Boutique theme to the Cymbal Shops Branding By default, when you deploy this sample app, the "Online Boutique" branding (logo and wording) will be used. But you may want to use Google Cloud's fictitious company, _Cymbal Shops_, instead. To use "Cymbal Shops" branding, set the `CYMBAL_BRANDING` environment variable to `"true"` in the the Kubernetes manifest (`.yaml`) for the `frontend` Deployment. ```yaml apiVersion: apps/v1 kind: Deployment metadata: name: frontend spec: ... template: ... spec: ... containers: ... env: ... - name: CYMBAL_BRANDING value: "true" ``` ## Deploy Online Boutique with the Cymbal Shops branding via Kustomize To automate the deployment of Online Boutique with the Cymbal Shops branding you can leverage the following variation with [Kustomize](../..). From the `kustomize/` folder at the root level of this repository, execute this command: ```bash kustomize edit add component components/cymbal-branding ``` This will update the `kustomize/kustomization.yaml` file which could be similar to: ```yaml apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization resources: - base components: - components/cymbal-branding ``` You can locally render these manifests by running `kubectl kustomize .` as well as deploying them by running `kubectl apply -k .`. ================================================ FILE: kustomize/components/cymbal-branding/kustomization.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. apiVersion: kustomize.config.k8s.io/v1alpha1 kind: Component patches: - patch: |- apiVersion: apps/v1 kind: Deployment metadata: name: frontend spec: template: spec: containers: - name: server env: - name: CYMBAL_BRANDING value: "true" ================================================ FILE: kustomize/components/google-cloud-operations/README.md ================================================ # Integrate Online Boutique with Google Cloud Operations By default, [Google Cloud Operations](https://cloud.google.com/products/operations) instrumentation is **turned off** for Online Boutique deployments. This includes Monitoring (Stats), Tracing, and Profiler. This means that even if you're running this app on [GKE](https://cloud.google.com/kubernetes-engine), traces (for example) will not be exported to [Google Cloud Trace](https://cloud.google.com/trace). If you want to re-enable Google Cloud Operations instrumentation, the easiest way is to enable the included kustomize module, which enables traces, metrics, and adds a deployment of the [Open Telemetry Collector](https://opentelemetry.io/docs/collector/) to gather the traces and metrics and forward them to the appropriate Google Cloud backend. From the `kustomize/` folder at the root level of this repository, execute this command: ```bash kustomize edit add component components/google-cloud-operations ``` This will update the `kustomize/kustomization.yaml` file which could be similar to: ```yaml apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization resources: - base components: - components/google-cloud-operations ``` You can locally render these manifests by running `kubectl kustomize .` as well as deploying them by running `kubectl apply -k .`. You will also need to make sure that you have the associated Google APIs enabled in your Google Cloud project: ```bash PROJECT_ID= gcloud services enable \ monitoring.googleapis.com \ cloudtrace.googleapis.com \ cloudprofiler.googleapis.com \ --project ${PROJECT_ID} ``` In addition to that, you will need to grant the following IAM roles associated to your Google Service Account (GSA): ```bash PROJECT_ID= GSA_NAME= gcloud projects add-iam-policy-binding ${PROJECT_ID} \ --member "serviceAccount:${GSA_NAME}@${PROJECT_ID}.iam.gserviceaccount.com" \ --role roles/cloudtrace.agent gcloud projects add-iam-policy-binding ${PROJECT_ID} \ --member "serviceAccount:${GSA_NAME}@${PROJECT_ID}.iam.gserviceaccount.com" \ --role roles/monitoring.metricWriter gcloud projects add-iam-policy-binding ${PROJECT_ID} \ --member "serviceAccount:${GSA_NAME}@${PROJECT_ID}.iam.gserviceaccount.com" \ --role roles/cloudprofiler.agent ``` **Note** Currently only trace is supported. Support for metrics, and more is coming soon. ## Changes When enabling this kustomize module, most services will be patched with a configuration similar to the following: ```yaml apiVersion: apps/v1 kind: Deployment metadata: name: productcatalogservice spec: template: spec: containers: - name: server env: - name: COLLECTOR_SERVICE_ADDR value: "opentelemetrycollector:4317" - name: ENABLE_STATS value: "1" - name: ENABLE_TRACING value: "1" ``` This patch sets environment variables to enable export of stats and tracing, as well as a variable to tell the service how to reach the new collector deployment. ## OpenTelemetry Collector Currently, this component adds a single collector service which collects traces and metrics from individual services and forwards them to the appropriate Google Cloud backend. ![Collector Architecture Diagram](collector-model.png) If you wish to experiment with different backends, you can modify the appropriate lines in [otel-collector.yaml](otel-collector.yaml) to export traces or metrics to a different backend. See the [OpenTelemetry docs](https://opentelemetry.io/docs/collector/configuration/) for more details. ## Workload Identity If you are running this sample on GKE, your GKE cluster may be configured to use [Workload Identity](https://cloud.google.com/kubernetes-engine/docs/how-to/workload-identity) to manage access to Google Cloud APIs (like Cloud Trace). If this is the case, you may not see traces properly exported, or may see an error message like `failed to export to Google Cloud Trace: rpc error: code = PermissionDenied desc = The caller does not have permission` logged by your `opentelemetrycollector` Pod(s). In order to export traces with such a setup, you need to associate the Kubernetes [ServiceAccount](https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/) (`default/default`) with your [default compute service account](https://cloud.google.com/compute/docs/access/service-accounts#default_service_account) on Google Cloud (or a custom Google Cloud service account you may create for this purpose). * To get the email address associated with your Google service account, check in the IAM section of the Cloud Console. Or run the following command in your terminal: ```bash gcloud iam service-accounts list ``` * Then, allow the Kubernetes service account to act as your Google service account with the following command (using your own `PROJECT_ID` and the `GSA_EMAIL` you found in the previous step): ```bash gcloud iam service-accounts add-iam-policy-binding ${GSA_EMAIL} \ --role roles/iam.workloadIdentityUser \ --member "serviceAccount:${PROJECT_ID}.svc.id.goog[default/default]" ``` * Annotate your Kubernetes service account (`default/default` for the `default` namespace) to use the Google IAM service account: ```bash kubectl annotate serviceaccount default \ iam.gke.io/gcp-service-account=${GSA_EMAIL} ``` * Finally, restart your `opentelemetrycollector` deployment to reflect the new settings: ```bash kubectl rollout restart deployment opentelemetrycollector ``` When the new Pod rolls out, you should start to see traces appear in the cloud console. ================================================ FILE: kustomize/components/google-cloud-operations/kustomization.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. apiVersion: kustomize.config.k8s.io/v1alpha1 kind: Component resources: - otel-collector.yaml patches: # adservice - not yet implemented # checkoutservice - tracing, profiler - patch: |- apiVersion: apps/v1 kind: Deployment metadata: name: checkoutservice spec: template: spec: containers: - name: server env: - name: COLLECTOR_SERVICE_ADDR value: "opentelemetrycollector:4317" - name: OTEL_SERVICE_NAME value: "checkoutservice" - name: ENABLE_TRACING value: "1" - name: ENABLE_PROFILER value: "1" # currencyservice - tracing, profiler - patch: |- apiVersion: apps/v1 kind: Deployment metadata: name: currencyservice spec: template: spec: containers: - name: server env: - name: COLLECTOR_SERVICE_ADDR value: "opentelemetrycollector:4317" - name: OTEL_SERVICE_NAME value: "currencyservice" - name: ENABLE_TRACING value: "1" - name: DISABLE_PROFILER $patch: delete # emailservice - tracing, profiler - patch: |- apiVersion: apps/v1 kind: Deployment metadata: name: emailservice spec: template: spec: containers: - name: server env: - name: COLLECTOR_SERVICE_ADDR value: "opentelemetrycollector:4317" - name: OTEL_SERVICE_NAME value: "emailservice" - name: ENABLE_TRACING value: "1" - name: DISABLE_PROFILER $patch: delete # frontend - tracing, profiler - patch: |- apiVersion: apps/v1 kind: Deployment metadata: name: frontend spec: template: spec: containers: - name: server env: - name: ENABLE_TRACING value: "1" - name: COLLECTOR_SERVICE_ADDR value: "opentelemetrycollector:4317" - name: OTEL_SERVICE_NAME value: "frontend" - name: ENABLE_PROFILER value: "1" # paymentservice - tracing, profiler - patch: |- apiVersion: apps/v1 kind: Deployment metadata: name: paymentservice spec: template: spec: containers: - name: server env: - name: COLLECTOR_SERVICE_ADDR value: "opentelemetrycollector:4317" - name: OTEL_SERVICE_NAME value: "paymentservice" - name: ENABLE_TRACING value: "1" - name: DISABLE_PROFILER $patch: delete # productcatalogservice - tracing, profiler - patch: |- apiVersion: apps/v1 kind: Deployment metadata: name: productcatalogservice spec: template: spec: containers: - name: server env: - name: COLLECTOR_SERVICE_ADDR value: "opentelemetrycollector:4317" - name: OTEL_SERVICE_NAME value: "productcatalogservice" - name: ENABLE_TRACING value: "1" - name: DISABLE_PROFILER value: "1" # recommendationservice - tracing, profiler - patch: |- apiVersion: apps/v1 kind: Deployment metadata: name: recommendationservice spec: template: spec: containers: - name: server env: - name: COLLECTOR_SERVICE_ADDR value: "opentelemetrycollector:4317" - name: OTEL_SERVICE_NAME value: "recommendationservice" - name: ENABLE_TRACING value: "1" - name: DISABLE_PROFILER $patch: delete # shippingservice - stats, tracing, profiler - patch: |- apiVersion: apps/v1 kind: Deployment metadata: name: shippingservice spec: template: spec: containers: - name: server env: - name: DISABLE_PROFILER $patch: delete ================================================ FILE: kustomize/components/google-cloud-operations/otel-collector.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. --- apiVersion: apps/v1 kind: Deployment metadata: name: opentelemetrycollector spec: replicas: 1 selector: matchLabels: app: opentelemetrycollector template: metadata: labels: app: opentelemetrycollector spec: securityContext: fsGroup: 1000 runAsGroup: 1000 runAsNonRoot: true runAsUser: 1000 # Init container retrieves the current cloud project id from the metadata server # and inserts it into the collector config template # https://cloud.google.com/compute/docs/storing-retrieving-metadata initContainers: - name: otel-gateway-init securityContext: allowPrivilegeEscalation: false capabilities: drop: - ALL privileged: false readOnlyRootFilesystem: true image: busybox:latest@sha256:b3255e7dfbcd10cb367af0d409747d511aeb66dfac98cf30e97e87e4207dd76f command: - '/bin/sh' - '-c' - | sed "s/{{PROJECT_ID}}/$(curl -H 'Metadata-Flavor: Google' http://metadata.google.internal/computeMetadata/v1/project/project-id)/" /template/collector-gateway-config-template.yaml >> /conf/collector-gateway-config.yaml volumeMounts: - name: collector-gateway-config-template mountPath: /template - name: collector-gateway-config mountPath: /conf containers: # This gateway container will receive traces and metrics from each microservice # and forward it to GCP - name: otel-gateway securityContext: allowPrivilegeEscalation: false capabilities: drop: - ALL privileged: false readOnlyRootFilesystem: true args: - --config=/conf/collector-gateway-config.yaml image: otel/opentelemetry-collector-contrib:0.147.0@sha256:e7c92c715f28ff142f3bcaccd4fc5603cf4c71276ef09954a38eb4038500a5a5 volumeMounts: - name: collector-gateway-config mountPath: /conf volumes: # Simple ConfigMap volume with template file - name: collector-gateway-config-template configMap: items: - key: collector-gateway-config-template.yaml path: collector-gateway-config-template.yaml name: collector-gateway-config-template # Create a volume to store the expanded template (with correct cloud project ID) - name: collector-gateway-config emptyDir: {} --- apiVersion: v1 kind: Service metadata: name: opentelemetrycollector spec: ports: - name: grpc-otlp port: 4317 protocol: TCP targetPort: 4317 selector: app: opentelemetrycollector type: ClusterIP --- apiVersion: v1 kind: ConfigMap metadata: name: collector-gateway-config-template # Open Telemetry Collector config # https://opentelemetry.io/docs/collector/configuration/ data: collector-gateway-config-template.yaml: | receivers: otlp: protocols: grpc: processors: exporters: googlecloud: project: {{PROJECT_ID}} service: pipelines: traces: receivers: [otlp] # Receive otlp-formatted data from other collector instances processors: [] exporters: [googlecloud] # Export traces directly to Google Cloud metrics: receivers: [otlp] processors: [] exporters: [googlecloud] # Export metrics to Google Cloud ================================================ FILE: kustomize/components/memorystore/README.md ================================================ # Integrate Online Boutique with Memorystore (Redis) By default the `cartservice` app is serializing the data in an in-cluster Redis database. Using a database outside your GKE cluster could bring more resiliency and more security with a managed service like Google Cloud Memorystore (Redis). ![Architecture diagram with Memorystore](/docs/img/memorystore.png) ## Provision a Memorystore (Redis) instance Important notes: - You can connect to a Memorystore (Redis) instance from GKE clusters that are in the same region and use the same network as your instance. - You cannot connect to a Memorystore (Redis) instance from a GKE cluster without VPC-native/IP aliasing enabled. To provision a Memorystore (Redis) instance you can follow the following instructions: ```bash ZONE="" REGION="" gcloud services enable redis.googleapis.com gcloud redis instances create redis-cart \ --size=1 \ --region=${REGION} \ --zone=${ZONE} \ --redis-version=redis_7_0 ``` _Note: You can also find in this repository the Terraform script to provision the Memorystore (Redis) instance alongside the GKE cluster, more information [here](/terraform)._ ## Deploy Online Boutique connected to a Memorystore (Redis) instance To automate the deployment of Online Boutique integrated with Memorystore (Redis) you can leverage the following variation with [Kustomize](../..). From the `kustomize/` folder at the root level of this repository, execute this command: ```bash kustomize edit add component components/memorystore ``` _Note: this Kustomize component will also remove the `redis-cart` `Deployment` and `Service` not used anymore._ This will update the `kustomize/kustomization.yaml` file which could be similar to: ```yaml apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization resources: - base components: - components/memorystore ``` Update current Kustomize manifest to target this Memorystore (Redis) instance. ```bash REDIS_IP=$(gcloud redis instances describe redis-cart --region=${REGION} --format='get(host)') REDIS_PORT=$(gcloud redis instances describe redis-cart --region=${REGION} --format='get(port)') sed -i "s/REDIS_CONNECTION_STRING/${REDIS_IP}:${REDIS_PORT}/g" components/memorystore/kustomization.yaml ``` You can locally render these manifests by running `kubectl kustomize .` as well as deploying them by running `kubectl apply -k .`. ## Resources - [Connecting to a Redis instance from a Google Kubernetes Engine cluster](https://cloud.google.com/memorystore/docs/redis/connect-redis-instance-gke) - [Seamlessly encrypt traffic from any apps in your Mesh to Memorystore (Redis)](https://medium.com/google-cloud/64b71969318d) ================================================ FILE: kustomize/components/memorystore/kustomization.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. apiVersion: kustomize.config.k8s.io/v1alpha1 kind: Component patches: # cartservice - replace REDIS_ADDR to target new Memorystore (redis) instance - patch: |- apiVersion: apps/v1 kind: Deployment metadata: name: cartservice spec: template: spec: containers: - name: server env: - name: REDIS_ADDR value: "REDIS_CONNECTION_STRING" # redis - remove the redis-cart Deployment - patch: |- apiVersion: apps/v1 kind: Deployment metadata: name: redis-cart $patch: delete # redis - remove the redis-cart Service - patch: |- apiVersion: v1 kind: Service metadata: name: redis-cart $patch: delete ================================================ FILE: kustomize/components/network-policies/README.md ================================================ # Secure Online Boutique with Network Policies You can use [Network Policies](https://kubernetes.io/docs/concepts/services-networking/network-policies/) enforcement to control the communication between your cluster's Pods and Services. To use `NetworkPolicies` in Google Kubernetes Engine (GKE), you will need a GKE cluster with network policy enforcement enabled, the recommended approach is to use [GKE Dataplane V2](https://cloud.google.com/kubernetes-engine/docs/how-to/dataplane-v2). To use `NetworkPolicies` on a local cluster such as [minikube](https://minikube.sigs.k8s.io/docs/start/), you will need to use an alternative CNI that supports `NetworkPolicies` like [Calico](https://projectcalico.docs.tigera.io/getting-started/kubernetes/minikube). To run a minikube cluster with Calico, run `minikube start --cni=calico`. By design, the minikube default CNI [Kindnet](https://github.com/aojea/kindnet) does not support it. ## Deploy Online Boutique with `NetworkPolicies` via Kustomize To automate the deployment of Online Boutique integrated with fine granular `NetworkPolicies` (one per `Deployment`), you can leverage the following variation with [Kustomize](../..). From the `kustomize/` folder at the root level of this repository, execute this command: ```bash kustomize edit add component components/network-policies ``` This will update the `kustomize/kustomization.yaml` file which could be similar to: ```yaml apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization resources: - base components: - components/network-policies ``` You can locally render these manifests by running `kubectl kustomize .` as well as deploying them by running `kubectl apply -k .`. Once deployed, you can verify that the `NetworkPolicies` are successfully deployed: ```bash kubectl get networkpolicy ``` The output could be similar to: ```output NAME POD-SELECTOR AGE adservice app=adservice 2m58s cartservice app=cartservice 2m58s checkoutservice app=checkoutservice 2m58s currencyservice app=currencyservice 2m58s deny-all 2m58s emailservice app=emailservice 2m58s frontend app=frontend 2m58s loadgenerator app=loadgenerator 2m58s paymentservice app=paymentservice 2m58s productcatalogservice app=productcatalogservice 2m58s recommendationservice app=recommendationservice 2m58s redis-cart app=redis-cart 2m58s shippingservice app=shippingservice 2m58s ``` _Note: `Egress` is wide open in these `NetworkPolicies` . In our case, we do this is on purpose because there are multiple egress destinations to take into consideration like the Kubernetes DNS, Istio control plane (`istiod`), Cloud Trace API, Cloud Profiler API, etc._ ## Related Resources - [GKE Dataplane V2 announcement](https://cloud.google.com/blog/products/containers-kubernetes/bringing-ebpf-and-cilium-to-google-kubernetes-engine) - [Kubernetes Network Policies](https://kubernetes.io/docs/concepts/services-networking/network-policies/) - [Kubernetes Network Policy Recipes](https://github.com/ahmetb/kubernetes-network-policy-recipes) - [Network policy logging](https://cloud.google.com/kubernetes-engine/docs/how-to/network-policy-logging) ================================================ FILE: kustomize/components/network-policies/kustomization.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. apiVersion: kustomize.config.k8s.io/v1alpha1 kind: Component resources: - network-policy-deny-all.yaml - network-policy-adservice.yaml - network-policy-cartservice.yaml - network-policy-checkoutservice.yaml - network-policy-currencyservice.yaml - network-policy-emailservice.yaml - network-policy-frontend.yaml - network-policy-loadgenerator.yaml - network-policy-paymentservice.yaml - network-policy-productcatalogservice.yaml - network-policy-recommendationservice.yaml - network-policy-redis.yaml - network-policy-shippingservice.yaml ================================================ FILE: kustomize/components/network-policies/network-policy-adservice.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. apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: adservice spec: podSelector: matchLabels: app: adservice policyTypes: - Ingress - Egress ingress: - from: - podSelector: matchLabels: app: frontend ports: - port: 9555 protocol: TCP egress: - {} ================================================ FILE: kustomize/components/network-policies/network-policy-cartservice.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. apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: cartservice spec: podSelector: matchLabels: app: cartservice policyTypes: - Ingress - Egress ingress: - from: - podSelector: matchLabels: app: frontend - podSelector: matchLabels: app: checkoutservice ports: - port: 7070 protocol: TCP egress: - {} ================================================ FILE: kustomize/components/network-policies/network-policy-checkoutservice.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. apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: checkoutservice spec: podSelector: matchLabels: app: checkoutservice policyTypes: - Ingress - Egress ingress: - from: - podSelector: matchLabels: app: frontend ports: - port: 5050 protocol: TCP egress: - {} ================================================ FILE: kustomize/components/network-policies/network-policy-currencyservice.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. apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: currencyservice spec: podSelector: matchLabels: app: currencyservice policyTypes: - Ingress - Egress ingress: - from: - podSelector: matchLabels: app: frontend - podSelector: matchLabels: app: checkoutservice ports: - port: 7000 protocol: TCP egress: - {} ================================================ FILE: kustomize/components/network-policies/network-policy-deny-all.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. apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: deny-all spec: podSelector: {} policyTypes: - Ingress - Egress ================================================ FILE: kustomize/components/network-policies/network-policy-emailservice.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. apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: emailservice spec: podSelector: matchLabels: app: emailservice policyTypes: - Ingress - Egress ingress: - from: - podSelector: matchLabels: app: checkoutservice ports: - port: 8080 protocol: TCP egress: - {} ================================================ FILE: kustomize/components/network-policies/network-policy-frontend.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. apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: frontend spec: podSelector: matchLabels: app: frontend policyTypes: - Ingress - Egress ingress: - {} egress: - {} ================================================ FILE: kustomize/components/network-policies/network-policy-loadgenerator.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. apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: loadgenerator spec: podSelector: matchLabels: app: loadgenerator policyTypes: - Egress egress: - {} ================================================ FILE: kustomize/components/network-policies/network-policy-paymentservice.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. apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: paymentservice spec: podSelector: matchLabels: app: paymentservice policyTypes: - Ingress - Egress ingress: - from: - podSelector: matchLabels: app: checkoutservice ports: - port: 50051 protocol: TCP egress: - {} ================================================ FILE: kustomize/components/network-policies/network-policy-productcatalogservice.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. apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: productcatalogservice spec: podSelector: matchLabels: app: productcatalogservice policyTypes: - Ingress - Egress ingress: - from: - podSelector: matchLabels: app: frontend - podSelector: matchLabels: app: checkoutservice - podSelector: matchLabels: app: recommendationservice ports: - port: 3550 protocol: TCP egress: - {} ================================================ FILE: kustomize/components/network-policies/network-policy-recommendationservice.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. apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: recommendationservice spec: podSelector: matchLabels: app: recommendationservice policyTypes: - Ingress - Egress ingress: - from: - podSelector: matchLabels: app: frontend ports: - port: 8080 protocol: TCP egress: - {} ================================================ FILE: kustomize/components/network-policies/network-policy-redis.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. apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: redis-cart spec: podSelector: matchLabels: app: redis-cart policyTypes: - Ingress - Egress ingress: - from: - podSelector: matchLabels: app: cartservice ports: - port: 6379 protocol: TCP egress: - {} ================================================ FILE: kustomize/components/network-policies/network-policy-shippingservice.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. apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: shippingservice spec: podSelector: matchLabels: app: shippingservice policyTypes: - Ingress - Egress ingress: - from: - podSelector: matchLabels: app: frontend - podSelector: matchLabels: app: checkoutservice ports: - port: 50051 protocol: TCP egress: - {} ================================================ FILE: kustomize/components/non-public-frontend/README.md ================================================ # Remove the public exposure of Online Boutique's frontend By default, when you deploy Online Boutique, a `Service` (named `frontend-external`) of type `LoadBalancer` is deployed with a publicly accessible IP address. But you may not want to expose this sample app publicly. ## Deploy Online Boutique without the default public endpoint To automate the deployment of Online Boutique without the default public endpoint you can leverage the following variation with [Kustomize](../..). From the `kustomize/` folder at the root level of this repository, execute this command: ```bash kustomize edit add component components/non-public-frontend ``` This will update the `kustomize/kustomization.yaml` file which could be similar to: ```yaml apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization resources: - base components: - components/non-public-frontend ``` You can locally render these manifests by running `kubectl kustomize .` as well as deploying them by running `kubectl apply -k .`. ================================================ FILE: kustomize/components/non-public-frontend/kustomization.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. apiVersion: kustomize.config.k8s.io/v1alpha1 kind: Component patches: # frontend - delete frontend-external service - patch: |- apiVersion: v1 kind: Service metadata: name: frontend-external $patch: delete ================================================ FILE: kustomize/components/service-mesh-istio/README.md ================================================ # Service mesh with Istio You can use [Istio](https://istio.io) to enable [service mesh features](https://cloud.google.com/service-mesh/docs/overview) such as traffic management, observability, and security. Istio can be provisioned using Cloud Service Mesh (CSM), the Open Source Software (OSS) istioctl tool, or via other Istio providers. You can then label individual namespaces for sidecar injection and configure an Istio gateway to replace the frontend-external load balancer. # Setup The following CLI tools needs to be installed and in the PATH: - `gcloud` - `kubectl` - `kustomize` - `istioctl` (optional) 1. Set-up some default environment variables. ```sh PROJECT_ID="" REGION=" 9555/TCP 49s service/cartservice ClusterIP 10.68.184.25 7070/TCP 49s service/checkoutservice ClusterIP 10.68.177.213 5050/TCP 49s service/currencyservice ClusterIP 10.68.249.87 7000/TCP 49s service/emailservice ClusterIP 10.68.205.123 5000/TCP 49s service/frontend ClusterIP 10.68.94.203 80/TCP 48s service/istio-gateway-istio LoadBalancer 10.68.147.158 35.247.123.146 15021:30376/TCP,80:30332/TCP 45s service/kubernetes ClusterIP 10.68.0.1 443/TCP 65m service/paymentservice ClusterIP 10.68.114.19 50051/TCP 48s service/productcatalogservice ClusterIP 10.68.240.153 3550/TCP 48s service/recommendationservice ClusterIP 10.68.117.97 8080/TCP 48s service/redis-cart ClusterIP 10.68.189.126 6379/TCP 48s service/shippingservice ClusterIP 10.68.221.62 50051/TCP 48s ``` 1. Find the external IP address of your Istio gateway. ```sh INGRESS_HOST="$(kubectl get gateway istio-gateway \ -o jsonpath='{.status.addresses[*].value}')" ``` 1. Navigate to the frontend in a web browser. ``` http://$INGRESS_HOST ``` # Additional service mesh demos using Online Boutique - [Canary deployment](https://github.com/GoogleCloudPlatform/istio-samples/tree/master/istio-canary-gke) - [Security (mTLS, JWT, Authorization)](https://github.com/GoogleCloudPlatform/istio-samples/tree/master/security-intro) - [Cloud Operations (Stackdriver)](https://github.com/GoogleCloudPlatform/istio-samples/tree/master/istio-stackdriver) - [Stackdriver metrics (Open source Istio)](https://github.com/GoogleCloudPlatform/istio-samples/tree/master/stackdriver-metrics) # Related resources - [Deploying classic istio-ingressgateway in ASM](https://cloud.google.com/service-mesh/docs/gateways#deploy_gateways) - [Uninstall Istio via istioctl](https://istio.io/latest/docs/setup/install/istioctl/#uninstall-istio) - [Uninstall Cloud Service Mesh](https://cloud.google.com/service-mesh/docs/uninstall) ================================================ FILE: kustomize/components/service-mesh-istio/allow-egress-googleapis.yaml ================================================ # Copyright 2018 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. apiVersion: networking.istio.io/v1alpha3 kind: ServiceEntry metadata: name: allow-egress-googleapis spec: hosts: - "accounts.google.com" # Used to get token - "*.googleapis.com" ports: - number: 80 protocol: HTTP name: http - number: 443 protocol: HTTPS name: https --- apiVersion: networking.istio.io/v1alpha3 kind: ServiceEntry metadata: name: allow-egress-google-metadata spec: hosts: - metadata.google.internal addresses: - 169.254.169.254 # GCE metadata server ports: - number: 80 name: http protocol: HTTP - number: 443 name: https protocol: HTTPS ================================================ FILE: kustomize/components/service-mesh-istio/frontend-gateway.yaml ================================================ # Copyright 2018 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. apiVersion: gateway.networking.k8s.io/v1beta1 kind: Gateway metadata: name: istio-gateway spec: gatewayClassName: istio listeners: - name: http port: 80 protocol: HTTP allowedRoutes: namespaces: from: Same --- apiVersion: gateway.networking.k8s.io/v1beta1 kind: HTTPRoute metadata: name: frontend-route spec: parentRefs: - name: istio-gateway rules: - matches: - path: value: / backendRefs: - name: frontend port: 80 ================================================ FILE: kustomize/components/service-mesh-istio/frontend.yaml ================================================ # Copyright 2018 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. apiVersion: networking.istio.io/v1alpha3 kind: VirtualService metadata: name: frontend spec: hosts: - "frontend.default.svc.cluster.local" http: - route: - destination: host: frontend port: number: 80 ================================================ FILE: kustomize/components/service-mesh-istio/kustomization.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 # # 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. apiVersion: kustomize.config.k8s.io/v1alpha1 kind: Component resources: - allow-egress-googleapis.yaml - frontend-gateway.yaml - frontend.yaml patches: # frontend - delete frontend-external service (same as non-public-frontend component) - patch: |- apiVersion: v1 kind: Service metadata: name: frontend-external $patch: delete ================================================ FILE: kustomize/components/shopping-assistant/README.md ================================================ # Shopping Assistant with RAG & AlloyDB This demo adds a new service to Online Boutique called `shoppingassistantservice` which, alongside an Alloy-DB backed products catalog, adds a RAG-featured AI assistant to the frontend experience, helping users suggest products matching their home decor. ## Setup instructions **Note:** This demo requires a Google Cloud project where you to have the `owner` role, else you may be unable to enable APIs or modify VPC rules that are needed for this demo. 1. Set some environment variables. ```sh export PROJECT_ID= export PROJECT_NUMBER= export PGPASSWORD= ``` **Note**: The project ID and project number of your Google Cloud project can be found in the Console. The PostgreSQL password can be set to anything you want, but make sure to note it down. 1. Change your default Google Cloud project. ```sh gcloud auth login gcloud config set project $PROJECT_ID ``` 1. Enable the Google Kubernetes Engine (GKE) and Artifact Registry (AR) APIs. ```sh gcloud services enable container.googleapis.com gcloud services enable artifactregistry.googleapis.com ``` 1. Create a GKE Autopilot cluster. This may take a few minutes. ```sh gcloud container clusters create-auto cymbal-shops \ --region=us-central1 ``` 1. Change your Kubernetes context to your newly created GKE cluster. ```sh gcloud container clusters get-credentials cymbal-shops \ --region us-central1 ``` 1. Create an Artifact Registry container image repository. ```sh gcloud artifacts repositories create images \ --repository-format=docker \ --location=us-central1 ``` 1. Clone the `microservices-demo` repository locally. ```sh git clone https://github.com/GoogleCloudPlatform/microservices-demo \ && cd microservices-demo/ ``` 1. Run script #1. If it asks about policy bindings, select the option `None`. This may take a few minutes. ```sh ./kustomize/components/shopping-assistant/scripts/1_deploy_alloydb_infra.sh ``` **Note**: If you are on macOS and use a non-GNU version of `sed`, you may have to tweak the script to use `gsed` instead. 1. Create a Linux VM in Compute Engine (GCE). ```sh gcloud compute instances create gce-linux \ --zone=us-central1-a \ --machine-type=e2-micro \ --image-family=debian-12 \ --image-project=debian-cloud ``` 1. SSH into the VM. From here until we exit, all steps happen in the VM. ```sh gcloud compute ssh gce-linux \ --zone "us-central1-a" ``` 1. Install the Postgres client and set your default Google Cloud project. ```sh sudo apt-get install -y postgresql-client gcloud auth login gcloud config set project ``` 1. Copy script #2, the python script, and the products.json to the VM. Make sure the scripts are executable. ```sh nano 2_create_populate_alloydb_tables.sh # paste content nano generate_sql_from_products.py # paste content nano products.json # paste content chmod +x 2_create_populate_alloydb_tables.sh chmod +x generate_sql_from_products.py ``` **Note:** You can find the files at the following places: - `kustomize/components/shopping-assistant/scripts/2_create_populate_alloydb_tables.sh` - `kustomize/components/shopping-assistant/scripts/generate_sql_from_products.py` - `src/productcatalogservice/products.json` 1. Run script #2 in the VM. If it asks for a postgres password, it should be the same that you set in script #1 earlier. This may take a few minutes. ```sh ./2_create_populate_alloydb_tables.sh ``` 1. Exit SSH. ```sh exit ``` 1. Create an API key in the [Credentials page](https://pantheon.corp.google.com/apis/credentials) with permissions for "Generative Language API", and make note of the secret key. 1. Replace the Google API key placeholder in the shoppingassistant service. ```sh export GOOGLE_API_KEY= sed -i "s/GOOGLE_API_KEY_VAL/${GOOGLE_API_KEY}/g" kustomize/components/shopping-assistant/shoppingassistantservice.yaml ``` 1. Edit the root Kustomize file to enable the `alloydb` and `shopping-assistant` components. ```sh nano kubernetes-manifests/kustomization.yaml # make the modifications below ``` ```yaml # ...head of the file components: # remove this comment # - ../kustomize/components/cymbal-branding # - ../kustomize/components/google-cloud-operations # - ../kustomize/components/memorystore # - ../kustomize/components/network-policies - ../kustomize/components/alloydb # remove this comment - ../kustomize/components/shopping-assistant # remove this comment # - ../kustomize/components/spanner # - ../kustomize/components/container-images-tag # - ../kustomize/components/container-images-tag-suffix # - ../kustomize/components/container-images-registry ``` 1. Deploy to the GKE cluster. ```sh skaffold run --default-repo=us-central1-docker.pkg.dev/$PROJECT_ID/images ``` 1. Wait for all the pods to be up and running. You can then find the external IP and navigate to it. ```sh kubectl get pods kubectl get services ``` ================================================ FILE: kustomize/components/shopping-assistant/kustomization.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 # # 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. apiVersion: kustomize.config.k8s.io/v1alpha1 kind: Component resources: - shoppingassistantservice.yaml patches: - patch: |- apiVersion: apps/v1 kind: Deployment metadata: name: frontend spec: template: spec: containers: - name: server env: - name: ENABLE_ASSISTANT value: "true" ================================================ FILE: kustomize/components/shopping-assistant/scripts/1_deploy_alloydb_infra.sh ================================================ #!/bin/sh # # 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 # # 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. set -e set -x # Replace me PROJECT_ID=$PROJECT_ID PROJECT_NUMBER=$PROJECT_NUMBER PGPASSWORD=$PGPASSWORD # Set sensible defaults REGION=us-central1 USE_GKE_GCLOUD_AUTH_PLUGIN=True ALLOYDB_NETWORK=default ALLOYDB_SERVICE_NAME=onlineboutique-network-range ALLOYDB_CLUSTER_NAME=onlineboutique-cluster ALLOYDB_INSTANCE_NAME=onlineboutique-instance ALLOYDB_CARTS_DATABASE_NAME=carts ALLOYDB_CARTS_TABLE_NAME=cart_items ALLOYDB_PRODUCTS_DATABASE_NAME=products ALLOYDB_PRODUCTS_TABLE_NAME=catalog_items ALLOYDB_USER_GSA_NAME=alloydb-user-sa ALLOYDB_USER_GSA_ID=${ALLOYDB_USER_GSA_NAME}@${PROJECT_ID}.iam.gserviceaccount.com ALLOYDB_SECRET_NAME=alloydb-secret # Enable services gcloud services enable alloydb.googleapis.com gcloud services enable servicenetworking.googleapis.com gcloud services enable secretmanager.googleapis.com gcloud services enable aiplatform.googleapis.com gcloud services enable generativelanguage.googleapis.com # Set our DB credentials behind the secret echo $PGPASSWORD | gcloud secrets create ${ALLOYDB_SECRET_NAME} --data-file=- # Set up needed service connection gcloud compute addresses create ${ALLOYDB_SERVICE_NAME} \ --global \ --purpose=VPC_PEERING \ --prefix-length=16 \ --description="Online Boutique Private Services" \ --network=${ALLOYDB_NETWORK} gcloud services vpc-peerings connect \ --service=servicenetworking.googleapis.com \ --ranges=${ALLOYDB_SERVICE_NAME} \ --network=${ALLOYDB_NETWORK} gcloud alloydb clusters create ${ALLOYDB_CLUSTER_NAME} \ --region=${REGION} \ --password=${PGPASSWORD} \ --disable-automated-backup \ --network=${ALLOYDB_NETWORK} gcloud alloydb instances create ${ALLOYDB_INSTANCE_NAME} \ --cluster=${ALLOYDB_CLUSTER_NAME} \ --region=${REGION} \ --cpu-count=4 \ --instance-type=PRIMARY gcloud alloydb instances create ${ALLOYDB_INSTANCE_NAME}-replica \ --cluster=${ALLOYDB_CLUSTER_NAME} \ --region=${REGION} \ --cpu-count=4 \ --instance-type=READ_POOL \ --read-pool-node-count=2 gcloud beta alloydb instances update ${ALLOYDB_INSTANCE_NAME} \ --cluster=${ALLOYDB_CLUSTER_NAME} \ --region=${REGION} \ --assign-inbound-public-ip=ASSIGN_IPV4 \ --database-flags password.enforce_complexity=on # Fetch the primary and read IPs ALLOYDB_PRIMARY_IP=`gcloud alloydb instances list --region=${REGION} --cluster=${ALLOYDB_CLUSTER_NAME} --filter="INSTANCE_TYPE:PRIMARY" --format=flattened | sed -nE "s/ipAddress:\s*(.*)/\1/p"` ALLOYDB_READ_IP=`gcloud alloydb instances list --region=${REGION} --cluster=${ALLOYDB_CLUSTER_NAME} --filter="INSTANCE_TYPE:READ_POOL" --format=flattened | sed -nE "s/ipAddress:\s*(.*)/\1/p"` # Substitute environment values (alloydb/kustomization.yaml) sed -i "s/PROJECT_ID_VAL/${PROJECT_ID}/g" kustomize/components/alloydb/kustomization.yaml sed -i "s/REGION_VAL/${REGION}/g" kustomize/components/alloydb/kustomization.yaml sed -i "s/ALLOYDB_PRIMARY_IP_VAL/${ALLOYDB_PRIMARY_IP}/g" kustomize/components/alloydb/kustomization.yaml sed -i "s/ALLOYDB_USER_GSA_ID/${ALLOYDB_USER_GSA_ID}/g" kustomize/components/alloydb/kustomization.yaml sed -i "s/ALLOYDB_CLUSTER_NAME_VAL/${ALLOYDB_CLUSTER_NAME}/g" kustomize/components/alloydb/kustomization.yaml sed -i "s/ALLOYDB_INSTANCE_NAME_VAL/${ALLOYDB_INSTANCE_NAME}/g" kustomize/components/alloydb/kustomization.yaml sed -i "s/ALLOYDB_CARTS_DATABASE_NAME_VAL/${ALLOYDB_CARTS_DATABASE_NAME}/g" kustomize/components/alloydb/kustomization.yaml sed -i "s/ALLOYDB_CARTS_TABLE_NAME_VAL/${ALLOYDB_CARTS_TABLE_NAME}/g" kustomize/components/alloydb/kustomization.yaml sed -i "s/ALLOYDB_PRODUCTS_DATABASE_NAME_VAL/${ALLOYDB_PRODUCTS_DATABASE_NAME}/g" kustomize/components/alloydb/kustomization.yaml sed -i "s/ALLOYDB_PRODUCTS_TABLE_NAME_VAL/${ALLOYDB_PRODUCTS_TABLE_NAME}/g" kustomize/components/alloydb/kustomization.yaml sed -i "s/ALLOYDB_SECRET_NAME_VAL/${ALLOYDB_SECRET_NAME}/g" kustomize/components/alloydb/kustomization.yaml # Substitute environment values (kustomize/components/shopping-assistant/shoppingassistantservice.yaml) sed -i "s/PROJECT_ID_VAL/${PROJECT_ID}/g" kustomize/components/shopping-assistant/shoppingassistantservice.yaml sed -i "s/REGION_VAL/${REGION}/g" kustomize/components/shopping-assistant/shoppingassistantservice.yaml sed -i "s/ALLOYDB_CLUSTER_NAME_VAL/${ALLOYDB_CLUSTER_NAME}/g" kustomize/components/shopping-assistant/shoppingassistantservice.yaml sed -i "s/ALLOYDB_INSTANCE_NAME_VAL/${ALLOYDB_INSTANCE_NAME}/g" kustomize/components/shopping-assistant/shoppingassistantservice.yaml sed -i "s/ALLOYDB_DATABASE_NAME_VAL/${ALLOYDB_PRODUCTS_DATABASE_NAME}/g" kustomize/components/shopping-assistant/shoppingassistantservice.yaml sed -i "s/ALLOYDB_TABLE_NAME_VAL/${ALLOYDB_PRODUCTS_TABLE_NAME}/g" kustomize/components/shopping-assistant/shoppingassistantservice.yaml sed -i "s/ALLOYDB_SECRET_NAME_VAL/${ALLOYDB_SECRET_NAME}/g" kustomize/components/shopping-assistant/shoppingassistantservice.yaml sed -i "s/ALLOYDB_USER_GSA_ID/${ALLOYDB_USER_GSA_ID}/g" kustomize/components/shopping-assistant/shoppingassistantservice.yaml # Create service account for the cart and shopping assistant services gcloud iam service-accounts create ${ALLOYDB_USER_GSA_NAME} \ --display-name=${ALLOYDB_USER_GSA_NAME} gcloud projects add-iam-policy-binding ${PROJECT_ID} --member=serviceAccount:${ALLOYDB_USER_GSA_ID} --role=roles/alloydb.client gcloud projects add-iam-policy-binding ${PROJECT_ID} --member=serviceAccount:${ALLOYDB_USER_GSA_ID} --role=roles/alloydb.databaseUser gcloud projects add-iam-policy-binding ${PROJECT_ID} --member=serviceAccount:${ALLOYDB_USER_GSA_ID} --role=roles/secretmanager.secretAccessor gcloud projects add-iam-policy-binding ${PROJECT_ID} --member=serviceAccount:${ALLOYDB_USER_GSA_ID} --role=roles/serviceusage.serviceUsageConsumer gcloud projects add-iam-policy-binding ${PROJECT_ID} --member=serviceAccount:service-${PROJECT_NUMBER}@gcp-sa-alloydb.iam.gserviceaccount.com --role=roles/aiplatform.user gcloud iam service-accounts add-iam-policy-binding ${ALLOYDB_USER_GSA_ID} \ --member "serviceAccount:${PROJECT_ID}.svc.id.goog[default/cartservice]" \ --role roles/iam.workloadIdentityUser gcloud iam service-accounts add-iam-policy-binding ${ALLOYDB_USER_GSA_ID} \ --member "serviceAccount:${PROJECT_ID}.svc.id.goog[default/shoppingassistantservice]" \ --role roles/iam.workloadIdentityUser gcloud iam service-accounts add-iam-policy-binding ${ALLOYDB_USER_GSA_ID} \ --member "serviceAccount:${PROJECT_ID}.svc.id.goog[default/productcatalogservice]" \ --role roles/iam.workloadIdentityUser ================================================ FILE: kustomize/components/shopping-assistant/scripts/2_create_populate_alloydb_tables.sh ================================================ #!/bin/sh # # 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 # # 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. set -e set -x # Set sensible defaults REGION=us-central1 ALLOYDB_CLUSTER_NAME=onlineboutique-cluster ALLOYDB_CARTS_DATABASE_NAME=carts ALLOYDB_CARTS_TABLE_NAME=cart_items ALLOYDB_PRODUCTS_DATABASE_NAME=products ALLOYDB_PRODUCTS_TABLE_NAME=catalog_items # Fetch the primary and read IPs ALLOYDB_PRIMARY_IP=`gcloud alloydb instances list --region=${REGION} --cluster=${ALLOYDB_CLUSTER_NAME} --filter="INSTANCE_TYPE:PRIMARY" --format=flattened | sed -nE "s/ipAddress:\s*(.*)/\1/p"` # Create carts database and table psql -h ${ALLOYDB_PRIMARY_IP} -U postgres -c "CREATE DATABASE ${ALLOYDB_CARTS_DATABASE_NAME}" psql -h ${ALLOYDB_PRIMARY_IP} -U postgres -d ${ALLOYDB_CARTS_DATABASE_NAME} -c "CREATE TABLE ${ALLOYDB_CARTS_TABLE_NAME} (userId text, productId text, quantity int, PRIMARY KEY(userId, productId))" psql -h ${ALLOYDB_PRIMARY_IP} -U postgres -d ${ALLOYDB_CARTS_DATABASE_NAME} -c "CREATE INDEX cartItemsByUserId ON ${ALLOYDB_CARTS_TABLE_NAME}(userId)" # Create products database, table, and extensions psql -h ${ALLOYDB_PRIMARY_IP} -U postgres -c "CREATE DATABASE ${ALLOYDB_PRODUCTS_DATABASE_NAME}" psql -h ${ALLOYDB_PRIMARY_IP} -U postgres -d ${ALLOYDB_PRODUCTS_DATABASE_NAME} -c "CREATE EXTENSION IF NOT EXISTS vector" psql -h ${ALLOYDB_PRIMARY_IP} -U postgres -d ${ALLOYDB_PRODUCTS_DATABASE_NAME} -c "CREATE EXTENSION IF NOT EXISTS google_ml_integration CASCADE;" psql -h ${ALLOYDB_PRIMARY_IP} -U postgres -d ${ALLOYDB_PRODUCTS_DATABASE_NAME} -c "GRANT EXECUTE ON FUNCTION embedding TO postgres;" psql -h ${ALLOYDB_PRIMARY_IP} -U postgres -d ${ALLOYDB_PRODUCTS_DATABASE_NAME} -c "CREATE TABLE ${ALLOYDB_PRODUCTS_TABLE_NAME} (id TEXT PRIMARY KEY, name TEXT, description TEXT, picture TEXT, price_usd_currency_code TEXT, price_usd_units INTEGER, price_usd_nanos BIGINT, categories TEXT, product_embedding VECTOR(768), embed_model TEXT)" # Generate and insert products table entries python3 ./generate_sql_from_products.py > products.sql psql -h ${ALLOYDB_PRIMARY_IP} -U postgres -d ${ALLOYDB_PRODUCTS_DATABASE_NAME} -f products.sql rm products.sql # Generate vector embeddings psql -h ${ALLOYDB_PRIMARY_IP} -U postgres -d ${ALLOYDB_PRODUCTS_DATABASE_NAME} -c "UPDATE ${ALLOYDB_PRODUCTS_TABLE_NAME} SET product_embedding = embedding('textembedding-gecko@003', description), embed_model='textembedding-gecko@003';" ================================================ FILE: kustomize/components/shopping-assistant/scripts/generate_sql_from_products.py ================================================ # 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 # # 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. import json table_name = "catalog_items" fields = [ 'id', 'name', 'description', 'picture', 'price_usd_currency_code', 'price_usd_units', 'price_usd_nanos', 'categories' ] # Load the produts JSON with open("products.json", 'r') as f: data = json.load(f) # Generate SQL INSERT statements for product in data['products']: columns = ', '.join(fields) placeholders = ', '.join(['{}'] * len(fields)) sql = f"INSERT INTO {table_name} ({columns}) VALUES ({placeholders});" # Escape single quotes within product data product['name'] = product['name'].replace("'", "") product['description'] = product['description'].replace("'", "") escaped_values = ( f"'{product['id']}'", f"'{product['name']}'", f"'{product['description']}'", f"'{product['picture']}'", f"'{product['priceUsd']['currencyCode']}'", product['priceUsd']['units'], product['priceUsd']['nanos'], f"'{','.join(product['categories'])}'" ) # Render the formatted SQL query print(sql.format(*escaped_values)) ================================================ FILE: kustomize/components/shopping-assistant/shoppingassistantservice.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 # # 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. apiVersion: apps/v1 kind: Deployment metadata: name: shoppingassistantservice labels: app: shoppingassistantservice spec: selector: matchLabels: app: shoppingassistantservice template: metadata: labels: app: shoppingassistantservice spec: serviceAccountName: shoppingassistantservice terminationGracePeriodSeconds: 5 securityContext: fsGroup: 1000 runAsGroup: 1000 runAsNonRoot: true runAsUser: 1000 containers: - name: server securityContext: allowPrivilegeEscalation: false capabilities: drop: - ALL privileged: false readOnlyRootFilesystem: false image: shoppingassistantservice ports: - name: http containerPort: 8080 env: - name: GOOGLE_API_KEY value: GOOGLE_API_KEY_VAL - name: ALLOYDB_CLUSTER_NAME value: ALLOYDB_CLUSTER_NAME_VAL - name: ALLOYDB_INSTANCE_NAME value: ALLOYDB_INSTANCE_NAME_VAL - name: ALLOYDB_DATABASE_NAME value: ALLOYDB_DATABASE_NAME_VAL - name: ALLOYDB_TABLE_NAME value: ALLOYDB_TABLE_NAME_VAL - name: ALLOYDB_SECRET_NAME value: ALLOYDB_SECRET_NAME_VAL - name: PROJECT_ID value: PROJECT_ID_VAL - name: REGION value: REGION_VAL resources: requests: cpu: 100m memory: 64Mi limits: cpu: 200m memory: 128Mi --- apiVersion: v1 kind: Service metadata: name: shoppingassistantservice labels: app: shoppingassistantservice spec: type: ClusterIP selector: app: shoppingassistantservice ports: - name: http port: 80 targetPort: 8080 --- apiVersion: v1 kind: ServiceAccount metadata: name: shoppingassistantservice annotations: iam.gke.io/gcp-service-account: ALLOYDB_USER_GSA_ID ================================================ FILE: kustomize/components/single-shared-session/README.md ================================================ # Manage a single shared session for the Online Boutique apps By default, when you deploy this sample app, the Online Boutique's `frontend` generates a `shop_session-id` cookie per browser session. But you may want to share one unique `shop_session-id` cookie across all browser sessions. This is useful for multi-cluster environments. ## Deploy Online Boutique to generate a single shared session To automate the deployment of Online Boutique to manage a single shared session you can leverage the following variation with [Kustomize](../..). From the `kustomize/` folder at the root level of this repository, execute this command: ```bash kustomize edit add component components/single-shared-session ``` This will update the `kustomize/kustomization.yaml` file which could be similar to: ```yaml apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization resources: - base components: - components/single-shared-session ``` You can locally render these manifests by running `kubectl kustomize .` as well as deploying them by running `kubectl apply -k .`. ================================================ FILE: kustomize/components/single-shared-session/kustomization.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. apiVersion: kustomize.config.k8s.io/v1alpha1 kind: Component patches: # frontend - ENABLE_SINGLE_SHARED_SESSION - patch: |- apiVersion: apps/v1 kind: Deployment metadata: name: frontend spec: template: spec: containers: - name: server env: - name: ENABLE_SINGLE_SHARED_SESSION value: "true" ================================================ FILE: kustomize/components/spanner/README.md ================================================ # Integrate Online Boutique with Spanner By default the `cartservice` stores its data in an in-cluster Redis database. Using a fully managed database service outside your GKE cluster (such as [Google Cloud Spanner](https://cloud.google.com/spanner)) could bring more resiliency and more security. ## Provision a Spanner database To provision a Spanner instance you can follow the following instructions: ```bash gcloud services enable spanner.googleapis.com SPANNER_REGION_CONFIG="" # e.g. "regional-us-east5" SPANNER_INSTANCE_NAME=onlineboutique gcloud spanner instances create ${SPANNER_INSTANCE_NAME} \ --description="online boutique shopping cart" \ --config ${SPANNER_REGION_CONFIG} \ --instance-type free-instance ``` _Note: With latest version of `gcloud` we are creating a free Spanner instance._ To provision a Spanner database you can follow the following instructions: ```bash SPANNER_DATABASE_NAME=carts gcloud spanner databases create ${SPANNER_DATABASE_NAME} \ --instance ${SPANNER_INSTANCE_NAME} \ --database-dialect GOOGLE_STANDARD_SQL \ --ddl "CREATE TABLE CartItems (userId STRING(1024), productId STRING(1024), quantity INT64) PRIMARY KEY (userId, productId); CREATE INDEX CartItemsByUserId ON CartItems(userId);" ``` ## Grant the `cartservice`'s service account access to the Spanner database **Important note:** Your GKE cluster should have [Workload Identity enabled](https://cloud.google.com/kubernetes-engine/docs/how-to/workload-identity#enable). As a good practice, let's create a dedicated least privilege Google Service Account to allow the `cartservice` to communicate with the Spanner database: ```bash PROJECT_ID= SPANNER_DB_USER_GSA_NAME=spanner-db-user-sa SPANNER_DB_USER_GSA_ID=${SPANNER_DB_USER_GSA_NAME}@${PROJECT_ID}.iam.gserviceaccount.com ONLINEBOUTIQUE_NAMESPACE=default CARTSERVICE_KSA_NAME=cartservice gcloud iam service-accounts create ${SPANNER_DB_USER_GSA_NAME} \ --display-name=${SPANNER_DB_USER_GSA_NAME} gcloud spanner databases add-iam-policy-binding ${SPANNER_DATABASE_NAME} \ --member "serviceAccount:${SPANNER_DB_USER_GSA_ID}" \ --role roles/spanner.databaseUser gcloud iam service-accounts add-iam-policy-binding ${SPANNER_DB_USER_GSA_ID} \ --member "serviceAccount:${PROJECT_ID}.svc.id.goog[${ONLINEBOUTIQUE_NAMESPACE}/${CARTSERVICE_KSA_NAME}]" \ --role roles/iam.workloadIdentityUser ``` ## Deploy Online Boutique connected to a Spanner database To automate the deployment of Online Boutique integrated with Spanner you can leverage the following variation with [Kustomize](../..). From the `kustomize/` folder at the root level of this repository, execute these commands: ```bash kustomize edit add component components/spanner ``` _Note: this Kustomize component will also remove the `redis-cart` `Deployment` and `Service` not used anymore._ This will update the `kustomize/kustomization.yaml` file which could be similar to: ```yaml apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization resources: - base components: - components/spanner ``` Update current Kustomize manifest to target this Spanner database. ```bash sed -i "s/SPANNER_PROJECT/${PROJECT_ID}/g" components/spanner/kustomization.yaml sed -i "s/SPANNER_INSTANCE/${SPANNER_INSTANCE_NAME}/g" components/spanner/kustomization.yaml sed -i "s/SPANNER_DATABASE/${SPANNER_DATABASE_NAME}/g" components/spanner/kustomization.yaml sed -i "s/SPANNER_DB_USER_GSA_ID/${SPANNER_DB_USER_GSA_ID}/g" components/spanner/kustomization.yaml ``` You can locally render these manifests by running `kubectl kustomize .` as well as deploying them by running `kubectl apply -k .`. ## Note on Spanner connection environment variables The following environment variables will be used by the `cartservice`, if present: - `SPANNER_INSTANCE`: defaults to `onlineboutique`, unless specified. - `SPANNER_DATABASE`: defaults to `carts`, unless specified. - `SPANNER_CONNECTION_STRING`: defaults to `projects/${SPANNER_PROJECT}/instances/${SPANNER_INSTANCE}/databases/${SPANNER_DATABASE}`. If this variable is defined explicitly, all other environment variables will be ignored. ## Resources - [Use Google Cloud Spanner with the Online Boutique sample apps](https://medium.com/google-cloud/f7248e077339) ================================================ FILE: kustomize/components/spanner/kustomization.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. apiVersion: kustomize.config.k8s.io/v1alpha1 kind: Component patches: # cartservice - replace REDIS_ADDR by SPANNER_CONNECTION_STRING for the cartservice Deployment - patch: |- apiVersion: apps/v1 kind: Deployment metadata: name: cartservice spec: template: spec: containers: - name: server env: - name: REDIS_ADDR $patch: delete - name: SPANNER_CONNECTION_STRING value: projects/SPANNER_PROJECT/instances/SPANNER_INSTANCE/databases/SPANNER_DATABASE # cartservice - add the GSA annotation for the cartservice KSA - patch: |- apiVersion: v1 kind: ServiceAccount metadata: name: cartservice annotations: iam.gke.io/gcp-service-account: SPANNER_DB_USER_GSA_ID # redis - remove the redis-cart Deployment - patch: |- apiVersion: apps/v1 kind: Deployment metadata: name: redis-cart $patch: delete # redis - remove the redis-cart Service - patch: |- apiVersion: v1 kind: Service metadata: name: redis-cart $patch: delete ================================================ FILE: kustomize/components/without-loadgenerator/README.md ================================================ # Exclude the loadgenerator By default, when you deploy Online Boutique, its [loadgenerator](/src/loadgenerator/) will also be deployed. You can use this Kustomize component to exclude the loadgenerator. Note: This Kustomize component has not been tested with [other Kustomize Components](/kustomize/components/) that rely on the loadgenerator. ## Use this component From the `kustomize/` folder at the root level of this repository, execute this command: ```bash kustomize edit add component components/without-loadgenerator ``` This will update the `kustomize/kustomization.yaml` file which could be similar to: ```yaml apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization resources: - base components: - components/without-loadgenerator ``` You can then deploy Online Boutique and this component to your cluster using `kubectl apply -k .`. If you just want to render the YAML manifest (without deploying to your cluster), run `kubectl kustomize .`. Learn more about Online Boutique's kustomize components at [/kustomize](/kustomize#readme). ================================================ FILE: kustomize/components/without-loadgenerator/delete-loadgenerator.patch.yaml ================================================ apiVersion: apps/v1 kind: Deployment metadata: name: loadgenerator $patch: delete ================================================ FILE: kustomize/components/without-loadgenerator/kustomization.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 # # 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. apiVersion: kustomize.config.k8s.io/v1alpha1 kind: Component patches: - path: delete-loadgenerator.patch.yaml ================================================ FILE: kustomize/kustomization.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. apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization resources: - base components: # - components/cymbal-branding # - components/google-cloud-operations # - components/memorystore # - components/network-policies # - components/non-public-frontend # - components/service-accounts # - components/alloydb # - components/single-shared-session # - components/spanner # - components/service-mesh-istio # - components/without-loadgenerator # These must be run last and in this order # - components/container-images-tag # - components/container-images-tag-suffix # - components/container-images-registry ================================================ FILE: kustomize/tests/README.md ================================================ This directory contains a list of scenarios (different combinations of Kustomize Components) used for testing. See [/.github/workflows/kustomize-build-ci.yaml](../../.github/workflows/kustomize-build-ci.yaml). ================================================ FILE: kustomize/tests/memorystore-with-all-components/kustomization.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. apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization resources: - ../../base components: - ../../components/cymbal-branding - ../../components/google-cloud-operations - ../../components/network-policies - ../../components/memorystore ================================================ FILE: kustomize/tests/service-mesh-istio-with-all-components/kustomization.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 # # 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. apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization resources: - ../../base components: - ../../components/cymbal-branding - ../../components/google-cloud-operations - ../../components/network-policies - ../../components/service-mesh-istio ================================================ FILE: kustomize/tests/spanner-with-all-components/kustomization.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. apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization resources: - ../../base components: - ../../components/cymbal-branding - ../../components/google-cloud-operations - ../../components/network-policies - ../../components/spanner ================================================ FILE: protos/demo.proto ================================================ // Copyright 2020 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. syntax = "proto3"; package hipstershop; option go_package = "github.com/GoogleCloudPlatform/microservices-demo/hipstershop"; // -----------------Cart service----------------- service CartService { rpc AddItem(AddItemRequest) returns (Empty) {} rpc GetCart(GetCartRequest) returns (Cart) {} rpc EmptyCart(EmptyCartRequest) returns (Empty) {} } message CartItem { string product_id = 1; int32 quantity = 2; } message AddItemRequest { string user_id = 1; CartItem item = 2; } message EmptyCartRequest { string user_id = 1; } message GetCartRequest { string user_id = 1; } message Cart { string user_id = 1; repeated CartItem items = 2; } message Empty {} // ---------------Recommendation service---------- service RecommendationService { rpc ListRecommendations(ListRecommendationsRequest) returns (ListRecommendationsResponse){} } message ListRecommendationsRequest { string user_id = 1; repeated string product_ids = 2; } message ListRecommendationsResponse { repeated string product_ids = 1; } // ---------------Product Catalog---------------- service ProductCatalogService { rpc ListProducts(Empty) returns (ListProductsResponse) {} rpc GetProduct(GetProductRequest) returns (Product) {} rpc SearchProducts(SearchProductsRequest) returns (SearchProductsResponse) {} } message Product { string id = 1; string name = 2; string description = 3; string picture = 4; Money price_usd = 5; // Categories such as "clothing" or "kitchen" that can be used to look up // other related products. repeated string categories = 6; } message ListProductsResponse { repeated Product products = 1; } message GetProductRequest { string id = 1; } message SearchProductsRequest { string query = 1; } message SearchProductsResponse { repeated Product results = 1; } // ---------------Shipping Service---------- service ShippingService { rpc GetQuote(GetQuoteRequest) returns (GetQuoteResponse) {} rpc ShipOrder(ShipOrderRequest) returns (ShipOrderResponse) {} } message GetQuoteRequest { Address address = 1; repeated CartItem items = 2; } message GetQuoteResponse { Money cost_usd = 1; } message ShipOrderRequest { Address address = 1; repeated CartItem items = 2; } message ShipOrderResponse { string tracking_id = 1; } message Address { string street_address = 1; string city = 2; string state = 3; string country = 4; int32 zip_code = 5; } // -----------------Currency service----------------- service CurrencyService { rpc GetSupportedCurrencies(Empty) returns (GetSupportedCurrenciesResponse) {} rpc Convert(CurrencyConversionRequest) returns (Money) {} } // Represents an amount of money with its currency type. message Money { // The 3-letter currency code defined in ISO 4217. string currency_code = 1; // The whole units of the amount. // For example if `currencyCode` is `"USD"`, then 1 unit is one US dollar. int64 units = 2; // Number of nano (10^-9) units of the amount. // The value must be between -999,999,999 and +999,999,999 inclusive. // If `units` is positive, `nanos` must be positive or zero. // If `units` is zero, `nanos` can be positive, zero, or negative. // If `units` is negative, `nanos` must be negative or zero. // For example $-1.75 is represented as `units`=-1 and `nanos`=-750,000,000. int32 nanos = 3; } message GetSupportedCurrenciesResponse { // The 3-letter currency code defined in ISO 4217. repeated string currency_codes = 1; } message CurrencyConversionRequest { Money from = 1; // The 3-letter currency code defined in ISO 4217. string to_code = 2; } // -------------Payment service----------------- service PaymentService { rpc Charge(ChargeRequest) returns (ChargeResponse) {} } message CreditCardInfo { string credit_card_number = 1; int32 credit_card_cvv = 2; int32 credit_card_expiration_year = 3; int32 credit_card_expiration_month = 4; } message ChargeRequest { Money amount = 1; CreditCardInfo credit_card = 2; } message ChargeResponse { string transaction_id = 1; } // -------------Email service----------------- service EmailService { rpc SendOrderConfirmation(SendOrderConfirmationRequest) returns (Empty) {} } message OrderItem { CartItem item = 1; Money cost = 2; } message OrderResult { string order_id = 1; string shipping_tracking_id = 2; Money shipping_cost = 3; Address shipping_address = 4; repeated OrderItem items = 5; } message SendOrderConfirmationRequest { string email = 1; OrderResult order = 2; } // -------------Checkout service----------------- service CheckoutService { rpc PlaceOrder(PlaceOrderRequest) returns (PlaceOrderResponse) {} } message PlaceOrderRequest { string user_id = 1; string user_currency = 2; Address address = 3; string email = 5; CreditCardInfo credit_card = 6; } message PlaceOrderResponse { OrderResult order = 1; } // ------------Ad service------------------ service AdService { rpc GetAds(AdRequest) returns (AdResponse) {} } message AdRequest { // List of important key words from the current page describing the context. repeated string context_keys = 1; } message AdResponse { repeated Ad ads = 1; } message Ad { // url to redirect to when an ad is clicked. string redirect_url = 1; // short advertisement text to display. string text = 2; } ================================================ FILE: protos/grpc/health/v1/health.proto ================================================ // Copyright 2015 The gRPC Authors // // 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. // The canonical version of this proto can be found at // https://github.com/grpc/grpc-proto/blob/master/grpc/health/v1/health.proto syntax = "proto3"; package grpc.health.v1; option csharp_namespace = "Grpc.Health.V1"; option go_package = "google.golang.org/grpc/health/grpc_health_v1"; option java_multiple_files = true; option java_outer_classname = "HealthProto"; option java_package = "io.grpc.health.v1"; message HealthCheckRequest { string service = 1; } message HealthCheckResponse { enum ServingStatus { UNKNOWN = 0; SERVING = 1; NOT_SERVING = 2; } ServingStatus status = 1; } service Health { rpc Check(HealthCheckRequest) returns (HealthCheckResponse); } ================================================ FILE: release/istio-manifests.yaml ================================================ # Copyright 2025 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. # ---------------------------------------------------------- # WARNING: This file is autogenerated. Do not manually edit. # ---------------------------------------------------------- # [START servicemesh_release_istio_manifests_microservices_demo] --- apiVersion: gateway.networking.k8s.io/v1beta1 kind: Gateway metadata: name: istio-gateway spec: gatewayClassName: istio listeners: - name: http port: 80 protocol: HTTP allowedRoutes: namespaces: from: Same --- apiVersion: gateway.networking.k8s.io/v1beta1 kind: HTTPRoute metadata: name: frontend-route spec: parentRefs: - name: istio-gateway rules: - matches: - path: value: / backendRefs: - name: frontend port: 80 --- apiVersion: networking.istio.io/v1alpha3 kind: ServiceEntry metadata: name: allow-egress-googleapis spec: hosts: - "accounts.google.com" # Used to get token - "*.googleapis.com" ports: - number: 80 protocol: HTTP name: http - number: 443 protocol: HTTPS name: https --- apiVersion: networking.istio.io/v1alpha3 kind: ServiceEntry metadata: name: allow-egress-google-metadata spec: hosts: - metadata.google.internal addresses: - 169.254.169.254 # GCE metadata server ports: - number: 80 name: http protocol: HTTP - number: 443 name: https protocol: HTTPS --- apiVersion: networking.istio.io/v1alpha3 kind: VirtualService metadata: name: frontend spec: hosts: - "frontend.default.svc.cluster.local" http: - route: - destination: host: frontend port: number: 80 # [END servicemesh_release_istio_manifests_microservices_demo] ================================================ FILE: release/kubernetes-manifests.yaml ================================================ # Copyright 2025 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. # ---------------------------------------------------------- # WARNING: This file is autogenerated. Do not manually edit. # ---------------------------------------------------------- # [START gke_release_kubernetes_manifests_microservices_demo] --- apiVersion: apps/v1 kind: Deployment metadata: name: currencyservice labels: app: currencyservice spec: selector: matchLabels: app: currencyservice template: metadata: labels: app: currencyservice spec: serviceAccountName: currencyservice terminationGracePeriodSeconds: 5 securityContext: fsGroup: 1000 runAsGroup: 1000 runAsNonRoot: true runAsUser: 1000 containers: - name: server securityContext: allowPrivilegeEscalation: false capabilities: drop: - ALL privileged: false readOnlyRootFilesystem: true image: us-central1-docker.pkg.dev/google-samples/microservices-demo/currencyservice:v0.10.5 ports: - name: grpc containerPort: 7000 env: - name: PORT value: "7000" - name: DISABLE_PROFILER value: "1" readinessProbe: grpc: port: 7000 livenessProbe: grpc: port: 7000 resources: requests: cpu: 100m memory: 64Mi limits: cpu: 200m memory: 128Mi --- apiVersion: v1 kind: Service metadata: name: currencyservice labels: app: currencyservice spec: type: ClusterIP selector: app: currencyservice ports: - name: grpc port: 7000 targetPort: 7000 --- apiVersion: v1 kind: ServiceAccount metadata: name: currencyservice --- apiVersion: apps/v1 kind: Deployment metadata: name: loadgenerator labels: app: loadgenerator spec: selector: matchLabels: app: loadgenerator replicas: 1 template: metadata: labels: app: loadgenerator annotations: sidecar.istio.io/rewriteAppHTTPProbers: "true" spec: serviceAccountName: loadgenerator terminationGracePeriodSeconds: 5 restartPolicy: Always securityContext: fsGroup: 1000 runAsGroup: 1000 runAsNonRoot: true runAsUser: 1000 initContainers: - command: - /bin/sh - -exc - | MAX_RETRIES=12 RETRY_INTERVAL=10 for i in $(seq 1 $MAX_RETRIES); do echo "Attempt $i: Pinging frontend: ${FRONTEND_ADDR}..." STATUSCODE=$(wget --server-response http://${FRONTEND_ADDR} 2>&1 | awk '/^ HTTP/{print $2}') if [ $STATUSCODE -eq 200 ]; then echo "Frontend is reachable." exit 0 fi echo "Error: Could not reach frontend - Status code: ${STATUSCODE}" sleep $RETRY_INTERVAL done echo "Failed to reach frontend after $MAX_RETRIES attempts." exit 1 name: frontend-check securityContext: allowPrivilegeEscalation: false capabilities: drop: - ALL privileged: false readOnlyRootFilesystem: true image: busybox:latest env: - name: FRONTEND_ADDR value: "frontend:80" containers: - name: main securityContext: allowPrivilegeEscalation: false capabilities: drop: - ALL privileged: false readOnlyRootFilesystem: true image: us-central1-docker.pkg.dev/google-samples/microservices-demo/loadgenerator:v0.10.5 env: - name: FRONTEND_ADDR value: "frontend:80" - name: USERS value: "10" - name: RATE value: "1" resources: requests: cpu: 300m memory: 256Mi limits: cpu: 500m memory: 512Mi --- apiVersion: v1 kind: ServiceAccount metadata: name: loadgenerator --- apiVersion: apps/v1 kind: Deployment metadata: name: productcatalogservice labels: app: productcatalogservice spec: selector: matchLabels: app: productcatalogservice template: metadata: labels: app: productcatalogservice spec: serviceAccountName: productcatalogservice terminationGracePeriodSeconds: 5 securityContext: fsGroup: 1000 runAsGroup: 1000 runAsNonRoot: true runAsUser: 1000 containers: - name: server securityContext: allowPrivilegeEscalation: false capabilities: drop: - ALL privileged: false readOnlyRootFilesystem: true image: us-central1-docker.pkg.dev/google-samples/microservices-demo/productcatalogservice:v0.10.5 ports: - containerPort: 3550 env: - name: PORT value: "3550" - name: DISABLE_PROFILER value: "1" readinessProbe: grpc: port: 3550 livenessProbe: grpc: port: 3550 resources: requests: cpu: 100m memory: 64Mi limits: cpu: 200m memory: 128Mi --- apiVersion: v1 kind: Service metadata: name: productcatalogservice labels: app: productcatalogservice spec: type: ClusterIP selector: app: productcatalogservice ports: - name: grpc port: 3550 targetPort: 3550 --- apiVersion: v1 kind: ServiceAccount metadata: name: productcatalogservice --- apiVersion: apps/v1 kind: Deployment metadata: name: checkoutservice labels: app: checkoutservice spec: selector: matchLabels: app: checkoutservice template: metadata: labels: app: checkoutservice spec: serviceAccountName: checkoutservice securityContext: fsGroup: 1000 runAsGroup: 1000 runAsNonRoot: true runAsUser: 1000 containers: - name: server securityContext: allowPrivilegeEscalation: false capabilities: drop: - ALL privileged: false readOnlyRootFilesystem: true image: us-central1-docker.pkg.dev/google-samples/microservices-demo/checkoutservice:v0.10.5 ports: - containerPort: 5050 readinessProbe: grpc: port: 5050 livenessProbe: grpc: port: 5050 env: - name: PORT value: "5050" - name: PRODUCT_CATALOG_SERVICE_ADDR value: "productcatalogservice:3550" - name: SHIPPING_SERVICE_ADDR value: "shippingservice:50051" - name: PAYMENT_SERVICE_ADDR value: "paymentservice:50051" - name: EMAIL_SERVICE_ADDR value: "emailservice:5000" - name: CURRENCY_SERVICE_ADDR value: "currencyservice:7000" - name: CART_SERVICE_ADDR value: "cartservice:7070" resources: requests: cpu: 100m memory: 64Mi limits: cpu: 200m memory: 128Mi --- apiVersion: v1 kind: Service metadata: name: checkoutservice labels: app: checkoutservice spec: type: ClusterIP selector: app: checkoutservice ports: - name: grpc port: 5050 targetPort: 5050 --- apiVersion: v1 kind: ServiceAccount metadata: name: checkoutservice --- apiVersion: apps/v1 kind: Deployment metadata: name: shippingservice labels: app: shippingservice spec: selector: matchLabels: app: shippingservice template: metadata: labels: app: shippingservice spec: serviceAccountName: shippingservice securityContext: fsGroup: 1000 runAsGroup: 1000 runAsNonRoot: true runAsUser: 1000 containers: - name: server securityContext: allowPrivilegeEscalation: false capabilities: drop: - ALL privileged: false readOnlyRootFilesystem: true image: us-central1-docker.pkg.dev/google-samples/microservices-demo/shippingservice:v0.10.5 ports: - containerPort: 50051 env: - name: PORT value: "50051" - name: DISABLE_PROFILER value: "1" readinessProbe: periodSeconds: 5 grpc: port: 50051 livenessProbe: grpc: port: 50051 resources: requests: cpu: 100m memory: 64Mi limits: cpu: 200m memory: 128Mi --- apiVersion: v1 kind: Service metadata: name: shippingservice labels: app: shippingservice spec: type: ClusterIP selector: app: shippingservice ports: - name: grpc port: 50051 targetPort: 50051 --- apiVersion: v1 kind: ServiceAccount metadata: name: shippingservice --- apiVersion: apps/v1 kind: Deployment metadata: name: cartservice labels: app: cartservice spec: selector: matchLabels: app: cartservice template: metadata: labels: app: cartservice spec: serviceAccountName: cartservice terminationGracePeriodSeconds: 5 securityContext: fsGroup: 1000 runAsGroup: 1000 runAsNonRoot: true runAsUser: 1000 containers: - name: server securityContext: allowPrivilegeEscalation: false capabilities: drop: - ALL privileged: false readOnlyRootFilesystem: true image: us-central1-docker.pkg.dev/google-samples/microservices-demo/cartservice:v0.10.5 ports: - containerPort: 7070 env: - name: REDIS_ADDR value: "redis-cart:6379" resources: requests: cpu: 200m memory: 64Mi limits: cpu: 300m memory: 128Mi readinessProbe: initialDelaySeconds: 15 grpc: port: 7070 livenessProbe: initialDelaySeconds: 15 periodSeconds: 10 grpc: port: 7070 --- apiVersion: v1 kind: Service metadata: name: cartservice labels: app: cartservice spec: type: ClusterIP selector: app: cartservice ports: - name: grpc port: 7070 targetPort: 7070 --- apiVersion: v1 kind: ServiceAccount metadata: name: cartservice --- apiVersion: apps/v1 kind: Deployment metadata: name: redis-cart labels: app: redis-cart spec: selector: matchLabels: app: redis-cart template: metadata: labels: app: redis-cart spec: securityContext: fsGroup: 1000 runAsGroup: 1000 runAsNonRoot: true runAsUser: 1000 containers: - name: redis securityContext: allowPrivilegeEscalation: false capabilities: drop: - ALL privileged: false readOnlyRootFilesystem: true image: redis:alpine ports: - containerPort: 6379 readinessProbe: periodSeconds: 5 tcpSocket: port: 6379 livenessProbe: periodSeconds: 5 tcpSocket: port: 6379 volumeMounts: - mountPath: /data name: redis-data resources: limits: memory: 256Mi cpu: 125m requests: cpu: 70m memory: 200Mi volumes: - name: redis-data emptyDir: {} --- apiVersion: v1 kind: Service metadata: name: redis-cart labels: app: redis-cart spec: type: ClusterIP selector: app: redis-cart ports: - name: tcp-redis port: 6379 targetPort: 6379 --- apiVersion: apps/v1 kind: Deployment metadata: name: emailservice labels: app: emailservice spec: selector: matchLabels: app: emailservice template: metadata: labels: app: emailservice spec: serviceAccountName: emailservice terminationGracePeriodSeconds: 5 securityContext: fsGroup: 1000 runAsGroup: 1000 runAsNonRoot: true runAsUser: 1000 containers: - name: server securityContext: allowPrivilegeEscalation: false capabilities: drop: - ALL privileged: false readOnlyRootFilesystem: true image: us-central1-docker.pkg.dev/google-samples/microservices-demo/emailservice:v0.10.5 ports: - containerPort: 8080 env: - name: PORT value: "8080" - name: DISABLE_PROFILER value: "1" readinessProbe: periodSeconds: 5 grpc: port: 8080 livenessProbe: periodSeconds: 5 grpc: port: 8080 resources: requests: cpu: 100m memory: 64Mi limits: cpu: 200m memory: 128Mi --- apiVersion: v1 kind: Service metadata: name: emailservice labels: app: emailservice spec: type: ClusterIP selector: app: emailservice ports: - name: grpc port: 5000 targetPort: 8080 --- apiVersion: v1 kind: ServiceAccount metadata: name: emailservice --- apiVersion: apps/v1 kind: Deployment metadata: name: paymentservice labels: app: paymentservice spec: selector: matchLabels: app: paymentservice template: metadata: labels: app: paymentservice spec: serviceAccountName: paymentservice terminationGracePeriodSeconds: 5 securityContext: fsGroup: 1000 runAsGroup: 1000 runAsNonRoot: true runAsUser: 1000 containers: - name: server securityContext: allowPrivilegeEscalation: false capabilities: drop: - ALL privileged: false readOnlyRootFilesystem: true image: us-central1-docker.pkg.dev/google-samples/microservices-demo/paymentservice:v0.10.5 ports: - containerPort: 50051 env: - name: PORT value: "50051" - name: DISABLE_PROFILER value: "1" readinessProbe: grpc: port: 50051 livenessProbe: grpc: port: 50051 resources: requests: cpu: 100m memory: 64Mi limits: cpu: 200m memory: 128Mi --- apiVersion: v1 kind: Service metadata: name: paymentservice labels: app: paymentservice spec: type: ClusterIP selector: app: paymentservice ports: - name: grpc port: 50051 targetPort: 50051 --- apiVersion: v1 kind: ServiceAccount metadata: name: paymentservice --- apiVersion: apps/v1 kind: Deployment metadata: name: frontend labels: app: frontend spec: selector: matchLabels: app: frontend template: metadata: labels: app: frontend annotations: sidecar.istio.io/rewriteAppHTTPProbers: "true" spec: serviceAccountName: frontend securityContext: fsGroup: 1000 runAsGroup: 1000 runAsNonRoot: true runAsUser: 1000 containers: - name: server securityContext: allowPrivilegeEscalation: false capabilities: drop: - ALL privileged: false readOnlyRootFilesystem: true image: us-central1-docker.pkg.dev/google-samples/microservices-demo/frontend:v0.10.5 ports: - containerPort: 8080 readinessProbe: initialDelaySeconds: 10 httpGet: path: "/_healthz" port: 8080 httpHeaders: - name: "Cookie" value: "shop_session-id=x-readiness-probe" livenessProbe: initialDelaySeconds: 10 httpGet: path: "/_healthz" port: 8080 httpHeaders: - name: "Cookie" value: "shop_session-id=x-liveness-probe" env: - name: PORT value: "8080" - name: PRODUCT_CATALOG_SERVICE_ADDR value: "productcatalogservice:3550" - name: CURRENCY_SERVICE_ADDR value: "currencyservice:7000" - name: CART_SERVICE_ADDR value: "cartservice:7070" - name: RECOMMENDATION_SERVICE_ADDR value: "recommendationservice:8080" - name: SHIPPING_SERVICE_ADDR value: "shippingservice:50051" - name: CHECKOUT_SERVICE_ADDR value: "checkoutservice:5050" - name: AD_SERVICE_ADDR value: "adservice:9555" - name: SHOPPING_ASSISTANT_SERVICE_ADDR value: "shoppingassistantservice:80" # # ENV_PLATFORM: One of: local, gcp, aws, azure, onprem, alibaba # # When not set, defaults to "local" unless running in GKE, otherwies auto-sets to gcp # - name: ENV_PLATFORM # value: "aws" - name: ENABLE_PROFILER value: "0" # - name: CYMBAL_BRANDING # value: "true" # - name: ENABLE_ASSISTANT # value: "true" # - name: FRONTEND_MESSAGE # value: "Replace this with a message you want to display on all pages." # As part of an optional Google Cloud demo, you can run an optional microservice called the "packaging service". # - name: PACKAGING_SERVICE_URL # value: "" # This value would look like "http://123.123.123" resources: requests: cpu: 100m memory: 64Mi limits: cpu: 200m memory: 128Mi --- apiVersion: v1 kind: Service metadata: name: frontend labels: app: frontend spec: type: ClusterIP selector: app: frontend ports: - name: http port: 80 targetPort: 8080 --- apiVersion: v1 kind: Service metadata: name: frontend-external labels: app: frontend spec: type: LoadBalancer selector: app: frontend ports: - name: http port: 80 targetPort: 8080 --- apiVersion: v1 kind: ServiceAccount metadata: name: frontend --- apiVersion: apps/v1 kind: Deployment metadata: name: recommendationservice labels: app: recommendationservice spec: selector: matchLabels: app: recommendationservice template: metadata: labels: app: recommendationservice spec: serviceAccountName: recommendationservice terminationGracePeriodSeconds: 5 securityContext: fsGroup: 1000 runAsGroup: 1000 runAsNonRoot: true runAsUser: 1000 containers: - name: server securityContext: allowPrivilegeEscalation: false capabilities: drop: - ALL privileged: false readOnlyRootFilesystem: true image: us-central1-docker.pkg.dev/google-samples/microservices-demo/recommendationservice:v0.10.5 ports: - containerPort: 8080 readinessProbe: periodSeconds: 5 grpc: port: 8080 livenessProbe: periodSeconds: 5 grpc: port: 8080 env: - name: PORT value: "8080" - name: PRODUCT_CATALOG_SERVICE_ADDR value: "productcatalogservice:3550" - name: DISABLE_PROFILER value: "1" resources: requests: cpu: 100m memory: 220Mi limits: cpu: 200m memory: 450Mi --- apiVersion: v1 kind: Service metadata: name: recommendationservice labels: app: recommendationservice spec: type: ClusterIP selector: app: recommendationservice ports: - name: grpc port: 8080 targetPort: 8080 --- apiVersion: v1 kind: ServiceAccount metadata: name: recommendationservice --- apiVersion: apps/v1 kind: Deployment metadata: name: adservice labels: app: adservice spec: selector: matchLabels: app: adservice template: metadata: labels: app: adservice spec: serviceAccountName: adservice terminationGracePeriodSeconds: 5 securityContext: fsGroup: 1000 runAsGroup: 1000 runAsNonRoot: true runAsUser: 1000 containers: - name: server securityContext: allowPrivilegeEscalation: false capabilities: drop: - ALL privileged: false readOnlyRootFilesystem: true image: us-central1-docker.pkg.dev/google-samples/microservices-demo/adservice:v0.10.5 ports: - containerPort: 9555 env: - name: PORT value: "9555" resources: requests: cpu: 200m memory: 180Mi limits: cpu: 300m memory: 300Mi readinessProbe: initialDelaySeconds: 20 periodSeconds: 15 grpc: port: 9555 livenessProbe: initialDelaySeconds: 20 periodSeconds: 15 grpc: port: 9555 --- apiVersion: v1 kind: Service metadata: name: adservice labels: app: adservice spec: type: ClusterIP selector: app: adservice ports: - name: grpc port: 9555 targetPort: 9555 --- apiVersion: v1 kind: ServiceAccount metadata: name: adservice # [END gke_release_kubernetes_manifests_microservices_demo] ================================================ FILE: skaffold.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. apiVersion: skaffold/v3 kind: Config metadata: name: app build: platforms: ["linux/amd64", "linux/arm64"] artifacts: # image tags are relative; to specify an image repo (e.g. GCR), you # must provide a "default repo" using one of the methods described # here: # https://skaffold.dev/docs/concepts/#image-repository-handling - image: emailservice context: src/emailservice - image: productcatalogservice context: src/productcatalogservice - image: recommendationservice context: src/recommendationservice - image: shoppingassistantservice context: src/shoppingassistantservice - image: shippingservice context: src/shippingservice - image: checkoutservice context: src/checkoutservice - image: paymentservice context: src/paymentservice - image: currencyservice context: src/currencyservice - image: cartservice context: src/cartservice/src docker: dockerfile: Dockerfile - image: frontend context: src/frontend - image: adservice context: src/adservice tagPolicy: gitCommit: {} local: useDockerCLI: true useBuildkit: true manifests: kustomize: paths: - kubernetes-manifests deploy: kubectl: {} # "gcb" profile allows building and pushing the images # on Google Container Builder without requiring docker # installed on the developer machine. However, note that # since GCB does not cache the builds, each build will # start from scratch and therefore take a long time. # # This is not used by default. To use it, run: # skaffold run -p gcb profiles: - name: gcb build: googleCloudBuild: diskSizeGb: 300 machineType: N1_HIGHCPU_32 timeout: 4000s # "debug" profile replaces the default Dockerfile in cartservice with Dockerfile.debug, # which enables debugging via skaffold. # # This profile is used by default when running skaffold debug. - name: debug activation: - command: debug patches: - op: replace path: /build/artifacts/7/docker/dockerfile value: Dockerfile.debug # The "network-policies" profile is not used by default. # You can use it in isolation or in combination with other profiles: # skaffold run -p network-policies, debug - name: network-policies patches: - op: add path: /manifests/kustomize/paths/1 value: kustomize/components/network-policies --- apiVersion: skaffold/v3 kind: Config metadata: name: loadgenerator requires: - configs: - app build: platforms: ["linux/amd64", "linux/arm64"] artifacts: - image: loadgenerator context: src/loadgenerator local: useDockerCLI: true useBuildkit: true manifests: rawYaml: - ./kubernetes-manifests/loadgenerator.yaml deploy: kubectl: {} profiles: - name: gcb build: googleCloudBuild: diskSizeGb: 300 machineType: N1_HIGHCPU_32 timeout: 4000s ================================================ FILE: src/adservice/Dockerfile ================================================ # Copyright 2020 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. # Define a default value so it's not empty if the builder fails to provide it ARG BUILDPLATFORM=linux/amd64 FROM --platform=$BUILDPLATFORM eclipse-temurin:24.0.2_12-jdk-noble@sha256:dacac8e9a0df0d2bd24e702b4431132875c249930b70555ebd7ca285b5bee684 AS builder WORKDIR /app COPY ["build.gradle", "gradlew", "./"] COPY gradle gradle RUN chmod +x gradlew RUN ./gradlew downloadRepos COPY . . RUN chmod +x gradlew RUN ./gradlew installDist FROM eclipse-temurin:25.0.2_10-jre-alpine@sha256:f10d6259d0798c1e12179b6bf3b63cea0d6843f7b09c9f9c9c422c50e44379ec # @TODO: https://github.com/GoogleCloudPlatform/microservices-demo/issues/2517 # Download Stackdriver Profiler Java agent # RUN mkdir -p /opt/cprof && \ # wget -q -O- https://storage.googleapis.com/cloud-profiler/java/latest/profiler_java_agent_alpine.tar.gz \ # | tar xzv -C /opt/cprof && \ # rm -rf profiler_java_agent.tar.gz WORKDIR /app COPY --from=builder /app . EXPOSE 9555 ENTRYPOINT ["/app/build/install/hipstershop/bin/AdService"] ================================================ FILE: src/adservice/README.md ================================================ # Ad Service The Ad service provides advertisement based on context keys. If no context keys are provided then it returns random ads. ## Building locally The Ad service uses gradlew to compile/install/distribute. Gradle wrapper is already part of the source code. To build Ad Service, run: ``` ./gradlew installDist ``` It will create executable script src/adservice/build/install/hipstershop/bin/AdService ### Upgrading gradle version If you need to upgrade the version of gradle then run ``` ./gradlew wrapper --gradle-version ``` ## Building docker image From `src/adservice/`, run: ``` docker build ./ ``` ================================================ FILE: src/adservice/build.gradle ================================================ plugins { id 'com.google.protobuf' version '0.9.6' id 'com.github.sherter.google-java-format' version '0.9' id 'idea' id 'application' } repositories { mavenCentral() mavenLocal() } description = 'Ad Service' group = "adservice" version = "0.1.0-SNAPSHOT" def grpcVersion = "1.79.0" def jacksonCoreVersion = "2.21.1" def jacksonDatabindVersion = "2.21.1" def protocVersion = "4.34.0" tasks.withType(JavaCompile) { sourceCompatibility = JavaVersion.VERSION_21 targetCompatibility = JavaVersion.VERSION_21 } ext { speed = project.hasProperty('speed') ? project.getProperty('speed') : false offlineCompile = new File("$buildDir/output/lib") } dependencies { if (speed) { implementation fileTree(dir: offlineCompile, include: '*.jar') } else { implementation "com.google.api.grpc:proto-google-common-protos:2.66.0", "javax.annotation:javax.annotation-api:1.3.2", "io.grpc:grpc-protobuf:${grpcVersion}", "io.grpc:grpc-stub:${grpcVersion}", "io.grpc:grpc-netty:${grpcVersion}", "io.grpc:grpc-services:${grpcVersion}", "io.grpc:grpc-census:${grpcVersion}", "org.apache.logging.log4j:log4j-core:2.25.3", "com.google.protobuf:protobuf-java:${protocVersion}" runtimeOnly "com.fasterxml.jackson.core:jackson-core:${jacksonCoreVersion}", "com.fasterxml.jackson.core:jackson-databind:${jacksonDatabindVersion}", "io.netty:netty-tcnative-boringssl-static:2.0.75.Final" } } protobuf { protoc { artifact = "com.google.protobuf:protoc:${protocVersion}" } plugins { grpc { artifact = "io.grpc:protoc-gen-grpc-java:${grpcVersion}" } } generateProtoTasks { all()*.plugins { grpc {} } ofSourceSet('main') } } googleJavaFormat { toolVersion '1.35.0' } // Inform IDEs like IntelliJ IDEA, Eclipse or NetBeans about the generated code. sourceSets { main { java { srcDirs 'hipstershop' srcDirs 'build/generated/source/proto/main/java/hipstershop' srcDirs 'build/generated/source/proto/main/grpc/hipstershop' } } } startScripts.enabled = false // This to cache dependencies during Docker image building. First build will take time. // Subsequent build will be incremental. task downloadRepos(type: Copy) { from configurations.compileClasspath into offlineCompile from configurations.compileClasspath into offlineCompile } task adService(type: CreateStartScripts) { mainClass.set('hipstershop.AdService') applicationName = 'AdService' outputDir = new File(project.buildDir, 'tmp') classpath = startScripts.classpath // @TODO: https://github.com/GoogleCloudPlatform/microservices-demo/issues/2517 // defaultJvmOpts = // ["-agentpath:/opt/cprof/profiler_java_agent.so=-cprof_service=adservice,-cprof_service_version=1.0.0"] } task adServiceClient(type: CreateStartScripts) { mainClass.set('hipstershop.AdServiceClient') applicationName = 'AdServiceClient' outputDir = new File(project.buildDir, 'tmp') classpath = startScripts.classpath // @TODO: https://github.com/GoogleCloudPlatform/microservices-demo/issues/2517 // defaultJvmOpts = // ["-agentpath:/opt/cprof/profiler_java_agent.so=-cprof_service=adserviceclient,-cprof_service_version=1.0.0"] } applicationDistribution.into('bin') { from(adService) from(adServiceClient) fileMode = 0755 } ================================================ FILE: src/adservice/genproto.sh ================================================ #!/bin/bash -eu # # Copyright 2018 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 gke_adservice_genproto] # protos are needed in adservice folder for compiling during Docker build. mkdir -p proto && \ cp ../../protos/demo.proto src/main/proto # [END gke_adservice_genproto] ================================================ FILE: src/adservice/gradle/wrapper/gradle-wrapper.properties ================================================ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.4-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists ================================================ FILE: src/adservice/gradlew ================================================ #!/bin/sh # # Copyright © 2015-2021 the original authors. # # 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. # # SPDX-License-Identifier: Apache-2.0 # ############################################################################## # # Gradle start up script for POSIX generated by Gradle. # # Important for running: # # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is # noncompliant, but you have some other compliant shell such as ksh or # bash, then to run this script, type that shell name before the whole # command line, like: # # ksh Gradle # # Busybox and similar reduced shells will NOT work, because this script # requires all of these POSIX shell features: # * functions; # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», # «${var#prefix}», «${var%suffix}», and «$( cmd )»; # * compound commands having a testable exit status, especially «case»; # * various built-in commands including «command», «set», and «ulimit». # # Important for patching: # # (2) This script targets any POSIX shell, so it avoids extensions provided # by Bash, Ksh, etc; in particular arrays are avoided. # # The "traditional" practice of packing multiple parameters into a # space-separated string is a well documented source of bugs and security # problems, so this is (mostly) avoided, by progressively accumulating # options in "$@", and eventually passing that to Java. # # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; # see the in-line comments for details. # # There are tweaks for specific operating systems such as AIX, CygWin, # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template # https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. # ############################################################################## # Attempt to set APP_HOME # Resolve links: $0 may be a link app_path=$0 # Need this for daisy-chained symlinks. while APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path [ -h "$app_path" ] do ls=$( ls -ld "$app_path" ) link=${ls#*' -> '} case $link in #( /*) app_path=$link ;; #( *) app_path=$APP_HOME$link ;; esac done # This is normally unused # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum warn () { echo "$*" } >&2 die () { echo echo "$*" echo exit 1 } >&2 # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false case "$( uname )" in #( CYGWIN* ) cygwin=true ;; #( Darwin* ) darwin=true ;; #( MSYS* | MINGW* ) msys=true ;; #( NONSTOP* ) nonstop=true ;; esac CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables JAVACMD=$JAVA_HOME/jre/sh/java else JAVACMD=$JAVA_HOME/bin/java fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else JAVACMD=java if ! command -v java >/dev/null 2>&1 then die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi fi # Increase the maximum file descriptors if we can. if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then case $MAX_FD in #( max*) # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. # shellcheck disable=SC2039,SC3045 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac case $MAX_FD in #( '' | soft) :;; #( *) # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. # shellcheck disable=SC2039,SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac fi # Collect all arguments for the java command, stacking in reverse order: # * args from the command line # * the main class name # * -classpath # * -D...appname settings # * --module-path (only if needed) # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. # For Cygwin or MSYS, switch paths to Windows format before running java if "$cygwin" || "$msys" ; then APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) JAVACMD=$( cygpath --unix "$JAVACMD" ) # Now convert the arguments - kludge to limit ourselves to /bin/sh for arg do if case $arg in #( -*) false ;; # don't mess with options #( /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath [ -e "$t" ] ;; #( *) false ;; esac then arg=$( cygpath --path --ignore --mixed "$arg" ) fi # Roll the args list around exactly as many times as the number of # args, so each arg winds up back in the position where it started, but # possibly modified. # # NB: a `for` loop captures its iteration list before it begins, so # changing the positional parameters here affects neither the number of # iterations, nor the values presented in `arg`. shift # remove old arg set -- "$@" "$arg" # push replacement arg done fi # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: # * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, # and any embedded shellness will be escaped. # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be # treated as '${Hostname}' itself on the command line. set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. if ! command -v xargs >/dev/null 2>&1 then die "xargs is not available" fi # Use "xargs" to parse quoted args. # # With -n1 it outputs one arg per line, with the quotes and backslashes removed. # # In Bash we could simply go: # # readarray ARGS < <( xargs -n1 <<<"$var" ) && # set -- "${ARGS[@]}" "$@" # # but POSIX shell has neither arrays nor command substitution, so instead we # post-process each arg (as a line of input to sed) to backslash-escape any # character that might be a shell metacharacter, then use eval to reverse # that process (while maintaining the separation between arguments), and wrap # the whole thing up as a single "set" statement. # # This will of course break if any of these variables contains a newline or # an unmatched quote. # eval "set -- $( printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | xargs -n1 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | tr '\n' ' ' )" '"$@"' exec "$JAVACMD" "$@" ================================================ FILE: src/adservice/gradlew.bat ================================================ @rem @rem Copyright 2015 the original author or authors. @rem @rem Licensed under the Apache License, Version 2.0 (the "License"); @rem you may not use this file except in compliance with the License. @rem You may obtain a copy of the License at @rem @rem https://www.apache.org/licenses/LICENSE-2.0 @rem @rem Unless required by applicable law or agreed to in writing, software @rem distributed under the License is distributed on an "AS IS" BASIS, @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem @rem SPDX-License-Identifier: Apache-2.0 @rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @rem @rem ########################################################################## @rem Set local scope for the variables with windows NT shell if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 if "%DIRNAME%"=="" set DIRNAME=. @rem This is normally unused set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @rem Resolve any "." and ".." in APP_HOME to make it shorter. for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute echo. 1>&2 echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 echo. 1>&2 echo Please set the JAVA_HOME variable in your environment to match the 1>&2 echo location of your Java installation. 1>&2 goto fail :findJavaFromJavaHome set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute echo. 1>&2 echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 echo. 1>&2 echo Please set the JAVA_HOME variable in your environment to match the 1>&2 echo location of your Java installation. 1>&2 goto fail :execute @rem Setup the command line set CLASSPATH= @rem Execute Gradle "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell if %ERRORLEVEL% equ 0 goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! set EXIT_CODE=%ERRORLEVEL% if %EXIT_CODE% equ 0 set EXIT_CODE=1 if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% exit /b %EXIT_CODE% :mainEnd if "%OS%"=="Windows_NT" endlocal :omega ================================================ FILE: src/adservice/settings.gradle ================================================ rootProject.name = 'hipstershop' ================================================ FILE: src/adservice/src/main/java/hipstershop/AdService.java ================================================ /* * Copyright 2018, 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. */ package hipstershop; import com.google.common.collect.ImmutableListMultimap; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Iterables; import hipstershop.Demo.Ad; import hipstershop.Demo.AdRequest; import hipstershop.Demo.AdResponse; import io.grpc.Server; import io.grpc.ServerBuilder; import io.grpc.StatusRuntimeException; import io.grpc.health.v1.HealthCheckResponse.ServingStatus; import io.grpc.services.*; import io.grpc.stub.StreamObserver; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Random; import java.util.concurrent.TimeUnit; import org.apache.logging.log4j.Level; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; public final class AdService { private static final Logger logger = LogManager.getLogger(AdService.class); @SuppressWarnings("FieldCanBeLocal") private static int MAX_ADS_TO_SERVE = 2; private Server server; private HealthStatusManager healthMgr; private static final AdService service = new AdService(); private void start() throws IOException { int port = Integer.parseInt(System.getenv().getOrDefault("PORT", "9555")); healthMgr = new HealthStatusManager(); server = ServerBuilder.forPort(port) .addService(new AdServiceImpl()) .addService(healthMgr.getHealthService()) .build() .start(); logger.info("Ad Service started, listening on " + port); Runtime.getRuntime() .addShutdownHook( new Thread( () -> { // Use stderr here since the logger may have been reset by its JVM shutdown hook. System.err.println( "*** shutting down gRPC ads server since JVM is shutting down"); AdService.this.stop(); System.err.println("*** server shut down"); })); healthMgr.setStatus("", ServingStatus.SERVING); } private void stop() { if (server != null) { healthMgr.clearStatus(""); server.shutdown(); } } private static class AdServiceImpl extends hipstershop.AdServiceGrpc.AdServiceImplBase { /** * Retrieves ads based on context provided in the request {@code AdRequest}. * * @param req the request containing context. * @param responseObserver the stream observer which gets notified with the value of {@code * AdResponse} */ @Override public void getAds(AdRequest req, StreamObserver responseObserver) { AdService service = AdService.getInstance(); try { List allAds = new ArrayList<>(); logger.info("received ad request (context_words=" + req.getContextKeysList() + ")"); if (req.getContextKeysCount() > 0) { for (int i = 0; i < req.getContextKeysCount(); i++) { Collection ads = service.getAdsByCategory(req.getContextKeys(i)); allAds.addAll(ads); } } else { allAds = service.getRandomAds(); } if (allAds.isEmpty()) { // Serve random ads. allAds = service.getRandomAds(); } AdResponse reply = AdResponse.newBuilder().addAllAds(allAds).build(); responseObserver.onNext(reply); responseObserver.onCompleted(); } catch (StatusRuntimeException e) { logger.log(Level.WARN, "GetAds Failed with status {}", e.getStatus()); responseObserver.onError(e); } } } private static final ImmutableListMultimap adsMap = createAdsMap(); private Collection getAdsByCategory(String category) { return adsMap.get(category); } private static final Random random = new Random(); private List getRandomAds() { List ads = new ArrayList<>(MAX_ADS_TO_SERVE); Collection allAds = adsMap.values(); for (int i = 0; i < MAX_ADS_TO_SERVE; i++) { ads.add(Iterables.get(allAds, random.nextInt(allAds.size()))); } return ads; } private static AdService getInstance() { return service; } /** Await termination on the main thread since the grpc library uses daemon threads. */ private void blockUntilShutdown() throws InterruptedException { if (server != null) { server.awaitTermination(); } } private static ImmutableListMultimap createAdsMap() { Ad hairdryer = Ad.newBuilder() .setRedirectUrl("/product/2ZYFJ3GM2N") .setText("Hairdryer for sale. 50% off.") .build(); Ad tankTop = Ad.newBuilder() .setRedirectUrl("/product/66VCHSJNUP") .setText("Tank top for sale. 20% off.") .build(); Ad candleHolder = Ad.newBuilder() .setRedirectUrl("/product/0PUK6V6EV0") .setText("Candle holder for sale. 30% off.") .build(); Ad bambooGlassJar = Ad.newBuilder() .setRedirectUrl("/product/9SIQT8TOJO") .setText("Bamboo glass jar for sale. 10% off.") .build(); Ad watch = Ad.newBuilder() .setRedirectUrl("/product/1YMWWN1N4O") .setText("Watch for sale. Buy one, get second kit for free") .build(); Ad mug = Ad.newBuilder() .setRedirectUrl("/product/6E92ZMYYFZ") .setText("Mug for sale. Buy two, get third one for free") .build(); Ad loafers = Ad.newBuilder() .setRedirectUrl("/product/L9ECAV7KIM") .setText("Loafers for sale. Buy one, get second one for free") .build(); return ImmutableListMultimap.builder() .putAll("clothing", tankTop) .putAll("accessories", watch) .putAll("footwear", loafers) .putAll("hair", hairdryer) .putAll("decor", candleHolder) .putAll("kitchen", bambooGlassJar, mug) .build(); } private static void initStats() { if (System.getenv("DISABLE_STATS") != null) { logger.info("Stats disabled."); return; } logger.info("Stats enabled, but temporarily unavailable"); long sleepTime = 10; /* seconds */ int maxAttempts = 5; // TODO(arbrown) Implement OpenTelemetry stats } private static void initTracing() { if (System.getenv("DISABLE_TRACING") != null) { logger.info("Tracing disabled."); return; } logger.info("Tracing enabled but temporarily unavailable"); logger.info("See https://github.com/GoogleCloudPlatform/microservices-demo/issues/422 for more info."); // TODO(arbrown) Implement OpenTelemetry tracing logger.info("Tracing enabled - Stackdriver exporter initialized."); } /** Main launches the server from the command line. */ public static void main(String[] args) throws IOException, InterruptedException { new Thread( () -> { initStats(); initTracing(); }) .start(); // Start the RPC server. You shouldn't see any output from gRPC before this. logger.info("AdService starting."); final AdService service = AdService.getInstance(); service.start(); service.blockUntilShutdown(); } } ================================================ FILE: src/adservice/src/main/java/hipstershop/AdServiceClient.java ================================================ /* * Copyright 2018, 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. */ package hipstershop; import hipstershop.Demo.Ad; import hipstershop.Demo.AdRequest; import hipstershop.Demo.AdResponse; import io.grpc.ManagedChannel; import io.grpc.ManagedChannelBuilder; import io.grpc.StatusRuntimeException; import java.util.concurrent.TimeUnit; import javax.annotation.Nullable; import org.apache.logging.log4j.Level; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; /** A simple client that requests ads from the Ads Service. */ public class AdServiceClient { private static final Logger logger = LogManager.getLogger(AdServiceClient.class); private final ManagedChannel channel; private final hipstershop.AdServiceGrpc.AdServiceBlockingStub blockingStub; /** Construct client connecting to Ad Service at {@code host:port}. */ private AdServiceClient(String host, int port) { this( ManagedChannelBuilder.forAddress(host, port) // Channels are secure by default (via SSL/TLS). For the example we disable TLS to avoid // needing certificates. .usePlaintext() .build()); } /** Construct client for accessing RouteGuide server using the existing channel. */ private AdServiceClient(ManagedChannel channel) { this.channel = channel; blockingStub = hipstershop.AdServiceGrpc.newBlockingStub(channel); } private void shutdown() throws InterruptedException { channel.shutdown().awaitTermination(5, TimeUnit.SECONDS); } /** Get Ads from Server. */ public void getAds(String contextKey) { logger.info("Get Ads with context " + contextKey + " ..."); AdRequest request = AdRequest.newBuilder().addContextKeys(contextKey).build(); AdResponse response; try { response = blockingStub.getAds(request); } catch (StatusRuntimeException e) { logger.log(Level.WARN, "RPC failed: " + e.getStatus()); return; } for (Ad ads : response.getAdsList()) { logger.info("Ads: " + ads.getText()); } } private static int getPortOrDefaultFromArgs(String[] args) { int portNumber = 9555; if (2 < args.length) { try { portNumber = Integer.parseInt(args[2]); } catch (NumberFormatException e) { logger.warn(String.format("Port %s is invalid, use default port %d.", args[2], 9555)); } } return portNumber; } private static String getStringOrDefaultFromArgs( String[] args, int index, @Nullable String defaultString) { String s = defaultString; if (index < args.length) { s = args[index]; } return s; } /** * Ads Service Client main. If provided, the first element of {@code args} is the context key to * get the ads from the Ads Service */ public static void main(String[] args) throws InterruptedException { // Add final keyword to pass checkStyle. final String contextKeys = getStringOrDefaultFromArgs(args, 0, "camera"); final String host = getStringOrDefaultFromArgs(args, 1, "localhost"); final int serverPort = getPortOrDefaultFromArgs(args); AdServiceClient client = new AdServiceClient(host, serverPort); try { client.getAds(contextKeys); } finally { client.shutdown(); } logger.info("Exiting AdServiceClient..."); } } ================================================ FILE: src/adservice/src/main/proto/demo.proto ================================================ // Copyright 2020 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. syntax = "proto3"; package hipstershop; // -----------------Cart service----------------- service CartService { rpc AddItem(AddItemRequest) returns (Empty) {} rpc GetCart(GetCartRequest) returns (Cart) {} rpc EmptyCart(EmptyCartRequest) returns (Empty) {} } message CartItem { string product_id = 1; int32 quantity = 2; } message AddItemRequest { string user_id = 1; CartItem item = 2; } message EmptyCartRequest { string user_id = 1; } message GetCartRequest { string user_id = 1; } message Cart { string user_id = 1; repeated CartItem items = 2; } message Empty {} // ---------------Recommendation service---------- service RecommendationService { rpc ListRecommendations(ListRecommendationsRequest) returns (ListRecommendationsResponse){} } message ListRecommendationsRequest { string user_id = 1; repeated string product_ids = 2; } message ListRecommendationsResponse { repeated string product_ids = 1; } // ---------------Product Catalog---------------- service ProductCatalogService { rpc ListProducts(Empty) returns (ListProductsResponse) {} rpc GetProduct(GetProductRequest) returns (Product) {} rpc SearchProducts(SearchProductsRequest) returns (SearchProductsResponse) {} } message Product { string id = 1; string name = 2; string description = 3; string picture = 4; Money price_usd = 5; // Categories such as "clothing" or "kitchen" that can be used to look up // other related products. repeated string categories = 6; } message ListProductsResponse { repeated Product products = 1; } message GetProductRequest { string id = 1; } message SearchProductsRequest { string query = 1; } message SearchProductsResponse { repeated Product results = 1; } // ---------------Shipping Service---------- service ShippingService { rpc GetQuote(GetQuoteRequest) returns (GetQuoteResponse) {} rpc ShipOrder(ShipOrderRequest) returns (ShipOrderResponse) {} } message GetQuoteRequest { Address address = 1; repeated CartItem items = 2; } message GetQuoteResponse { Money cost_usd = 1; } message ShipOrderRequest { Address address = 1; repeated CartItem items = 2; } message ShipOrderResponse { string tracking_id = 1; } message Address { string street_address = 1; string city = 2; string state = 3; string country = 4; int32 zip_code = 5; } // -----------------Currency service----------------- service CurrencyService { rpc GetSupportedCurrencies(Empty) returns (GetSupportedCurrenciesResponse) {} rpc Convert(CurrencyConversionRequest) returns (Money) {} } // Represents an amount of money with its currency type. message Money { // The 3-letter currency code defined in ISO 4217. string currency_code = 1; // The whole units of the amount. // For example if `currencyCode` is `"USD"`, then 1 unit is one US dollar. int64 units = 2; // Number of nano (10^-9) units of the amount. // The value must be between -999,999,999 and +999,999,999 inclusive. // If `units` is positive, `nanos` must be positive or zero. // If `units` is zero, `nanos` can be positive, zero, or negative. // If `units` is negative, `nanos` must be negative or zero. // For example $-1.75 is represented as `units`=-1 and `nanos`=-750,000,000. int32 nanos = 3; } message GetSupportedCurrenciesResponse { // The 3-letter currency code defined in ISO 4217. repeated string currency_codes = 1; } message CurrencyConversionRequest { Money from = 1; // The 3-letter currency code defined in ISO 4217. string to_code = 2; } // -------------Payment service----------------- service PaymentService { rpc Charge(ChargeRequest) returns (ChargeResponse) {} } message CreditCardInfo { string credit_card_number = 1; int32 credit_card_cvv = 2; int32 credit_card_expiration_year = 3; int32 credit_card_expiration_month = 4; } message ChargeRequest { Money amount = 1; CreditCardInfo credit_card = 2; } message ChargeResponse { string transaction_id = 1; } // -------------Email service----------------- service EmailService { rpc SendOrderConfirmation(SendOrderConfirmationRequest) returns (Empty) {} } message OrderItem { CartItem item = 1; Money cost = 2; } message OrderResult { string order_id = 1; string shipping_tracking_id = 2; Money shipping_cost = 3; Address shipping_address = 4; repeated OrderItem items = 5; } message SendOrderConfirmationRequest { string email = 1; OrderResult order = 2; } // -------------Checkout service----------------- service CheckoutService { rpc PlaceOrder(PlaceOrderRequest) returns (PlaceOrderResponse) {} } message PlaceOrderRequest { string user_id = 1; string user_currency = 2; Address address = 3; string email = 5; CreditCardInfo credit_card = 6; } message PlaceOrderResponse { OrderResult order = 1; } // ------------Ad service------------------ service AdService { rpc GetAds(AdRequest) returns (AdResponse) {} } message AdRequest { // List of important key words from the current page describing the context. repeated string context_keys = 1; } message AdResponse { repeated Ad ads = 1; } message Ad { // url to redirect to when an ad is clicked. string redirect_url = 1; // short advertisement text to display. string text = 2; } ================================================ FILE: src/adservice/src/main/resources/log4j2.xml ================================================ ================================================ FILE: src/cartservice/cartservice.sln ================================================  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 VisualStudioVersion = 15.0.26124.0 MinimumVisualStudioVersion = 15.0.26124.0 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "cartservice", "src\cartservice.csproj", "{2348C29F-E8D3-4955-916D-D609CBC97FCB}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "cartservice.tests", "tests\cartservice.tests.csproj", "{59825342-CE64-4AFA-8744-781692C0811B}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Debug|x64 = Debug|x64 Debug|x86 = Debug|x86 Release|Any CPU = Release|Any CPU Release|x64 = Release|x64 Release|x86 = Release|x86 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {2348C29F-E8D3-4955-916D-D609CBC97FCB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {2348C29F-E8D3-4955-916D-D609CBC97FCB}.Debug|Any CPU.Build.0 = Debug|Any CPU {2348C29F-E8D3-4955-916D-D609CBC97FCB}.Debug|x64.ActiveCfg = Debug|Any CPU {2348C29F-E8D3-4955-916D-D609CBC97FCB}.Debug|x64.Build.0 = Debug|Any CPU {2348C29F-E8D3-4955-916D-D609CBC97FCB}.Debug|x86.ActiveCfg = Debug|Any CPU {2348C29F-E8D3-4955-916D-D609CBC97FCB}.Debug|x86.Build.0 = Debug|Any CPU {2348C29F-E8D3-4955-916D-D609CBC97FCB}.Release|Any CPU.ActiveCfg = Release|Any CPU {2348C29F-E8D3-4955-916D-D609CBC97FCB}.Release|Any CPU.Build.0 = Release|Any CPU {2348C29F-E8D3-4955-916D-D609CBC97FCB}.Release|x64.ActiveCfg = Release|Any CPU {2348C29F-E8D3-4955-916D-D609CBC97FCB}.Release|x64.Build.0 = Release|Any CPU {2348C29F-E8D3-4955-916D-D609CBC97FCB}.Release|x86.ActiveCfg = Release|Any CPU {2348C29F-E8D3-4955-916D-D609CBC97FCB}.Release|x86.Build.0 = Release|Any CPU {59825342-CE64-4AFA-8744-781692C0811B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {59825342-CE64-4AFA-8744-781692C0811B}.Debug|Any CPU.Build.0 = Debug|Any CPU {59825342-CE64-4AFA-8744-781692C0811B}.Debug|x64.ActiveCfg = Debug|Any CPU {59825342-CE64-4AFA-8744-781692C0811B}.Debug|x64.Build.0 = Debug|Any CPU {59825342-CE64-4AFA-8744-781692C0811B}.Debug|x86.ActiveCfg = Debug|Any CPU {59825342-CE64-4AFA-8744-781692C0811B}.Debug|x86.Build.0 = Debug|Any CPU {59825342-CE64-4AFA-8744-781692C0811B}.Release|Any CPU.ActiveCfg = Release|Any CPU {59825342-CE64-4AFA-8744-781692C0811B}.Release|Any CPU.Build.0 = Release|Any CPU {59825342-CE64-4AFA-8744-781692C0811B}.Release|x64.ActiveCfg = Release|Any CPU {59825342-CE64-4AFA-8744-781692C0811B}.Release|x64.Build.0 = Release|Any CPU {59825342-CE64-4AFA-8744-781692C0811B}.Release|x86.ActiveCfg = Release|Any CPU {59825342-CE64-4AFA-8744-781692C0811B}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection EndGlobal ================================================ FILE: src/cartservice/src/.dockerignore ================================================ **/*.sh **/*.bat **/bin/ **/obj/ **/out/ Dockerfile* ================================================ FILE: src/cartservice/src/Dockerfile ================================================ # 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. # Define a default value so it's not empty if the builder fails to provide it ARG BUILDPLATFORM=linux/amd64 # https://mcr.microsoft.com/product/dotnet/sdk FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:10.0.100-noble@sha256:c7445f141c04f1a6b454181bd098dcfa606c61ba0bd213d0a702489e5bd4cd71 AS builder ARG TARGETARCH=amd64 WORKDIR /app COPY cartservice.csproj . RUN dotnet restore cartservice.csproj \ -a $TARGETARCH COPY . . RUN dotnet publish cartservice.csproj \ -p:PublishSingleFile=true \ -a $TARGETARCH \ --self-contained true \ -p:PublishTrimmed=true \ -p:TrimMode=full \ -c release \ -o /cartservice # https://mcr.microsoft.com/product/dotnet/runtime-deps FROM mcr.microsoft.com/dotnet/runtime-deps:10.0.0-noble-chiseled@sha256:b857c8cb8d929183cfe4c6dd9994abba92a2639dd2dbaf06005379f815991604 WORKDIR /app COPY --from=builder /cartservice . EXPOSE 7070 ENV DOTNET_EnableDiagnostics=0 \ ASPNETCORE_HTTP_PORTS=7070 USER 1000 ENTRYPOINT ["/app/cartservice"] ================================================ FILE: src/cartservice/src/Dockerfile.debug ================================================ # 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. FROM mcr.microsoft.com/dotnet/sdk:10.0@sha256:478b9038d187e5b5c29bfa8173ded5d29e864b5ad06102a12106380ee01e2e49 AS build WORKDIR /app COPY . . RUN dotnet restore cartservice.csproj RUN dotnet build "./cartservice.csproj" -c Debug -o /out FROM build AS publish RUN dotnet publish cartservice.csproj -c Debug -o /out # Building final image used in running container FROM mcr.microsoft.com/dotnet/aspnet:10.0@sha256:a04d1c1d2d26119049494057d80ea6cda25bbd8aef7c444a1fc1ef874fd3955b AS final # Installing procps on the container to enable debugging of .NET Core RUN apt-get update \ && apt-get install -y unzip procps wget WORKDIR /app COPY --from=publish /out . ENV ASPNETCORE_HTTP_PORTS=7070 ENTRYPOINT ["dotnet", "cartservice.dll"] ================================================ FILE: src/cartservice/src/Program.cs ================================================ // Copyright 2020 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. using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Hosting; using cartservice; CreateHostBuilder(args).Build().Run(); static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args) .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup(); }); ================================================ FILE: src/cartservice/src/Startup.cs ================================================ using System; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Diagnostics.HealthChecks; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Diagnostics.HealthChecks; using Microsoft.Extensions.Hosting; using cartservice.cartstore; using cartservice.services; using Microsoft.Extensions.Caching.StackExchangeRedis; namespace cartservice { public class Startup { public Startup(IConfiguration configuration) { Configuration = configuration; } public IConfiguration Configuration { get; } // This method gets called by the runtime. Use this method to add services to the container. // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940 public void ConfigureServices(IServiceCollection services) { string redisAddress = Configuration["REDIS_ADDR"]; string spannerProjectId = Configuration["SPANNER_PROJECT"]; string spannerConnectionString = Configuration["SPANNER_CONNECTION_STRING"]; string alloyDBConnectionString = Configuration["ALLOYDB_PRIMARY_IP"]; if (!string.IsNullOrEmpty(redisAddress)) { services.AddStackExchangeRedisCache(options => { options.Configuration = redisAddress; }); services.AddSingleton(); } else if (!string.IsNullOrEmpty(spannerProjectId) || !string.IsNullOrEmpty(spannerConnectionString)) { services.AddSingleton(); } else if (!string.IsNullOrEmpty(alloyDBConnectionString)) { Console.WriteLine("Creating AlloyDB cart store"); services.AddSingleton(); } else { Console.WriteLine("Redis cache host(hostname+port) was not specified. Starting a cart service using in memory store"); services.AddDistributedMemoryCache(); services.AddSingleton(); } services.AddGrpc(); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseRouting(); app.UseEndpoints(endpoints => { endpoints.MapGrpcService(); endpoints.MapGrpcService(); endpoints.MapGet("/", async context => { await context.Response.WriteAsync("Communication with gRPC endpoints must be made through a gRPC client. To learn how to create a client, visit: https://go.microsoft.com/fwlink/?linkid=2086909"); }); }); } } } ================================================ FILE: src/cartservice/src/appsettings.json ================================================ { "Logging": { "LogLevel": { "Default": "Information", "Microsoft": "Warning", "Microsoft.Hosting.Lifetime": "Information" } }, "AllowedHosts": "*", "Kestrel": { "EndpointDefaults": { "Protocols": "Http2" } } } ================================================ FILE: src/cartservice/src/cartservice.csproj ================================================ net10.0 ================================================ FILE: src/cartservice/src/cartstore/AlloyDBCartStore.cs ================================================ // 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. using System; using Grpc.Core; using Npgsql; using Microsoft.Extensions.Configuration; using System.Threading.Tasks; using Google.Api.Gax.ResourceNames; using Google.Cloud.SecretManager.V1; namespace cartservice.cartstore { public class AlloyDBCartStore : ICartStore { private readonly string tableName; private readonly string connectionString; public AlloyDBCartStore(IConfiguration configuration) { // Create a Cloud Secrets client. SecretManagerServiceClient client = SecretManagerServiceClient.Create(); var projectId = configuration["PROJECT_ID"]; var secretId = configuration["ALLOYDB_SECRET_NAME"]; SecretVersionName secretVersionName = new SecretVersionName(projectId, secretId, "latest"); AccessSecretVersionResponse result = client.AccessSecretVersion(secretVersionName); // Convert the payload to a string. Payloads are bytes by default. string alloyDBPassword = result.Payload.Data.ToStringUtf8().TrimEnd('\r', '\n'); // TODO: Create a separate user for connecting within the application // rather than using our superuser string alloyDBUser = "postgres"; string databaseName = configuration["ALLOYDB_DATABASE_NAME"]; // TODO: Consider splitting workloads into read vs. write and take // advantage of the AlloyDB read pools string primaryIPAddress = configuration["ALLOYDB_PRIMARY_IP"]; connectionString = "Host=" + primaryIPAddress + ";Username=" + alloyDBUser + ";Password=" + alloyDBPassword + ";Database=" + databaseName; tableName = configuration["ALLOYDB_TABLE_NAME"]; } public async Task AddItemAsync(string userId, string productId, int quantity) { Console.WriteLine($"AddItemAsync for {userId} called"); try { await using var dataSource = NpgsqlDataSource.Create(connectionString); var fetchCmd = $"SELECT quantity FROM {tableName} WHERE userID='{userId}' AND productID='{productId}'"; var currentQuantity = 0; var cmdRead = dataSource.CreateCommand(fetchCmd); await using (var reader = await cmdRead.ExecuteReaderAsync()) { while (await reader.ReadAsync()) currentQuantity += reader.GetInt32(0); } var totalQuantity = quantity + currentQuantity; // Use INSERT ... ON CONFLICT to prevent duplicate key error var insertCmd = $@" INSERT INTO {tableName} (userId, productId, quantity) VALUES ('{userId}', '{productId}', {totalQuantity}) ON CONFLICT (userId, productId) DO UPDATE SET quantity = {totalQuantity}; "; await using (var cmdInsert = dataSource.CreateCommand(insertCmd)) { await Task.Run(() => { return cmdInsert.ExecuteNonQueryAsync(); }); } } catch (Exception ex) { throw new RpcException( new Status(StatusCode.FailedPrecondition, $"Unable to access cart storage due to an internal error. {ex}")); } } public async Task GetCartAsync(string userId) { Console.WriteLine($"GetCartAsync called for userId={userId}"); Hipstershop.Cart cart = new(); cart.UserId = userId; try { await using var dataSource = NpgsqlDataSource.Create(connectionString); var cartFetchCmd = $"SELECT productId, quantity FROM {tableName} WHERE userId = '{userId}'"; var cmd = dataSource.CreateCommand(cartFetchCmd); await using (var reader = await cmd.ExecuteReaderAsync()) { while (await reader.ReadAsync()) { Hipstershop.CartItem item = new() { ProductId = reader.GetString(0), Quantity = reader.GetInt32(1) }; cart.Items.Add(item); } } await Task.Run(() => { return cart; }); } catch (Exception ex) { throw new RpcException( new Status(StatusCode.FailedPrecondition, $"Unable to access cart storage due to an internal error. {ex}")); } return cart; } public async Task EmptyCartAsync(string userId) { Console.WriteLine($"EmptyCartAsync called for userId={userId}"); try { await using var dataSource = NpgsqlDataSource.Create(connectionString); var deleteCmd = $"DELETE FROM {tableName} WHERE userID = '{userId}'"; await using (var cmd = dataSource.CreateCommand(deleteCmd)) { await Task.Run(() => { return cmd.ExecuteNonQueryAsync(); }); } } catch (Exception ex) { throw new RpcException( new Status(StatusCode.FailedPrecondition, $"Unable to access cart storage due to an internal error. {ex}")); } } public bool Ping() { try { return true; } catch (Exception) { return false; } } } } ================================================ FILE: src/cartservice/src/cartstore/ICartStore.cs ================================================ // Copyright 2018 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. using System.Threading.Tasks; namespace cartservice.cartstore { public interface ICartStore { Task AddItemAsync(string userId, string productId, int quantity); Task EmptyCartAsync(string userId); Task GetCartAsync(string userId); bool Ping(); } } ================================================ FILE: src/cartservice/src/cartstore/RedisCartStore.cs ================================================ // Copyright 2018 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. using System; using System.Linq; using System.Threading.Tasks; using Grpc.Core; using Microsoft.Extensions.Caching.Distributed; using Google.Protobuf; namespace cartservice.cartstore { public class RedisCartStore : ICartStore { private readonly IDistributedCache _cache; public RedisCartStore(IDistributedCache cache) { _cache = cache; } public async Task AddItemAsync(string userId, string productId, int quantity) { Console.WriteLine($"AddItemAsync called with userId={userId}, productId={productId}, quantity={quantity}"); try { Hipstershop.Cart cart; var value = await _cache.GetAsync(userId); if (value == null) { cart = new Hipstershop.Cart(); cart.UserId = userId; cart.Items.Add(new Hipstershop.CartItem { ProductId = productId, Quantity = quantity }); } else { cart = Hipstershop.Cart.Parser.ParseFrom(value); var existingItem = cart.Items.SingleOrDefault(i => i.ProductId == productId); if (existingItem == null) { cart.Items.Add(new Hipstershop.CartItem { ProductId = productId, Quantity = quantity }); } else { existingItem.Quantity += quantity; } } await _cache.SetAsync(userId, cart.ToByteArray()); } catch (Exception ex) { throw new RpcException(new Status(StatusCode.FailedPrecondition, $"Can't access cart storage. {ex}")); } } public async Task EmptyCartAsync(string userId) { Console.WriteLine($"EmptyCartAsync called with userId={userId}"); try { var cart = new Hipstershop.Cart(); await _cache.SetAsync(userId, cart.ToByteArray()); } catch (Exception ex) { throw new RpcException(new Status(StatusCode.FailedPrecondition, $"Can't access cart storage. {ex}")); } } public async Task GetCartAsync(string userId) { Console.WriteLine($"GetCartAsync called with userId={userId}"); try { // Access the cart from the cache var value = await _cache.GetAsync(userId); if (value != null) { return Hipstershop.Cart.Parser.ParseFrom(value); } // We decided to return empty cart in cases when user wasn't in the cache before return new Hipstershop.Cart(); } catch (Exception ex) { throw new RpcException(new Status(StatusCode.FailedPrecondition, $"Can't access cart storage. {ex}")); } } public bool Ping() { try { return true; } catch (Exception) { return false; } } } } ================================================ FILE: src/cartservice/src/cartstore/SpannerCartStore.cs ================================================ // 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. using System; using Google.Cloud.Spanner.Data; using Grpc.Core; using Microsoft.Extensions.Configuration; using System.Threading.Tasks; namespace cartservice.cartstore { public class SpannerCartStore : ICartStore { private static readonly string TableName = "CartItems"; private static readonly string DefaultInstanceName = "onlineboutique"; private static readonly string DefaultDatabaseName = "carts"; private readonly string databaseString; public SpannerCartStore(IConfiguration configuration) { string spannerProjectId = configuration["SPANNER_PROJECT"]; string spannerInstanceId = configuration["SPANNER_INSTANCE"]; string spannerDatabaseId = configuration["SPANNER_DATABASE"]; string spannerConnectionString = configuration["SPANNER_CONNECTION_STRING"]; SpannerConnectionStringBuilder builder = new(); if (!string.IsNullOrEmpty(spannerConnectionString)) { builder.DataSource = spannerConnectionString; databaseString = builder.ToString(); Console.WriteLine($"Spanner connection string: ${databaseString}"); return; } if (string.IsNullOrEmpty(spannerInstanceId)) spannerInstanceId = DefaultInstanceName; if (string.IsNullOrEmpty(spannerDatabaseId)) spannerDatabaseId = DefaultDatabaseName; builder.DataSource = $"projects/{spannerProjectId}/instances/{spannerInstanceId}/databases/{spannerDatabaseId}"; databaseString = builder.ToString(); Console.WriteLine($"Built Spanner connection string: '{databaseString}'"); } public async Task AddItemAsync(string userId, string productId, int quantity) { Console.WriteLine($"AddItemAsync for {userId} called"); try { using SpannerConnection spannerConnection = new(databaseString); await spannerConnection.RunWithRetriableTransactionAsync(async transaction => { int currentQuantity = 0; var quantityLookup = spannerConnection.CreateSelectCommand( $"SELECT * FROM {TableName} WHERE userId = @userId AND productId = @productId", new SpannerParameterCollection { { "userId", SpannerDbType.String }, { "productId", SpannerDbType.String } }); quantityLookup.Parameters["userId"].Value = userId; quantityLookup.Parameters["productId"].Value = productId; quantityLookup.Transaction = transaction; using (var reader = await quantityLookup.ExecuteReaderAsync()) { while (await reader.ReadAsync()) { currentQuantity += reader.GetFieldValue("quantity"); } } var cmd = spannerConnection.CreateInsertOrUpdateCommand(TableName, new SpannerParameterCollection { { "userId", SpannerDbType.String }, { "productId", SpannerDbType.String }, { "quantity", SpannerDbType.Int64 } }); cmd.Parameters["userId"].Value = userId; cmd.Parameters["productId"].Value = productId; cmd.Parameters["quantity"].Value = currentQuantity + quantity; cmd.Transaction = transaction; await Task.Run(() => { return cmd.ExecuteNonQueryAsync(); }); }); } catch (Exception ex) { throw new RpcException( new Status(StatusCode.FailedPrecondition, $"Can't access cart storage at {databaseString}. {ex}")); } } public async Task GetCartAsync(string userId) { Console.WriteLine($"GetCartAsync called for userId={userId}"); Hipstershop.Cart cart = new(); try { using SpannerConnection spannerConnection = new(databaseString); var cmd = spannerConnection.CreateSelectCommand( $"SELECT * FROM {TableName} WHERE userId = @userId", new SpannerParameterCollection { { "userId", SpannerDbType.String } } ); cmd.Parameters["userId"].Value = userId; using var reader = await cmd.ExecuteReaderAsync(); while (await reader.ReadAsync()) { // Only add the userId if something is in the cart. // This is based on how the cartservice example behaves. // An empty cart has no userId attached. cart.UserId = userId; Hipstershop.CartItem item = new() { ProductId = reader.GetFieldValue("productId"), Quantity = reader.GetFieldValue("quantity") }; cart.Items.Add(item); } return cart; } catch (Exception ex) { throw new RpcException( new Status(StatusCode.FailedPrecondition, $"Can't access cart storage at {databaseString}. {ex}")); } } public async Task EmptyCartAsync(string userId) { Console.WriteLine($"EmptyCartAsync called for userId={userId}"); try { using SpannerConnection spannerConnection = new(databaseString); await Task.Run(() => { var cmd = spannerConnection.CreateDmlCommand( $"DELETE FROM {TableName} WHERE userId = @userId", new SpannerParameterCollection { { "userId", SpannerDbType.String } }); cmd.Parameters["userId"].Value = userId; return cmd.ExecuteNonQueryAsync(); }); } catch (Exception ex) { throw new RpcException( new Status(StatusCode.FailedPrecondition, $"Can't access cart storage at {databaseString}. {ex}")); } } public bool Ping() { try { return true; } catch (Exception) { return false; } } } } ================================================ FILE: src/cartservice/src/protos/Cart.proto ================================================ // Copyright 2020 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. syntax = "proto3"; package hipstershop; // -----------------Cart service----------------- service CartService { rpc AddItem(AddItemRequest) returns (Empty) {} rpc GetCart(GetCartRequest) returns (Cart) {} rpc EmptyCart(EmptyCartRequest) returns (Empty) {} } message CartItem { string product_id = 1; int32 quantity = 2; } message AddItemRequest { string user_id = 1; CartItem item = 2; } message EmptyCartRequest { string user_id = 1; } message GetCartRequest { string user_id = 1; } message Cart { string user_id = 1; repeated CartItem items = 2; } message Empty {} ================================================ FILE: src/cartservice/src/services/CartService.cs ================================================ // Copyright 2020 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. using System; using System.Threading.Tasks; using Grpc.Core; using Microsoft.Extensions.Logging; using cartservice.cartstore; using Hipstershop; namespace cartservice.services { public class CartService : Hipstershop.CartService.CartServiceBase { private readonly static Empty Empty = new Empty(); private readonly ICartStore _cartStore; public CartService(ICartStore cartStore) { _cartStore = cartStore; } public async override Task AddItem(AddItemRequest request, ServerCallContext context) { await _cartStore.AddItemAsync(request.UserId, request.Item.ProductId, request.Item.Quantity); return Empty; } public override Task GetCart(GetCartRequest request, ServerCallContext context) { return _cartStore.GetCartAsync(request.UserId); } public async override Task EmptyCart(EmptyCartRequest request, ServerCallContext context) { await _cartStore.EmptyCartAsync(request.UserId); return Empty; } } } ================================================ FILE: src/cartservice/src/services/HealthCheckService.cs ================================================ // Copyright 2020 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. using System; using System.Threading.Tasks; using Grpc.Core; using Grpc.Health.V1; using static Grpc.Health.V1.Health; using cartservice.cartstore; namespace cartservice.services { internal class HealthCheckService : HealthBase { private ICartStore _cartStore { get; } public HealthCheckService (ICartStore cartStore) { _cartStore = cartStore; } public override Task Check(HealthCheckRequest request, ServerCallContext context) { Console.WriteLine ("Checking CartService Health"); return Task.FromResult(new HealthCheckResponse { Status = _cartStore.Ping() ? HealthCheckResponse.Types.ServingStatus.Serving : HealthCheckResponse.Types.ServingStatus.NotServing }); } } } ================================================ FILE: src/cartservice/tests/CartServiceTests.cs ================================================ // Copyright 2018 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. using System; using System.Threading.Tasks; using Grpc.Net.Client; using Hipstershop; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.TestHost; using Microsoft.Extensions.Hosting; using Xunit; using static Hipstershop.CartService; namespace cartservice.tests { public class CartServiceTests { private readonly IHostBuilder _host; public CartServiceTests() { _host = new HostBuilder().ConfigureWebHost(webBuilder => { webBuilder .UseStartup() .UseTestServer(); }); } [Fact] public async Task GetItem_NoAddItemBefore_EmptyCartReturned() { // Setup test server and client using var server = await _host.StartAsync(); var httpClient = server.GetTestClient(); string userId = Guid.NewGuid().ToString(); // Create a GRPC communication channel between the client and the server var channel = GrpcChannel.ForAddress(httpClient.BaseAddress, new GrpcChannelOptions { HttpClient = httpClient }); var cartClient = new CartServiceClient(channel); var request = new GetCartRequest { UserId = userId, }; var cart = await cartClient.GetCartAsync(request); Assert.NotNull(cart); // All grpc objects implement IEquitable, so we can compare equality with by-value semantics Assert.Equal(new Cart(), cart); } [Fact] public async Task AddItem_ItemExists_Updated() { // Setup test server and client using var server = await _host.StartAsync(); var httpClient = server.GetTestClient(); string userId = Guid.NewGuid().ToString(); // Create a GRPC communication channel between the client and the server var channel = GrpcChannel.ForAddress(httpClient.BaseAddress, new GrpcChannelOptions { HttpClient = httpClient }); var client = new CartServiceClient(channel); var request = new AddItemRequest { UserId = userId, Item = new CartItem { ProductId = "1", Quantity = 1 } }; // First add - nothing should fail await client.AddItemAsync(request); // Second add of existing product - quantity should be updated await client.AddItemAsync(request); var getCartRequest = new GetCartRequest { UserId = userId }; var cart = await client.GetCartAsync(getCartRequest); Assert.NotNull(cart); Assert.Equal(userId, cart.UserId); Assert.Single(cart.Items); Assert.Equal(2, cart.Items[0].Quantity); // Cleanup await client.EmptyCartAsync(new EmptyCartRequest { UserId = userId }); } [Fact] public async Task AddItem_New_Inserted() { // Setup test server and client using var server = await _host.StartAsync(); var httpClient = server.GetTestClient(); string userId = Guid.NewGuid().ToString(); // Create a GRPC communication channel between the client and the server var channel = GrpcChannel.ForAddress(httpClient.BaseAddress, new GrpcChannelOptions { HttpClient = httpClient }); // Create a proxy object to work with the server var client = new CartServiceClient(channel); var request = new AddItemRequest { UserId = userId, Item = new CartItem { ProductId = "1", Quantity = 1 } }; await client.AddItemAsync(request); var getCartRequest = new GetCartRequest { UserId = userId }; var cart = await client.GetCartAsync(getCartRequest); Assert.NotNull(cart); Assert.Equal(userId, cart.UserId); Assert.Single(cart.Items); await client.EmptyCartAsync(new EmptyCartRequest { UserId = userId }); cart = await client.GetCartAsync(getCartRequest); Assert.Empty(cart.Items); } } } ================================================ FILE: src/cartservice/tests/cartservice.tests.csproj ================================================ net10.0 false ================================================ FILE: src/checkoutservice/.dockerignore ================================================ vendor/ ================================================ FILE: src/checkoutservice/Dockerfile ================================================ # Copyright 2020 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. # Define a default value so it's not empty if the builder fails to provide it ARG BUILDPLATFORM=linux/amd64 FROM --platform=$BUILDPLATFORM golang:1.26.1-alpine@sha256:2389ebfa5b7f43eeafbd6be0c3700cc46690ef842ad962f6c5bd6be49ed82039 AS builder ARG TARGETOS=linux ARG TARGETARCH=amd64 WORKDIR /src # restore dependencies COPY go.mod go.sum ./ RUN go mod download COPY . . # Skaffold passes in debug-oriented compiler flags ARG SKAFFOLD_GO_GCFLAGS RUN GOOS=${TARGETOS} GOARCH=${TARGETARCH} CGO_ENABLED=0 go build -ldflags="-s -w" -gcflags="${SKAFFOLD_GO_GCFLAGS}" -o /checkoutservice . FROM gcr.io/distroless/static WORKDIR /src COPY --from=builder /checkoutservice /src/checkoutservice # Definition of this variable is used by 'skaffold debug' to identify a golang binary. # Default behavior - a failure prints a stack trace for the current goroutine. # See https://golang.org/pkg/runtime/ ENV GOTRACEBACK=single EXPOSE 5050 ENTRYPOINT ["/src/checkoutservice"] ================================================ FILE: src/checkoutservice/README.md ================================================ # checkoutservice Run the following command to restore dependencies to `vendor/` directory: dep ensure --vendor-only ================================================ FILE: src/checkoutservice/genproto/demo.pb.go ================================================ // Copyright 2020 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. // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.34.2 // protoc v3.6.1 // source: demo.proto package hipstershop import ( protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" sync "sync" ) const ( // Verify that this generated code is sufficiently up-to-date. _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) // Verify that runtime/protoimpl is sufficiently up-to-date. _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) type CartItem struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields ProductId string `protobuf:"bytes,1,opt,name=product_id,json=productId,proto3" json:"product_id,omitempty"` Quantity int32 `protobuf:"varint,2,opt,name=quantity,proto3" json:"quantity,omitempty"` } func (x *CartItem) Reset() { *x = CartItem{} if protoimpl.UnsafeEnabled { mi := &file_demo_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *CartItem) String() string { return protoimpl.X.MessageStringOf(x) } func (*CartItem) ProtoMessage() {} func (x *CartItem) ProtoReflect() protoreflect.Message { mi := &file_demo_proto_msgTypes[0] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use CartItem.ProtoReflect.Descriptor instead. func (*CartItem) Descriptor() ([]byte, []int) { return file_demo_proto_rawDescGZIP(), []int{0} } func (x *CartItem) GetProductId() string { if x != nil { return x.ProductId } return "" } func (x *CartItem) GetQuantity() int32 { if x != nil { return x.Quantity } return 0 } type AddItemRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields UserId string `protobuf:"bytes,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` Item *CartItem `protobuf:"bytes,2,opt,name=item,proto3" json:"item,omitempty"` } func (x *AddItemRequest) Reset() { *x = AddItemRequest{} if protoimpl.UnsafeEnabled { mi := &file_demo_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *AddItemRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*AddItemRequest) ProtoMessage() {} func (x *AddItemRequest) ProtoReflect() protoreflect.Message { mi := &file_demo_proto_msgTypes[1] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use AddItemRequest.ProtoReflect.Descriptor instead. func (*AddItemRequest) Descriptor() ([]byte, []int) { return file_demo_proto_rawDescGZIP(), []int{1} } func (x *AddItemRequest) GetUserId() string { if x != nil { return x.UserId } return "" } func (x *AddItemRequest) GetItem() *CartItem { if x != nil { return x.Item } return nil } type EmptyCartRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields UserId string `protobuf:"bytes,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` } func (x *EmptyCartRequest) Reset() { *x = EmptyCartRequest{} if protoimpl.UnsafeEnabled { mi := &file_demo_proto_msgTypes[2] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *EmptyCartRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*EmptyCartRequest) ProtoMessage() {} func (x *EmptyCartRequest) ProtoReflect() protoreflect.Message { mi := &file_demo_proto_msgTypes[2] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use EmptyCartRequest.ProtoReflect.Descriptor instead. func (*EmptyCartRequest) Descriptor() ([]byte, []int) { return file_demo_proto_rawDescGZIP(), []int{2} } func (x *EmptyCartRequest) GetUserId() string { if x != nil { return x.UserId } return "" } type GetCartRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields UserId string `protobuf:"bytes,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` } func (x *GetCartRequest) Reset() { *x = GetCartRequest{} if protoimpl.UnsafeEnabled { mi := &file_demo_proto_msgTypes[3] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *GetCartRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*GetCartRequest) ProtoMessage() {} func (x *GetCartRequest) ProtoReflect() protoreflect.Message { mi := &file_demo_proto_msgTypes[3] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use GetCartRequest.ProtoReflect.Descriptor instead. func (*GetCartRequest) Descriptor() ([]byte, []int) { return file_demo_proto_rawDescGZIP(), []int{3} } func (x *GetCartRequest) GetUserId() string { if x != nil { return x.UserId } return "" } type Cart struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields UserId string `protobuf:"bytes,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` Items []*CartItem `protobuf:"bytes,2,rep,name=items,proto3" json:"items,omitempty"` } func (x *Cart) Reset() { *x = Cart{} if protoimpl.UnsafeEnabled { mi := &file_demo_proto_msgTypes[4] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Cart) String() string { return protoimpl.X.MessageStringOf(x) } func (*Cart) ProtoMessage() {} func (x *Cart) ProtoReflect() protoreflect.Message { mi := &file_demo_proto_msgTypes[4] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use Cart.ProtoReflect.Descriptor instead. func (*Cart) Descriptor() ([]byte, []int) { return file_demo_proto_rawDescGZIP(), []int{4} } func (x *Cart) GetUserId() string { if x != nil { return x.UserId } return "" } func (x *Cart) GetItems() []*CartItem { if x != nil { return x.Items } return nil } type Empty struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields } func (x *Empty) Reset() { *x = Empty{} if protoimpl.UnsafeEnabled { mi := &file_demo_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Empty) String() string { return protoimpl.X.MessageStringOf(x) } func (*Empty) ProtoMessage() {} func (x *Empty) ProtoReflect() protoreflect.Message { mi := &file_demo_proto_msgTypes[5] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use Empty.ProtoReflect.Descriptor instead. func (*Empty) Descriptor() ([]byte, []int) { return file_demo_proto_rawDescGZIP(), []int{5} } type ListRecommendationsRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields UserId string `protobuf:"bytes,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` ProductIds []string `protobuf:"bytes,2,rep,name=product_ids,json=productIds,proto3" json:"product_ids,omitempty"` } func (x *ListRecommendationsRequest) Reset() { *x = ListRecommendationsRequest{} if protoimpl.UnsafeEnabled { mi := &file_demo_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *ListRecommendationsRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*ListRecommendationsRequest) ProtoMessage() {} func (x *ListRecommendationsRequest) ProtoReflect() protoreflect.Message { mi := &file_demo_proto_msgTypes[6] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use ListRecommendationsRequest.ProtoReflect.Descriptor instead. func (*ListRecommendationsRequest) Descriptor() ([]byte, []int) { return file_demo_proto_rawDescGZIP(), []int{6} } func (x *ListRecommendationsRequest) GetUserId() string { if x != nil { return x.UserId } return "" } func (x *ListRecommendationsRequest) GetProductIds() []string { if x != nil { return x.ProductIds } return nil } type ListRecommendationsResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields ProductIds []string `protobuf:"bytes,1,rep,name=product_ids,json=productIds,proto3" json:"product_ids,omitempty"` } func (x *ListRecommendationsResponse) Reset() { *x = ListRecommendationsResponse{} if protoimpl.UnsafeEnabled { mi := &file_demo_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *ListRecommendationsResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*ListRecommendationsResponse) ProtoMessage() {} func (x *ListRecommendationsResponse) ProtoReflect() protoreflect.Message { mi := &file_demo_proto_msgTypes[7] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use ListRecommendationsResponse.ProtoReflect.Descriptor instead. func (*ListRecommendationsResponse) Descriptor() ([]byte, []int) { return file_demo_proto_rawDescGZIP(), []int{7} } func (x *ListRecommendationsResponse) GetProductIds() []string { if x != nil { return x.ProductIds } return nil } type Product struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` Description string `protobuf:"bytes,3,opt,name=description,proto3" json:"description,omitempty"` Picture string `protobuf:"bytes,4,opt,name=picture,proto3" json:"picture,omitempty"` PriceUsd *Money `protobuf:"bytes,5,opt,name=price_usd,json=priceUsd,proto3" json:"price_usd,omitempty"` // Categories such as "clothing" or "kitchen" that can be used to look up // other related products. Categories []string `protobuf:"bytes,6,rep,name=categories,proto3" json:"categories,omitempty"` } func (x *Product) Reset() { *x = Product{} if protoimpl.UnsafeEnabled { mi := &file_demo_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Product) String() string { return protoimpl.X.MessageStringOf(x) } func (*Product) ProtoMessage() {} func (x *Product) ProtoReflect() protoreflect.Message { mi := &file_demo_proto_msgTypes[8] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use Product.ProtoReflect.Descriptor instead. func (*Product) Descriptor() ([]byte, []int) { return file_demo_proto_rawDescGZIP(), []int{8} } func (x *Product) GetId() string { if x != nil { return x.Id } return "" } func (x *Product) GetName() string { if x != nil { return x.Name } return "" } func (x *Product) GetDescription() string { if x != nil { return x.Description } return "" } func (x *Product) GetPicture() string { if x != nil { return x.Picture } return "" } func (x *Product) GetPriceUsd() *Money { if x != nil { return x.PriceUsd } return nil } func (x *Product) GetCategories() []string { if x != nil { return x.Categories } return nil } type ListProductsResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Products []*Product `protobuf:"bytes,1,rep,name=products,proto3" json:"products,omitempty"` } func (x *ListProductsResponse) Reset() { *x = ListProductsResponse{} if protoimpl.UnsafeEnabled { mi := &file_demo_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *ListProductsResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*ListProductsResponse) ProtoMessage() {} func (x *ListProductsResponse) ProtoReflect() protoreflect.Message { mi := &file_demo_proto_msgTypes[9] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use ListProductsResponse.ProtoReflect.Descriptor instead. func (*ListProductsResponse) Descriptor() ([]byte, []int) { return file_demo_proto_rawDescGZIP(), []int{9} } func (x *ListProductsResponse) GetProducts() []*Product { if x != nil { return x.Products } return nil } type GetProductRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` } func (x *GetProductRequest) Reset() { *x = GetProductRequest{} if protoimpl.UnsafeEnabled { mi := &file_demo_proto_msgTypes[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *GetProductRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*GetProductRequest) ProtoMessage() {} func (x *GetProductRequest) ProtoReflect() protoreflect.Message { mi := &file_demo_proto_msgTypes[10] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use GetProductRequest.ProtoReflect.Descriptor instead. func (*GetProductRequest) Descriptor() ([]byte, []int) { return file_demo_proto_rawDescGZIP(), []int{10} } func (x *GetProductRequest) GetId() string { if x != nil { return x.Id } return "" } type SearchProductsRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Query string `protobuf:"bytes,1,opt,name=query,proto3" json:"query,omitempty"` } func (x *SearchProductsRequest) Reset() { *x = SearchProductsRequest{} if protoimpl.UnsafeEnabled { mi := &file_demo_proto_msgTypes[11] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *SearchProductsRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*SearchProductsRequest) ProtoMessage() {} func (x *SearchProductsRequest) ProtoReflect() protoreflect.Message { mi := &file_demo_proto_msgTypes[11] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use SearchProductsRequest.ProtoReflect.Descriptor instead. func (*SearchProductsRequest) Descriptor() ([]byte, []int) { return file_demo_proto_rawDescGZIP(), []int{11} } func (x *SearchProductsRequest) GetQuery() string { if x != nil { return x.Query } return "" } type SearchProductsResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Results []*Product `protobuf:"bytes,1,rep,name=results,proto3" json:"results,omitempty"` } func (x *SearchProductsResponse) Reset() { *x = SearchProductsResponse{} if protoimpl.UnsafeEnabled { mi := &file_demo_proto_msgTypes[12] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *SearchProductsResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*SearchProductsResponse) ProtoMessage() {} func (x *SearchProductsResponse) ProtoReflect() protoreflect.Message { mi := &file_demo_proto_msgTypes[12] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use SearchProductsResponse.ProtoReflect.Descriptor instead. func (*SearchProductsResponse) Descriptor() ([]byte, []int) { return file_demo_proto_rawDescGZIP(), []int{12} } func (x *SearchProductsResponse) GetResults() []*Product { if x != nil { return x.Results } return nil } type GetQuoteRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Address *Address `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"` Items []*CartItem `protobuf:"bytes,2,rep,name=items,proto3" json:"items,omitempty"` } func (x *GetQuoteRequest) Reset() { *x = GetQuoteRequest{} if protoimpl.UnsafeEnabled { mi := &file_demo_proto_msgTypes[13] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *GetQuoteRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*GetQuoteRequest) ProtoMessage() {} func (x *GetQuoteRequest) ProtoReflect() protoreflect.Message { mi := &file_demo_proto_msgTypes[13] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use GetQuoteRequest.ProtoReflect.Descriptor instead. func (*GetQuoteRequest) Descriptor() ([]byte, []int) { return file_demo_proto_rawDescGZIP(), []int{13} } func (x *GetQuoteRequest) GetAddress() *Address { if x != nil { return x.Address } return nil } func (x *GetQuoteRequest) GetItems() []*CartItem { if x != nil { return x.Items } return nil } type GetQuoteResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields CostUsd *Money `protobuf:"bytes,1,opt,name=cost_usd,json=costUsd,proto3" json:"cost_usd,omitempty"` } func (x *GetQuoteResponse) Reset() { *x = GetQuoteResponse{} if protoimpl.UnsafeEnabled { mi := &file_demo_proto_msgTypes[14] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *GetQuoteResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*GetQuoteResponse) ProtoMessage() {} func (x *GetQuoteResponse) ProtoReflect() protoreflect.Message { mi := &file_demo_proto_msgTypes[14] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use GetQuoteResponse.ProtoReflect.Descriptor instead. func (*GetQuoteResponse) Descriptor() ([]byte, []int) { return file_demo_proto_rawDescGZIP(), []int{14} } func (x *GetQuoteResponse) GetCostUsd() *Money { if x != nil { return x.CostUsd } return nil } type ShipOrderRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Address *Address `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"` Items []*CartItem `protobuf:"bytes,2,rep,name=items,proto3" json:"items,omitempty"` } func (x *ShipOrderRequest) Reset() { *x = ShipOrderRequest{} if protoimpl.UnsafeEnabled { mi := &file_demo_proto_msgTypes[15] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *ShipOrderRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*ShipOrderRequest) ProtoMessage() {} func (x *ShipOrderRequest) ProtoReflect() protoreflect.Message { mi := &file_demo_proto_msgTypes[15] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use ShipOrderRequest.ProtoReflect.Descriptor instead. func (*ShipOrderRequest) Descriptor() ([]byte, []int) { return file_demo_proto_rawDescGZIP(), []int{15} } func (x *ShipOrderRequest) GetAddress() *Address { if x != nil { return x.Address } return nil } func (x *ShipOrderRequest) GetItems() []*CartItem { if x != nil { return x.Items } return nil } type ShipOrderResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields TrackingId string `protobuf:"bytes,1,opt,name=tracking_id,json=trackingId,proto3" json:"tracking_id,omitempty"` } func (x *ShipOrderResponse) Reset() { *x = ShipOrderResponse{} if protoimpl.UnsafeEnabled { mi := &file_demo_proto_msgTypes[16] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *ShipOrderResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*ShipOrderResponse) ProtoMessage() {} func (x *ShipOrderResponse) ProtoReflect() protoreflect.Message { mi := &file_demo_proto_msgTypes[16] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use ShipOrderResponse.ProtoReflect.Descriptor instead. func (*ShipOrderResponse) Descriptor() ([]byte, []int) { return file_demo_proto_rawDescGZIP(), []int{16} } func (x *ShipOrderResponse) GetTrackingId() string { if x != nil { return x.TrackingId } return "" } type Address struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields StreetAddress string `protobuf:"bytes,1,opt,name=street_address,json=streetAddress,proto3" json:"street_address,omitempty"` City string `protobuf:"bytes,2,opt,name=city,proto3" json:"city,omitempty"` State string `protobuf:"bytes,3,opt,name=state,proto3" json:"state,omitempty"` Country string `protobuf:"bytes,4,opt,name=country,proto3" json:"country,omitempty"` ZipCode int32 `protobuf:"varint,5,opt,name=zip_code,json=zipCode,proto3" json:"zip_code,omitempty"` } func (x *Address) Reset() { *x = Address{} if protoimpl.UnsafeEnabled { mi := &file_demo_proto_msgTypes[17] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Address) String() string { return protoimpl.X.MessageStringOf(x) } func (*Address) ProtoMessage() {} func (x *Address) ProtoReflect() protoreflect.Message { mi := &file_demo_proto_msgTypes[17] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use Address.ProtoReflect.Descriptor instead. func (*Address) Descriptor() ([]byte, []int) { return file_demo_proto_rawDescGZIP(), []int{17} } func (x *Address) GetStreetAddress() string { if x != nil { return x.StreetAddress } return "" } func (x *Address) GetCity() string { if x != nil { return x.City } return "" } func (x *Address) GetState() string { if x != nil { return x.State } return "" } func (x *Address) GetCountry() string { if x != nil { return x.Country } return "" } func (x *Address) GetZipCode() int32 { if x != nil { return x.ZipCode } return 0 } // Represents an amount of money with its currency type. type Money struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // The 3-letter currency code defined in ISO 4217. CurrencyCode string `protobuf:"bytes,1,opt,name=currency_code,json=currencyCode,proto3" json:"currency_code,omitempty"` // The whole units of the amount. // For example if `currencyCode` is `"USD"`, then 1 unit is one US dollar. Units int64 `protobuf:"varint,2,opt,name=units,proto3" json:"units,omitempty"` // Number of nano (10^-9) units of the amount. // The value must be between -999,999,999 and +999,999,999 inclusive. // If `units` is positive, `nanos` must be positive or zero. // If `units` is zero, `nanos` can be positive, zero, or negative. // If `units` is negative, `nanos` must be negative or zero. // For example $-1.75 is represented as `units`=-1 and `nanos`=-750,000,000. Nanos int32 `protobuf:"varint,3,opt,name=nanos,proto3" json:"nanos,omitempty"` } func (x *Money) Reset() { *x = Money{} if protoimpl.UnsafeEnabled { mi := &file_demo_proto_msgTypes[18] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Money) String() string { return protoimpl.X.MessageStringOf(x) } func (*Money) ProtoMessage() {} func (x *Money) ProtoReflect() protoreflect.Message { mi := &file_demo_proto_msgTypes[18] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use Money.ProtoReflect.Descriptor instead. func (*Money) Descriptor() ([]byte, []int) { return file_demo_proto_rawDescGZIP(), []int{18} } func (x *Money) GetCurrencyCode() string { if x != nil { return x.CurrencyCode } return "" } func (x *Money) GetUnits() int64 { if x != nil { return x.Units } return 0 } func (x *Money) GetNanos() int32 { if x != nil { return x.Nanos } return 0 } type GetSupportedCurrenciesResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // The 3-letter currency code defined in ISO 4217. CurrencyCodes []string `protobuf:"bytes,1,rep,name=currency_codes,json=currencyCodes,proto3" json:"currency_codes,omitempty"` } func (x *GetSupportedCurrenciesResponse) Reset() { *x = GetSupportedCurrenciesResponse{} if protoimpl.UnsafeEnabled { mi := &file_demo_proto_msgTypes[19] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *GetSupportedCurrenciesResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*GetSupportedCurrenciesResponse) ProtoMessage() {} func (x *GetSupportedCurrenciesResponse) ProtoReflect() protoreflect.Message { mi := &file_demo_proto_msgTypes[19] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use GetSupportedCurrenciesResponse.ProtoReflect.Descriptor instead. func (*GetSupportedCurrenciesResponse) Descriptor() ([]byte, []int) { return file_demo_proto_rawDescGZIP(), []int{19} } func (x *GetSupportedCurrenciesResponse) GetCurrencyCodes() []string { if x != nil { return x.CurrencyCodes } return nil } type CurrencyConversionRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields From *Money `protobuf:"bytes,1,opt,name=from,proto3" json:"from,omitempty"` // The 3-letter currency code defined in ISO 4217. ToCode string `protobuf:"bytes,2,opt,name=to_code,json=toCode,proto3" json:"to_code,omitempty"` } func (x *CurrencyConversionRequest) Reset() { *x = CurrencyConversionRequest{} if protoimpl.UnsafeEnabled { mi := &file_demo_proto_msgTypes[20] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *CurrencyConversionRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*CurrencyConversionRequest) ProtoMessage() {} func (x *CurrencyConversionRequest) ProtoReflect() protoreflect.Message { mi := &file_demo_proto_msgTypes[20] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use CurrencyConversionRequest.ProtoReflect.Descriptor instead. func (*CurrencyConversionRequest) Descriptor() ([]byte, []int) { return file_demo_proto_rawDescGZIP(), []int{20} } func (x *CurrencyConversionRequest) GetFrom() *Money { if x != nil { return x.From } return nil } func (x *CurrencyConversionRequest) GetToCode() string { if x != nil { return x.ToCode } return "" } type CreditCardInfo struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields CreditCardNumber string `protobuf:"bytes,1,opt,name=credit_card_number,json=creditCardNumber,proto3" json:"credit_card_number,omitempty"` CreditCardCvv int32 `protobuf:"varint,2,opt,name=credit_card_cvv,json=creditCardCvv,proto3" json:"credit_card_cvv,omitempty"` CreditCardExpirationYear int32 `protobuf:"varint,3,opt,name=credit_card_expiration_year,json=creditCardExpirationYear,proto3" json:"credit_card_expiration_year,omitempty"` CreditCardExpirationMonth int32 `protobuf:"varint,4,opt,name=credit_card_expiration_month,json=creditCardExpirationMonth,proto3" json:"credit_card_expiration_month,omitempty"` } func (x *CreditCardInfo) Reset() { *x = CreditCardInfo{} if protoimpl.UnsafeEnabled { mi := &file_demo_proto_msgTypes[21] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *CreditCardInfo) String() string { return protoimpl.X.MessageStringOf(x) } func (*CreditCardInfo) ProtoMessage() {} func (x *CreditCardInfo) ProtoReflect() protoreflect.Message { mi := &file_demo_proto_msgTypes[21] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use CreditCardInfo.ProtoReflect.Descriptor instead. func (*CreditCardInfo) Descriptor() ([]byte, []int) { return file_demo_proto_rawDescGZIP(), []int{21} } func (x *CreditCardInfo) GetCreditCardNumber() string { if x != nil { return x.CreditCardNumber } return "" } func (x *CreditCardInfo) GetCreditCardCvv() int32 { if x != nil { return x.CreditCardCvv } return 0 } func (x *CreditCardInfo) GetCreditCardExpirationYear() int32 { if x != nil { return x.CreditCardExpirationYear } return 0 } func (x *CreditCardInfo) GetCreditCardExpirationMonth() int32 { if x != nil { return x.CreditCardExpirationMonth } return 0 } type ChargeRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Amount *Money `protobuf:"bytes,1,opt,name=amount,proto3" json:"amount,omitempty"` CreditCard *CreditCardInfo `protobuf:"bytes,2,opt,name=credit_card,json=creditCard,proto3" json:"credit_card,omitempty"` } func (x *ChargeRequest) Reset() { *x = ChargeRequest{} if protoimpl.UnsafeEnabled { mi := &file_demo_proto_msgTypes[22] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *ChargeRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*ChargeRequest) ProtoMessage() {} func (x *ChargeRequest) ProtoReflect() protoreflect.Message { mi := &file_demo_proto_msgTypes[22] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use ChargeRequest.ProtoReflect.Descriptor instead. func (*ChargeRequest) Descriptor() ([]byte, []int) { return file_demo_proto_rawDescGZIP(), []int{22} } func (x *ChargeRequest) GetAmount() *Money { if x != nil { return x.Amount } return nil } func (x *ChargeRequest) GetCreditCard() *CreditCardInfo { if x != nil { return x.CreditCard } return nil } type ChargeResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields TransactionId string `protobuf:"bytes,1,opt,name=transaction_id,json=transactionId,proto3" json:"transaction_id,omitempty"` } func (x *ChargeResponse) Reset() { *x = ChargeResponse{} if protoimpl.UnsafeEnabled { mi := &file_demo_proto_msgTypes[23] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *ChargeResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*ChargeResponse) ProtoMessage() {} func (x *ChargeResponse) ProtoReflect() protoreflect.Message { mi := &file_demo_proto_msgTypes[23] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use ChargeResponse.ProtoReflect.Descriptor instead. func (*ChargeResponse) Descriptor() ([]byte, []int) { return file_demo_proto_rawDescGZIP(), []int{23} } func (x *ChargeResponse) GetTransactionId() string { if x != nil { return x.TransactionId } return "" } type OrderItem struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Item *CartItem `protobuf:"bytes,1,opt,name=item,proto3" json:"item,omitempty"` Cost *Money `protobuf:"bytes,2,opt,name=cost,proto3" json:"cost,omitempty"` } func (x *OrderItem) Reset() { *x = OrderItem{} if protoimpl.UnsafeEnabled { mi := &file_demo_proto_msgTypes[24] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *OrderItem) String() string { return protoimpl.X.MessageStringOf(x) } func (*OrderItem) ProtoMessage() {} func (x *OrderItem) ProtoReflect() protoreflect.Message { mi := &file_demo_proto_msgTypes[24] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use OrderItem.ProtoReflect.Descriptor instead. func (*OrderItem) Descriptor() ([]byte, []int) { return file_demo_proto_rawDescGZIP(), []int{24} } func (x *OrderItem) GetItem() *CartItem { if x != nil { return x.Item } return nil } func (x *OrderItem) GetCost() *Money { if x != nil { return x.Cost } return nil } type OrderResult struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields OrderId string `protobuf:"bytes,1,opt,name=order_id,json=orderId,proto3" json:"order_id,omitempty"` ShippingTrackingId string `protobuf:"bytes,2,opt,name=shipping_tracking_id,json=shippingTrackingId,proto3" json:"shipping_tracking_id,omitempty"` ShippingCost *Money `protobuf:"bytes,3,opt,name=shipping_cost,json=shippingCost,proto3" json:"shipping_cost,omitempty"` ShippingAddress *Address `protobuf:"bytes,4,opt,name=shipping_address,json=shippingAddress,proto3" json:"shipping_address,omitempty"` Items []*OrderItem `protobuf:"bytes,5,rep,name=items,proto3" json:"items,omitempty"` } func (x *OrderResult) Reset() { *x = OrderResult{} if protoimpl.UnsafeEnabled { mi := &file_demo_proto_msgTypes[25] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *OrderResult) String() string { return protoimpl.X.MessageStringOf(x) } func (*OrderResult) ProtoMessage() {} func (x *OrderResult) ProtoReflect() protoreflect.Message { mi := &file_demo_proto_msgTypes[25] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use OrderResult.ProtoReflect.Descriptor instead. func (*OrderResult) Descriptor() ([]byte, []int) { return file_demo_proto_rawDescGZIP(), []int{25} } func (x *OrderResult) GetOrderId() string { if x != nil { return x.OrderId } return "" } func (x *OrderResult) GetShippingTrackingId() string { if x != nil { return x.ShippingTrackingId } return "" } func (x *OrderResult) GetShippingCost() *Money { if x != nil { return x.ShippingCost } return nil } func (x *OrderResult) GetShippingAddress() *Address { if x != nil { return x.ShippingAddress } return nil } func (x *OrderResult) GetItems() []*OrderItem { if x != nil { return x.Items } return nil } type SendOrderConfirmationRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Email string `protobuf:"bytes,1,opt,name=email,proto3" json:"email,omitempty"` Order *OrderResult `protobuf:"bytes,2,opt,name=order,proto3" json:"order,omitempty"` } func (x *SendOrderConfirmationRequest) Reset() { *x = SendOrderConfirmationRequest{} if protoimpl.UnsafeEnabled { mi := &file_demo_proto_msgTypes[26] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *SendOrderConfirmationRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*SendOrderConfirmationRequest) ProtoMessage() {} func (x *SendOrderConfirmationRequest) ProtoReflect() protoreflect.Message { mi := &file_demo_proto_msgTypes[26] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use SendOrderConfirmationRequest.ProtoReflect.Descriptor instead. func (*SendOrderConfirmationRequest) Descriptor() ([]byte, []int) { return file_demo_proto_rawDescGZIP(), []int{26} } func (x *SendOrderConfirmationRequest) GetEmail() string { if x != nil { return x.Email } return "" } func (x *SendOrderConfirmationRequest) GetOrder() *OrderResult { if x != nil { return x.Order } return nil } type PlaceOrderRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields UserId string `protobuf:"bytes,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` UserCurrency string `protobuf:"bytes,2,opt,name=user_currency,json=userCurrency,proto3" json:"user_currency,omitempty"` Address *Address `protobuf:"bytes,3,opt,name=address,proto3" json:"address,omitempty"` Email string `protobuf:"bytes,5,opt,name=email,proto3" json:"email,omitempty"` CreditCard *CreditCardInfo `protobuf:"bytes,6,opt,name=credit_card,json=creditCard,proto3" json:"credit_card,omitempty"` } func (x *PlaceOrderRequest) Reset() { *x = PlaceOrderRequest{} if protoimpl.UnsafeEnabled { mi := &file_demo_proto_msgTypes[27] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *PlaceOrderRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*PlaceOrderRequest) ProtoMessage() {} func (x *PlaceOrderRequest) ProtoReflect() protoreflect.Message { mi := &file_demo_proto_msgTypes[27] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use PlaceOrderRequest.ProtoReflect.Descriptor instead. func (*PlaceOrderRequest) Descriptor() ([]byte, []int) { return file_demo_proto_rawDescGZIP(), []int{27} } func (x *PlaceOrderRequest) GetUserId() string { if x != nil { return x.UserId } return "" } func (x *PlaceOrderRequest) GetUserCurrency() string { if x != nil { return x.UserCurrency } return "" } func (x *PlaceOrderRequest) GetAddress() *Address { if x != nil { return x.Address } return nil } func (x *PlaceOrderRequest) GetEmail() string { if x != nil { return x.Email } return "" } func (x *PlaceOrderRequest) GetCreditCard() *CreditCardInfo { if x != nil { return x.CreditCard } return nil } type PlaceOrderResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Order *OrderResult `protobuf:"bytes,1,opt,name=order,proto3" json:"order,omitempty"` } func (x *PlaceOrderResponse) Reset() { *x = PlaceOrderResponse{} if protoimpl.UnsafeEnabled { mi := &file_demo_proto_msgTypes[28] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *PlaceOrderResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*PlaceOrderResponse) ProtoMessage() {} func (x *PlaceOrderResponse) ProtoReflect() protoreflect.Message { mi := &file_demo_proto_msgTypes[28] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use PlaceOrderResponse.ProtoReflect.Descriptor instead. func (*PlaceOrderResponse) Descriptor() ([]byte, []int) { return file_demo_proto_rawDescGZIP(), []int{28} } func (x *PlaceOrderResponse) GetOrder() *OrderResult { if x != nil { return x.Order } return nil } type AdRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // List of important key words from the current page describing the context. ContextKeys []string `protobuf:"bytes,1,rep,name=context_keys,json=contextKeys,proto3" json:"context_keys,omitempty"` } func (x *AdRequest) Reset() { *x = AdRequest{} if protoimpl.UnsafeEnabled { mi := &file_demo_proto_msgTypes[29] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *AdRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*AdRequest) ProtoMessage() {} func (x *AdRequest) ProtoReflect() protoreflect.Message { mi := &file_demo_proto_msgTypes[29] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use AdRequest.ProtoReflect.Descriptor instead. func (*AdRequest) Descriptor() ([]byte, []int) { return file_demo_proto_rawDescGZIP(), []int{29} } func (x *AdRequest) GetContextKeys() []string { if x != nil { return x.ContextKeys } return nil } type AdResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Ads []*Ad `protobuf:"bytes,1,rep,name=ads,proto3" json:"ads,omitempty"` } func (x *AdResponse) Reset() { *x = AdResponse{} if protoimpl.UnsafeEnabled { mi := &file_demo_proto_msgTypes[30] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *AdResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*AdResponse) ProtoMessage() {} func (x *AdResponse) ProtoReflect() protoreflect.Message { mi := &file_demo_proto_msgTypes[30] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use AdResponse.ProtoReflect.Descriptor instead. func (*AdResponse) Descriptor() ([]byte, []int) { return file_demo_proto_rawDescGZIP(), []int{30} } func (x *AdResponse) GetAds() []*Ad { if x != nil { return x.Ads } return nil } type Ad struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // url to redirect to when an ad is clicked. RedirectUrl string `protobuf:"bytes,1,opt,name=redirect_url,json=redirectUrl,proto3" json:"redirect_url,omitempty"` // short advertisement text to display. Text string `protobuf:"bytes,2,opt,name=text,proto3" json:"text,omitempty"` } func (x *Ad) Reset() { *x = Ad{} if protoimpl.UnsafeEnabled { mi := &file_demo_proto_msgTypes[31] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Ad) String() string { return protoimpl.X.MessageStringOf(x) } func (*Ad) ProtoMessage() {} func (x *Ad) ProtoReflect() protoreflect.Message { mi := &file_demo_proto_msgTypes[31] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use Ad.ProtoReflect.Descriptor instead. func (*Ad) Descriptor() ([]byte, []int) { return file_demo_proto_rawDescGZIP(), []int{31} } func (x *Ad) GetRedirectUrl() string { if x != nil { return x.RedirectUrl } return "" } func (x *Ad) GetText() string { if x != nil { return x.Text } return "" } var File_demo_proto protoreflect.FileDescriptor var file_demo_proto_rawDesc = []byte{ 0x0a, 0x0a, 0x64, 0x65, 0x6d, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0b, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x22, 0x45, 0x0a, 0x08, 0x43, 0x61, 0x72, 0x74, 0x49, 0x74, 0x65, 0x6d, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x71, 0x75, 0x61, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x71, 0x75, 0x61, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x22, 0x54, 0x0a, 0x0e, 0x41, 0x64, 0x64, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x29, 0x0a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x43, 0x61, 0x72, 0x74, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x22, 0x2b, 0x0a, 0x10, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x43, 0x61, 0x72, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0x29, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x43, 0x61, 0x72, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0x4c, 0x0a, 0x04, 0x43, 0x61, 0x72, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x2b, 0x0a, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x43, 0x61, 0x72, 0x74, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x22, 0x07, 0x0a, 0x05, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x56, 0x0a, 0x1a, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x49, 0x64, 0x73, 0x22, 0x3e, 0x0a, 0x1b, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x49, 0x64, 0x73, 0x22, 0xba, 0x01, 0x0a, 0x07, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x69, 0x63, 0x74, 0x75, 0x72, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x70, 0x69, 0x63, 0x74, 0x75, 0x72, 0x65, 0x12, 0x2f, 0x0a, 0x09, 0x70, 0x72, 0x69, 0x63, 0x65, 0x5f, 0x75, 0x73, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x4d, 0x6f, 0x6e, 0x65, 0x79, 0x52, 0x08, 0x70, 0x72, 0x69, 0x63, 0x65, 0x55, 0x73, 0x64, 0x12, 0x1e, 0x0a, 0x0a, 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x69, 0x65, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x69, 0x65, 0x73, 0x22, 0x48, 0x0a, 0x14, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x30, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x73, 0x22, 0x23, 0x0a, 0x11, 0x47, 0x65, 0x74, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x22, 0x2d, 0x0a, 0x15, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x22, 0x48, 0x0a, 0x16, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2e, 0x0a, 0x07, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x52, 0x07, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x22, 0x6e, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x51, 0x75, 0x6f, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x2b, 0x0a, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x43, 0x61, 0x72, 0x74, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x22, 0x41, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x51, 0x75, 0x6f, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2d, 0x0a, 0x08, 0x63, 0x6f, 0x73, 0x74, 0x5f, 0x75, 0x73, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x4d, 0x6f, 0x6e, 0x65, 0x79, 0x52, 0x07, 0x63, 0x6f, 0x73, 0x74, 0x55, 0x73, 0x64, 0x22, 0x6f, 0x0a, 0x10, 0x53, 0x68, 0x69, 0x70, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x2b, 0x0a, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x43, 0x61, 0x72, 0x74, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x22, 0x34, 0x0a, 0x11, 0x53, 0x68, 0x69, 0x70, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x74, 0x72, 0x61, 0x63, 0x6b, 0x69, 0x6e, 0x67, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x74, 0x72, 0x61, 0x63, 0x6b, 0x69, 0x6e, 0x67, 0x49, 0x64, 0x22, 0x8f, 0x01, 0x0a, 0x07, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x25, 0x0a, 0x0e, 0x73, 0x74, 0x72, 0x65, 0x65, 0x74, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x73, 0x74, 0x72, 0x65, 0x65, 0x74, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x63, 0x69, 0x74, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x63, 0x69, 0x74, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x19, 0x0a, 0x08, 0x7a, 0x69, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x07, 0x7a, 0x69, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x22, 0x58, 0x0a, 0x05, 0x4d, 0x6f, 0x6e, 0x65, 0x79, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x75, 0x6e, 0x69, 0x74, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x75, 0x6e, 0x69, 0x74, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x6e, 0x61, 0x6e, 0x6f, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x6e, 0x61, 0x6e, 0x6f, 0x73, 0x22, 0x47, 0x0a, 0x1e, 0x47, 0x65, 0x74, 0x53, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x69, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0d, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x43, 0x6f, 0x64, 0x65, 0x73, 0x22, 0x5c, 0x0a, 0x19, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x43, 0x6f, 0x6e, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x26, 0x0a, 0x04, 0x66, 0x72, 0x6f, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x4d, 0x6f, 0x6e, 0x65, 0x79, 0x52, 0x04, 0x66, 0x72, 0x6f, 0x6d, 0x12, 0x17, 0x0a, 0x07, 0x74, 0x6f, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x74, 0x6f, 0x43, 0x6f, 0x64, 0x65, 0x22, 0xe6, 0x01, 0x0a, 0x0e, 0x43, 0x72, 0x65, 0x64, 0x69, 0x74, 0x43, 0x61, 0x72, 0x64, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x2c, 0x0a, 0x12, 0x63, 0x72, 0x65, 0x64, 0x69, 0x74, 0x5f, 0x63, 0x61, 0x72, 0x64, 0x5f, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x63, 0x72, 0x65, 0x64, 0x69, 0x74, 0x43, 0x61, 0x72, 0x64, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x26, 0x0a, 0x0f, 0x63, 0x72, 0x65, 0x64, 0x69, 0x74, 0x5f, 0x63, 0x61, 0x72, 0x64, 0x5f, 0x63, 0x76, 0x76, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0d, 0x63, 0x72, 0x65, 0x64, 0x69, 0x74, 0x43, 0x61, 0x72, 0x64, 0x43, 0x76, 0x76, 0x12, 0x3d, 0x0a, 0x1b, 0x63, 0x72, 0x65, 0x64, 0x69, 0x74, 0x5f, 0x63, 0x61, 0x72, 0x64, 0x5f, 0x65, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x79, 0x65, 0x61, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x18, 0x63, 0x72, 0x65, 0x64, 0x69, 0x74, 0x43, 0x61, 0x72, 0x64, 0x45, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x59, 0x65, 0x61, 0x72, 0x12, 0x3f, 0x0a, 0x1c, 0x63, 0x72, 0x65, 0x64, 0x69, 0x74, 0x5f, 0x63, 0x61, 0x72, 0x64, 0x5f, 0x65, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6d, 0x6f, 0x6e, 0x74, 0x68, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x19, 0x63, 0x72, 0x65, 0x64, 0x69, 0x74, 0x43, 0x61, 0x72, 0x64, 0x45, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x6f, 0x6e, 0x74, 0x68, 0x22, 0x79, 0x0a, 0x0d, 0x43, 0x68, 0x61, 0x72, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2a, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x4d, 0x6f, 0x6e, 0x65, 0x79, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x3c, 0x0a, 0x0b, 0x63, 0x72, 0x65, 0x64, 0x69, 0x74, 0x5f, 0x63, 0x61, 0x72, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x43, 0x72, 0x65, 0x64, 0x69, 0x74, 0x43, 0x61, 0x72, 0x64, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x0a, 0x63, 0x72, 0x65, 0x64, 0x69, 0x74, 0x43, 0x61, 0x72, 0x64, 0x22, 0x37, 0x0a, 0x0e, 0x43, 0x68, 0x61, 0x72, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x22, 0x5e, 0x0a, 0x09, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x49, 0x74, 0x65, 0x6d, 0x12, 0x29, 0x0a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x43, 0x61, 0x72, 0x74, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x12, 0x26, 0x0a, 0x04, 0x63, 0x6f, 0x73, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x4d, 0x6f, 0x6e, 0x65, 0x79, 0x52, 0x04, 0x63, 0x6f, 0x73, 0x74, 0x22, 0x82, 0x02, 0x0a, 0x0b, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x49, 0x64, 0x12, 0x30, 0x0a, 0x14, 0x73, 0x68, 0x69, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x5f, 0x74, 0x72, 0x61, 0x63, 0x6b, 0x69, 0x6e, 0x67, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x12, 0x73, 0x68, 0x69, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x54, 0x72, 0x61, 0x63, 0x6b, 0x69, 0x6e, 0x67, 0x49, 0x64, 0x12, 0x37, 0x0a, 0x0d, 0x73, 0x68, 0x69, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x6f, 0x73, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x4d, 0x6f, 0x6e, 0x65, 0x79, 0x52, 0x0c, 0x73, 0x68, 0x69, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x73, 0x74, 0x12, 0x3f, 0x0a, 0x10, 0x73, 0x68, 0x69, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, 0x0f, 0x73, 0x68, 0x69, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x2c, 0x0a, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x22, 0x64, 0x0a, 0x1c, 0x53, 0x65, 0x6e, 0x64, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x12, 0x2e, 0x0a, 0x05, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x05, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x22, 0xd5, 0x01, 0x0a, 0x11, 0x50, 0x6c, 0x61, 0x63, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x75, 0x73, 0x65, 0x72, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x12, 0x2e, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x12, 0x3c, 0x0a, 0x0b, 0x63, 0x72, 0x65, 0x64, 0x69, 0x74, 0x5f, 0x63, 0x61, 0x72, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x43, 0x72, 0x65, 0x64, 0x69, 0x74, 0x43, 0x61, 0x72, 0x64, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x0a, 0x63, 0x72, 0x65, 0x64, 0x69, 0x74, 0x43, 0x61, 0x72, 0x64, 0x22, 0x44, 0x0a, 0x12, 0x50, 0x6c, 0x61, 0x63, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2e, 0x0a, 0x05, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x05, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x22, 0x2e, 0x0a, 0x09, 0x41, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x5f, 0x6b, 0x65, 0x79, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x4b, 0x65, 0x79, 0x73, 0x22, 0x2f, 0x0a, 0x0a, 0x41, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x21, 0x0a, 0x03, 0x61, 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x41, 0x64, 0x52, 0x03, 0x61, 0x64, 0x73, 0x22, 0x3b, 0x0a, 0x02, 0x41, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x72, 0x65, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x72, 0x65, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x55, 0x72, 0x6c, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x65, 0x78, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x65, 0x78, 0x74, 0x32, 0xca, 0x01, 0x0a, 0x0b, 0x43, 0x61, 0x72, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x3c, 0x0a, 0x07, 0x41, 0x64, 0x64, 0x49, 0x74, 0x65, 0x6d, 0x12, 0x1b, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x41, 0x64, 0x64, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x3b, 0x0a, 0x07, 0x47, 0x65, 0x74, 0x43, 0x61, 0x72, 0x74, 0x12, 0x1b, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x61, 0x72, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x11, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x43, 0x61, 0x72, 0x74, 0x22, 0x00, 0x12, 0x40, 0x0a, 0x09, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x43, 0x61, 0x72, 0x74, 0x12, 0x1d, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x43, 0x61, 0x72, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x32, 0x83, 0x01, 0x0a, 0x15, 0x52, 0x65, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x6a, 0x0a, 0x13, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x27, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x32, 0x83, 0x02, 0x0a, 0x15, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x43, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x47, 0x0a, 0x0c, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x73, 0x12, 0x12, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x21, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x44, 0x0a, 0x0a, 0x47, 0x65, 0x74, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x12, 0x1e, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x47, 0x65, 0x74, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x22, 0x00, 0x12, 0x5b, 0x0a, 0x0e, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x73, 0x12, 0x22, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x32, 0xaa, 0x01, 0x0a, 0x0f, 0x53, 0x68, 0x69, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x49, 0x0a, 0x08, 0x47, 0x65, 0x74, 0x51, 0x75, 0x6f, 0x74, 0x65, 0x12, 0x1c, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x47, 0x65, 0x74, 0x51, 0x75, 0x6f, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x47, 0x65, 0x74, 0x51, 0x75, 0x6f, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x4c, 0x0a, 0x09, 0x53, 0x68, 0x69, 0x70, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x1d, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x53, 0x68, 0x69, 0x70, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x53, 0x68, 0x69, 0x70, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x32, 0xb7, 0x01, 0x0a, 0x0f, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x5b, 0x0a, 0x16, 0x47, 0x65, 0x74, 0x53, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x69, 0x65, 0x73, 0x12, 0x12, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x2b, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x69, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x47, 0x0a, 0x07, 0x43, 0x6f, 0x6e, 0x76, 0x65, 0x72, 0x74, 0x12, 0x26, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x43, 0x6f, 0x6e, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x4d, 0x6f, 0x6e, 0x65, 0x79, 0x22, 0x00, 0x32, 0x55, 0x0a, 0x0e, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x43, 0x0a, 0x06, 0x43, 0x68, 0x61, 0x72, 0x67, 0x65, 0x12, 0x1a, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x43, 0x68, 0x61, 0x72, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x43, 0x68, 0x61, 0x72, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x32, 0x68, 0x0a, 0x0c, 0x45, 0x6d, 0x61, 0x69, 0x6c, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x58, 0x0a, 0x15, 0x53, 0x65, 0x6e, 0x64, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x29, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x32, 0x62, 0x0a, 0x0f, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x6f, 0x75, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x4f, 0x0a, 0x0a, 0x50, 0x6c, 0x61, 0x63, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x1e, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x50, 0x6c, 0x61, 0x63, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x50, 0x6c, 0x61, 0x63, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x32, 0x48, 0x0a, 0x09, 0x41, 0x64, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x3b, 0x0a, 0x06, 0x47, 0x65, 0x74, 0x41, 0x64, 0x73, 0x12, 0x16, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x41, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x41, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x40, 0x5a, 0x3e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x47, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x43, 0x6c, 0x6f, 0x75, 0x64, 0x50, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x2f, 0x6d, 0x69, 0x73, 0x63, 0x72, 0x6f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2d, 0x64, 0x65, 0x6d, 0x6f, 0x2f, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( file_demo_proto_rawDescOnce sync.Once file_demo_proto_rawDescData = file_demo_proto_rawDesc ) func file_demo_proto_rawDescGZIP() []byte { file_demo_proto_rawDescOnce.Do(func() { file_demo_proto_rawDescData = protoimpl.X.CompressGZIP(file_demo_proto_rawDescData) }) return file_demo_proto_rawDescData } var file_demo_proto_msgTypes = make([]protoimpl.MessageInfo, 32) var file_demo_proto_goTypes = []any{ (*CartItem)(nil), // 0: hipstershop.CartItem (*AddItemRequest)(nil), // 1: hipstershop.AddItemRequest (*EmptyCartRequest)(nil), // 2: hipstershop.EmptyCartRequest (*GetCartRequest)(nil), // 3: hipstershop.GetCartRequest (*Cart)(nil), // 4: hipstershop.Cart (*Empty)(nil), // 5: hipstershop.Empty (*ListRecommendationsRequest)(nil), // 6: hipstershop.ListRecommendationsRequest (*ListRecommendationsResponse)(nil), // 7: hipstershop.ListRecommendationsResponse (*Product)(nil), // 8: hipstershop.Product (*ListProductsResponse)(nil), // 9: hipstershop.ListProductsResponse (*GetProductRequest)(nil), // 10: hipstershop.GetProductRequest (*SearchProductsRequest)(nil), // 11: hipstershop.SearchProductsRequest (*SearchProductsResponse)(nil), // 12: hipstershop.SearchProductsResponse (*GetQuoteRequest)(nil), // 13: hipstershop.GetQuoteRequest (*GetQuoteResponse)(nil), // 14: hipstershop.GetQuoteResponse (*ShipOrderRequest)(nil), // 15: hipstershop.ShipOrderRequest (*ShipOrderResponse)(nil), // 16: hipstershop.ShipOrderResponse (*Address)(nil), // 17: hipstershop.Address (*Money)(nil), // 18: hipstershop.Money (*GetSupportedCurrenciesResponse)(nil), // 19: hipstershop.GetSupportedCurrenciesResponse (*CurrencyConversionRequest)(nil), // 20: hipstershop.CurrencyConversionRequest (*CreditCardInfo)(nil), // 21: hipstershop.CreditCardInfo (*ChargeRequest)(nil), // 22: hipstershop.ChargeRequest (*ChargeResponse)(nil), // 23: hipstershop.ChargeResponse (*OrderItem)(nil), // 24: hipstershop.OrderItem (*OrderResult)(nil), // 25: hipstershop.OrderResult (*SendOrderConfirmationRequest)(nil), // 26: hipstershop.SendOrderConfirmationRequest (*PlaceOrderRequest)(nil), // 27: hipstershop.PlaceOrderRequest (*PlaceOrderResponse)(nil), // 28: hipstershop.PlaceOrderResponse (*AdRequest)(nil), // 29: hipstershop.AdRequest (*AdResponse)(nil), // 30: hipstershop.AdResponse (*Ad)(nil), // 31: hipstershop.Ad } var file_demo_proto_depIdxs = []int32{ 0, // 0: hipstershop.AddItemRequest.item:type_name -> hipstershop.CartItem 0, // 1: hipstershop.Cart.items:type_name -> hipstershop.CartItem 18, // 2: hipstershop.Product.price_usd:type_name -> hipstershop.Money 8, // 3: hipstershop.ListProductsResponse.products:type_name -> hipstershop.Product 8, // 4: hipstershop.SearchProductsResponse.results:type_name -> hipstershop.Product 17, // 5: hipstershop.GetQuoteRequest.address:type_name -> hipstershop.Address 0, // 6: hipstershop.GetQuoteRequest.items:type_name -> hipstershop.CartItem 18, // 7: hipstershop.GetQuoteResponse.cost_usd:type_name -> hipstershop.Money 17, // 8: hipstershop.ShipOrderRequest.address:type_name -> hipstershop.Address 0, // 9: hipstershop.ShipOrderRequest.items:type_name -> hipstershop.CartItem 18, // 10: hipstershop.CurrencyConversionRequest.from:type_name -> hipstershop.Money 18, // 11: hipstershop.ChargeRequest.amount:type_name -> hipstershop.Money 21, // 12: hipstershop.ChargeRequest.credit_card:type_name -> hipstershop.CreditCardInfo 0, // 13: hipstershop.OrderItem.item:type_name -> hipstershop.CartItem 18, // 14: hipstershop.OrderItem.cost:type_name -> hipstershop.Money 18, // 15: hipstershop.OrderResult.shipping_cost:type_name -> hipstershop.Money 17, // 16: hipstershop.OrderResult.shipping_address:type_name -> hipstershop.Address 24, // 17: hipstershop.OrderResult.items:type_name -> hipstershop.OrderItem 25, // 18: hipstershop.SendOrderConfirmationRequest.order:type_name -> hipstershop.OrderResult 17, // 19: hipstershop.PlaceOrderRequest.address:type_name -> hipstershop.Address 21, // 20: hipstershop.PlaceOrderRequest.credit_card:type_name -> hipstershop.CreditCardInfo 25, // 21: hipstershop.PlaceOrderResponse.order:type_name -> hipstershop.OrderResult 31, // 22: hipstershop.AdResponse.ads:type_name -> hipstershop.Ad 1, // 23: hipstershop.CartService.AddItem:input_type -> hipstershop.AddItemRequest 3, // 24: hipstershop.CartService.GetCart:input_type -> hipstershop.GetCartRequest 2, // 25: hipstershop.CartService.EmptyCart:input_type -> hipstershop.EmptyCartRequest 6, // 26: hipstershop.RecommendationService.ListRecommendations:input_type -> hipstershop.ListRecommendationsRequest 5, // 27: hipstershop.ProductCatalogService.ListProducts:input_type -> hipstershop.Empty 10, // 28: hipstershop.ProductCatalogService.GetProduct:input_type -> hipstershop.GetProductRequest 11, // 29: hipstershop.ProductCatalogService.SearchProducts:input_type -> hipstershop.SearchProductsRequest 13, // 30: hipstershop.ShippingService.GetQuote:input_type -> hipstershop.GetQuoteRequest 15, // 31: hipstershop.ShippingService.ShipOrder:input_type -> hipstershop.ShipOrderRequest 5, // 32: hipstershop.CurrencyService.GetSupportedCurrencies:input_type -> hipstershop.Empty 20, // 33: hipstershop.CurrencyService.Convert:input_type -> hipstershop.CurrencyConversionRequest 22, // 34: hipstershop.PaymentService.Charge:input_type -> hipstershop.ChargeRequest 26, // 35: hipstershop.EmailService.SendOrderConfirmation:input_type -> hipstershop.SendOrderConfirmationRequest 27, // 36: hipstershop.CheckoutService.PlaceOrder:input_type -> hipstershop.PlaceOrderRequest 29, // 37: hipstershop.AdService.GetAds:input_type -> hipstershop.AdRequest 5, // 38: hipstershop.CartService.AddItem:output_type -> hipstershop.Empty 4, // 39: hipstershop.CartService.GetCart:output_type -> hipstershop.Cart 5, // 40: hipstershop.CartService.EmptyCart:output_type -> hipstershop.Empty 7, // 41: hipstershop.RecommendationService.ListRecommendations:output_type -> hipstershop.ListRecommendationsResponse 9, // 42: hipstershop.ProductCatalogService.ListProducts:output_type -> hipstershop.ListProductsResponse 8, // 43: hipstershop.ProductCatalogService.GetProduct:output_type -> hipstershop.Product 12, // 44: hipstershop.ProductCatalogService.SearchProducts:output_type -> hipstershop.SearchProductsResponse 14, // 45: hipstershop.ShippingService.GetQuote:output_type -> hipstershop.GetQuoteResponse 16, // 46: hipstershop.ShippingService.ShipOrder:output_type -> hipstershop.ShipOrderResponse 19, // 47: hipstershop.CurrencyService.GetSupportedCurrencies:output_type -> hipstershop.GetSupportedCurrenciesResponse 18, // 48: hipstershop.CurrencyService.Convert:output_type -> hipstershop.Money 23, // 49: hipstershop.PaymentService.Charge:output_type -> hipstershop.ChargeResponse 5, // 50: hipstershop.EmailService.SendOrderConfirmation:output_type -> hipstershop.Empty 28, // 51: hipstershop.CheckoutService.PlaceOrder:output_type -> hipstershop.PlaceOrderResponse 30, // 52: hipstershop.AdService.GetAds:output_type -> hipstershop.AdResponse 38, // [38:53] is the sub-list for method output_type 23, // [23:38] is the sub-list for method input_type 23, // [23:23] is the sub-list for extension type_name 23, // [23:23] is the sub-list for extension extendee 0, // [0:23] is the sub-list for field type_name } func init() { file_demo_proto_init() } func file_demo_proto_init() { if File_demo_proto != nil { return } if !protoimpl.UnsafeEnabled { file_demo_proto_msgTypes[0].Exporter = func(v any, i int) any { switch v := v.(*CartItem); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_demo_proto_msgTypes[1].Exporter = func(v any, i int) any { switch v := v.(*AddItemRequest); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_demo_proto_msgTypes[2].Exporter = func(v any, i int) any { switch v := v.(*EmptyCartRequest); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_demo_proto_msgTypes[3].Exporter = func(v any, i int) any { switch v := v.(*GetCartRequest); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_demo_proto_msgTypes[4].Exporter = func(v any, i int) any { switch v := v.(*Cart); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_demo_proto_msgTypes[5].Exporter = func(v any, i int) any { switch v := v.(*Empty); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_demo_proto_msgTypes[6].Exporter = func(v any, i int) any { switch v := v.(*ListRecommendationsRequest); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_demo_proto_msgTypes[7].Exporter = func(v any, i int) any { switch v := v.(*ListRecommendationsResponse); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_demo_proto_msgTypes[8].Exporter = func(v any, i int) any { switch v := v.(*Product); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_demo_proto_msgTypes[9].Exporter = func(v any, i int) any { switch v := v.(*ListProductsResponse); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_demo_proto_msgTypes[10].Exporter = func(v any, i int) any { switch v := v.(*GetProductRequest); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_demo_proto_msgTypes[11].Exporter = func(v any, i int) any { switch v := v.(*SearchProductsRequest); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_demo_proto_msgTypes[12].Exporter = func(v any, i int) any { switch v := v.(*SearchProductsResponse); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_demo_proto_msgTypes[13].Exporter = func(v any, i int) any { switch v := v.(*GetQuoteRequest); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_demo_proto_msgTypes[14].Exporter = func(v any, i int) any { switch v := v.(*GetQuoteResponse); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_demo_proto_msgTypes[15].Exporter = func(v any, i int) any { switch v := v.(*ShipOrderRequest); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_demo_proto_msgTypes[16].Exporter = func(v any, i int) any { switch v := v.(*ShipOrderResponse); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_demo_proto_msgTypes[17].Exporter = func(v any, i int) any { switch v := v.(*Address); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_demo_proto_msgTypes[18].Exporter = func(v any, i int) any { switch v := v.(*Money); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_demo_proto_msgTypes[19].Exporter = func(v any, i int) any { switch v := v.(*GetSupportedCurrenciesResponse); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_demo_proto_msgTypes[20].Exporter = func(v any, i int) any { switch v := v.(*CurrencyConversionRequest); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_demo_proto_msgTypes[21].Exporter = func(v any, i int) any { switch v := v.(*CreditCardInfo); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_demo_proto_msgTypes[22].Exporter = func(v any, i int) any { switch v := v.(*ChargeRequest); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_demo_proto_msgTypes[23].Exporter = func(v any, i int) any { switch v := v.(*ChargeResponse); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_demo_proto_msgTypes[24].Exporter = func(v any, i int) any { switch v := v.(*OrderItem); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_demo_proto_msgTypes[25].Exporter = func(v any, i int) any { switch v := v.(*OrderResult); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_demo_proto_msgTypes[26].Exporter = func(v any, i int) any { switch v := v.(*SendOrderConfirmationRequest); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_demo_proto_msgTypes[27].Exporter = func(v any, i int) any { switch v := v.(*PlaceOrderRequest); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_demo_proto_msgTypes[28].Exporter = func(v any, i int) any { switch v := v.(*PlaceOrderResponse); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_demo_proto_msgTypes[29].Exporter = func(v any, i int) any { switch v := v.(*AdRequest); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_demo_proto_msgTypes[30].Exporter = func(v any, i int) any { switch v := v.(*AdResponse); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_demo_proto_msgTypes[31].Exporter = func(v any, i int) any { switch v := v.(*Ad); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_demo_proto_rawDesc, NumEnums: 0, NumMessages: 32, NumExtensions: 0, NumServices: 9, }, GoTypes: file_demo_proto_goTypes, DependencyIndexes: file_demo_proto_depIdxs, MessageInfos: file_demo_proto_msgTypes, }.Build() File_demo_proto = out.File file_demo_proto_rawDesc = nil file_demo_proto_goTypes = nil file_demo_proto_depIdxs = nil } ================================================ FILE: src/checkoutservice/genproto/demo_grpc.pb.go ================================================ // Copyright 2020 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. // Code generated by protoc-gen-go-grpc. DO NOT EDIT. // versions: // - protoc-gen-go-grpc v1.5.1 // - protoc v3.6.1 // source: demo.proto package hipstershop import ( context "context" grpc "google.golang.org/grpc" codes "google.golang.org/grpc/codes" status "google.golang.org/grpc/status" ) // This is a compile-time assertion to ensure that this generated file // is compatible with the grpc package it is being compiled against. // Requires gRPC-Go v1.64.0 or later. const _ = grpc.SupportPackageIsVersion9 const ( CartService_AddItem_FullMethodName = "/hipstershop.CartService/AddItem" CartService_GetCart_FullMethodName = "/hipstershop.CartService/GetCart" CartService_EmptyCart_FullMethodName = "/hipstershop.CartService/EmptyCart" ) // CartServiceClient is the client API for CartService service. // // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. type CartServiceClient interface { AddItem(ctx context.Context, in *AddItemRequest, opts ...grpc.CallOption) (*Empty, error) GetCart(ctx context.Context, in *GetCartRequest, opts ...grpc.CallOption) (*Cart, error) EmptyCart(ctx context.Context, in *EmptyCartRequest, opts ...grpc.CallOption) (*Empty, error) } type cartServiceClient struct { cc grpc.ClientConnInterface } func NewCartServiceClient(cc grpc.ClientConnInterface) CartServiceClient { return &cartServiceClient{cc} } func (c *cartServiceClient) AddItem(ctx context.Context, in *AddItemRequest, opts ...grpc.CallOption) (*Empty, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(Empty) err := c.cc.Invoke(ctx, CartService_AddItem_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } return out, nil } func (c *cartServiceClient) GetCart(ctx context.Context, in *GetCartRequest, opts ...grpc.CallOption) (*Cart, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(Cart) err := c.cc.Invoke(ctx, CartService_GetCart_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } return out, nil } func (c *cartServiceClient) EmptyCart(ctx context.Context, in *EmptyCartRequest, opts ...grpc.CallOption) (*Empty, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(Empty) err := c.cc.Invoke(ctx, CartService_EmptyCart_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } return out, nil } // CartServiceServer is the server API for CartService service. // All implementations must embed UnimplementedCartServiceServer // for forward compatibility. type CartServiceServer interface { AddItem(context.Context, *AddItemRequest) (*Empty, error) GetCart(context.Context, *GetCartRequest) (*Cart, error) EmptyCart(context.Context, *EmptyCartRequest) (*Empty, error) mustEmbedUnimplementedCartServiceServer() } // UnimplementedCartServiceServer must be embedded to have // forward compatible implementations. // // NOTE: this should be embedded by value instead of pointer to avoid a nil // pointer dereference when methods are called. type UnimplementedCartServiceServer struct{} func (UnimplementedCartServiceServer) AddItem(context.Context, *AddItemRequest) (*Empty, error) { return nil, status.Errorf(codes.Unimplemented, "method AddItem not implemented") } func (UnimplementedCartServiceServer) GetCart(context.Context, *GetCartRequest) (*Cart, error) { return nil, status.Errorf(codes.Unimplemented, "method GetCart not implemented") } func (UnimplementedCartServiceServer) EmptyCart(context.Context, *EmptyCartRequest) (*Empty, error) { return nil, status.Errorf(codes.Unimplemented, "method EmptyCart not implemented") } func (UnimplementedCartServiceServer) mustEmbedUnimplementedCartServiceServer() {} func (UnimplementedCartServiceServer) testEmbeddedByValue() {} // UnsafeCartServiceServer may be embedded to opt out of forward compatibility for this service. // Use of this interface is not recommended, as added methods to CartServiceServer will // result in compilation errors. type UnsafeCartServiceServer interface { mustEmbedUnimplementedCartServiceServer() } func RegisterCartServiceServer(s grpc.ServiceRegistrar, srv CartServiceServer) { // If the following call pancis, it indicates UnimplementedCartServiceServer was // embedded by pointer and is nil. This will cause panics if an // unimplemented method is ever invoked, so we test this at initialization // time to prevent it from happening at runtime later due to I/O. if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { t.testEmbeddedByValue() } s.RegisterService(&CartService_ServiceDesc, srv) } func _CartService_AddItem_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(AddItemRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(CartServiceServer).AddItem(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: CartService_AddItem_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(CartServiceServer).AddItem(ctx, req.(*AddItemRequest)) } return interceptor(ctx, in, info, handler) } func _CartService_GetCart_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(GetCartRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(CartServiceServer).GetCart(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: CartService_GetCart_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(CartServiceServer).GetCart(ctx, req.(*GetCartRequest)) } return interceptor(ctx, in, info, handler) } func _CartService_EmptyCart_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(EmptyCartRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(CartServiceServer).EmptyCart(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: CartService_EmptyCart_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(CartServiceServer).EmptyCart(ctx, req.(*EmptyCartRequest)) } return interceptor(ctx, in, info, handler) } // CartService_ServiceDesc is the grpc.ServiceDesc for CartService service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) var CartService_ServiceDesc = grpc.ServiceDesc{ ServiceName: "hipstershop.CartService", HandlerType: (*CartServiceServer)(nil), Methods: []grpc.MethodDesc{ { MethodName: "AddItem", Handler: _CartService_AddItem_Handler, }, { MethodName: "GetCart", Handler: _CartService_GetCart_Handler, }, { MethodName: "EmptyCart", Handler: _CartService_EmptyCart_Handler, }, }, Streams: []grpc.StreamDesc{}, Metadata: "demo.proto", } const ( RecommendationService_ListRecommendations_FullMethodName = "/hipstershop.RecommendationService/ListRecommendations" ) // RecommendationServiceClient is the client API for RecommendationService service. // // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. type RecommendationServiceClient interface { ListRecommendations(ctx context.Context, in *ListRecommendationsRequest, opts ...grpc.CallOption) (*ListRecommendationsResponse, error) } type recommendationServiceClient struct { cc grpc.ClientConnInterface } func NewRecommendationServiceClient(cc grpc.ClientConnInterface) RecommendationServiceClient { return &recommendationServiceClient{cc} } func (c *recommendationServiceClient) ListRecommendations(ctx context.Context, in *ListRecommendationsRequest, opts ...grpc.CallOption) (*ListRecommendationsResponse, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(ListRecommendationsResponse) err := c.cc.Invoke(ctx, RecommendationService_ListRecommendations_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } return out, nil } // RecommendationServiceServer is the server API for RecommendationService service. // All implementations must embed UnimplementedRecommendationServiceServer // for forward compatibility. type RecommendationServiceServer interface { ListRecommendations(context.Context, *ListRecommendationsRequest) (*ListRecommendationsResponse, error) mustEmbedUnimplementedRecommendationServiceServer() } // UnimplementedRecommendationServiceServer must be embedded to have // forward compatible implementations. // // NOTE: this should be embedded by value instead of pointer to avoid a nil // pointer dereference when methods are called. type UnimplementedRecommendationServiceServer struct{} func (UnimplementedRecommendationServiceServer) ListRecommendations(context.Context, *ListRecommendationsRequest) (*ListRecommendationsResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method ListRecommendations not implemented") } func (UnimplementedRecommendationServiceServer) mustEmbedUnimplementedRecommendationServiceServer() {} func (UnimplementedRecommendationServiceServer) testEmbeddedByValue() {} // UnsafeRecommendationServiceServer may be embedded to opt out of forward compatibility for this service. // Use of this interface is not recommended, as added methods to RecommendationServiceServer will // result in compilation errors. type UnsafeRecommendationServiceServer interface { mustEmbedUnimplementedRecommendationServiceServer() } func RegisterRecommendationServiceServer(s grpc.ServiceRegistrar, srv RecommendationServiceServer) { // If the following call pancis, it indicates UnimplementedRecommendationServiceServer was // embedded by pointer and is nil. This will cause panics if an // unimplemented method is ever invoked, so we test this at initialization // time to prevent it from happening at runtime later due to I/O. if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { t.testEmbeddedByValue() } s.RegisterService(&RecommendationService_ServiceDesc, srv) } func _RecommendationService_ListRecommendations_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(ListRecommendationsRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(RecommendationServiceServer).ListRecommendations(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: RecommendationService_ListRecommendations_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(RecommendationServiceServer).ListRecommendations(ctx, req.(*ListRecommendationsRequest)) } return interceptor(ctx, in, info, handler) } // RecommendationService_ServiceDesc is the grpc.ServiceDesc for RecommendationService service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) var RecommendationService_ServiceDesc = grpc.ServiceDesc{ ServiceName: "hipstershop.RecommendationService", HandlerType: (*RecommendationServiceServer)(nil), Methods: []grpc.MethodDesc{ { MethodName: "ListRecommendations", Handler: _RecommendationService_ListRecommendations_Handler, }, }, Streams: []grpc.StreamDesc{}, Metadata: "demo.proto", } const ( ProductCatalogService_ListProducts_FullMethodName = "/hipstershop.ProductCatalogService/ListProducts" ProductCatalogService_GetProduct_FullMethodName = "/hipstershop.ProductCatalogService/GetProduct" ProductCatalogService_SearchProducts_FullMethodName = "/hipstershop.ProductCatalogService/SearchProducts" ) // ProductCatalogServiceClient is the client API for ProductCatalogService service. // // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. type ProductCatalogServiceClient interface { ListProducts(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*ListProductsResponse, error) GetProduct(ctx context.Context, in *GetProductRequest, opts ...grpc.CallOption) (*Product, error) SearchProducts(ctx context.Context, in *SearchProductsRequest, opts ...grpc.CallOption) (*SearchProductsResponse, error) } type productCatalogServiceClient struct { cc grpc.ClientConnInterface } func NewProductCatalogServiceClient(cc grpc.ClientConnInterface) ProductCatalogServiceClient { return &productCatalogServiceClient{cc} } func (c *productCatalogServiceClient) ListProducts(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*ListProductsResponse, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(ListProductsResponse) err := c.cc.Invoke(ctx, ProductCatalogService_ListProducts_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } return out, nil } func (c *productCatalogServiceClient) GetProduct(ctx context.Context, in *GetProductRequest, opts ...grpc.CallOption) (*Product, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(Product) err := c.cc.Invoke(ctx, ProductCatalogService_GetProduct_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } return out, nil } func (c *productCatalogServiceClient) SearchProducts(ctx context.Context, in *SearchProductsRequest, opts ...grpc.CallOption) (*SearchProductsResponse, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(SearchProductsResponse) err := c.cc.Invoke(ctx, ProductCatalogService_SearchProducts_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } return out, nil } // ProductCatalogServiceServer is the server API for ProductCatalogService service. // All implementations must embed UnimplementedProductCatalogServiceServer // for forward compatibility. type ProductCatalogServiceServer interface { ListProducts(context.Context, *Empty) (*ListProductsResponse, error) GetProduct(context.Context, *GetProductRequest) (*Product, error) SearchProducts(context.Context, *SearchProductsRequest) (*SearchProductsResponse, error) mustEmbedUnimplementedProductCatalogServiceServer() } // UnimplementedProductCatalogServiceServer must be embedded to have // forward compatible implementations. // // NOTE: this should be embedded by value instead of pointer to avoid a nil // pointer dereference when methods are called. type UnimplementedProductCatalogServiceServer struct{} func (UnimplementedProductCatalogServiceServer) ListProducts(context.Context, *Empty) (*ListProductsResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method ListProducts not implemented") } func (UnimplementedProductCatalogServiceServer) GetProduct(context.Context, *GetProductRequest) (*Product, error) { return nil, status.Errorf(codes.Unimplemented, "method GetProduct not implemented") } func (UnimplementedProductCatalogServiceServer) SearchProducts(context.Context, *SearchProductsRequest) (*SearchProductsResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method SearchProducts not implemented") } func (UnimplementedProductCatalogServiceServer) mustEmbedUnimplementedProductCatalogServiceServer() {} func (UnimplementedProductCatalogServiceServer) testEmbeddedByValue() {} // UnsafeProductCatalogServiceServer may be embedded to opt out of forward compatibility for this service. // Use of this interface is not recommended, as added methods to ProductCatalogServiceServer will // result in compilation errors. type UnsafeProductCatalogServiceServer interface { mustEmbedUnimplementedProductCatalogServiceServer() } func RegisterProductCatalogServiceServer(s grpc.ServiceRegistrar, srv ProductCatalogServiceServer) { // If the following call pancis, it indicates UnimplementedProductCatalogServiceServer was // embedded by pointer and is nil. This will cause panics if an // unimplemented method is ever invoked, so we test this at initialization // time to prevent it from happening at runtime later due to I/O. if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { t.testEmbeddedByValue() } s.RegisterService(&ProductCatalogService_ServiceDesc, srv) } func _ProductCatalogService_ListProducts_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(Empty) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(ProductCatalogServiceServer).ListProducts(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: ProductCatalogService_ListProducts_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(ProductCatalogServiceServer).ListProducts(ctx, req.(*Empty)) } return interceptor(ctx, in, info, handler) } func _ProductCatalogService_GetProduct_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(GetProductRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(ProductCatalogServiceServer).GetProduct(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: ProductCatalogService_GetProduct_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(ProductCatalogServiceServer).GetProduct(ctx, req.(*GetProductRequest)) } return interceptor(ctx, in, info, handler) } func _ProductCatalogService_SearchProducts_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(SearchProductsRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(ProductCatalogServiceServer).SearchProducts(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: ProductCatalogService_SearchProducts_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(ProductCatalogServiceServer).SearchProducts(ctx, req.(*SearchProductsRequest)) } return interceptor(ctx, in, info, handler) } // ProductCatalogService_ServiceDesc is the grpc.ServiceDesc for ProductCatalogService service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) var ProductCatalogService_ServiceDesc = grpc.ServiceDesc{ ServiceName: "hipstershop.ProductCatalogService", HandlerType: (*ProductCatalogServiceServer)(nil), Methods: []grpc.MethodDesc{ { MethodName: "ListProducts", Handler: _ProductCatalogService_ListProducts_Handler, }, { MethodName: "GetProduct", Handler: _ProductCatalogService_GetProduct_Handler, }, { MethodName: "SearchProducts", Handler: _ProductCatalogService_SearchProducts_Handler, }, }, Streams: []grpc.StreamDesc{}, Metadata: "demo.proto", } const ( ShippingService_GetQuote_FullMethodName = "/hipstershop.ShippingService/GetQuote" ShippingService_ShipOrder_FullMethodName = "/hipstershop.ShippingService/ShipOrder" ) // ShippingServiceClient is the client API for ShippingService service. // // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. type ShippingServiceClient interface { GetQuote(ctx context.Context, in *GetQuoteRequest, opts ...grpc.CallOption) (*GetQuoteResponse, error) ShipOrder(ctx context.Context, in *ShipOrderRequest, opts ...grpc.CallOption) (*ShipOrderResponse, error) } type shippingServiceClient struct { cc grpc.ClientConnInterface } func NewShippingServiceClient(cc grpc.ClientConnInterface) ShippingServiceClient { return &shippingServiceClient{cc} } func (c *shippingServiceClient) GetQuote(ctx context.Context, in *GetQuoteRequest, opts ...grpc.CallOption) (*GetQuoteResponse, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(GetQuoteResponse) err := c.cc.Invoke(ctx, ShippingService_GetQuote_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } return out, nil } func (c *shippingServiceClient) ShipOrder(ctx context.Context, in *ShipOrderRequest, opts ...grpc.CallOption) (*ShipOrderResponse, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(ShipOrderResponse) err := c.cc.Invoke(ctx, ShippingService_ShipOrder_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } return out, nil } // ShippingServiceServer is the server API for ShippingService service. // All implementations must embed UnimplementedShippingServiceServer // for forward compatibility. type ShippingServiceServer interface { GetQuote(context.Context, *GetQuoteRequest) (*GetQuoteResponse, error) ShipOrder(context.Context, *ShipOrderRequest) (*ShipOrderResponse, error) mustEmbedUnimplementedShippingServiceServer() } // UnimplementedShippingServiceServer must be embedded to have // forward compatible implementations. // // NOTE: this should be embedded by value instead of pointer to avoid a nil // pointer dereference when methods are called. type UnimplementedShippingServiceServer struct{} func (UnimplementedShippingServiceServer) GetQuote(context.Context, *GetQuoteRequest) (*GetQuoteResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method GetQuote not implemented") } func (UnimplementedShippingServiceServer) ShipOrder(context.Context, *ShipOrderRequest) (*ShipOrderResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method ShipOrder not implemented") } func (UnimplementedShippingServiceServer) mustEmbedUnimplementedShippingServiceServer() {} func (UnimplementedShippingServiceServer) testEmbeddedByValue() {} // UnsafeShippingServiceServer may be embedded to opt out of forward compatibility for this service. // Use of this interface is not recommended, as added methods to ShippingServiceServer will // result in compilation errors. type UnsafeShippingServiceServer interface { mustEmbedUnimplementedShippingServiceServer() } func RegisterShippingServiceServer(s grpc.ServiceRegistrar, srv ShippingServiceServer) { // If the following call pancis, it indicates UnimplementedShippingServiceServer was // embedded by pointer and is nil. This will cause panics if an // unimplemented method is ever invoked, so we test this at initialization // time to prevent it from happening at runtime later due to I/O. if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { t.testEmbeddedByValue() } s.RegisterService(&ShippingService_ServiceDesc, srv) } func _ShippingService_GetQuote_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(GetQuoteRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(ShippingServiceServer).GetQuote(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: ShippingService_GetQuote_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(ShippingServiceServer).GetQuote(ctx, req.(*GetQuoteRequest)) } return interceptor(ctx, in, info, handler) } func _ShippingService_ShipOrder_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(ShipOrderRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(ShippingServiceServer).ShipOrder(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: ShippingService_ShipOrder_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(ShippingServiceServer).ShipOrder(ctx, req.(*ShipOrderRequest)) } return interceptor(ctx, in, info, handler) } // ShippingService_ServiceDesc is the grpc.ServiceDesc for ShippingService service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) var ShippingService_ServiceDesc = grpc.ServiceDesc{ ServiceName: "hipstershop.ShippingService", HandlerType: (*ShippingServiceServer)(nil), Methods: []grpc.MethodDesc{ { MethodName: "GetQuote", Handler: _ShippingService_GetQuote_Handler, }, { MethodName: "ShipOrder", Handler: _ShippingService_ShipOrder_Handler, }, }, Streams: []grpc.StreamDesc{}, Metadata: "demo.proto", } const ( CurrencyService_GetSupportedCurrencies_FullMethodName = "/hipstershop.CurrencyService/GetSupportedCurrencies" CurrencyService_Convert_FullMethodName = "/hipstershop.CurrencyService/Convert" ) // CurrencyServiceClient is the client API for CurrencyService service. // // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. type CurrencyServiceClient interface { GetSupportedCurrencies(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*GetSupportedCurrenciesResponse, error) Convert(ctx context.Context, in *CurrencyConversionRequest, opts ...grpc.CallOption) (*Money, error) } type currencyServiceClient struct { cc grpc.ClientConnInterface } func NewCurrencyServiceClient(cc grpc.ClientConnInterface) CurrencyServiceClient { return ¤cyServiceClient{cc} } func (c *currencyServiceClient) GetSupportedCurrencies(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*GetSupportedCurrenciesResponse, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(GetSupportedCurrenciesResponse) err := c.cc.Invoke(ctx, CurrencyService_GetSupportedCurrencies_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } return out, nil } func (c *currencyServiceClient) Convert(ctx context.Context, in *CurrencyConversionRequest, opts ...grpc.CallOption) (*Money, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(Money) err := c.cc.Invoke(ctx, CurrencyService_Convert_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } return out, nil } // CurrencyServiceServer is the server API for CurrencyService service. // All implementations must embed UnimplementedCurrencyServiceServer // for forward compatibility. type CurrencyServiceServer interface { GetSupportedCurrencies(context.Context, *Empty) (*GetSupportedCurrenciesResponse, error) Convert(context.Context, *CurrencyConversionRequest) (*Money, error) mustEmbedUnimplementedCurrencyServiceServer() } // UnimplementedCurrencyServiceServer must be embedded to have // forward compatible implementations. // // NOTE: this should be embedded by value instead of pointer to avoid a nil // pointer dereference when methods are called. type UnimplementedCurrencyServiceServer struct{} func (UnimplementedCurrencyServiceServer) GetSupportedCurrencies(context.Context, *Empty) (*GetSupportedCurrenciesResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method GetSupportedCurrencies not implemented") } func (UnimplementedCurrencyServiceServer) Convert(context.Context, *CurrencyConversionRequest) (*Money, error) { return nil, status.Errorf(codes.Unimplemented, "method Convert not implemented") } func (UnimplementedCurrencyServiceServer) mustEmbedUnimplementedCurrencyServiceServer() {} func (UnimplementedCurrencyServiceServer) testEmbeddedByValue() {} // UnsafeCurrencyServiceServer may be embedded to opt out of forward compatibility for this service. // Use of this interface is not recommended, as added methods to CurrencyServiceServer will // result in compilation errors. type UnsafeCurrencyServiceServer interface { mustEmbedUnimplementedCurrencyServiceServer() } func RegisterCurrencyServiceServer(s grpc.ServiceRegistrar, srv CurrencyServiceServer) { // If the following call pancis, it indicates UnimplementedCurrencyServiceServer was // embedded by pointer and is nil. This will cause panics if an // unimplemented method is ever invoked, so we test this at initialization // time to prevent it from happening at runtime later due to I/O. if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { t.testEmbeddedByValue() } s.RegisterService(&CurrencyService_ServiceDesc, srv) } func _CurrencyService_GetSupportedCurrencies_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(Empty) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(CurrencyServiceServer).GetSupportedCurrencies(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: CurrencyService_GetSupportedCurrencies_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(CurrencyServiceServer).GetSupportedCurrencies(ctx, req.(*Empty)) } return interceptor(ctx, in, info, handler) } func _CurrencyService_Convert_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(CurrencyConversionRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(CurrencyServiceServer).Convert(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: CurrencyService_Convert_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(CurrencyServiceServer).Convert(ctx, req.(*CurrencyConversionRequest)) } return interceptor(ctx, in, info, handler) } // CurrencyService_ServiceDesc is the grpc.ServiceDesc for CurrencyService service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) var CurrencyService_ServiceDesc = grpc.ServiceDesc{ ServiceName: "hipstershop.CurrencyService", HandlerType: (*CurrencyServiceServer)(nil), Methods: []grpc.MethodDesc{ { MethodName: "GetSupportedCurrencies", Handler: _CurrencyService_GetSupportedCurrencies_Handler, }, { MethodName: "Convert", Handler: _CurrencyService_Convert_Handler, }, }, Streams: []grpc.StreamDesc{}, Metadata: "demo.proto", } const ( PaymentService_Charge_FullMethodName = "/hipstershop.PaymentService/Charge" ) // PaymentServiceClient is the client API for PaymentService service. // // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. type PaymentServiceClient interface { Charge(ctx context.Context, in *ChargeRequest, opts ...grpc.CallOption) (*ChargeResponse, error) } type paymentServiceClient struct { cc grpc.ClientConnInterface } func NewPaymentServiceClient(cc grpc.ClientConnInterface) PaymentServiceClient { return &paymentServiceClient{cc} } func (c *paymentServiceClient) Charge(ctx context.Context, in *ChargeRequest, opts ...grpc.CallOption) (*ChargeResponse, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(ChargeResponse) err := c.cc.Invoke(ctx, PaymentService_Charge_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } return out, nil } // PaymentServiceServer is the server API for PaymentService service. // All implementations must embed UnimplementedPaymentServiceServer // for forward compatibility. type PaymentServiceServer interface { Charge(context.Context, *ChargeRequest) (*ChargeResponse, error) mustEmbedUnimplementedPaymentServiceServer() } // UnimplementedPaymentServiceServer must be embedded to have // forward compatible implementations. // // NOTE: this should be embedded by value instead of pointer to avoid a nil // pointer dereference when methods are called. type UnimplementedPaymentServiceServer struct{} func (UnimplementedPaymentServiceServer) Charge(context.Context, *ChargeRequest) (*ChargeResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method Charge not implemented") } func (UnimplementedPaymentServiceServer) mustEmbedUnimplementedPaymentServiceServer() {} func (UnimplementedPaymentServiceServer) testEmbeddedByValue() {} // UnsafePaymentServiceServer may be embedded to opt out of forward compatibility for this service. // Use of this interface is not recommended, as added methods to PaymentServiceServer will // result in compilation errors. type UnsafePaymentServiceServer interface { mustEmbedUnimplementedPaymentServiceServer() } func RegisterPaymentServiceServer(s grpc.ServiceRegistrar, srv PaymentServiceServer) { // If the following call pancis, it indicates UnimplementedPaymentServiceServer was // embedded by pointer and is nil. This will cause panics if an // unimplemented method is ever invoked, so we test this at initialization // time to prevent it from happening at runtime later due to I/O. if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { t.testEmbeddedByValue() } s.RegisterService(&PaymentService_ServiceDesc, srv) } func _PaymentService_Charge_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(ChargeRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(PaymentServiceServer).Charge(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: PaymentService_Charge_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(PaymentServiceServer).Charge(ctx, req.(*ChargeRequest)) } return interceptor(ctx, in, info, handler) } // PaymentService_ServiceDesc is the grpc.ServiceDesc for PaymentService service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) var PaymentService_ServiceDesc = grpc.ServiceDesc{ ServiceName: "hipstershop.PaymentService", HandlerType: (*PaymentServiceServer)(nil), Methods: []grpc.MethodDesc{ { MethodName: "Charge", Handler: _PaymentService_Charge_Handler, }, }, Streams: []grpc.StreamDesc{}, Metadata: "demo.proto", } const ( EmailService_SendOrderConfirmation_FullMethodName = "/hipstershop.EmailService/SendOrderConfirmation" ) // EmailServiceClient is the client API for EmailService service. // // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. type EmailServiceClient interface { SendOrderConfirmation(ctx context.Context, in *SendOrderConfirmationRequest, opts ...grpc.CallOption) (*Empty, error) } type emailServiceClient struct { cc grpc.ClientConnInterface } func NewEmailServiceClient(cc grpc.ClientConnInterface) EmailServiceClient { return &emailServiceClient{cc} } func (c *emailServiceClient) SendOrderConfirmation(ctx context.Context, in *SendOrderConfirmationRequest, opts ...grpc.CallOption) (*Empty, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(Empty) err := c.cc.Invoke(ctx, EmailService_SendOrderConfirmation_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } return out, nil } // EmailServiceServer is the server API for EmailService service. // All implementations must embed UnimplementedEmailServiceServer // for forward compatibility. type EmailServiceServer interface { SendOrderConfirmation(context.Context, *SendOrderConfirmationRequest) (*Empty, error) mustEmbedUnimplementedEmailServiceServer() } // UnimplementedEmailServiceServer must be embedded to have // forward compatible implementations. // // NOTE: this should be embedded by value instead of pointer to avoid a nil // pointer dereference when methods are called. type UnimplementedEmailServiceServer struct{} func (UnimplementedEmailServiceServer) SendOrderConfirmation(context.Context, *SendOrderConfirmationRequest) (*Empty, error) { return nil, status.Errorf(codes.Unimplemented, "method SendOrderConfirmation not implemented") } func (UnimplementedEmailServiceServer) mustEmbedUnimplementedEmailServiceServer() {} func (UnimplementedEmailServiceServer) testEmbeddedByValue() {} // UnsafeEmailServiceServer may be embedded to opt out of forward compatibility for this service. // Use of this interface is not recommended, as added methods to EmailServiceServer will // result in compilation errors. type UnsafeEmailServiceServer interface { mustEmbedUnimplementedEmailServiceServer() } func RegisterEmailServiceServer(s grpc.ServiceRegistrar, srv EmailServiceServer) { // If the following call pancis, it indicates UnimplementedEmailServiceServer was // embedded by pointer and is nil. This will cause panics if an // unimplemented method is ever invoked, so we test this at initialization // time to prevent it from happening at runtime later due to I/O. if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { t.testEmbeddedByValue() } s.RegisterService(&EmailService_ServiceDesc, srv) } func _EmailService_SendOrderConfirmation_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(SendOrderConfirmationRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(EmailServiceServer).SendOrderConfirmation(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: EmailService_SendOrderConfirmation_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(EmailServiceServer).SendOrderConfirmation(ctx, req.(*SendOrderConfirmationRequest)) } return interceptor(ctx, in, info, handler) } // EmailService_ServiceDesc is the grpc.ServiceDesc for EmailService service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) var EmailService_ServiceDesc = grpc.ServiceDesc{ ServiceName: "hipstershop.EmailService", HandlerType: (*EmailServiceServer)(nil), Methods: []grpc.MethodDesc{ { MethodName: "SendOrderConfirmation", Handler: _EmailService_SendOrderConfirmation_Handler, }, }, Streams: []grpc.StreamDesc{}, Metadata: "demo.proto", } const ( CheckoutService_PlaceOrder_FullMethodName = "/hipstershop.CheckoutService/PlaceOrder" ) // CheckoutServiceClient is the client API for CheckoutService service. // // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. type CheckoutServiceClient interface { PlaceOrder(ctx context.Context, in *PlaceOrderRequest, opts ...grpc.CallOption) (*PlaceOrderResponse, error) } type checkoutServiceClient struct { cc grpc.ClientConnInterface } func NewCheckoutServiceClient(cc grpc.ClientConnInterface) CheckoutServiceClient { return &checkoutServiceClient{cc} } func (c *checkoutServiceClient) PlaceOrder(ctx context.Context, in *PlaceOrderRequest, opts ...grpc.CallOption) (*PlaceOrderResponse, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(PlaceOrderResponse) err := c.cc.Invoke(ctx, CheckoutService_PlaceOrder_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } return out, nil } // CheckoutServiceServer is the server API for CheckoutService service. // All implementations must embed UnimplementedCheckoutServiceServer // for forward compatibility. type CheckoutServiceServer interface { PlaceOrder(context.Context, *PlaceOrderRequest) (*PlaceOrderResponse, error) mustEmbedUnimplementedCheckoutServiceServer() } // UnimplementedCheckoutServiceServer must be embedded to have // forward compatible implementations. // // NOTE: this should be embedded by value instead of pointer to avoid a nil // pointer dereference when methods are called. type UnimplementedCheckoutServiceServer struct{} func (UnimplementedCheckoutServiceServer) PlaceOrder(context.Context, *PlaceOrderRequest) (*PlaceOrderResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method PlaceOrder not implemented") } func (UnimplementedCheckoutServiceServer) mustEmbedUnimplementedCheckoutServiceServer() {} func (UnimplementedCheckoutServiceServer) testEmbeddedByValue() {} // UnsafeCheckoutServiceServer may be embedded to opt out of forward compatibility for this service. // Use of this interface is not recommended, as added methods to CheckoutServiceServer will // result in compilation errors. type UnsafeCheckoutServiceServer interface { mustEmbedUnimplementedCheckoutServiceServer() } func RegisterCheckoutServiceServer(s grpc.ServiceRegistrar, srv CheckoutServiceServer) { // If the following call pancis, it indicates UnimplementedCheckoutServiceServer was // embedded by pointer and is nil. This will cause panics if an // unimplemented method is ever invoked, so we test this at initialization // time to prevent it from happening at runtime later due to I/O. if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { t.testEmbeddedByValue() } s.RegisterService(&CheckoutService_ServiceDesc, srv) } func _CheckoutService_PlaceOrder_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(PlaceOrderRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(CheckoutServiceServer).PlaceOrder(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: CheckoutService_PlaceOrder_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(CheckoutServiceServer).PlaceOrder(ctx, req.(*PlaceOrderRequest)) } return interceptor(ctx, in, info, handler) } // CheckoutService_ServiceDesc is the grpc.ServiceDesc for CheckoutService service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) var CheckoutService_ServiceDesc = grpc.ServiceDesc{ ServiceName: "hipstershop.CheckoutService", HandlerType: (*CheckoutServiceServer)(nil), Methods: []grpc.MethodDesc{ { MethodName: "PlaceOrder", Handler: _CheckoutService_PlaceOrder_Handler, }, }, Streams: []grpc.StreamDesc{}, Metadata: "demo.proto", } const ( AdService_GetAds_FullMethodName = "/hipstershop.AdService/GetAds" ) // AdServiceClient is the client API for AdService service. // // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. type AdServiceClient interface { GetAds(ctx context.Context, in *AdRequest, opts ...grpc.CallOption) (*AdResponse, error) } type adServiceClient struct { cc grpc.ClientConnInterface } func NewAdServiceClient(cc grpc.ClientConnInterface) AdServiceClient { return &adServiceClient{cc} } func (c *adServiceClient) GetAds(ctx context.Context, in *AdRequest, opts ...grpc.CallOption) (*AdResponse, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(AdResponse) err := c.cc.Invoke(ctx, AdService_GetAds_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } return out, nil } // AdServiceServer is the server API for AdService service. // All implementations must embed UnimplementedAdServiceServer // for forward compatibility. type AdServiceServer interface { GetAds(context.Context, *AdRequest) (*AdResponse, error) mustEmbedUnimplementedAdServiceServer() } // UnimplementedAdServiceServer must be embedded to have // forward compatible implementations. // // NOTE: this should be embedded by value instead of pointer to avoid a nil // pointer dereference when methods are called. type UnimplementedAdServiceServer struct{} func (UnimplementedAdServiceServer) GetAds(context.Context, *AdRequest) (*AdResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method GetAds not implemented") } func (UnimplementedAdServiceServer) mustEmbedUnimplementedAdServiceServer() {} func (UnimplementedAdServiceServer) testEmbeddedByValue() {} // UnsafeAdServiceServer may be embedded to opt out of forward compatibility for this service. // Use of this interface is not recommended, as added methods to AdServiceServer will // result in compilation errors. type UnsafeAdServiceServer interface { mustEmbedUnimplementedAdServiceServer() } func RegisterAdServiceServer(s grpc.ServiceRegistrar, srv AdServiceServer) { // If the following call pancis, it indicates UnimplementedAdServiceServer was // embedded by pointer and is nil. This will cause panics if an // unimplemented method is ever invoked, so we test this at initialization // time to prevent it from happening at runtime later due to I/O. if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { t.testEmbeddedByValue() } s.RegisterService(&AdService_ServiceDesc, srv) } func _AdService_GetAds_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(AdRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(AdServiceServer).GetAds(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: AdService_GetAds_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(AdServiceServer).GetAds(ctx, req.(*AdRequest)) } return interceptor(ctx, in, info, handler) } // AdService_ServiceDesc is the grpc.ServiceDesc for AdService service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) var AdService_ServiceDesc = grpc.ServiceDesc{ ServiceName: "hipstershop.AdService", HandlerType: (*AdServiceServer)(nil), Methods: []grpc.MethodDesc{ { MethodName: "GetAds", Handler: _AdService_GetAds_Handler, }, }, Streams: []grpc.StreamDesc{}, Metadata: "demo.proto", } ================================================ FILE: src/checkoutservice/genproto.sh ================================================ #!/bin/bash -eu # # Copyright 2018 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 gke_checkoutservice_genproto] PATH=$PATH:$(go env GOPATH)/bin protodir=../../protos outdir=./genproto protoc --proto_path=$protodir --go_out=./$outdir --go_opt=paths=source_relative --go-grpc_out=./$outdir --go-grpc_opt=paths=source_relative $protodir/demo.proto # [END gke_checkoutservice_genproto] ================================================ FILE: src/checkoutservice/go.mod ================================================ module github.com/GoogleCloudPlatform/microservices-demo/src/checkoutservice go 1.25.0 toolchain go1.26.1 require ( cloud.google.com/go/profiler v0.4.3 github.com/google/uuid v1.6.0 github.com/pkg/errors v0.9.1 github.com/sirupsen/logrus v1.9.4 go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0 go.opentelemetry.io/otel v1.42.0 go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.42.0 go.opentelemetry.io/otel/sdk v1.42.0 google.golang.org/grpc v1.79.2 google.golang.org/protobuf v1.36.11 ) require ( cloud.google.com/go v0.123.0 // indirect cloud.google.com/go/auth v0.17.0 // indirect cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect cloud.google.com/go/compute/metadata v0.9.0 // indirect github.com/cenkalti/backoff/v5 v5.0.3 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/google/pprof v0.0.0-20251114195745-4902fdda35c8 // indirect github.com/google/s2a-go v0.1.9 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.7 // indirect github.com/googleapis/gax-go/v2 v2.15.0 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.42.0 // indirect go.opentelemetry.io/otel/metric v1.42.0 // indirect go.opentelemetry.io/otel/trace v1.42.0 // indirect go.opentelemetry.io/proto/otlp v1.9.0 // indirect golang.org/x/crypto v0.48.0 // indirect golang.org/x/net v0.51.0 // indirect golang.org/x/oauth2 v0.35.0 // indirect golang.org/x/sync v0.19.0 // indirect golang.org/x/sys v0.41.0 // indirect golang.org/x/text v0.34.0 // indirect golang.org/x/time v0.14.0 // indirect google.golang.org/api v0.256.0 // indirect google.golang.org/genproto v0.0.0-20251124214823-79d6a2a48846 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 // indirect ) ================================================ FILE: src/checkoutservice/go.sum ================================================ cel.dev/expr v0.24.0 h1:56OvJKSH3hDGL0ml5uSxZmz3/3Pq4tJ+fb1unVLAFcY= cel.dev/expr v0.24.0/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw= cloud.google.com/go v0.123.0 h1:2NAUJwPR47q+E35uaJeYoNhuNEM9kM8SjgRgdeOJUSE= cloud.google.com/go v0.123.0/go.mod h1:xBoMV08QcqUGuPW65Qfm1o9Y4zKZBpGS+7bImXLTAZU= cloud.google.com/go/auth v0.17.0 h1:74yCm7hCj2rUyyAocqnFzsAYXgJhrG26XCFimrc/Kz4= cloud.google.com/go/auth v0.17.0/go.mod h1:6wv/t5/6rOPAX4fJiRjKkJCvswLwdet7G8+UGXt7nCQ= cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc= cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c= cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs= cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10= cloud.google.com/go/iam v1.5.3 h1:+vMINPiDF2ognBJ97ABAYYwRgsaqxPbQDlMnbHMjolc= cloud.google.com/go/iam v1.5.3/go.mod h1:MR3v9oLkZCTlaqljW6Eb2d3HGDGK5/bDv93jhfISFvU= cloud.google.com/go/monitoring v1.24.3 h1:dde+gMNc0UhPZD1Azu6at2e79bfdztVDS5lvhOdsgaE= cloud.google.com/go/monitoring v1.24.3/go.mod h1:nYP6W0tm3N9H/bOw8am7t62YTzZY+zUeQ+Bi6+2eonI= cloud.google.com/go/profiler v0.4.3 h1:IY3QNKlr8VbXwGWHcZbJQsMA/83ZTH6uAHf8jYyj7OI= cloud.google.com/go/profiler v0.4.3/go.mod h1:3xFodugWfPIQZWFcXdUmfa+yTiiyQ8fWrdT+d2Sg4J0= cloud.google.com/go/storage v1.56.0 h1:iixmq2Fse2tqxMbWhLWC9HfBj1qdxqAmiK8/eqtsLxI= cloud.google.com/go/storage v1.56.0/go.mod h1:Tpuj6t4NweCLzlNbw9Z9iwxEkrSem20AetIeH/shgVU= github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0 h1:sBEjpZlNHzK1voKq9695PJSX2o5NEXl7/OL3coiIY0c= github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0/go.mod h1:P4WPRUkOhJC13W//jWpyfJNDAIpvRbAUIYLX/4jtlE0= github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.53.0 h1:owcC2UnmsZycprQ5RfRgjydWhuoxg71LUfyiQdijZuM= github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.53.0/go.mod h1:ZPpqegjbE99EPKsu3iUWV22A04wzGPcAY/ziSIQEEgs= github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.53.0 h1:Ron4zCA/yk6U7WOBXhTJcDpsUBG9npumK6xw2auFltQ= github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.53.0/go.mod h1:cSgYe11MCNYunTnRXrKiR/tHc0eoKjICUuWpNZoVCOo= github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cncf/xds/go v0.0.0-20251022180443-0feb69152e9f h1:Y8xYupdHxryycyPlc9Y+bSQAYZnetRJ70VMVKm5CKI0= github.com/cncf/xds/go v0.0.0-20251022180443-0feb69152e9f/go.mod h1:HlzOvOjVBOfTGSRXRyY0OiCS/3J1akRGQQpRO/7zyF4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/envoyproxy/go-control-plane v0.13.5-0.20251024222203-75eaa193e329 h1:K+fnvUM0VZ7ZFJf0n4L/BRlnsb9pL/GuDG6FqaH+PwM= github.com/envoyproxy/go-control-plane/envoy v1.35.0 h1:ixjkELDE+ru6idPxcHLj8LBVc2bFP7iBytj353BoHUo= github.com/envoyproxy/go-control-plane/envoy v1.35.0/go.mod h1:09qwbGVuSWWAyN5t/b3iyVfz5+z8QWGrzkoqm/8SbEs= github.com/envoyproxy/protoc-gen-validate v1.2.1 h1:DEo3O99U8j4hBFwbJfrz9VtgcDfUKS7KJ7spH3d86P8= github.com/envoyproxy/protoc-gen-validate v1.2.1/go.mod h1:d/C80l/jxXLdfEIhX1W2TmLfsJ31lvEjwamM4DxlWXU= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/go-jose/go-jose/v4 v4.1.3 h1:CVLmWDhDVRa6Mi/IgCgaopNosCaHz7zrMeF9MlZRkrs= github.com/go-jose/go-jose/v4 v4.1.3/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/golang/mock v1.7.0-rc.1 h1:YojYx61/OLFsiv6Rw1Z96LpldJIy31o+UHmwAUMJ6/U= github.com/golang/mock v1.7.0-rc.1/go.mod h1:s42URUywIqd+OcERslBJvOjepvNymP31m3q8d/GkuRs= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/pprof v0.0.0-20251114195745-4902fdda35c8 h1:3DsUAV+VNEQa2CUVLxCY3f87278uWfIDhJnbdvDjvmE= github.com/google/pprof v0.0.0-20251114195745-4902fdda35c8/go.mod h1:I6V7YzU0XDpsHqbsyrghnFZLO1gwK6NPTNvmetQIk9U= github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0= github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/enterprise-certificate-proxy v0.3.7 h1:zrn2Ee/nWmHulBx5sAVrGgAa0f2/R35S4DJwfFaUPFQ= github.com/googleapis/enterprise-certificate-proxy v0.3.7/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA= github.com/googleapis/gax-go/v2 v2.15.0 h1:SyjDc1mGgZU5LncH8gimWo9lW1DtIfPibOG81vgd/bo= github.com/googleapis/gax-go/v2 v2.15.0/go.mod h1:zVVkkxAQHa1RQpg9z2AUCMnKhi0Qld9rcmyfL1OZhoc= github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 h1:NmZ1PKzSTQbuGHw9DGPFomqkkLWMC+vZCkfs+FHv1Vg= github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3/go.mod h1:zQrxl1YP88HQlA6i9c63DSVPFklWpGX4OWAc9bFuaH4= github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 h1:HWRh5R2+9EifMyIHV7ZV+MIZqgz+PMpZ14Jynv3O2Zs= github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0/go.mod h1:JfhWUomR1baixubs02l85lZYYOm7LV6om4ceouMv45c= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo= github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w= github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g= github.com/spiffe/go-spiffe/v2 v2.6.0 h1:l+DolpxNWYgruGQVV0xsfeya3CsC7m8iBzDnMpsbLuo= github.com/spiffe/go-spiffe/v2 v2.6.0/go.mod h1:gm2SeUoMZEtpnzPNs2Csc0D/gX33k1xIx7lEzqblHEs= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/contrib/detectors/gcp v1.38.0 h1:ZoYbqX7OaA/TAikspPl3ozPI6iY6LiIY9I8cUfm+pJs= go.opentelemetry.io/contrib/detectors/gcp v1.38.0/go.mod h1:SU+iU7nu5ud4oCb3LQOhIZ3nRLj6FNVrKgtflbaf2ts= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0 h1:YH4g8lQroajqUwWbq/tr2QX1JFmEXaDLgG+ew9bLMWo= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0/go.mod h1:fvPi2qXDqFs8M4B4fmJhE92TyQs9Ydjlg3RvfUp+NbQ= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.64.0 h1:RN3ifU8y4prNWeEnQp2kRRHz8UwonAEYZl8tUzHEXAk= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.64.0/go.mod h1:habDz3tEWiFANTo6oUE99EmaFUrCNYAAg3wiVmusm70= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0 h1:yI1/OhfEPy7J9eoa6Sj051C7n5dvpj0QX8g4sRchg04= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0/go.mod h1:NoUCKYWK+3ecatC4HjkRktREheMeEtrXoQxrqYFeHSc= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q= go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= go.opentelemetry.io/otel v1.42.0 h1:lSQGzTgVR3+sgJDAU/7/ZMjN9Z+vUip7leaqBKy4sho= go.opentelemetry.io/otel v1.42.0/go.mod h1:lJNsdRMxCUIWuMlVJWzecSMuNjE7dOYyWlqOXWkdqCc= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 h1:GqRJVj7UmLjCVyVJ3ZFLdPRmhDUp2zFmQe3RHIOsw24= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0/go.mod h1:ri3aaHSmCTVYu2AWv44YMauwAQc0aqI9gHKIcSbI1pU= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0 h1:f0cb2XPmrqn4XMy9PNliTgRKJgS5WcL/u0/WRYGz4t0= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0/go.mod h1:vnakAaFckOMiMtOIhFI2MNH4FYrZzXCYxmb1LlhoGz8= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.42.0 h1:THuZiwpQZuHPul65w4WcwEnkX2QIuMT+UFoOrygtoJw= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.42.0/go.mod h1:J2pvYM5NGHofZ2/Ru6zw/TNWnEQp5crgyDeSrYpXkAw= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 h1:lwI4Dc5leUqENgGuQImwLo4WnuXFPetmPpkLi2IrX54= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0/go.mod h1:Kz/oCE7z5wuyhPxsXDuaPteSWqjSBD5YaSdbxZYGbGk= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0 h1:in9O8ESIOlwJAEGTkkf34DesGRAc/Pn8qJ7k3r/42LM= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0/go.mod h1:Rp0EXBm5tfnv0WL+ARyO/PHBEaEAT8UUHQ6AGJcSq6c= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.42.0 h1:zWWrB1U6nqhS/k6zYB74CjRpuiitRtLLi68VcgmOEto= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.42.0/go.mod h1:2qXPNBX1OVRC0IwOnfo1ljoid+RD0QK3443EaqVlsOU= go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= go.opentelemetry.io/otel/metric v1.42.0 h1:2jXG+3oZLNXEPfNmnpxKDeZsFI5o4J+nz6xUlaFdF/4= go.opentelemetry.io/otel/metric v1.42.0/go.mod h1:RlUN/7vTU7Ao/diDkEpQpnz3/92J9ko05BIwxYa2SSI= go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E= go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg= go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= go.opentelemetry.io/otel/sdk v1.42.0 h1:LyC8+jqk6UJwdrI/8VydAq/hvkFKNHZVIWuslJXYsDo= go.opentelemetry.io/otel/sdk v1.42.0/go.mod h1:rGHCAxd9DAph0joO4W6OPwxjNTYWghRWmkHuGbayMts= go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM= go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= go.opentelemetry.io/otel/trace v1.42.0 h1:OUCgIPt+mzOnaUTpOQcBiM/PLQ/Op7oq6g4LenLmOYY= go.opentelemetry.io/otel/trace v1.42.0/go.mod h1:f3K9S+IFqnumBkKhRJMeaZeNk9epyhnCmQh/EysQCdc= go.opentelemetry.io/proto/otlp v1.9.0 h1:l706jCMITVouPOqEnii2fIAuO3IVGBRPV5ICjceRb/A= go.opentelemetry.io/proto/otlp v1.9.0/go.mod h1:xE+Cx5E/eEHw+ISFkwPLwCZefwVjY+pqKg1qcK03+/4= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q= golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4= golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts= golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos= golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo= golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y= golang.org/x/oauth2 v0.33.0 h1:4Q+qn+E5z8gPRJfmRy7C2gGG3T4jIprK6aSYgTXGRpo= golang.org/x/oauth2 v0.33.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/oauth2 v0.35.0 h1:Mv2mzuHuZuY2+bkyWXIHMfhNdJAdwW3FuWeCPYN5GVQ= golang.org/x/oauth2 v0.35.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k= golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk= golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA= golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= google.golang.org/api v0.256.0 h1:u6Khm8+F9sxbCTYNoBHg6/Hwv0N/i+V94MvkOSor6oI= google.golang.org/api v0.256.0/go.mod h1:KIgPhksXADEKJlnEoRa9qAII4rXcy40vfI8HRqcU964= google.golang.org/genproto v0.0.0-20251124214823-79d6a2a48846 h1:dDbsTLIK7EzwUq36kCSAsk0slouq/S0tWHeeGi97cD8= google.golang.org/genproto v0.0.0-20251124214823-79d6a2a48846/go.mod h1:PP0g88Dz3C7hRAfbQCQggeWAXjuqGsNPLE4s7jh0RGU= google.golang.org/genproto/googleapis/api v0.0.0-20251124214823-79d6a2a48846 h1:ZdyUkS9po3H7G0tuh955QVyyotWvOD4W0aEapeGeUYk= google.golang.org/genproto/googleapis/api v0.0.0-20251124214823-79d6a2a48846/go.mod h1:Fk4kyraUvqD7i5H6S43sj2W98fbZa75lpZz/eUyhfO0= google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 h1:fCvbg86sFXwdrl5LgVcTEvNC+2txB5mgROGmRL5mrls= google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:+rXWjjaukWZun3mLfjmVnQi18E1AsFbDN9QdJ5YXLto= google.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57 h1:JLQynH/LBHfCTSbDWl+py8C+Rg/k1OVH3xfcaiANuF0= google.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57/go.mod h1:kSJwQxqmFXeo79zOmbrALdflXQeAYcUbgS7PbpMknCY= google.golang.org/genproto/googleapis/rpc v0.0.0-20251124214823-79d6a2a48846 h1:Wgl1rcDNThT+Zn47YyCXOXyX/COgMTIdhJ717F0l4xk= google.golang.org/genproto/googleapis/rpc v0.0.0-20251124214823-79d6a2a48846/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 h1:gRkg/vSppuSQoDjxyiGfN4Upv/h/DQmIR10ZU8dh4Ww= google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 h1:ggcbiqK8WWh6l1dnltU4BgWGIGo+EVYxCaAPih/zQXQ= google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM= google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig= google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc= google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U= google.golang.org/grpc v1.79.2 h1:fRMD94s2tITpyJGtBBn7MkMseNpOZU8ZxgC3MMBaXRU= google.golang.org/grpc v1.79.2/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= ================================================ FILE: src/checkoutservice/main.go ================================================ // Copyright 2018 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. package main import ( "context" "fmt" "net" "os" "time" "cloud.google.com/go/profiler" "github.com/google/uuid" "github.com/pkg/errors" "github.com/sirupsen/logrus" "google.golang.org/grpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/credentials/insecure" "google.golang.org/grpc/health" "google.golang.org/grpc/status" pb "github.com/GoogleCloudPlatform/microservices-demo/src/checkoutservice/genproto" money "github.com/GoogleCloudPlatform/microservices-demo/src/checkoutservice/money" healthpb "google.golang.org/grpc/health/grpc_health_v1" "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc" "go.opentelemetry.io/otel/propagation" sdktrace "go.opentelemetry.io/otel/sdk/trace" ) const ( listenPort = "5050" usdCurrency = "USD" ) var log *logrus.Logger func init() { log = logrus.New() log.Level = logrus.DebugLevel log.Formatter = &logrus.JSONFormatter{ FieldMap: logrus.FieldMap{ logrus.FieldKeyTime: "timestamp", logrus.FieldKeyLevel: "severity", logrus.FieldKeyMsg: "message", }, TimestampFormat: time.RFC3339Nano, } log.Out = os.Stdout } type checkoutService struct { pb.UnimplementedCheckoutServiceServer productCatalogSvcAddr string productCatalogSvcConn *grpc.ClientConn cartSvcAddr string cartSvcConn *grpc.ClientConn currencySvcAddr string currencySvcConn *grpc.ClientConn shippingSvcAddr string shippingSvcConn *grpc.ClientConn emailSvcAddr string emailSvcConn *grpc.ClientConn paymentSvcAddr string paymentSvcConn *grpc.ClientConn } func main() { ctx := context.Background() if os.Getenv("ENABLE_TRACING") == "1" { log.Info("Tracing enabled.") initTracing() } else { log.Info("Tracing disabled.") } if os.Getenv("ENABLE_PROFILER") == "1" { log.Info("Profiling enabled.") go initProfiling("checkoutservice", "1.0.0") } else { log.Info("Profiling disabled.") } port := listenPort if os.Getenv("PORT") != "" { port = os.Getenv("PORT") } svc := new(checkoutService) mustMapEnv(&svc.shippingSvcAddr, "SHIPPING_SERVICE_ADDR") mustMapEnv(&svc.productCatalogSvcAddr, "PRODUCT_CATALOG_SERVICE_ADDR") mustMapEnv(&svc.cartSvcAddr, "CART_SERVICE_ADDR") mustMapEnv(&svc.currencySvcAddr, "CURRENCY_SERVICE_ADDR") mustMapEnv(&svc.emailSvcAddr, "EMAIL_SERVICE_ADDR") mustMapEnv(&svc.paymentSvcAddr, "PAYMENT_SERVICE_ADDR") mustConnGRPC(ctx, &svc.shippingSvcConn, svc.shippingSvcAddr) mustConnGRPC(ctx, &svc.productCatalogSvcConn, svc.productCatalogSvcAddr) mustConnGRPC(ctx, &svc.cartSvcConn, svc.cartSvcAddr) mustConnGRPC(ctx, &svc.currencySvcConn, svc.currencySvcAddr) mustConnGRPC(ctx, &svc.emailSvcConn, svc.emailSvcAddr) mustConnGRPC(ctx, &svc.paymentSvcConn, svc.paymentSvcAddr) log.Infof("service config: %+v", svc) lis, err := net.Listen("tcp", fmt.Sprintf(":%s", port)) if err != nil { log.Fatal(err) } var srv *grpc.Server // Propagate trace context always otel.SetTextMapPropagator( propagation.NewCompositeTextMapPropagator( propagation.TraceContext{}, propagation.Baggage{})) srv = grpc.NewServer( grpc.StatsHandler(otelgrpc.NewServerHandler()), ) pb.RegisterCheckoutServiceServer(srv, svc) healthcheck := health.NewServer() healthpb.RegisterHealthServer(srv, healthcheck) log.Infof("starting to listen on tcp: %q", lis.Addr().String()) err = srv.Serve(lis) log.Fatal(err) } func initStats() { //TODO(arbrown) Implement OpenTelemetry stats } func initTracing() { var ( collectorAddr string collectorConn *grpc.ClientConn ) ctx := context.Background() ctx, cancel := context.WithTimeout(ctx, time.Second*3) defer cancel() mustMapEnv(&collectorAddr, "COLLECTOR_SERVICE_ADDR") mustConnGRPC(ctx, &collectorConn, collectorAddr) exporter, err := otlptracegrpc.New( ctx, otlptracegrpc.WithGRPCConn(collectorConn)) if err != nil { log.Warnf("warn: Failed to create trace exporter: %v", err) } tp := sdktrace.NewTracerProvider( sdktrace.WithBatcher(exporter), sdktrace.WithSampler(sdktrace.AlwaysSample())) otel.SetTracerProvider(tp) } func initProfiling(service, version string) { // TODO(ahmetb) this method is duplicated in other microservices using Go // since they are not sharing packages. for i := 1; i <= 3; i++ { if err := profiler.Start(profiler.Config{ Service: service, ServiceVersion: version, // ProjectID must be set if not running on GCP. // ProjectID: "my-project", }); err != nil { log.Warnf("failed to start profiler: %+v", err) } else { log.Info("started Stackdriver profiler") return } d := time.Second * 10 * time.Duration(i) log.Infof("sleeping %v to retry initializing Stackdriver profiler", d) time.Sleep(d) } log.Warn("could not initialize Stackdriver profiler after retrying, giving up") } func mustMapEnv(target *string, envKey string) { v := os.Getenv(envKey) if v == "" { panic(fmt.Sprintf("environment variable %q not set", envKey)) } *target = v } func mustConnGRPC(ctx context.Context, conn **grpc.ClientConn, addr string) { var err error _, cancel := context.WithTimeout(ctx, time.Second*3) defer cancel() *conn, err = grpc.NewClient(addr, grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithStatsHandler(otelgrpc.NewClientHandler())) if err != nil { panic(errors.Wrapf(err, "grpc: failed to connect %s", addr)) } } func (cs *checkoutService) Check(ctx context.Context, req *healthpb.HealthCheckRequest) (*healthpb.HealthCheckResponse, error) { return &healthpb.HealthCheckResponse{Status: healthpb.HealthCheckResponse_SERVING}, nil } func (cs *checkoutService) Watch(req *healthpb.HealthCheckRequest, ws healthpb.Health_WatchServer) error { return status.Errorf(codes.Unimplemented, "health check via Watch not implemented") } func (cs *checkoutService) PlaceOrder(ctx context.Context, req *pb.PlaceOrderRequest) (*pb.PlaceOrderResponse, error) { log.Infof("[PlaceOrder] user_id=%q user_currency=%q", req.UserId, req.UserCurrency) orderID, err := uuid.NewUUID() if err != nil { return nil, status.Errorf(codes.Internal, "failed to generate order uuid") } prep, err := cs.prepareOrderItemsAndShippingQuoteFromCart(ctx, req.UserId, req.UserCurrency, req.Address) if err != nil { return nil, status.Errorf(codes.Internal, err.Error()) } total := pb.Money{CurrencyCode: req.UserCurrency, Units: 0, Nanos: 0} total = money.Must(money.Sum(total, *prep.shippingCostLocalized)) for _, it := range prep.orderItems { multPrice := money.MultiplySlow(*it.Cost, uint32(it.GetItem().GetQuantity())) total = money.Must(money.Sum(total, multPrice)) } txID, err := cs.chargeCard(ctx, &total, req.CreditCard) if err != nil { return nil, status.Errorf(codes.Internal, "failed to charge card: %+v", err) } log.Infof("payment went through (transaction_id: %s)", txID) shippingTrackingID, err := cs.shipOrder(ctx, req.Address, prep.cartItems) if err != nil { return nil, status.Errorf(codes.Unavailable, "shipping error: %+v", err) } _ = cs.emptyUserCart(ctx, req.UserId) orderResult := &pb.OrderResult{ OrderId: orderID.String(), ShippingTrackingId: shippingTrackingID, ShippingCost: prep.shippingCostLocalized, ShippingAddress: req.Address, Items: prep.orderItems, } if err := cs.sendOrderConfirmation(ctx, req.Email, orderResult); err != nil { log.Warnf("failed to send order confirmation to %q: %+v", req.Email, err) } else { log.Infof("order confirmation email sent to %q", req.Email) } resp := &pb.PlaceOrderResponse{Order: orderResult} return resp, nil } type orderPrep struct { orderItems []*pb.OrderItem cartItems []*pb.CartItem shippingCostLocalized *pb.Money } func (cs *checkoutService) prepareOrderItemsAndShippingQuoteFromCart(ctx context.Context, userID, userCurrency string, address *pb.Address) (orderPrep, error) { var out orderPrep cartItems, err := cs.getUserCart(ctx, userID) if err != nil { return out, fmt.Errorf("cart failure: %+v", err) } orderItems, err := cs.prepOrderItems(ctx, cartItems, userCurrency) if err != nil { return out, fmt.Errorf("failed to prepare order: %+v", err) } shippingUSD, err := cs.quoteShipping(ctx, address, cartItems) if err != nil { return out, fmt.Errorf("shipping quote failure: %+v", err) } shippingPrice, err := cs.convertCurrency(ctx, shippingUSD, userCurrency) if err != nil { return out, fmt.Errorf("failed to convert shipping cost to currency: %+v", err) } out.shippingCostLocalized = shippingPrice out.cartItems = cartItems out.orderItems = orderItems return out, nil } func (cs *checkoutService) quoteShipping(ctx context.Context, address *pb.Address, items []*pb.CartItem) (*pb.Money, error) { shippingQuote, err := pb.NewShippingServiceClient(cs.shippingSvcConn). GetQuote(ctx, &pb.GetQuoteRequest{ Address: address, Items: items}) if err != nil { return nil, fmt.Errorf("failed to get shipping quote: %+v", err) } return shippingQuote.GetCostUsd(), nil } func (cs *checkoutService) getUserCart(ctx context.Context, userID string) ([]*pb.CartItem, error) { cart, err := pb.NewCartServiceClient(cs.cartSvcConn).GetCart(ctx, &pb.GetCartRequest{UserId: userID}) if err != nil { return nil, fmt.Errorf("failed to get user cart during checkout: %+v", err) } return cart.GetItems(), nil } func (cs *checkoutService) emptyUserCart(ctx context.Context, userID string) error { if _, err := pb.NewCartServiceClient(cs.cartSvcConn).EmptyCart(ctx, &pb.EmptyCartRequest{UserId: userID}); err != nil { return fmt.Errorf("failed to empty user cart during checkout: %+v", err) } return nil } func (cs *checkoutService) prepOrderItems(ctx context.Context, items []*pb.CartItem, userCurrency string) ([]*pb.OrderItem, error) { out := make([]*pb.OrderItem, len(items)) cl := pb.NewProductCatalogServiceClient(cs.productCatalogSvcConn) for i, item := range items { product, err := cl.GetProduct(ctx, &pb.GetProductRequest{Id: item.GetProductId()}) if err != nil { return nil, fmt.Errorf("failed to get product #%q", item.GetProductId()) } price, err := cs.convertCurrency(ctx, product.GetPriceUsd(), userCurrency) if err != nil { return nil, fmt.Errorf("failed to convert price of %q to %s", item.GetProductId(), userCurrency) } out[i] = &pb.OrderItem{ Item: item, Cost: price} } return out, nil } func (cs *checkoutService) convertCurrency(ctx context.Context, from *pb.Money, toCurrency string) (*pb.Money, error) { result, err := pb.NewCurrencyServiceClient(cs.currencySvcConn).Convert(context.TODO(), &pb.CurrencyConversionRequest{ From: from, ToCode: toCurrency}) if err != nil { return nil, fmt.Errorf("failed to convert currency: %+v", err) } return result, err } func (cs *checkoutService) chargeCard(ctx context.Context, amount *pb.Money, paymentInfo *pb.CreditCardInfo) (string, error) { paymentResp, err := pb.NewPaymentServiceClient(cs.paymentSvcConn).Charge(ctx, &pb.ChargeRequest{ Amount: amount, CreditCard: paymentInfo}) if err != nil { return "", fmt.Errorf("could not charge the card: %+v", err) } return paymentResp.GetTransactionId(), nil } func (cs *checkoutService) sendOrderConfirmation(ctx context.Context, email string, order *pb.OrderResult) error { _, err := pb.NewEmailServiceClient(cs.emailSvcConn).SendOrderConfirmation(ctx, &pb.SendOrderConfirmationRequest{ Email: email, Order: order}) return err } func (cs *checkoutService) shipOrder(ctx context.Context, address *pb.Address, items []*pb.CartItem) (string, error) { resp, err := pb.NewShippingServiceClient(cs.shippingSvcConn).ShipOrder(ctx, &pb.ShipOrderRequest{ Address: address, Items: items}) if err != nil { return "", fmt.Errorf("shipment failed: %+v", err) } return resp.GetTrackingId(), nil } ================================================ FILE: src/checkoutservice/money/money.go ================================================ // Copyright 2018 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. package money import ( "errors" pb "github.com/GoogleCloudPlatform/microservices-demo/src/checkoutservice/genproto" ) const ( nanosMin = -999999999 nanosMax = +999999999 nanosMod = 1000000000 ) var ( ErrInvalidValue = errors.New("one of the specified money values is invalid") ErrMismatchingCurrency = errors.New("mismatching currency codes") ) // IsValid checks if specified value has a valid units/nanos signs and ranges. func IsValid(m pb.Money) bool { return signMatches(m) && validNanos(m.GetNanos()) } func signMatches(m pb.Money) bool { return m.GetNanos() == 0 || m.GetUnits() == 0 || (m.GetNanos() < 0) == (m.GetUnits() < 0) } func validNanos(nanos int32) bool { return nanosMin <= nanos && nanos <= nanosMax } // IsZero returns true if the specified money value is equal to zero. func IsZero(m pb.Money) bool { return m.GetUnits() == 0 && m.GetNanos() == 0 } // IsPositive returns true if the specified money value is valid and is // positive. func IsPositive(m pb.Money) bool { return IsValid(m) && m.GetUnits() > 0 || (m.GetUnits() == 0 && m.GetNanos() > 0) } // IsNegative returns true if the specified money value is valid and is // negative. func IsNegative(m pb.Money) bool { return IsValid(m) && m.GetUnits() < 0 || (m.GetUnits() == 0 && m.GetNanos() < 0) } // AreSameCurrency returns true if values l and r have a currency code and // they are the same values. func AreSameCurrency(l, r pb.Money) bool { return l.GetCurrencyCode() == r.GetCurrencyCode() && l.GetCurrencyCode() != "" } // AreEquals returns true if values l and r are the equal, including the // currency. This does not check validity of the provided values. func AreEquals(l, r pb.Money) bool { return l.GetCurrencyCode() == r.GetCurrencyCode() && l.GetUnits() == r.GetUnits() && l.GetNanos() == r.GetNanos() } // Negate returns the same amount with the sign negated. func Negate(m pb.Money) pb.Money { return pb.Money{ Units: -m.GetUnits(), Nanos: -m.GetNanos(), CurrencyCode: m.GetCurrencyCode()} } // Must panics if the given error is not nil. This can be used with other // functions like: "m := Must(Sum(a,b))". func Must(v pb.Money, err error) pb.Money { if err != nil { panic(err) } return v } // Sum adds two values. Returns an error if one of the values are invalid or // currency codes are not matching (unless currency code is unspecified for // both). func Sum(l, r pb.Money) (pb.Money, error) { if !IsValid(l) || !IsValid(r) { return pb.Money{}, ErrInvalidValue } else if l.GetCurrencyCode() != r.GetCurrencyCode() { return pb.Money{}, ErrMismatchingCurrency } units := l.GetUnits() + r.GetUnits() nanos := l.GetNanos() + r.GetNanos() if (units == 0 && nanos == 0) || (units > 0 && nanos >= 0) || (units < 0 && nanos <= 0) { // same sign units += int64(nanos / nanosMod) nanos = nanos % nanosMod } else { // different sign. nanos guaranteed to not to go over the limit if units > 0 { units-- nanos += nanosMod } else { units++ nanos -= nanosMod } } return pb.Money{ Units: units, Nanos: nanos, CurrencyCode: l.GetCurrencyCode()}, nil } // MultiplySlow is a slow multiplication operation done through adding the value // to itself n-1 times. func MultiplySlow(m pb.Money, n uint32) pb.Money { out := m for n > 1 { out = Must(Sum(out, m)) n-- } return out } ================================================ FILE: src/checkoutservice/money/money_test.go ================================================ // Copyright 2018 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. package money import ( "fmt" "reflect" "testing" pb "github.com/GoogleCloudPlatform/microservices-demo/src/checkoutservice/genproto" ) func mmc(u int64, n int32, c string) pb.Money { return pb.Money{Units: u, Nanos: n, CurrencyCode: c} } func mm(u int64, n int32) pb.Money { return mmc(u, n, "") } func TestIsValid(t *testing.T) { tests := []struct { name string in pb.Money want bool }{ {"valid -/-", mm(-981273891273, -999999999), true}, {"invalid -/+", mm(-981273891273, +999999999), false}, {"valid +/+", mm(981273891273, 999999999), true}, {"invalid +/-", mm(981273891273, -999999999), false}, {"invalid +/+overflow", mm(3, 1000000000), false}, {"invalid +/-overflow", mm(3, -1000000000), false}, {"invalid -/+overflow", mm(-3, 1000000000), false}, {"invalid -/-overflow", mm(-3, -1000000000), false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := IsValid(tt.in); got != tt.want { t.Errorf("IsValid(%v) = %v, want %v", tt.in, got, tt.want) } }) } } func TestIsZero(t *testing.T) { tests := []struct { name string in pb.Money want bool }{ {"zero", mm(0, 0), true}, {"not-zero (-/+)", mm(-1, +1), false}, {"not-zero (-/-)", mm(-1, -1), false}, {"not-zero (+/+)", mm(+1, +1), false}, {"not-zero (+/-)", mm(+1, -1), false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := IsZero(tt.in); got != tt.want { t.Errorf("IsZero(%v) = %v, want %v", tt.in, got, tt.want) } }) } } func TestIsPositive(t *testing.T) { tests := []struct { name string in pb.Money want bool }{ {"zero", mm(0, 0), false}, {"positive (+/+)", mm(+1, +1), true}, {"invalid (-/+)", mm(-1, +1), false}, {"negative (-/-)", mm(-1, -1), false}, {"invalid (+/-)", mm(+1, -1), false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := IsPositive(tt.in); got != tt.want { t.Errorf("IsPositive(%v) = %v, want %v", tt.in, got, tt.want) } }) } } func TestIsNegative(t *testing.T) { tests := []struct { name string in pb.Money want bool }{ {"zero", mm(0, 0), false}, {"positive (+/+)", mm(+1, +1), false}, {"invalid (-/+)", mm(-1, +1), false}, {"negative (-/-)", mm(-1, -1), true}, {"invalid (+/-)", mm(+1, -1), false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := IsNegative(tt.in); got != tt.want { t.Errorf("IsNegative(%v) = %v, want %v", tt.in, got, tt.want) } }) } } func TestAreSameCurrency(t *testing.T) { type args struct { l pb.Money r pb.Money } tests := []struct { name string args args want bool }{ {"both empty currency", args{mmc(1, 0, ""), mmc(2, 0, "")}, false}, {"left empty currency", args{mmc(1, 0, ""), mmc(2, 0, "USD")}, false}, {"right empty currency", args{mmc(1, 0, "USD"), mmc(2, 0, "")}, false}, {"mismatching", args{mmc(1, 0, "USD"), mmc(2, 0, "CAD")}, false}, {"matching", args{mmc(1, 0, "USD"), mmc(2, 0, "USD")}, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := AreSameCurrency(tt.args.l, tt.args.r); got != tt.want { t.Errorf("AreSameCurrency([%v],[%v]) = %v, want %v", tt.args.l, tt.args.r, got, tt.want) } }) } } func TestAreEquals(t *testing.T) { type args struct { l pb.Money r pb.Money } tests := []struct { name string args args want bool }{ {"equals", args{mmc(1, 2, "USD"), mmc(1, 2, "USD")}, true}, {"mismatching currency", args{mmc(1, 2, "USD"), mmc(1, 2, "CAD")}, false}, {"mismatching units", args{mmc(10, 20, "USD"), mmc(1, 20, "USD")}, false}, {"mismatching nanos", args{mmc(1, 2, "USD"), mmc(1, 20, "USD")}, false}, {"negated", args{mmc(1, 2, "USD"), mmc(-1, -2, "USD")}, false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := AreEquals(tt.args.l, tt.args.r); got != tt.want { t.Errorf("AreEquals([%v],[%v]) = %v, want %v", tt.args.l, tt.args.r, got, tt.want) } }) } } func TestNegate(t *testing.T) { tests := []struct { name string in pb.Money want pb.Money }{ {"zero", mm(0, 0), mm(0, 0)}, {"negative", mm(-1, -200), mm(1, 200)}, {"positive", mm(1, 200), mm(-1, -200)}, {"carries currency code", mmc(0, 0, "XXX"), mmc(0, 0, "XXX")}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := Negate(tt.in); !AreEquals(got, tt.want) { t.Errorf("Negate([%v]) = %v, want %v", tt.in, got, tt.want) } }) } } func TestMust_pass(t *testing.T) { v := Must(mm(2, 3), nil) if !AreEquals(v, mm(2, 3)) { t.Errorf("returned the wrong value: %v", v) } } func TestMust_panic(t *testing.T) { defer func() { if r := recover(); r != nil { t.Logf("panic captured: %v", r) } }() Must(mm(2, 3), fmt.Errorf("some error")) t.Fatal("this should not have executed due to the panic above") } func TestSum(t *testing.T) { type args struct { l pb.Money r pb.Money } tests := []struct { name string args args want pb.Money wantErr error }{ {"0+0=0", args{mm(0, 0), mm(0, 0)}, mm(0, 0), nil}, {"Error: currency code on left", args{mmc(0, 0, "XXX"), mm(0, 0)}, mm(0, 0), ErrMismatchingCurrency}, {"Error: currency code on right", args{mm(0, 0), mmc(0, 0, "YYY")}, mm(0, 0), ErrMismatchingCurrency}, {"Error: currency code mismatch", args{mmc(0, 0, "AAA"), mmc(0, 0, "BBB")}, mm(0, 0), ErrMismatchingCurrency}, {"Error: invalid +/-", args{mm(+1, -1), mm(0, 0)}, mm(0, 0), ErrInvalidValue}, {"Error: invalid -/+", args{mm(0, 0), mm(-1, +2)}, mm(0, 0), ErrInvalidValue}, {"Error: invalid nanos", args{mm(0, 1000000000), mm(1, 0)}, mm(0, 0), ErrInvalidValue}, {"both positive (no carry)", args{mm(2, 200000000), mm(2, 200000000)}, mm(4, 400000000), nil}, {"both positive (nanos=max)", args{mm(2, 111111111), mm(2, 888888888)}, mm(4, 999999999), nil}, {"both positive (carry)", args{mm(2, 200000000), mm(2, 900000000)}, mm(5, 100000000), nil}, {"both negative (no carry)", args{mm(-2, -200000000), mm(-2, -200000000)}, mm(-4, -400000000), nil}, {"both negative (carry)", args{mm(-2, -200000000), mm(-2, -900000000)}, mm(-5, -100000000), nil}, {"mixed (larger positive, just decimals)", args{mm(11, 0), mm(-2, 0)}, mm(9, 0), nil}, {"mixed (larger negative, just decimals)", args{mm(-11, 0), mm(2, 0)}, mm(-9, 0), nil}, {"mixed (larger positive, no borrow)", args{mm(11, 100000000), mm(-2, -100000000)}, mm(9, 0), nil}, {"mixed (larger positive, with borrow)", args{mm(11, 100000000), mm(-2, -9000000 /*.09*/)}, mm(9, 91000000 /*.091*/), nil}, {"mixed (larger negative, no borrow)", args{mm(-11, -100000000), mm(2, 100000000)}, mm(-9, 0), nil}, {"mixed (larger negative, with borrow)", args{mm(-11, -100000000), mm(2, 9000000 /*.09*/)}, mm(-9, -91000000 /*.091*/), nil}, {"0+negative", args{mm(0, 0), mm(-2, -100000000)}, mm(-2, -100000000), nil}, {"negative+0", args{mm(-2, -100000000), mm(0, 0)}, mm(-2, -100000000), nil}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := Sum(tt.args.l, tt.args.r) if err != tt.wantErr { t.Errorf("Sum([%v],[%v]): expected err=\"%v\" got=\"%v\"", tt.args.l, tt.args.r, tt.wantErr, err) } if !reflect.DeepEqual(got, tt.want) { t.Errorf("Sum([%v],[%v]) = %v, want %v", tt.args.l, tt.args.r, got, tt.want) } }) } } ================================================ FILE: src/currencyservice/.dockerignore ================================================ client.js node_modules/ ================================================ FILE: src/currencyservice/.gitignore ================================================ node_modules/ ================================================ FILE: src/currencyservice/Dockerfile ================================================ # Copyright 2020 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. # Define a default value so it's not empty if the builder fails to provide it ARG BUILDPLATFORM=linux/amd64 FROM --platform=$BUILDPLATFORM node:20.20.1-alpine@sha256:b88333c42c23fbd91596ebd7fd10de239cedab9617de04142dde7315e3bc0afa AS builder # Some packages (e.g. @google-cloud/profiler) require additional # deps for post-install scripts RUN apk add --update --no-cache \ python3 \ make \ g++ WORKDIR /usr/src/app COPY package*.json ./ RUN npm install --only=production FROM alpine:3.23.3@sha256:25109184c71bdad752c8312a8623239686a9a2071e8825f20acb8f2198c3f659 RUN apk add --no-cache nodejs WORKDIR /usr/src/app COPY --from=builder /usr/src/app/node_modules ./node_modules COPY . . EXPOSE 7000 ENTRYPOINT [ "node", "server.js" ] ================================================ FILE: src/currencyservice/client.js ================================================ /* * * Copyright 2015 gRPC authors. * * 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. * */ require('@google-cloud/trace-agent').start(); const path = require('path'); const grpc = require('grpc'); const pino = require('pino'); const PROTO_PATH = path.join(__dirname, './proto/demo.proto'); const PORT = 7000; const shopProto = grpc.load(PROTO_PATH).hipstershop; const client = new shopProto.CurrencyService(`localhost:${PORT}`, grpc.credentials.createInsecure()); const logger = pino({ name: 'currencyservice-client', messageKey: 'message', formatters: { level (logLevelString, logLevelNum) { return { severity: logLevelString } } } }); const request = { from: { currency_code: 'CHF', units: 300, nanos: 0 }, to_code: 'EUR' }; function _moneyToString (m) { return `${m.units}.${m.nanos.toString().padStart(9,'0')} ${m.currency_code}`; } client.getSupportedCurrencies({}, (err, response) => { if (err) { logger.error(`Error in getSupportedCurrencies: ${err}`); } else { logger.info(`Currency codes: ${response.currency_codes}`); } }); client.convert(request, (err, response) => { if (err) { logger.error(`Error in convert: ${err}`); } else { logger.log(`Convert: ${_moneyToString(request.from)} to ${_moneyToString(response)}`); } }); ================================================ FILE: src/currencyservice/data/currency_conversion.json ================================================ { "EUR": "1.0", "USD": "1.1305", "JPY": "126.40", "BGN": "1.9558", "CZK": "25.592", "DKK": "7.4609", "GBP": "0.85970", "HUF": "315.51", "PLN": "4.2996", "RON": "4.7463", "SEK": "10.5375", "CHF": "1.1360", "ISK": "136.80", "NOK": "9.8040", "HRK": "7.4210", "RUB": "74.4208", "TRY": "6.1247", "AUD": "1.6072", "BRL": "4.2682", "CAD": "1.5128", "CNY": "7.5857", "HKD": "8.8743", "IDR": "15999.40", "ILS": "4.0875", "INR": "79.4320", "KRW": "1275.05", "MXN": "21.7999", "MYR": "4.6289", "NZD": "1.6679", "PHP": "59.083", "SGD": "1.5349", "THB": "36.012", "ZAR": "16.0583" } ================================================ FILE: src/currencyservice/genproto.sh ================================================ #!/bin/bash -eu # # Copyright 2018 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 gke_currencyservice_genproto] # protos are loaded dynamically for node, simply copies over the proto. mkdir -p proto cp -r ../../protos/* ./proto # [END gke_currencyservice_genproto] ================================================ FILE: src/currencyservice/package.json ================================================ { "name": "grpc-currency-service", "version": "0.1.0", "description": "A gRPC currency conversion microservice", "repository": "https://github.com/GoogleCloudPlatform/microservices-demo", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "license": "Apache-2.0", "dependencies": { "@google-cloud/profiler": "6.0.4", "@google-cloud/trace-agent": "8.0.0", "@grpc/grpc-js": "1.14.3", "@grpc/proto-loader": "0.8.0", "async": "3.2.6", "google-protobuf": "4.0.2", "@opentelemetry/api": "1.9.0", "@opentelemetry/exporter-otlp-grpc": "0.26.0", "@opentelemetry/instrumentation-grpc": "0.213.0", "@opentelemetry/resources": "2.6.0", "@opentelemetry/semantic-conventions": "1.40.0", "@opentelemetry/sdk-trace-base": "2.6.0", "@opentelemetry/sdk-node": "0.213.0", "pino": "10.3.1", "xml2js": "0.6.2" } } ================================================ FILE: src/currencyservice/proto/demo.proto ================================================ // Copyright 2020 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. syntax = "proto3"; package hipstershop; // -----------------Cart service----------------- service CartService { rpc AddItem(AddItemRequest) returns (Empty) {} rpc GetCart(GetCartRequest) returns (Cart) {} rpc EmptyCart(EmptyCartRequest) returns (Empty) {} } message CartItem { string product_id = 1; int32 quantity = 2; } message AddItemRequest { string user_id = 1; CartItem item = 2; } message EmptyCartRequest { string user_id = 1; } message GetCartRequest { string user_id = 1; } message Cart { string user_id = 1; repeated CartItem items = 2; } message Empty {} // ---------------Recommendation service---------- service RecommendationService { rpc ListRecommendations(ListRecommendationsRequest) returns (ListRecommendationsResponse){} } message ListRecommendationsRequest { string user_id = 1; repeated string product_ids = 2; } message ListRecommendationsResponse { repeated string product_ids = 1; } // ---------------Product Catalog---------------- service ProductCatalogService { rpc ListProducts(Empty) returns (ListProductsResponse) {} rpc GetProduct(GetProductRequest) returns (Product) {} rpc SearchProducts(SearchProductsRequest) returns (SearchProductsResponse) {} } message Product { string id = 1; string name = 2; string description = 3; string picture = 4; Money price_usd = 5; // Categories such as "clothing" or "kitchen" that can be used to look up // other related products. repeated string categories = 6; } message ListProductsResponse { repeated Product products = 1; } message GetProductRequest { string id = 1; } message SearchProductsRequest { string query = 1; } message SearchProductsResponse { repeated Product results = 1; } // ---------------Shipping Service---------- service ShippingService { rpc GetQuote(GetQuoteRequest) returns (GetQuoteResponse) {} rpc ShipOrder(ShipOrderRequest) returns (ShipOrderResponse) {} } message GetQuoteRequest { Address address = 1; repeated CartItem items = 2; } message GetQuoteResponse { Money cost_usd = 1; } message ShipOrderRequest { Address address = 1; repeated CartItem items = 2; } message ShipOrderResponse { string tracking_id = 1; } message Address { string street_address = 1; string city = 2; string state = 3; string country = 4; int32 zip_code = 5; } // -----------------Currency service----------------- service CurrencyService { rpc GetSupportedCurrencies(Empty) returns (GetSupportedCurrenciesResponse) {} rpc Convert(CurrencyConversionRequest) returns (Money) {} } // Represents an amount of money with its currency type. message Money { // The 3-letter currency code defined in ISO 4217. string currency_code = 1; // The whole units of the amount. // For example if `currencyCode` is `"USD"`, then 1 unit is one US dollar. int64 units = 2; // Number of nano (10^-9) units of the amount. // The value must be between -999,999,999 and +999,999,999 inclusive. // If `units` is positive, `nanos` must be positive or zero. // If `units` is zero, `nanos` can be positive, zero, or negative. // If `units` is negative, `nanos` must be negative or zero. // For example $-1.75 is represented as `units`=-1 and `nanos`=-750,000,000. int32 nanos = 3; } message GetSupportedCurrenciesResponse { // The 3-letter currency code defined in ISO 4217. repeated string currency_codes = 1; } message CurrencyConversionRequest { Money from = 1; // The 3-letter currency code defined in ISO 4217. string to_code = 2; } // -------------Payment service----------------- service PaymentService { rpc Charge(ChargeRequest) returns (ChargeResponse) {} } message CreditCardInfo { string credit_card_number = 1; int32 credit_card_cvv = 2; int32 credit_card_expiration_year = 3; int32 credit_card_expiration_month = 4; } message ChargeRequest { Money amount = 1; CreditCardInfo credit_card = 2; } message ChargeResponse { string transaction_id = 1; } // -------------Email service----------------- service EmailService { rpc SendOrderConfirmation(SendOrderConfirmationRequest) returns (Empty) {} } message OrderItem { CartItem item = 1; Money cost = 2; } message OrderResult { string order_id = 1; string shipping_tracking_id = 2; Money shipping_cost = 3; Address shipping_address = 4; repeated OrderItem items = 5; } message SendOrderConfirmationRequest { string email = 1; OrderResult order = 2; } // -------------Checkout service----------------- service CheckoutService { rpc PlaceOrder(PlaceOrderRequest) returns (PlaceOrderResponse) {} } message PlaceOrderRequest { string user_id = 1; string user_currency = 2; Address address = 3; string email = 5; CreditCardInfo credit_card = 6; } message PlaceOrderResponse { OrderResult order = 1; } // ------------Ad service------------------ service AdService { rpc GetAds(AdRequest) returns (AdResponse) {} } message AdRequest { // List of important key words from the current page describing the context. repeated string context_keys = 1; } message AdResponse { repeated Ad ads = 1; } message Ad { // url to redirect to when an ad is clicked. string redirect_url = 1; // short advertisement text to display. string text = 2; } ================================================ FILE: src/currencyservice/proto/grpc/health/v1/health.proto ================================================ // Copyright 2015 The gRPC Authors // // 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. // The canonical version of this proto can be found at // https://github.com/grpc/grpc-proto/blob/master/grpc/health/v1/health.proto syntax = "proto3"; package grpc.health.v1; option csharp_namespace = "Grpc.Health.V1"; option go_package = "google.golang.org/grpc/health/grpc_health_v1"; option java_multiple_files = true; option java_outer_classname = "HealthProto"; option java_package = "io.grpc.health.v1"; message HealthCheckRequest { string service = 1; } message HealthCheckResponse { enum ServingStatus { UNKNOWN = 0; SERVING = 1; NOT_SERVING = 2; } ServingStatus status = 1; } service Health { rpc Check(HealthCheckRequest) returns (HealthCheckResponse); } ================================================ FILE: src/currencyservice/server.js ================================================ /* * Copyright 2018 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. */ const pino = require('pino'); const logger = pino({ name: 'currencyservice-server', messageKey: 'message', formatters: { level (logLevelString, logLevelNum) { return { severity: logLevelString } } } }); if(process.env.DISABLE_PROFILER) { logger.info("Profiler disabled.") } else { logger.info("Profiler enabled.") require('@google-cloud/profiler').start({ serviceContext: { service: 'currencyservice', version: '1.0.0' } }); } // Register GRPC OTel Instrumentation for trace propagation // regardless of whether tracing is emitted. const { GrpcInstrumentation } = require('@opentelemetry/instrumentation-grpc'); const { registerInstrumentations } = require('@opentelemetry/instrumentation'); registerInstrumentations({ instrumentations: [new GrpcInstrumentation()] }); if(process.env.ENABLE_TRACING == "1") { logger.info("Tracing enabled.") const { resourceFromAttributes } = require('@opentelemetry/resources'); const { ATTR_SERVICE_NAME } = require('@opentelemetry/semantic-conventions'); const opentelemetry = require('@opentelemetry/sdk-node'); const { OTLPTraceExporter } = require('@opentelemetry/exporter-otlp-grpc'); const collectorUrl = process.env.COLLECTOR_SERVICE_ADDR; const traceExporter = new OTLPTraceExporter({url: collectorUrl}); const sdk = new opentelemetry.NodeSDK({ resource: resourceFromAttributes({ [ ATTR_SERVICE_NAME ]: process.env.OTEL_SERVICE_NAME || 'currencyservice', }), traceExporter: traceExporter, }); sdk.start() } else { logger.info("Tracing disabled.") } const path = require('path'); const grpc = require('@grpc/grpc-js'); const protoLoader = require('@grpc/proto-loader'); const MAIN_PROTO_PATH = path.join(__dirname, './proto/demo.proto'); const HEALTH_PROTO_PATH = path.join(__dirname, './proto/grpc/health/v1/health.proto'); const PORT = process.env.PORT; const shopProto = _loadProto(MAIN_PROTO_PATH).hipstershop; const healthProto = _loadProto(HEALTH_PROTO_PATH).grpc.health.v1; /** * Helper function that loads a protobuf file. */ function _loadProto (path) { const packageDefinition = protoLoader.loadSync( path, { keepCase: true, longs: String, enums: String, defaults: true, oneofs: true } ); return grpc.loadPackageDefinition(packageDefinition); } /** * Helper function that gets currency data from a stored JSON file * Uses public data from European Central Bank */ function _getCurrencyData (callback) { const data = require('./data/currency_conversion.json'); callback(data); } /** * Helper function that handles decimal/fractional carrying */ function _carry (amount) { const fractionSize = Math.pow(10, 9); amount.nanos += (amount.units % 1) * fractionSize; amount.units = Math.floor(amount.units) + Math.floor(amount.nanos / fractionSize); amount.nanos = amount.nanos % fractionSize; return amount; } /** * Lists the supported currencies */ function getSupportedCurrencies (call, callback) { logger.info('Getting supported currencies...'); _getCurrencyData((data) => { callback(null, {currency_codes: Object.keys(data)}); }); } /** * Converts between currencies */ function convert (call, callback) { try { _getCurrencyData((data) => { const request = call.request; // Convert: from_currency --> EUR const from = request.from; const euros = _carry({ units: from.units / data[from.currency_code], nanos: from.nanos / data[from.currency_code] }); euros.nanos = Math.round(euros.nanos); // Convert: EUR --> to_currency const result = _carry({ units: euros.units * data[request.to_code], nanos: euros.nanos * data[request.to_code] }); result.units = Math.floor(result.units); result.nanos = Math.floor(result.nanos); result.currency_code = request.to_code; logger.info(`conversion request successful`); callback(null, result); }); } catch (err) { logger.error(`conversion request failed: ${err}`); callback(err.message); } } /** * Endpoint for health checks */ function check (call, callback) { callback(null, { status: 'SERVING' }); } /** * Starts an RPC server that receives requests for the * CurrencyConverter service at the sample server port */ function main () { logger.info(`Starting gRPC server on port ${PORT}...`); const server = new grpc.Server(); server.addService(shopProto.CurrencyService.service, {getSupportedCurrencies, convert}); server.addService(healthProto.Health.service, {check}); server.bindAsync( `[::]:${PORT}`, grpc.ServerCredentials.createInsecure(), function() { logger.info(`CurrencyService gRPC server started on port ${PORT}`); server.start(); }, ); } main(); ================================================ FILE: src/emailservice/Dockerfile ================================================ # Copyright 2020 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. # Define a default value so it's not empty if the builder fails to provide it ARG BUILDPLATFORM=linux/amd64 FROM --platform=$BUILDPLATFORM python:3.14.3-alpine@sha256:faee120f7885a06fcc9677922331391fa690d911c020abb9e8025ff3d908e510 AS base FROM base AS builder ENV PYTHONDONTWRITEBYTECODE=1 ENV PYTHONUNBUFFERED=1 RUN apk update \ && apk add --no-cache g++ linux-headers \ && rm -rf /var/cache/apk/* # get packages COPY requirements.txt . RUN pip install -r requirements.txt FROM base ENV PYTHONDONTWRITEBYTECODE=1 ENV PYTHONUNBUFFERED=1 # Enable Profiler ENV ENABLE_PROFILER=1 RUN apk update \ && apk add --no-cache libstdc++ \ && rm -rf /var/cache/apk/* WORKDIR /email_server # Grab packages from builder COPY --from=builder /usr/local/lib/python3.14/ /usr/local/lib/python3.14/ # Add the application COPY . . EXPOSE 8080 ENTRYPOINT [ "python", "email_server.py" ] ================================================ FILE: src/emailservice/demo_pb2.py ================================================ #!/usr/bin/python # # Copyright 2018 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. # -*- coding: utf-8 -*- # Generated by the protocol buffer compiler. DO NOT EDIT! # source: demo.proto """Generated protocol buffer code.""" from google.protobuf.internal import builder as _builder from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool from google.protobuf import symbol_database as _symbol_database # @@protoc_insertion_point(imports) _sym_db = _symbol_database.Default() DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\ndemo.proto\x12\x0bhipstershop\"0\n\x08\x43\x61rtItem\x12\x12\n\nproduct_id\x18\x01 \x01(\t\x12\x10\n\x08quantity\x18\x02 \x01(\x05\"F\n\x0e\x41\x64\x64ItemRequest\x12\x0f\n\x07user_id\x18\x01 \x01(\t\x12#\n\x04item\x18\x02 \x01(\x0b\x32\x15.hipstershop.CartItem\"#\n\x10\x45mptyCartRequest\x12\x0f\n\x07user_id\x18\x01 \x01(\t\"!\n\x0eGetCartRequest\x12\x0f\n\x07user_id\x18\x01 \x01(\t\"=\n\x04\x43\x61rt\x12\x0f\n\x07user_id\x18\x01 \x01(\t\x12$\n\x05items\x18\x02 \x03(\x0b\x32\x15.hipstershop.CartItem\"\x07\n\x05\x45mpty\"B\n\x1aListRecommendationsRequest\x12\x0f\n\x07user_id\x18\x01 \x01(\t\x12\x13\n\x0bproduct_ids\x18\x02 \x03(\t\"2\n\x1bListRecommendationsResponse\x12\x13\n\x0bproduct_ids\x18\x01 \x03(\t\"\x84\x01\n\x07Product\x12\n\n\x02id\x18\x01 \x01(\t\x12\x0c\n\x04name\x18\x02 \x01(\t\x12\x13\n\x0b\x64\x65scription\x18\x03 \x01(\t\x12\x0f\n\x07picture\x18\x04 \x01(\t\x12%\n\tprice_usd\x18\x05 \x01(\x0b\x32\x12.hipstershop.Money\x12\x12\n\ncategories\x18\x06 \x03(\t\">\n\x14ListProductsResponse\x12&\n\x08products\x18\x01 \x03(\x0b\x32\x14.hipstershop.Product\"\x1f\n\x11GetProductRequest\x12\n\n\x02id\x18\x01 \x01(\t\"&\n\x15SearchProductsRequest\x12\r\n\x05query\x18\x01 \x01(\t\"?\n\x16SearchProductsResponse\x12%\n\x07results\x18\x01 \x03(\x0b\x32\x14.hipstershop.Product\"^\n\x0fGetQuoteRequest\x12%\n\x07\x61\x64\x64ress\x18\x01 \x01(\x0b\x32\x14.hipstershop.Address\x12$\n\x05items\x18\x02 \x03(\x0b\x32\x15.hipstershop.CartItem\"8\n\x10GetQuoteResponse\x12$\n\x08\x63ost_usd\x18\x01 \x01(\x0b\x32\x12.hipstershop.Money\"_\n\x10ShipOrderRequest\x12%\n\x07\x61\x64\x64ress\x18\x01 \x01(\x0b\x32\x14.hipstershop.Address\x12$\n\x05items\x18\x02 \x03(\x0b\x32\x15.hipstershop.CartItem\"(\n\x11ShipOrderResponse\x12\x13\n\x0btracking_id\x18\x01 \x01(\t\"a\n\x07\x41\x64\x64ress\x12\x16\n\x0estreet_address\x18\x01 \x01(\t\x12\x0c\n\x04\x63ity\x18\x02 \x01(\t\x12\r\n\x05state\x18\x03 \x01(\t\x12\x0f\n\x07\x63ountry\x18\x04 \x01(\t\x12\x10\n\x08zip_code\x18\x05 \x01(\x05\"<\n\x05Money\x12\x15\n\rcurrency_code\x18\x01 \x01(\t\x12\r\n\x05units\x18\x02 \x01(\x03\x12\r\n\x05nanos\x18\x03 \x01(\x05\"8\n\x1eGetSupportedCurrenciesResponse\x12\x16\n\x0e\x63urrency_codes\x18\x01 \x03(\t\"N\n\x19\x43urrencyConversionRequest\x12 \n\x04\x66rom\x18\x01 \x01(\x0b\x32\x12.hipstershop.Money\x12\x0f\n\x07to_code\x18\x02 \x01(\t\"\x90\x01\n\x0e\x43reditCardInfo\x12\x1a\n\x12\x63redit_card_number\x18\x01 \x01(\t\x12\x17\n\x0f\x63redit_card_cvv\x18\x02 \x01(\x05\x12#\n\x1b\x63redit_card_expiration_year\x18\x03 \x01(\x05\x12$\n\x1c\x63redit_card_expiration_month\x18\x04 \x01(\x05\"e\n\rChargeRequest\x12\"\n\x06\x61mount\x18\x01 \x01(\x0b\x32\x12.hipstershop.Money\x12\x30\n\x0b\x63redit_card\x18\x02 \x01(\x0b\x32\x1b.hipstershop.CreditCardInfo\"(\n\x0e\x43hargeResponse\x12\x16\n\x0etransaction_id\x18\x01 \x01(\t\"R\n\tOrderItem\x12#\n\x04item\x18\x01 \x01(\x0b\x32\x15.hipstershop.CartItem\x12 \n\x04\x63ost\x18\x02 \x01(\x0b\x32\x12.hipstershop.Money\"\xbf\x01\n\x0bOrderResult\x12\x10\n\x08order_id\x18\x01 \x01(\t\x12\x1c\n\x14shipping_tracking_id\x18\x02 \x01(\t\x12)\n\rshipping_cost\x18\x03 \x01(\x0b\x32\x12.hipstershop.Money\x12.\n\x10shipping_address\x18\x04 \x01(\x0b\x32\x14.hipstershop.Address\x12%\n\x05items\x18\x05 \x03(\x0b\x32\x16.hipstershop.OrderItem\"V\n\x1cSendOrderConfirmationRequest\x12\r\n\x05\x65mail\x18\x01 \x01(\t\x12\'\n\x05order\x18\x02 \x01(\x0b\x32\x18.hipstershop.OrderResult\"\xa3\x01\n\x11PlaceOrderRequest\x12\x0f\n\x07user_id\x18\x01 \x01(\t\x12\x15\n\ruser_currency\x18\x02 \x01(\t\x12%\n\x07\x61\x64\x64ress\x18\x03 \x01(\x0b\x32\x14.hipstershop.Address\x12\r\n\x05\x65mail\x18\x05 \x01(\t\x12\x30\n\x0b\x63redit_card\x18\x06 \x01(\x0b\x32\x1b.hipstershop.CreditCardInfo\"=\n\x12PlaceOrderResponse\x12\'\n\x05order\x18\x01 \x01(\x0b\x32\x18.hipstershop.OrderResult\"!\n\tAdRequest\x12\x14\n\x0c\x63ontext_keys\x18\x01 \x03(\t\"*\n\nAdResponse\x12\x1c\n\x03\x61\x64s\x18\x01 \x03(\x0b\x32\x0f.hipstershop.Ad\"(\n\x02\x41\x64\x12\x14\n\x0credirect_url\x18\x01 \x01(\t\x12\x0c\n\x04text\x18\x02 \x01(\t2\xca\x01\n\x0b\x43\x61rtService\x12<\n\x07\x41\x64\x64Item\x12\x1b.hipstershop.AddItemRequest\x1a\x12.hipstershop.Empty\"\x00\x12;\n\x07GetCart\x12\x1b.hipstershop.GetCartRequest\x1a\x11.hipstershop.Cart\"\x00\x12@\n\tEmptyCart\x12\x1d.hipstershop.EmptyCartRequest\x1a\x12.hipstershop.Empty\"\x00\x32\x83\x01\n\x15RecommendationService\x12j\n\x13ListRecommendations\x12\'.hipstershop.ListRecommendationsRequest\x1a(.hipstershop.ListRecommendationsResponse\"\x00\x32\x83\x02\n\x15ProductCatalogService\x12G\n\x0cListProducts\x12\x12.hipstershop.Empty\x1a!.hipstershop.ListProductsResponse\"\x00\x12\x44\n\nGetProduct\x12\x1e.hipstershop.GetProductRequest\x1a\x14.hipstershop.Product\"\x00\x12[\n\x0eSearchProducts\x12\".hipstershop.SearchProductsRequest\x1a#.hipstershop.SearchProductsResponse\"\x00\x32\xaa\x01\n\x0fShippingService\x12I\n\x08GetQuote\x12\x1c.hipstershop.GetQuoteRequest\x1a\x1d.hipstershop.GetQuoteResponse\"\x00\x12L\n\tShipOrder\x12\x1d.hipstershop.ShipOrderRequest\x1a\x1e.hipstershop.ShipOrderResponse\"\x00\x32\xb7\x01\n\x0f\x43urrencyService\x12[\n\x16GetSupportedCurrencies\x12\x12.hipstershop.Empty\x1a+.hipstershop.GetSupportedCurrenciesResponse\"\x00\x12G\n\x07\x43onvert\x12&.hipstershop.CurrencyConversionRequest\x1a\x12.hipstershop.Money\"\x00\x32U\n\x0ePaymentService\x12\x43\n\x06\x43harge\x12\x1a.hipstershop.ChargeRequest\x1a\x1b.hipstershop.ChargeResponse\"\x00\x32h\n\x0c\x45mailService\x12X\n\x15SendOrderConfirmation\x12).hipstershop.SendOrderConfirmationRequest\x1a\x12.hipstershop.Empty\"\x00\x32\x62\n\x0f\x43heckoutService\x12O\n\nPlaceOrder\x12\x1e.hipstershop.PlaceOrderRequest\x1a\x1f.hipstershop.PlaceOrderResponse\"\x00\x32H\n\tAdService\x12;\n\x06GetAds\x12\x16.hipstershop.AdRequest\x1a\x17.hipstershop.AdResponse\"\x00\x62\x06proto3') _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals()) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'demo_pb2', globals()) if _descriptor._USE_C_DESCRIPTORS == False: DESCRIPTOR._options = None _CARTITEM._serialized_start=27 _CARTITEM._serialized_end=75 _ADDITEMREQUEST._serialized_start=77 _ADDITEMREQUEST._serialized_end=147 _EMPTYCARTREQUEST._serialized_start=149 _EMPTYCARTREQUEST._serialized_end=184 _GETCARTREQUEST._serialized_start=186 _GETCARTREQUEST._serialized_end=219 _CART._serialized_start=221 _CART._serialized_end=282 _EMPTY._serialized_start=284 _EMPTY._serialized_end=291 _LISTRECOMMENDATIONSREQUEST._serialized_start=293 _LISTRECOMMENDATIONSREQUEST._serialized_end=359 _LISTRECOMMENDATIONSRESPONSE._serialized_start=361 _LISTRECOMMENDATIONSRESPONSE._serialized_end=411 _PRODUCT._serialized_start=414 _PRODUCT._serialized_end=546 _LISTPRODUCTSRESPONSE._serialized_start=548 _LISTPRODUCTSRESPONSE._serialized_end=610 _GETPRODUCTREQUEST._serialized_start=612 _GETPRODUCTREQUEST._serialized_end=643 _SEARCHPRODUCTSREQUEST._serialized_start=645 _SEARCHPRODUCTSREQUEST._serialized_end=683 _SEARCHPRODUCTSRESPONSE._serialized_start=685 _SEARCHPRODUCTSRESPONSE._serialized_end=748 _GETQUOTEREQUEST._serialized_start=750 _GETQUOTEREQUEST._serialized_end=844 _GETQUOTERESPONSE._serialized_start=846 _GETQUOTERESPONSE._serialized_end=902 _SHIPORDERREQUEST._serialized_start=904 _SHIPORDERREQUEST._serialized_end=999 _SHIPORDERRESPONSE._serialized_start=1001 _SHIPORDERRESPONSE._serialized_end=1041 _ADDRESS._serialized_start=1043 _ADDRESS._serialized_end=1140 _MONEY._serialized_start=1142 _MONEY._serialized_end=1202 _GETSUPPORTEDCURRENCIESRESPONSE._serialized_start=1204 _GETSUPPORTEDCURRENCIESRESPONSE._serialized_end=1260 _CURRENCYCONVERSIONREQUEST._serialized_start=1262 _CURRENCYCONVERSIONREQUEST._serialized_end=1340 _CREDITCARDINFO._serialized_start=1343 _CREDITCARDINFO._serialized_end=1487 _CHARGEREQUEST._serialized_start=1489 _CHARGEREQUEST._serialized_end=1590 _CHARGERESPONSE._serialized_start=1592 _CHARGERESPONSE._serialized_end=1632 _ORDERITEM._serialized_start=1634 _ORDERITEM._serialized_end=1716 _ORDERRESULT._serialized_start=1719 _ORDERRESULT._serialized_end=1910 _SENDORDERCONFIRMATIONREQUEST._serialized_start=1912 _SENDORDERCONFIRMATIONREQUEST._serialized_end=1998 _PLACEORDERREQUEST._serialized_start=2001 _PLACEORDERREQUEST._serialized_end=2164 _PLACEORDERRESPONSE._serialized_start=2166 _PLACEORDERRESPONSE._serialized_end=2227 _ADREQUEST._serialized_start=2229 _ADREQUEST._serialized_end=2262 _ADRESPONSE._serialized_start=2264 _ADRESPONSE._serialized_end=2306 _AD._serialized_start=2308 _AD._serialized_end=2348 _CARTSERVICE._serialized_start=2351 _CARTSERVICE._serialized_end=2553 _RECOMMENDATIONSERVICE._serialized_start=2556 _RECOMMENDATIONSERVICE._serialized_end=2687 _PRODUCTCATALOGSERVICE._serialized_start=2690 _PRODUCTCATALOGSERVICE._serialized_end=2949 _SHIPPINGSERVICE._serialized_start=2952 _SHIPPINGSERVICE._serialized_end=3122 _CURRENCYSERVICE._serialized_start=3125 _CURRENCYSERVICE._serialized_end=3308 _PAYMENTSERVICE._serialized_start=3310 _PAYMENTSERVICE._serialized_end=3395 _EMAILSERVICE._serialized_start=3397 _EMAILSERVICE._serialized_end=3501 _CHECKOUTSERVICE._serialized_start=3503 _CHECKOUTSERVICE._serialized_end=3601 _ADSERVICE._serialized_start=3603 _ADSERVICE._serialized_end=3675 # @@protoc_insertion_point(module_scope) ================================================ FILE: src/emailservice/demo_pb2_grpc.py ================================================ #!/usr/bin/python # # Copyright 2018 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. # Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! """Client and server classes corresponding to protobuf-defined services.""" import grpc import demo_pb2 as demo__pb2 class CartServiceStub(object): """-----------------Cart service----------------- """ def __init__(self, channel): """Constructor. Args: channel: A grpc.Channel. """ self.AddItem = channel.unary_unary( '/hipstershop.CartService/AddItem', request_serializer=demo__pb2.AddItemRequest.SerializeToString, response_deserializer=demo__pb2.Empty.FromString, ) self.GetCart = channel.unary_unary( '/hipstershop.CartService/GetCart', request_serializer=demo__pb2.GetCartRequest.SerializeToString, response_deserializer=demo__pb2.Cart.FromString, ) self.EmptyCart = channel.unary_unary( '/hipstershop.CartService/EmptyCart', request_serializer=demo__pb2.EmptyCartRequest.SerializeToString, response_deserializer=demo__pb2.Empty.FromString, ) class CartServiceServicer(object): """-----------------Cart service----------------- """ def AddItem(self, request, context): """Missing associated documentation comment in .proto file.""" context.set_code(grpc.StatusCode.UNIMPLEMENTED) context.set_details('Method not implemented!') raise NotImplementedError('Method not implemented!') def GetCart(self, request, context): """Missing associated documentation comment in .proto file.""" context.set_code(grpc.StatusCode.UNIMPLEMENTED) context.set_details('Method not implemented!') raise NotImplementedError('Method not implemented!') def EmptyCart(self, request, context): """Missing associated documentation comment in .proto file.""" context.set_code(grpc.StatusCode.UNIMPLEMENTED) context.set_details('Method not implemented!') raise NotImplementedError('Method not implemented!') def add_CartServiceServicer_to_server(servicer, server): rpc_method_handlers = { 'AddItem': grpc.unary_unary_rpc_method_handler( servicer.AddItem, request_deserializer=demo__pb2.AddItemRequest.FromString, response_serializer=demo__pb2.Empty.SerializeToString, ), 'GetCart': grpc.unary_unary_rpc_method_handler( servicer.GetCart, request_deserializer=demo__pb2.GetCartRequest.FromString, response_serializer=demo__pb2.Cart.SerializeToString, ), 'EmptyCart': grpc.unary_unary_rpc_method_handler( servicer.EmptyCart, request_deserializer=demo__pb2.EmptyCartRequest.FromString, response_serializer=demo__pb2.Empty.SerializeToString, ), } generic_handler = grpc.method_handlers_generic_handler( 'hipstershop.CartService', rpc_method_handlers) server.add_generic_rpc_handlers((generic_handler,)) # This class is part of an EXPERIMENTAL API. class CartService(object): """-----------------Cart service----------------- """ @staticmethod def AddItem(request, target, options=(), channel_credentials=None, call_credentials=None, insecure=False, compression=None, wait_for_ready=None, timeout=None, metadata=None): return grpc.experimental.unary_unary(request, target, '/hipstershop.CartService/AddItem', demo__pb2.AddItemRequest.SerializeToString, demo__pb2.Empty.FromString, options, channel_credentials, insecure, call_credentials, compression, wait_for_ready, timeout, metadata) @staticmethod def GetCart(request, target, options=(), channel_credentials=None, call_credentials=None, insecure=False, compression=None, wait_for_ready=None, timeout=None, metadata=None): return grpc.experimental.unary_unary(request, target, '/hipstershop.CartService/GetCart', demo__pb2.GetCartRequest.SerializeToString, demo__pb2.Cart.FromString, options, channel_credentials, insecure, call_credentials, compression, wait_for_ready, timeout, metadata) @staticmethod def EmptyCart(request, target, options=(), channel_credentials=None, call_credentials=None, insecure=False, compression=None, wait_for_ready=None, timeout=None, metadata=None): return grpc.experimental.unary_unary(request, target, '/hipstershop.CartService/EmptyCart', demo__pb2.EmptyCartRequest.SerializeToString, demo__pb2.Empty.FromString, options, channel_credentials, insecure, call_credentials, compression, wait_for_ready, timeout, metadata) class RecommendationServiceStub(object): """---------------Recommendation service---------- """ def __init__(self, channel): """Constructor. Args: channel: A grpc.Channel. """ self.ListRecommendations = channel.unary_unary( '/hipstershop.RecommendationService/ListRecommendations', request_serializer=demo__pb2.ListRecommendationsRequest.SerializeToString, response_deserializer=demo__pb2.ListRecommendationsResponse.FromString, ) class RecommendationServiceServicer(object): """---------------Recommendation service---------- """ def ListRecommendations(self, request, context): """Missing associated documentation comment in .proto file.""" context.set_code(grpc.StatusCode.UNIMPLEMENTED) context.set_details('Method not implemented!') raise NotImplementedError('Method not implemented!') def add_RecommendationServiceServicer_to_server(servicer, server): rpc_method_handlers = { 'ListRecommendations': grpc.unary_unary_rpc_method_handler( servicer.ListRecommendations, request_deserializer=demo__pb2.ListRecommendationsRequest.FromString, response_serializer=demo__pb2.ListRecommendationsResponse.SerializeToString, ), } generic_handler = grpc.method_handlers_generic_handler( 'hipstershop.RecommendationService', rpc_method_handlers) server.add_generic_rpc_handlers((generic_handler,)) # This class is part of an EXPERIMENTAL API. class RecommendationService(object): """---------------Recommendation service---------- """ @staticmethod def ListRecommendations(request, target, options=(), channel_credentials=None, call_credentials=None, insecure=False, compression=None, wait_for_ready=None, timeout=None, metadata=None): return grpc.experimental.unary_unary(request, target, '/hipstershop.RecommendationService/ListRecommendations', demo__pb2.ListRecommendationsRequest.SerializeToString, demo__pb2.ListRecommendationsResponse.FromString, options, channel_credentials, insecure, call_credentials, compression, wait_for_ready, timeout, metadata) class ProductCatalogServiceStub(object): """---------------Product Catalog---------------- """ def __init__(self, channel): """Constructor. Args: channel: A grpc.Channel. """ self.ListProducts = channel.unary_unary( '/hipstershop.ProductCatalogService/ListProducts', request_serializer=demo__pb2.Empty.SerializeToString, response_deserializer=demo__pb2.ListProductsResponse.FromString, ) self.GetProduct = channel.unary_unary( '/hipstershop.ProductCatalogService/GetProduct', request_serializer=demo__pb2.GetProductRequest.SerializeToString, response_deserializer=demo__pb2.Product.FromString, ) self.SearchProducts = channel.unary_unary( '/hipstershop.ProductCatalogService/SearchProducts', request_serializer=demo__pb2.SearchProductsRequest.SerializeToString, response_deserializer=demo__pb2.SearchProductsResponse.FromString, ) class ProductCatalogServiceServicer(object): """---------------Product Catalog---------------- """ def ListProducts(self, request, context): """Missing associated documentation comment in .proto file.""" context.set_code(grpc.StatusCode.UNIMPLEMENTED) context.set_details('Method not implemented!') raise NotImplementedError('Method not implemented!') def GetProduct(self, request, context): """Missing associated documentation comment in .proto file.""" context.set_code(grpc.StatusCode.UNIMPLEMENTED) context.set_details('Method not implemented!') raise NotImplementedError('Method not implemented!') def SearchProducts(self, request, context): """Missing associated documentation comment in .proto file.""" context.set_code(grpc.StatusCode.UNIMPLEMENTED) context.set_details('Method not implemented!') raise NotImplementedError('Method not implemented!') def add_ProductCatalogServiceServicer_to_server(servicer, server): rpc_method_handlers = { 'ListProducts': grpc.unary_unary_rpc_method_handler( servicer.ListProducts, request_deserializer=demo__pb2.Empty.FromString, response_serializer=demo__pb2.ListProductsResponse.SerializeToString, ), 'GetProduct': grpc.unary_unary_rpc_method_handler( servicer.GetProduct, request_deserializer=demo__pb2.GetProductRequest.FromString, response_serializer=demo__pb2.Product.SerializeToString, ), 'SearchProducts': grpc.unary_unary_rpc_method_handler( servicer.SearchProducts, request_deserializer=demo__pb2.SearchProductsRequest.FromString, response_serializer=demo__pb2.SearchProductsResponse.SerializeToString, ), } generic_handler = grpc.method_handlers_generic_handler( 'hipstershop.ProductCatalogService', rpc_method_handlers) server.add_generic_rpc_handlers((generic_handler,)) # This class is part of an EXPERIMENTAL API. class ProductCatalogService(object): """---------------Product Catalog---------------- """ @staticmethod def ListProducts(request, target, options=(), channel_credentials=None, call_credentials=None, insecure=False, compression=None, wait_for_ready=None, timeout=None, metadata=None): return grpc.experimental.unary_unary(request, target, '/hipstershop.ProductCatalogService/ListProducts', demo__pb2.Empty.SerializeToString, demo__pb2.ListProductsResponse.FromString, options, channel_credentials, insecure, call_credentials, compression, wait_for_ready, timeout, metadata) @staticmethod def GetProduct(request, target, options=(), channel_credentials=None, call_credentials=None, insecure=False, compression=None, wait_for_ready=None, timeout=None, metadata=None): return grpc.experimental.unary_unary(request, target, '/hipstershop.ProductCatalogService/GetProduct', demo__pb2.GetProductRequest.SerializeToString, demo__pb2.Product.FromString, options, channel_credentials, insecure, call_credentials, compression, wait_for_ready, timeout, metadata) @staticmethod def SearchProducts(request, target, options=(), channel_credentials=None, call_credentials=None, insecure=False, compression=None, wait_for_ready=None, timeout=None, metadata=None): return grpc.experimental.unary_unary(request, target, '/hipstershop.ProductCatalogService/SearchProducts', demo__pb2.SearchProductsRequest.SerializeToString, demo__pb2.SearchProductsResponse.FromString, options, channel_credentials, insecure, call_credentials, compression, wait_for_ready, timeout, metadata) class ShippingServiceStub(object): """---------------Shipping Service---------- """ def __init__(self, channel): """Constructor. Args: channel: A grpc.Channel. """ self.GetQuote = channel.unary_unary( '/hipstershop.ShippingService/GetQuote', request_serializer=demo__pb2.GetQuoteRequest.SerializeToString, response_deserializer=demo__pb2.GetQuoteResponse.FromString, ) self.ShipOrder = channel.unary_unary( '/hipstershop.ShippingService/ShipOrder', request_serializer=demo__pb2.ShipOrderRequest.SerializeToString, response_deserializer=demo__pb2.ShipOrderResponse.FromString, ) class ShippingServiceServicer(object): """---------------Shipping Service---------- """ def GetQuote(self, request, context): """Missing associated documentation comment in .proto file.""" context.set_code(grpc.StatusCode.UNIMPLEMENTED) context.set_details('Method not implemented!') raise NotImplementedError('Method not implemented!') def ShipOrder(self, request, context): """Missing associated documentation comment in .proto file.""" context.set_code(grpc.StatusCode.UNIMPLEMENTED) context.set_details('Method not implemented!') raise NotImplementedError('Method not implemented!') def add_ShippingServiceServicer_to_server(servicer, server): rpc_method_handlers = { 'GetQuote': grpc.unary_unary_rpc_method_handler( servicer.GetQuote, request_deserializer=demo__pb2.GetQuoteRequest.FromString, response_serializer=demo__pb2.GetQuoteResponse.SerializeToString, ), 'ShipOrder': grpc.unary_unary_rpc_method_handler( servicer.ShipOrder, request_deserializer=demo__pb2.ShipOrderRequest.FromString, response_serializer=demo__pb2.ShipOrderResponse.SerializeToString, ), } generic_handler = grpc.method_handlers_generic_handler( 'hipstershop.ShippingService', rpc_method_handlers) server.add_generic_rpc_handlers((generic_handler,)) # This class is part of an EXPERIMENTAL API. class ShippingService(object): """---------------Shipping Service---------- """ @staticmethod def GetQuote(request, target, options=(), channel_credentials=None, call_credentials=None, insecure=False, compression=None, wait_for_ready=None, timeout=None, metadata=None): return grpc.experimental.unary_unary(request, target, '/hipstershop.ShippingService/GetQuote', demo__pb2.GetQuoteRequest.SerializeToString, demo__pb2.GetQuoteResponse.FromString, options, channel_credentials, insecure, call_credentials, compression, wait_for_ready, timeout, metadata) @staticmethod def ShipOrder(request, target, options=(), channel_credentials=None, call_credentials=None, insecure=False, compression=None, wait_for_ready=None, timeout=None, metadata=None): return grpc.experimental.unary_unary(request, target, '/hipstershop.ShippingService/ShipOrder', demo__pb2.ShipOrderRequest.SerializeToString, demo__pb2.ShipOrderResponse.FromString, options, channel_credentials, insecure, call_credentials, compression, wait_for_ready, timeout, metadata) class CurrencyServiceStub(object): """-----------------Currency service----------------- """ def __init__(self, channel): """Constructor. Args: channel: A grpc.Channel. """ self.GetSupportedCurrencies = channel.unary_unary( '/hipstershop.CurrencyService/GetSupportedCurrencies', request_serializer=demo__pb2.Empty.SerializeToString, response_deserializer=demo__pb2.GetSupportedCurrenciesResponse.FromString, ) self.Convert = channel.unary_unary( '/hipstershop.CurrencyService/Convert', request_serializer=demo__pb2.CurrencyConversionRequest.SerializeToString, response_deserializer=demo__pb2.Money.FromString, ) class CurrencyServiceServicer(object): """-----------------Currency service----------------- """ def GetSupportedCurrencies(self, request, context): """Missing associated documentation comment in .proto file.""" context.set_code(grpc.StatusCode.UNIMPLEMENTED) context.set_details('Method not implemented!') raise NotImplementedError('Method not implemented!') def Convert(self, request, context): """Missing associated documentation comment in .proto file.""" context.set_code(grpc.StatusCode.UNIMPLEMENTED) context.set_details('Method not implemented!') raise NotImplementedError('Method not implemented!') def add_CurrencyServiceServicer_to_server(servicer, server): rpc_method_handlers = { 'GetSupportedCurrencies': grpc.unary_unary_rpc_method_handler( servicer.GetSupportedCurrencies, request_deserializer=demo__pb2.Empty.FromString, response_serializer=demo__pb2.GetSupportedCurrenciesResponse.SerializeToString, ), 'Convert': grpc.unary_unary_rpc_method_handler( servicer.Convert, request_deserializer=demo__pb2.CurrencyConversionRequest.FromString, response_serializer=demo__pb2.Money.SerializeToString, ), } generic_handler = grpc.method_handlers_generic_handler( 'hipstershop.CurrencyService', rpc_method_handlers) server.add_generic_rpc_handlers((generic_handler,)) # This class is part of an EXPERIMENTAL API. class CurrencyService(object): """-----------------Currency service----------------- """ @staticmethod def GetSupportedCurrencies(request, target, options=(), channel_credentials=None, call_credentials=None, insecure=False, compression=None, wait_for_ready=None, timeout=None, metadata=None): return grpc.experimental.unary_unary(request, target, '/hipstershop.CurrencyService/GetSupportedCurrencies', demo__pb2.Empty.SerializeToString, demo__pb2.GetSupportedCurrenciesResponse.FromString, options, channel_credentials, insecure, call_credentials, compression, wait_for_ready, timeout, metadata) @staticmethod def Convert(request, target, options=(), channel_credentials=None, call_credentials=None, insecure=False, compression=None, wait_for_ready=None, timeout=None, metadata=None): return grpc.experimental.unary_unary(request, target, '/hipstershop.CurrencyService/Convert', demo__pb2.CurrencyConversionRequest.SerializeToString, demo__pb2.Money.FromString, options, channel_credentials, insecure, call_credentials, compression, wait_for_ready, timeout, metadata) class PaymentServiceStub(object): """-------------Payment service----------------- """ def __init__(self, channel): """Constructor. Args: channel: A grpc.Channel. """ self.Charge = channel.unary_unary( '/hipstershop.PaymentService/Charge', request_serializer=demo__pb2.ChargeRequest.SerializeToString, response_deserializer=demo__pb2.ChargeResponse.FromString, ) class PaymentServiceServicer(object): """-------------Payment service----------------- """ def Charge(self, request, context): """Missing associated documentation comment in .proto file.""" context.set_code(grpc.StatusCode.UNIMPLEMENTED) context.set_details('Method not implemented!') raise NotImplementedError('Method not implemented!') def add_PaymentServiceServicer_to_server(servicer, server): rpc_method_handlers = { 'Charge': grpc.unary_unary_rpc_method_handler( servicer.Charge, request_deserializer=demo__pb2.ChargeRequest.FromString, response_serializer=demo__pb2.ChargeResponse.SerializeToString, ), } generic_handler = grpc.method_handlers_generic_handler( 'hipstershop.PaymentService', rpc_method_handlers) server.add_generic_rpc_handlers((generic_handler,)) # This class is part of an EXPERIMENTAL API. class PaymentService(object): """-------------Payment service----------------- """ @staticmethod def Charge(request, target, options=(), channel_credentials=None, call_credentials=None, insecure=False, compression=None, wait_for_ready=None, timeout=None, metadata=None): return grpc.experimental.unary_unary(request, target, '/hipstershop.PaymentService/Charge', demo__pb2.ChargeRequest.SerializeToString, demo__pb2.ChargeResponse.FromString, options, channel_credentials, insecure, call_credentials, compression, wait_for_ready, timeout, metadata) class EmailServiceStub(object): """-------------Email service----------------- """ def __init__(self, channel): """Constructor. Args: channel: A grpc.Channel. """ self.SendOrderConfirmation = channel.unary_unary( '/hipstershop.EmailService/SendOrderConfirmation', request_serializer=demo__pb2.SendOrderConfirmationRequest.SerializeToString, response_deserializer=demo__pb2.Empty.FromString, ) class EmailServiceServicer(object): """-------------Email service----------------- """ def SendOrderConfirmation(self, request, context): """Missing associated documentation comment in .proto file.""" context.set_code(grpc.StatusCode.UNIMPLEMENTED) context.set_details('Method not implemented!') raise NotImplementedError('Method not implemented!') def add_EmailServiceServicer_to_server(servicer, server): rpc_method_handlers = { 'SendOrderConfirmation': grpc.unary_unary_rpc_method_handler( servicer.SendOrderConfirmation, request_deserializer=demo__pb2.SendOrderConfirmationRequest.FromString, response_serializer=demo__pb2.Empty.SerializeToString, ), } generic_handler = grpc.method_handlers_generic_handler( 'hipstershop.EmailService', rpc_method_handlers) server.add_generic_rpc_handlers((generic_handler,)) # This class is part of an EXPERIMENTAL API. class EmailService(object): """-------------Email service----------------- """ @staticmethod def SendOrderConfirmation(request, target, options=(), channel_credentials=None, call_credentials=None, insecure=False, compression=None, wait_for_ready=None, timeout=None, metadata=None): return grpc.experimental.unary_unary(request, target, '/hipstershop.EmailService/SendOrderConfirmation', demo__pb2.SendOrderConfirmationRequest.SerializeToString, demo__pb2.Empty.FromString, options, channel_credentials, insecure, call_credentials, compression, wait_for_ready, timeout, metadata) class CheckoutServiceStub(object): """-------------Checkout service----------------- """ def __init__(self, channel): """Constructor. Args: channel: A grpc.Channel. """ self.PlaceOrder = channel.unary_unary( '/hipstershop.CheckoutService/PlaceOrder', request_serializer=demo__pb2.PlaceOrderRequest.SerializeToString, response_deserializer=demo__pb2.PlaceOrderResponse.FromString, ) class CheckoutServiceServicer(object): """-------------Checkout service----------------- """ def PlaceOrder(self, request, context): """Missing associated documentation comment in .proto file.""" context.set_code(grpc.StatusCode.UNIMPLEMENTED) context.set_details('Method not implemented!') raise NotImplementedError('Method not implemented!') def add_CheckoutServiceServicer_to_server(servicer, server): rpc_method_handlers = { 'PlaceOrder': grpc.unary_unary_rpc_method_handler( servicer.PlaceOrder, request_deserializer=demo__pb2.PlaceOrderRequest.FromString, response_serializer=demo__pb2.PlaceOrderResponse.SerializeToString, ), } generic_handler = grpc.method_handlers_generic_handler( 'hipstershop.CheckoutService', rpc_method_handlers) server.add_generic_rpc_handlers((generic_handler,)) # This class is part of an EXPERIMENTAL API. class CheckoutService(object): """-------------Checkout service----------------- """ @staticmethod def PlaceOrder(request, target, options=(), channel_credentials=None, call_credentials=None, insecure=False, compression=None, wait_for_ready=None, timeout=None, metadata=None): return grpc.experimental.unary_unary(request, target, '/hipstershop.CheckoutService/PlaceOrder', demo__pb2.PlaceOrderRequest.SerializeToString, demo__pb2.PlaceOrderResponse.FromString, options, channel_credentials, insecure, call_credentials, compression, wait_for_ready, timeout, metadata) class AdServiceStub(object): """------------Ad service------------------ """ def __init__(self, channel): """Constructor. Args: channel: A grpc.Channel. """ self.GetAds = channel.unary_unary( '/hipstershop.AdService/GetAds', request_serializer=demo__pb2.AdRequest.SerializeToString, response_deserializer=demo__pb2.AdResponse.FromString, ) class AdServiceServicer(object): """------------Ad service------------------ """ def GetAds(self, request, context): """Missing associated documentation comment in .proto file.""" context.set_code(grpc.StatusCode.UNIMPLEMENTED) context.set_details('Method not implemented!') raise NotImplementedError('Method not implemented!') def add_AdServiceServicer_to_server(servicer, server): rpc_method_handlers = { 'GetAds': grpc.unary_unary_rpc_method_handler( servicer.GetAds, request_deserializer=demo__pb2.AdRequest.FromString, response_serializer=demo__pb2.AdResponse.SerializeToString, ), } generic_handler = grpc.method_handlers_generic_handler( 'hipstershop.AdService', rpc_method_handlers) server.add_generic_rpc_handlers((generic_handler,)) # This class is part of an EXPERIMENTAL API. class AdService(object): """------------Ad service------------------ """ @staticmethod def GetAds(request, target, options=(), channel_credentials=None, call_credentials=None, insecure=False, compression=None, wait_for_ready=None, timeout=None, metadata=None): return grpc.experimental.unary_unary(request, target, '/hipstershop.AdService/GetAds', demo__pb2.AdRequest.SerializeToString, demo__pb2.AdResponse.FromString, options, channel_credentials, insecure, call_credentials, compression, wait_for_ready, timeout, metadata) ================================================ FILE: src/emailservice/email_client.py ================================================ #!/usr/bin/python # # Copyright 2018 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. import grpc import demo_pb2 import demo_pb2_grpc from logger import getJSONLogger logger = getJSONLogger('emailservice-client') def send_confirmation_email(email, order): channel = grpc.insecure_channel('[::]:8080') stub = demo_pb2_grpc.EmailServiceStub(channel) try: response = stub.SendOrderConfirmation(demo_pb2.SendOrderConfirmationRequest( email = email, order = order )) logger.info('Request sent.') except grpc.RpcError as err: logger.error(err.details()) logger.error('{}, {}'.format(err.code().name, err.code().value)) if __name__ == '__main__': logger.info('Client for email service.') ================================================ FILE: src/emailservice/email_server.py ================================================ #!/usr/bin/python # # Copyright 2018 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. from concurrent import futures import argparse import os import sys import time import grpc import traceback from jinja2 import Environment, FileSystemLoader, select_autoescape, TemplateError from google.api_core.exceptions import GoogleAPICallError from google.auth.exceptions import DefaultCredentialsError import demo_pb2 import demo_pb2_grpc from grpc_health.v1 import health_pb2 from grpc_health.v1 import health_pb2_grpc from opentelemetry import trace from opentelemetry.instrumentation.grpc import GrpcInstrumentorServer from opentelemetry.sdk.trace import TracerProvider from opentelemetry.sdk.trace.export import BatchSpanProcessor from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter # @TODO: Temporarily removed in https://github.com/GoogleCloudPlatform/microservices-demo/pull/3196 # import googlecloudprofiler from logger import getJSONLogger logger = getJSONLogger('emailservice-server') # Loads confirmation email template from file env = Environment( loader=FileSystemLoader('templates'), autoescape=select_autoescape(['html', 'xml']) ) template = env.get_template('confirmation.html') class BaseEmailService(demo_pb2_grpc.EmailServiceServicer): def Check(self, request, context): return health_pb2.HealthCheckResponse( status=health_pb2.HealthCheckResponse.SERVING) def Watch(self, request, context): return health_pb2.HealthCheckResponse( status=health_pb2.HealthCheckResponse.UNIMPLEMENTED) class EmailService(BaseEmailService): def __init__(self): raise Exception('cloud mail client not implemented') super().__init__() @staticmethod def send_email(client, email_address, content): response = client.send_message( sender = client.sender_path(project_id, region, sender_id), envelope_from_authority = '', header_from_authority = '', envelope_from_address = from_address, simple_message = { "from": { "address_spec": from_address, }, "to": [{ "address_spec": email_address }], "subject": "Your Confirmation Email", "html_body": content } ) logger.info("Message sent: {}".format(response.rfc822_message_id)) def SendOrderConfirmation(self, request, context): email = request.email order = request.order try: confirmation = template.render(order = order) except TemplateError as err: context.set_details("An error occurred when preparing the confirmation mail.") logger.error(err.message) context.set_code(grpc.StatusCode.INTERNAL) return demo_pb2.Empty() try: EmailService.send_email(self.client, email, confirmation) except GoogleAPICallError as err: context.set_details("An error occurred when sending the email.") print(err.message) context.set_code(grpc.StatusCode.INTERNAL) return demo_pb2.Empty() return demo_pb2.Empty() class DummyEmailService(BaseEmailService): def SendOrderConfirmation(self, request, context): logger.info('A request to send order confirmation email to {} has been received.'.format(request.email)) return demo_pb2.Empty() class HealthCheck(): def Check(self, request, context): return health_pb2.HealthCheckResponse( status=health_pb2.HealthCheckResponse.SERVING) def start(dummy_mode): server = grpc.server(futures.ThreadPoolExecutor(max_workers=10),) service = None if dummy_mode: service = DummyEmailService() else: raise Exception('non-dummy mode not implemented yet') demo_pb2_grpc.add_EmailServiceServicer_to_server(service, server) health_pb2_grpc.add_HealthServicer_to_server(service, server) port = os.environ.get('PORT', "8080") logger.info("listening on port: "+port) server.add_insecure_port('[::]:'+port) server.start() try: while True: time.sleep(3600) except KeyboardInterrupt: server.stop(0) def initStackdriverProfiling(): project_id = None try: project_id = os.environ["GCP_PROJECT_ID"] except KeyError: # Environment variable not set pass # @TODO: Temporarily removed in https://github.com/GoogleCloudPlatform/microservices-demo/pull/3196 # for retry in range(1,4): # try: # if project_id: # googlecloudprofiler.start(service='email_server', service_version='1.0.0', verbose=0, project_id=project_id) # else: # googlecloudprofiler.start(service='email_server', service_version='1.0.0', verbose=0) # logger.info("Successfully started Stackdriver Profiler.") # return # except (BaseException) as exc: # logger.info("Unable to start Stackdriver Profiler Python agent. " + str(exc)) # if (retry < 4): # logger.info("Sleeping %d to retry initializing Stackdriver Profiler"%(retry*10)) # time.sleep (1) # else: # logger.warning("Could not initialize Stackdriver Profiler after retrying, giving up") return if __name__ == '__main__': logger.info('starting the email service in dummy mode.') # Profiler try: if "DISABLE_PROFILER" in os.environ: raise KeyError() else: logger.info("Profiler enabled.") initStackdriverProfiling() except KeyError: logger.info("Profiler disabled.") # Tracing try: if os.environ["ENABLE_TRACING"] == "1": otel_endpoint = os.getenv("COLLECTOR_SERVICE_ADDR", "localhost:4317") trace.set_tracer_provider(TracerProvider()) trace.get_tracer_provider().add_span_processor( BatchSpanProcessor( OTLPSpanExporter( endpoint = otel_endpoint, insecure = True ) ) ) grpc_server_instrumentor = GrpcInstrumentorServer() grpc_server_instrumentor.instrument() except (KeyError, DefaultCredentialsError): logger.info("Tracing disabled.") except Exception as e: logger.warn(f"Exception on Cloud Trace setup: {traceback.format_exc()}, tracing disabled.") start(dummy_mode = True) ================================================ FILE: src/emailservice/genproto.sh ================================================ #!/bin/bash -eu # # Copyright 2018 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 gke_emailservice_genproto] python -m grpc_tools.protoc -I../../protos --python_out=. --grpc_python_out=. ../../protos/demo.proto # [END gke_emailservice_genproto] ================================================ FILE: src/emailservice/logger.py ================================================ #!/usr/bin/python # # Copyright 2018 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. import logging import sys from pythonjsonlogger import jsonlogger # TODO(yoshifumi) this class is duplicated since other Python services are # not sharing the modules for logging. class CustomJsonFormatter(jsonlogger.JsonFormatter): def add_fields(self, log_record, record, message_dict): super(CustomJsonFormatter, self).add_fields(log_record, record, message_dict) if not log_record.get('timestamp'): log_record['timestamp'] = record.created if log_record.get('severity'): log_record['severity'] = log_record['severity'].upper() else: log_record['severity'] = record.levelname def getJSONLogger(name): logger = logging.getLogger(name) handler = logging.StreamHandler(sys.stdout) formatter = CustomJsonFormatter('%(timestamp)s %(severity)s %(name)s %(message)s') handler.setFormatter(formatter) logger.addHandler(handler) logger.setLevel(logging.INFO) logger.propagate = False return logger ================================================ FILE: src/emailservice/requirements.in ================================================ google-api-core==2.28.1 grpcio-health-checking==1.76.0 grpcio==1.76.0 jinja2==3.1.6 python-json-logger==4.0.0 google-cloud-trace==1.17.0 requests==2.32.5 opentelemetry-distro==0.60b1 opentelemetry-instrumentation-grpc==0.60b1 opentelemetry-exporter-otlp-proto-grpc==1.39.1 ================================================ FILE: src/emailservice/requirements.txt ================================================ # This file was autogenerated by uv via the following command: # uv pip compile requirements.in -o requirements.txt cachetools==5.3.2 # via google-auth certifi==2024.7.4 # via requests charset-normalizer==3.3.2 # via requests google-api-core[grpc]==2.28.1 # via # -r requirements.in # google-cloud-trace google-auth==2.23.4 # via # google-api-core # google-cloud-trace google-cloud-trace==1.17.0 # via -r requirements.in googleapis-common-protos==1.72.0 # via # google-api-core # grpcio-status # opentelemetry-exporter-otlp-proto-grpc grpcio==1.76.0 # via # -r requirements.in # google-api-core # google-cloud-trace # grpcio-health-checking # grpcio-status # opentelemetry-exporter-otlp-proto-grpc grpcio-health-checking==1.76.0 # via -r requirements.in grpcio-status==1.76.0 # via google-api-core idna==3.7 # via requests importlib-metadata==6.8.0 # via opentelemetry-api jinja2==3.1.6 # via -r requirements.in markupsafe==2.1.3 # via jinja2 opentelemetry-api==1.39.1 # via # opentelemetry-distro # opentelemetry-exporter-otlp-proto-grpc # opentelemetry-instrumentation # opentelemetry-instrumentation-grpc # opentelemetry-sdk # opentelemetry-semantic-conventions opentelemetry-distro==0.60b1 # via -r requirements.in opentelemetry-exporter-otlp-proto-common==1.39.1 # via opentelemetry-exporter-otlp-proto-grpc opentelemetry-exporter-otlp-proto-grpc==1.39.1 # via -r requirements.in opentelemetry-instrumentation==0.60b1 # via # opentelemetry-distro # opentelemetry-instrumentation-grpc opentelemetry-instrumentation-grpc==0.60b1 # via -r requirements.in opentelemetry-proto==1.39.1 # via # opentelemetry-exporter-otlp-proto-common # opentelemetry-exporter-otlp-proto-grpc opentelemetry-sdk==1.39.1 # via # opentelemetry-distro # opentelemetry-exporter-otlp-proto-grpc opentelemetry-semantic-conventions==0.60b1 # via # opentelemetry-instrumentation # opentelemetry-instrumentation-grpc # opentelemetry-sdk packaging==25.0 # via opentelemetry-instrumentation proto-plus==1.27.0 # via # google-api-core # google-cloud-trace protobuf==6.33.5 # via # google-api-core # google-cloud-trace # googleapis-common-protos # grpcio-health-checking # grpcio-status # opentelemetry-proto # proto-plus pyasn1==0.5.0 # via # pyasn1-modules # rsa pyasn1-modules==0.3.0 # via google-auth python-json-logger==4.0.0 # via -r requirements.in requests==2.32.5 # via # -r requirements.in # google-api-core rsa==4.9 # via google-auth typing-extensions==4.15.0 # via # grpcio # opentelemetry-api # opentelemetry-exporter-otlp-proto-grpc # opentelemetry-sdk # opentelemetry-semantic-conventions urllib3==2.6.3 # via requests wrapt==1.16.0 # via # opentelemetry-instrumentation # opentelemetry-instrumentation-grpc zipp==3.19.1 # via importlib-metadata ================================================ FILE: src/emailservice/templates/confirmation.html ================================================ Your Order Confirmation

Your Order Confirmation

Thanks for shopping with us!

Order ID

#{{ order.order_id }}

Shipping

#{{ order.shipping_tracking_id }}

{{ order.shipping_cost.units }}. {{ "%02d" | format(order.shipping_cost.nanos // 10000000) }} {{ order.shipping_cost.currency_code }}

{{ order.shipping_address.street_address_1 }}, {{order.shipping_address.street_address_2}}, {{order.shipping_address.city}}, {{order.shipping_address.country}} {{order.shipping_address.zip_code}}

Items

{% for item in order.items %} {% endfor %}
Item No. Quantity Price
#{{ item.item.product_id }} {{ item.item.quantity }} {{ item.cost.units }}.{{ "%02d" | format(item.cost.nanos // 10000000) }} {{ item.cost.currency_code }}
================================================ FILE: src/frontend/.dockerignore ================================================ vendor/ ================================================ FILE: src/frontend/.gitkeep ================================================ ================================================ FILE: src/frontend/Dockerfile ================================================ # Copyright 2020 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. # Define a default value so it's not empty if the builder fails to provide it ARG BUILDPLATFORM=linux/amd64 FROM --platform=$BUILDPLATFORM golang:1.26.1-alpine@sha256:2389ebfa5b7f43eeafbd6be0c3700cc46690ef842ad962f6c5bd6be49ed82039 AS builder ARG TARGETOS=linux ARG TARGETARCH=amd64 WORKDIR /src # restore dependencies COPY go.mod go.sum ./ RUN go mod download COPY . . # Skaffold passes in debug-oriented compiler flags ARG SKAFFOLD_GO_GCFLAGS RUN GOOS=${TARGETOS} GOARCH=${TARGETARCH} CGO_ENABLED=0 go build -ldflags="-s -w" -gcflags="${SKAFFOLD_GO_GCFLAGS}" -o /go/bin/frontend . FROM gcr.io/distroless/static WORKDIR /src COPY --from=builder /go/bin/frontend /src/server COPY ./templates ./templates COPY ./static ./static # Definition of this variable is used by 'skaffold debug' to identify a golang binary. # Default behavior - a failure prints a stack trace for the current goroutine. # See https://golang.org/pkg/runtime/ ENV GOTRACEBACK=single EXPOSE 8080 ENTRYPOINT ["/src/server"] ================================================ FILE: src/frontend/README.md ================================================ # frontend Run the following command to restore dependencies to `vendor/` directory: dep ensure --vendor-only ================================================ FILE: src/frontend/deployment_details.go ================================================ package main import ( "net/http" "os" "time" "cloud.google.com/go/compute/metadata" "github.com/sirupsen/logrus" ) var deploymentDetailsMap map[string]string var log *logrus.Logger func init() { initializeLogger() // Use a goroutine to ensure loadDeploymentDetails()'s GCP API // calls don't block non-GCP deployments. See issue #685. go loadDeploymentDetails() } func initializeLogger() { log = logrus.New() log.Level = logrus.DebugLevel log.Formatter = &logrus.JSONFormatter{ FieldMap: logrus.FieldMap{ logrus.FieldKeyTime: "timestamp", logrus.FieldKeyLevel: "severity", logrus.FieldKeyMsg: "message", }, TimestampFormat: time.RFC3339Nano, } log.Out = os.Stdout } func loadDeploymentDetails() { deploymentDetailsMap = make(map[string]string) var metaServerClient = metadata.NewClient(&http.Client{}) podHostname, err := os.Hostname() if err != nil { log.Error("Failed to fetch the hostname for the Pod", err) } podCluster, err := metaServerClient.InstanceAttributeValue("cluster-name") if err != nil { log.Error("Failed to fetch the name of the cluster in which the pod is running", err) } podZone, err := metaServerClient.Zone() if err != nil { log.Error("Failed to fetch the Zone of the node where the pod is scheduled", err) } deploymentDetailsMap["HOSTNAME"] = podHostname deploymentDetailsMap["CLUSTERNAME"] = podCluster deploymentDetailsMap["ZONE"] = podZone log.WithFields(logrus.Fields{ "cluster": podCluster, "zone": podZone, "hostname": podHostname, }).Debug("Loaded deployment details") } ================================================ FILE: src/frontend/genproto/demo.pb.go ================================================ // Copyright 2020 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. // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.34.2 // protoc v3.6.1 // source: demo.proto package hipstershop import ( protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" sync "sync" ) const ( // Verify that this generated code is sufficiently up-to-date. _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) // Verify that runtime/protoimpl is sufficiently up-to-date. _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) type CartItem struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields ProductId string `protobuf:"bytes,1,opt,name=product_id,json=productId,proto3" json:"product_id,omitempty"` Quantity int32 `protobuf:"varint,2,opt,name=quantity,proto3" json:"quantity,omitempty"` } func (x *CartItem) Reset() { *x = CartItem{} if protoimpl.UnsafeEnabled { mi := &file_demo_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *CartItem) String() string { return protoimpl.X.MessageStringOf(x) } func (*CartItem) ProtoMessage() {} func (x *CartItem) ProtoReflect() protoreflect.Message { mi := &file_demo_proto_msgTypes[0] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use CartItem.ProtoReflect.Descriptor instead. func (*CartItem) Descriptor() ([]byte, []int) { return file_demo_proto_rawDescGZIP(), []int{0} } func (x *CartItem) GetProductId() string { if x != nil { return x.ProductId } return "" } func (x *CartItem) GetQuantity() int32 { if x != nil { return x.Quantity } return 0 } type AddItemRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields UserId string `protobuf:"bytes,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` Item *CartItem `protobuf:"bytes,2,opt,name=item,proto3" json:"item,omitempty"` } func (x *AddItemRequest) Reset() { *x = AddItemRequest{} if protoimpl.UnsafeEnabled { mi := &file_demo_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *AddItemRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*AddItemRequest) ProtoMessage() {} func (x *AddItemRequest) ProtoReflect() protoreflect.Message { mi := &file_demo_proto_msgTypes[1] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use AddItemRequest.ProtoReflect.Descriptor instead. func (*AddItemRequest) Descriptor() ([]byte, []int) { return file_demo_proto_rawDescGZIP(), []int{1} } func (x *AddItemRequest) GetUserId() string { if x != nil { return x.UserId } return "" } func (x *AddItemRequest) GetItem() *CartItem { if x != nil { return x.Item } return nil } type EmptyCartRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields UserId string `protobuf:"bytes,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` } func (x *EmptyCartRequest) Reset() { *x = EmptyCartRequest{} if protoimpl.UnsafeEnabled { mi := &file_demo_proto_msgTypes[2] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *EmptyCartRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*EmptyCartRequest) ProtoMessage() {} func (x *EmptyCartRequest) ProtoReflect() protoreflect.Message { mi := &file_demo_proto_msgTypes[2] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use EmptyCartRequest.ProtoReflect.Descriptor instead. func (*EmptyCartRequest) Descriptor() ([]byte, []int) { return file_demo_proto_rawDescGZIP(), []int{2} } func (x *EmptyCartRequest) GetUserId() string { if x != nil { return x.UserId } return "" } type GetCartRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields UserId string `protobuf:"bytes,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` } func (x *GetCartRequest) Reset() { *x = GetCartRequest{} if protoimpl.UnsafeEnabled { mi := &file_demo_proto_msgTypes[3] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *GetCartRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*GetCartRequest) ProtoMessage() {} func (x *GetCartRequest) ProtoReflect() protoreflect.Message { mi := &file_demo_proto_msgTypes[3] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use GetCartRequest.ProtoReflect.Descriptor instead. func (*GetCartRequest) Descriptor() ([]byte, []int) { return file_demo_proto_rawDescGZIP(), []int{3} } func (x *GetCartRequest) GetUserId() string { if x != nil { return x.UserId } return "" } type Cart struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields UserId string `protobuf:"bytes,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` Items []*CartItem `protobuf:"bytes,2,rep,name=items,proto3" json:"items,omitempty"` } func (x *Cart) Reset() { *x = Cart{} if protoimpl.UnsafeEnabled { mi := &file_demo_proto_msgTypes[4] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Cart) String() string { return protoimpl.X.MessageStringOf(x) } func (*Cart) ProtoMessage() {} func (x *Cart) ProtoReflect() protoreflect.Message { mi := &file_demo_proto_msgTypes[4] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use Cart.ProtoReflect.Descriptor instead. func (*Cart) Descriptor() ([]byte, []int) { return file_demo_proto_rawDescGZIP(), []int{4} } func (x *Cart) GetUserId() string { if x != nil { return x.UserId } return "" } func (x *Cart) GetItems() []*CartItem { if x != nil { return x.Items } return nil } type Empty struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields } func (x *Empty) Reset() { *x = Empty{} if protoimpl.UnsafeEnabled { mi := &file_demo_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Empty) String() string { return protoimpl.X.MessageStringOf(x) } func (*Empty) ProtoMessage() {} func (x *Empty) ProtoReflect() protoreflect.Message { mi := &file_demo_proto_msgTypes[5] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use Empty.ProtoReflect.Descriptor instead. func (*Empty) Descriptor() ([]byte, []int) { return file_demo_proto_rawDescGZIP(), []int{5} } type ListRecommendationsRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields UserId string `protobuf:"bytes,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` ProductIds []string `protobuf:"bytes,2,rep,name=product_ids,json=productIds,proto3" json:"product_ids,omitempty"` } func (x *ListRecommendationsRequest) Reset() { *x = ListRecommendationsRequest{} if protoimpl.UnsafeEnabled { mi := &file_demo_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *ListRecommendationsRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*ListRecommendationsRequest) ProtoMessage() {} func (x *ListRecommendationsRequest) ProtoReflect() protoreflect.Message { mi := &file_demo_proto_msgTypes[6] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use ListRecommendationsRequest.ProtoReflect.Descriptor instead. func (*ListRecommendationsRequest) Descriptor() ([]byte, []int) { return file_demo_proto_rawDescGZIP(), []int{6} } func (x *ListRecommendationsRequest) GetUserId() string { if x != nil { return x.UserId } return "" } func (x *ListRecommendationsRequest) GetProductIds() []string { if x != nil { return x.ProductIds } return nil } type ListRecommendationsResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields ProductIds []string `protobuf:"bytes,1,rep,name=product_ids,json=productIds,proto3" json:"product_ids,omitempty"` } func (x *ListRecommendationsResponse) Reset() { *x = ListRecommendationsResponse{} if protoimpl.UnsafeEnabled { mi := &file_demo_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *ListRecommendationsResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*ListRecommendationsResponse) ProtoMessage() {} func (x *ListRecommendationsResponse) ProtoReflect() protoreflect.Message { mi := &file_demo_proto_msgTypes[7] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use ListRecommendationsResponse.ProtoReflect.Descriptor instead. func (*ListRecommendationsResponse) Descriptor() ([]byte, []int) { return file_demo_proto_rawDescGZIP(), []int{7} } func (x *ListRecommendationsResponse) GetProductIds() []string { if x != nil { return x.ProductIds } return nil } type Product struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` Description string `protobuf:"bytes,3,opt,name=description,proto3" json:"description,omitempty"` Picture string `protobuf:"bytes,4,opt,name=picture,proto3" json:"picture,omitempty"` PriceUsd *Money `protobuf:"bytes,5,opt,name=price_usd,json=priceUsd,proto3" json:"price_usd,omitempty"` // Categories such as "clothing" or "kitchen" that can be used to look up // other related products. Categories []string `protobuf:"bytes,6,rep,name=categories,proto3" json:"categories,omitempty"` } func (x *Product) Reset() { *x = Product{} if protoimpl.UnsafeEnabled { mi := &file_demo_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Product) String() string { return protoimpl.X.MessageStringOf(x) } func (*Product) ProtoMessage() {} func (x *Product) ProtoReflect() protoreflect.Message { mi := &file_demo_proto_msgTypes[8] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use Product.ProtoReflect.Descriptor instead. func (*Product) Descriptor() ([]byte, []int) { return file_demo_proto_rawDescGZIP(), []int{8} } func (x *Product) GetId() string { if x != nil { return x.Id } return "" } func (x *Product) GetName() string { if x != nil { return x.Name } return "" } func (x *Product) GetDescription() string { if x != nil { return x.Description } return "" } func (x *Product) GetPicture() string { if x != nil { return x.Picture } return "" } func (x *Product) GetPriceUsd() *Money { if x != nil { return x.PriceUsd } return nil } func (x *Product) GetCategories() []string { if x != nil { return x.Categories } return nil } type ListProductsResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Products []*Product `protobuf:"bytes,1,rep,name=products,proto3" json:"products,omitempty"` } func (x *ListProductsResponse) Reset() { *x = ListProductsResponse{} if protoimpl.UnsafeEnabled { mi := &file_demo_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *ListProductsResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*ListProductsResponse) ProtoMessage() {} func (x *ListProductsResponse) ProtoReflect() protoreflect.Message { mi := &file_demo_proto_msgTypes[9] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use ListProductsResponse.ProtoReflect.Descriptor instead. func (*ListProductsResponse) Descriptor() ([]byte, []int) { return file_demo_proto_rawDescGZIP(), []int{9} } func (x *ListProductsResponse) GetProducts() []*Product { if x != nil { return x.Products } return nil } type GetProductRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` } func (x *GetProductRequest) Reset() { *x = GetProductRequest{} if protoimpl.UnsafeEnabled { mi := &file_demo_proto_msgTypes[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *GetProductRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*GetProductRequest) ProtoMessage() {} func (x *GetProductRequest) ProtoReflect() protoreflect.Message { mi := &file_demo_proto_msgTypes[10] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use GetProductRequest.ProtoReflect.Descriptor instead. func (*GetProductRequest) Descriptor() ([]byte, []int) { return file_demo_proto_rawDescGZIP(), []int{10} } func (x *GetProductRequest) GetId() string { if x != nil { return x.Id } return "" } type SearchProductsRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Query string `protobuf:"bytes,1,opt,name=query,proto3" json:"query,omitempty"` } func (x *SearchProductsRequest) Reset() { *x = SearchProductsRequest{} if protoimpl.UnsafeEnabled { mi := &file_demo_proto_msgTypes[11] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *SearchProductsRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*SearchProductsRequest) ProtoMessage() {} func (x *SearchProductsRequest) ProtoReflect() protoreflect.Message { mi := &file_demo_proto_msgTypes[11] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use SearchProductsRequest.ProtoReflect.Descriptor instead. func (*SearchProductsRequest) Descriptor() ([]byte, []int) { return file_demo_proto_rawDescGZIP(), []int{11} } func (x *SearchProductsRequest) GetQuery() string { if x != nil { return x.Query } return "" } type SearchProductsResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Results []*Product `protobuf:"bytes,1,rep,name=results,proto3" json:"results,omitempty"` } func (x *SearchProductsResponse) Reset() { *x = SearchProductsResponse{} if protoimpl.UnsafeEnabled { mi := &file_demo_proto_msgTypes[12] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *SearchProductsResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*SearchProductsResponse) ProtoMessage() {} func (x *SearchProductsResponse) ProtoReflect() protoreflect.Message { mi := &file_demo_proto_msgTypes[12] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use SearchProductsResponse.ProtoReflect.Descriptor instead. func (*SearchProductsResponse) Descriptor() ([]byte, []int) { return file_demo_proto_rawDescGZIP(), []int{12} } func (x *SearchProductsResponse) GetResults() []*Product { if x != nil { return x.Results } return nil } type GetQuoteRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Address *Address `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"` Items []*CartItem `protobuf:"bytes,2,rep,name=items,proto3" json:"items,omitempty"` } func (x *GetQuoteRequest) Reset() { *x = GetQuoteRequest{} if protoimpl.UnsafeEnabled { mi := &file_demo_proto_msgTypes[13] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *GetQuoteRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*GetQuoteRequest) ProtoMessage() {} func (x *GetQuoteRequest) ProtoReflect() protoreflect.Message { mi := &file_demo_proto_msgTypes[13] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use GetQuoteRequest.ProtoReflect.Descriptor instead. func (*GetQuoteRequest) Descriptor() ([]byte, []int) { return file_demo_proto_rawDescGZIP(), []int{13} } func (x *GetQuoteRequest) GetAddress() *Address { if x != nil { return x.Address } return nil } func (x *GetQuoteRequest) GetItems() []*CartItem { if x != nil { return x.Items } return nil } type GetQuoteResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields CostUsd *Money `protobuf:"bytes,1,opt,name=cost_usd,json=costUsd,proto3" json:"cost_usd,omitempty"` } func (x *GetQuoteResponse) Reset() { *x = GetQuoteResponse{} if protoimpl.UnsafeEnabled { mi := &file_demo_proto_msgTypes[14] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *GetQuoteResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*GetQuoteResponse) ProtoMessage() {} func (x *GetQuoteResponse) ProtoReflect() protoreflect.Message { mi := &file_demo_proto_msgTypes[14] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use GetQuoteResponse.ProtoReflect.Descriptor instead. func (*GetQuoteResponse) Descriptor() ([]byte, []int) { return file_demo_proto_rawDescGZIP(), []int{14} } func (x *GetQuoteResponse) GetCostUsd() *Money { if x != nil { return x.CostUsd } return nil } type ShipOrderRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Address *Address `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"` Items []*CartItem `protobuf:"bytes,2,rep,name=items,proto3" json:"items,omitempty"` } func (x *ShipOrderRequest) Reset() { *x = ShipOrderRequest{} if protoimpl.UnsafeEnabled { mi := &file_demo_proto_msgTypes[15] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *ShipOrderRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*ShipOrderRequest) ProtoMessage() {} func (x *ShipOrderRequest) ProtoReflect() protoreflect.Message { mi := &file_demo_proto_msgTypes[15] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use ShipOrderRequest.ProtoReflect.Descriptor instead. func (*ShipOrderRequest) Descriptor() ([]byte, []int) { return file_demo_proto_rawDescGZIP(), []int{15} } func (x *ShipOrderRequest) GetAddress() *Address { if x != nil { return x.Address } return nil } func (x *ShipOrderRequest) GetItems() []*CartItem { if x != nil { return x.Items } return nil } type ShipOrderResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields TrackingId string `protobuf:"bytes,1,opt,name=tracking_id,json=trackingId,proto3" json:"tracking_id,omitempty"` } func (x *ShipOrderResponse) Reset() { *x = ShipOrderResponse{} if protoimpl.UnsafeEnabled { mi := &file_demo_proto_msgTypes[16] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *ShipOrderResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*ShipOrderResponse) ProtoMessage() {} func (x *ShipOrderResponse) ProtoReflect() protoreflect.Message { mi := &file_demo_proto_msgTypes[16] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use ShipOrderResponse.ProtoReflect.Descriptor instead. func (*ShipOrderResponse) Descriptor() ([]byte, []int) { return file_demo_proto_rawDescGZIP(), []int{16} } func (x *ShipOrderResponse) GetTrackingId() string { if x != nil { return x.TrackingId } return "" } type Address struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields StreetAddress string `protobuf:"bytes,1,opt,name=street_address,json=streetAddress,proto3" json:"street_address,omitempty"` City string `protobuf:"bytes,2,opt,name=city,proto3" json:"city,omitempty"` State string `protobuf:"bytes,3,opt,name=state,proto3" json:"state,omitempty"` Country string `protobuf:"bytes,4,opt,name=country,proto3" json:"country,omitempty"` ZipCode int32 `protobuf:"varint,5,opt,name=zip_code,json=zipCode,proto3" json:"zip_code,omitempty"` } func (x *Address) Reset() { *x = Address{} if protoimpl.UnsafeEnabled { mi := &file_demo_proto_msgTypes[17] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Address) String() string { return protoimpl.X.MessageStringOf(x) } func (*Address) ProtoMessage() {} func (x *Address) ProtoReflect() protoreflect.Message { mi := &file_demo_proto_msgTypes[17] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use Address.ProtoReflect.Descriptor instead. func (*Address) Descriptor() ([]byte, []int) { return file_demo_proto_rawDescGZIP(), []int{17} } func (x *Address) GetStreetAddress() string { if x != nil { return x.StreetAddress } return "" } func (x *Address) GetCity() string { if x != nil { return x.City } return "" } func (x *Address) GetState() string { if x != nil { return x.State } return "" } func (x *Address) GetCountry() string { if x != nil { return x.Country } return "" } func (x *Address) GetZipCode() int32 { if x != nil { return x.ZipCode } return 0 } // Represents an amount of money with its currency type. type Money struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // The 3-letter currency code defined in ISO 4217. CurrencyCode string `protobuf:"bytes,1,opt,name=currency_code,json=currencyCode,proto3" json:"currency_code,omitempty"` // The whole units of the amount. // For example if `currencyCode` is `"USD"`, then 1 unit is one US dollar. Units int64 `protobuf:"varint,2,opt,name=units,proto3" json:"units,omitempty"` // Number of nano (10^-9) units of the amount. // The value must be between -999,999,999 and +999,999,999 inclusive. // If `units` is positive, `nanos` must be positive or zero. // If `units` is zero, `nanos` can be positive, zero, or negative. // If `units` is negative, `nanos` must be negative or zero. // For example $-1.75 is represented as `units`=-1 and `nanos`=-750,000,000. Nanos int32 `protobuf:"varint,3,opt,name=nanos,proto3" json:"nanos,omitempty"` } func (x *Money) Reset() { *x = Money{} if protoimpl.UnsafeEnabled { mi := &file_demo_proto_msgTypes[18] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Money) String() string { return protoimpl.X.MessageStringOf(x) } func (*Money) ProtoMessage() {} func (x *Money) ProtoReflect() protoreflect.Message { mi := &file_demo_proto_msgTypes[18] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use Money.ProtoReflect.Descriptor instead. func (*Money) Descriptor() ([]byte, []int) { return file_demo_proto_rawDescGZIP(), []int{18} } func (x *Money) GetCurrencyCode() string { if x != nil { return x.CurrencyCode } return "" } func (x *Money) GetUnits() int64 { if x != nil { return x.Units } return 0 } func (x *Money) GetNanos() int32 { if x != nil { return x.Nanos } return 0 } type GetSupportedCurrenciesResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // The 3-letter currency code defined in ISO 4217. CurrencyCodes []string `protobuf:"bytes,1,rep,name=currency_codes,json=currencyCodes,proto3" json:"currency_codes,omitempty"` } func (x *GetSupportedCurrenciesResponse) Reset() { *x = GetSupportedCurrenciesResponse{} if protoimpl.UnsafeEnabled { mi := &file_demo_proto_msgTypes[19] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *GetSupportedCurrenciesResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*GetSupportedCurrenciesResponse) ProtoMessage() {} func (x *GetSupportedCurrenciesResponse) ProtoReflect() protoreflect.Message { mi := &file_demo_proto_msgTypes[19] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use GetSupportedCurrenciesResponse.ProtoReflect.Descriptor instead. func (*GetSupportedCurrenciesResponse) Descriptor() ([]byte, []int) { return file_demo_proto_rawDescGZIP(), []int{19} } func (x *GetSupportedCurrenciesResponse) GetCurrencyCodes() []string { if x != nil { return x.CurrencyCodes } return nil } type CurrencyConversionRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields From *Money `protobuf:"bytes,1,opt,name=from,proto3" json:"from,omitempty"` // The 3-letter currency code defined in ISO 4217. ToCode string `protobuf:"bytes,2,opt,name=to_code,json=toCode,proto3" json:"to_code,omitempty"` } func (x *CurrencyConversionRequest) Reset() { *x = CurrencyConversionRequest{} if protoimpl.UnsafeEnabled { mi := &file_demo_proto_msgTypes[20] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *CurrencyConversionRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*CurrencyConversionRequest) ProtoMessage() {} func (x *CurrencyConversionRequest) ProtoReflect() protoreflect.Message { mi := &file_demo_proto_msgTypes[20] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use CurrencyConversionRequest.ProtoReflect.Descriptor instead. func (*CurrencyConversionRequest) Descriptor() ([]byte, []int) { return file_demo_proto_rawDescGZIP(), []int{20} } func (x *CurrencyConversionRequest) GetFrom() *Money { if x != nil { return x.From } return nil } func (x *CurrencyConversionRequest) GetToCode() string { if x != nil { return x.ToCode } return "" } type CreditCardInfo struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields CreditCardNumber string `protobuf:"bytes,1,opt,name=credit_card_number,json=creditCardNumber,proto3" json:"credit_card_number,omitempty"` CreditCardCvv int32 `protobuf:"varint,2,opt,name=credit_card_cvv,json=creditCardCvv,proto3" json:"credit_card_cvv,omitempty"` CreditCardExpirationYear int32 `protobuf:"varint,3,opt,name=credit_card_expiration_year,json=creditCardExpirationYear,proto3" json:"credit_card_expiration_year,omitempty"` CreditCardExpirationMonth int32 `protobuf:"varint,4,opt,name=credit_card_expiration_month,json=creditCardExpirationMonth,proto3" json:"credit_card_expiration_month,omitempty"` } func (x *CreditCardInfo) Reset() { *x = CreditCardInfo{} if protoimpl.UnsafeEnabled { mi := &file_demo_proto_msgTypes[21] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *CreditCardInfo) String() string { return protoimpl.X.MessageStringOf(x) } func (*CreditCardInfo) ProtoMessage() {} func (x *CreditCardInfo) ProtoReflect() protoreflect.Message { mi := &file_demo_proto_msgTypes[21] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use CreditCardInfo.ProtoReflect.Descriptor instead. func (*CreditCardInfo) Descriptor() ([]byte, []int) { return file_demo_proto_rawDescGZIP(), []int{21} } func (x *CreditCardInfo) GetCreditCardNumber() string { if x != nil { return x.CreditCardNumber } return "" } func (x *CreditCardInfo) GetCreditCardCvv() int32 { if x != nil { return x.CreditCardCvv } return 0 } func (x *CreditCardInfo) GetCreditCardExpirationYear() int32 { if x != nil { return x.CreditCardExpirationYear } return 0 } func (x *CreditCardInfo) GetCreditCardExpirationMonth() int32 { if x != nil { return x.CreditCardExpirationMonth } return 0 } type ChargeRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Amount *Money `protobuf:"bytes,1,opt,name=amount,proto3" json:"amount,omitempty"` CreditCard *CreditCardInfo `protobuf:"bytes,2,opt,name=credit_card,json=creditCard,proto3" json:"credit_card,omitempty"` } func (x *ChargeRequest) Reset() { *x = ChargeRequest{} if protoimpl.UnsafeEnabled { mi := &file_demo_proto_msgTypes[22] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *ChargeRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*ChargeRequest) ProtoMessage() {} func (x *ChargeRequest) ProtoReflect() protoreflect.Message { mi := &file_demo_proto_msgTypes[22] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use ChargeRequest.ProtoReflect.Descriptor instead. func (*ChargeRequest) Descriptor() ([]byte, []int) { return file_demo_proto_rawDescGZIP(), []int{22} } func (x *ChargeRequest) GetAmount() *Money { if x != nil { return x.Amount } return nil } func (x *ChargeRequest) GetCreditCard() *CreditCardInfo { if x != nil { return x.CreditCard } return nil } type ChargeResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields TransactionId string `protobuf:"bytes,1,opt,name=transaction_id,json=transactionId,proto3" json:"transaction_id,omitempty"` } func (x *ChargeResponse) Reset() { *x = ChargeResponse{} if protoimpl.UnsafeEnabled { mi := &file_demo_proto_msgTypes[23] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *ChargeResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*ChargeResponse) ProtoMessage() {} func (x *ChargeResponse) ProtoReflect() protoreflect.Message { mi := &file_demo_proto_msgTypes[23] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use ChargeResponse.ProtoReflect.Descriptor instead. func (*ChargeResponse) Descriptor() ([]byte, []int) { return file_demo_proto_rawDescGZIP(), []int{23} } func (x *ChargeResponse) GetTransactionId() string { if x != nil { return x.TransactionId } return "" } type OrderItem struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Item *CartItem `protobuf:"bytes,1,opt,name=item,proto3" json:"item,omitempty"` Cost *Money `protobuf:"bytes,2,opt,name=cost,proto3" json:"cost,omitempty"` } func (x *OrderItem) Reset() { *x = OrderItem{} if protoimpl.UnsafeEnabled { mi := &file_demo_proto_msgTypes[24] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *OrderItem) String() string { return protoimpl.X.MessageStringOf(x) } func (*OrderItem) ProtoMessage() {} func (x *OrderItem) ProtoReflect() protoreflect.Message { mi := &file_demo_proto_msgTypes[24] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use OrderItem.ProtoReflect.Descriptor instead. func (*OrderItem) Descriptor() ([]byte, []int) { return file_demo_proto_rawDescGZIP(), []int{24} } func (x *OrderItem) GetItem() *CartItem { if x != nil { return x.Item } return nil } func (x *OrderItem) GetCost() *Money { if x != nil { return x.Cost } return nil } type OrderResult struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields OrderId string `protobuf:"bytes,1,opt,name=order_id,json=orderId,proto3" json:"order_id,omitempty"` ShippingTrackingId string `protobuf:"bytes,2,opt,name=shipping_tracking_id,json=shippingTrackingId,proto3" json:"shipping_tracking_id,omitempty"` ShippingCost *Money `protobuf:"bytes,3,opt,name=shipping_cost,json=shippingCost,proto3" json:"shipping_cost,omitempty"` ShippingAddress *Address `protobuf:"bytes,4,opt,name=shipping_address,json=shippingAddress,proto3" json:"shipping_address,omitempty"` Items []*OrderItem `protobuf:"bytes,5,rep,name=items,proto3" json:"items,omitempty"` } func (x *OrderResult) Reset() { *x = OrderResult{} if protoimpl.UnsafeEnabled { mi := &file_demo_proto_msgTypes[25] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *OrderResult) String() string { return protoimpl.X.MessageStringOf(x) } func (*OrderResult) ProtoMessage() {} func (x *OrderResult) ProtoReflect() protoreflect.Message { mi := &file_demo_proto_msgTypes[25] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use OrderResult.ProtoReflect.Descriptor instead. func (*OrderResult) Descriptor() ([]byte, []int) { return file_demo_proto_rawDescGZIP(), []int{25} } func (x *OrderResult) GetOrderId() string { if x != nil { return x.OrderId } return "" } func (x *OrderResult) GetShippingTrackingId() string { if x != nil { return x.ShippingTrackingId } return "" } func (x *OrderResult) GetShippingCost() *Money { if x != nil { return x.ShippingCost } return nil } func (x *OrderResult) GetShippingAddress() *Address { if x != nil { return x.ShippingAddress } return nil } func (x *OrderResult) GetItems() []*OrderItem { if x != nil { return x.Items } return nil } type SendOrderConfirmationRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Email string `protobuf:"bytes,1,opt,name=email,proto3" json:"email,omitempty"` Order *OrderResult `protobuf:"bytes,2,opt,name=order,proto3" json:"order,omitempty"` } func (x *SendOrderConfirmationRequest) Reset() { *x = SendOrderConfirmationRequest{} if protoimpl.UnsafeEnabled { mi := &file_demo_proto_msgTypes[26] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *SendOrderConfirmationRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*SendOrderConfirmationRequest) ProtoMessage() {} func (x *SendOrderConfirmationRequest) ProtoReflect() protoreflect.Message { mi := &file_demo_proto_msgTypes[26] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use SendOrderConfirmationRequest.ProtoReflect.Descriptor instead. func (*SendOrderConfirmationRequest) Descriptor() ([]byte, []int) { return file_demo_proto_rawDescGZIP(), []int{26} } func (x *SendOrderConfirmationRequest) GetEmail() string { if x != nil { return x.Email } return "" } func (x *SendOrderConfirmationRequest) GetOrder() *OrderResult { if x != nil { return x.Order } return nil } type PlaceOrderRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields UserId string `protobuf:"bytes,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` UserCurrency string `protobuf:"bytes,2,opt,name=user_currency,json=userCurrency,proto3" json:"user_currency,omitempty"` Address *Address `protobuf:"bytes,3,opt,name=address,proto3" json:"address,omitempty"` Email string `protobuf:"bytes,5,opt,name=email,proto3" json:"email,omitempty"` CreditCard *CreditCardInfo `protobuf:"bytes,6,opt,name=credit_card,json=creditCard,proto3" json:"credit_card,omitempty"` } func (x *PlaceOrderRequest) Reset() { *x = PlaceOrderRequest{} if protoimpl.UnsafeEnabled { mi := &file_demo_proto_msgTypes[27] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *PlaceOrderRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*PlaceOrderRequest) ProtoMessage() {} func (x *PlaceOrderRequest) ProtoReflect() protoreflect.Message { mi := &file_demo_proto_msgTypes[27] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use PlaceOrderRequest.ProtoReflect.Descriptor instead. func (*PlaceOrderRequest) Descriptor() ([]byte, []int) { return file_demo_proto_rawDescGZIP(), []int{27} } func (x *PlaceOrderRequest) GetUserId() string { if x != nil { return x.UserId } return "" } func (x *PlaceOrderRequest) GetUserCurrency() string { if x != nil { return x.UserCurrency } return "" } func (x *PlaceOrderRequest) GetAddress() *Address { if x != nil { return x.Address } return nil } func (x *PlaceOrderRequest) GetEmail() string { if x != nil { return x.Email } return "" } func (x *PlaceOrderRequest) GetCreditCard() *CreditCardInfo { if x != nil { return x.CreditCard } return nil } type PlaceOrderResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Order *OrderResult `protobuf:"bytes,1,opt,name=order,proto3" json:"order,omitempty"` } func (x *PlaceOrderResponse) Reset() { *x = PlaceOrderResponse{} if protoimpl.UnsafeEnabled { mi := &file_demo_proto_msgTypes[28] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *PlaceOrderResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*PlaceOrderResponse) ProtoMessage() {} func (x *PlaceOrderResponse) ProtoReflect() protoreflect.Message { mi := &file_demo_proto_msgTypes[28] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use PlaceOrderResponse.ProtoReflect.Descriptor instead. func (*PlaceOrderResponse) Descriptor() ([]byte, []int) { return file_demo_proto_rawDescGZIP(), []int{28} } func (x *PlaceOrderResponse) GetOrder() *OrderResult { if x != nil { return x.Order } return nil } type AdRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // List of important key words from the current page describing the context. ContextKeys []string `protobuf:"bytes,1,rep,name=context_keys,json=contextKeys,proto3" json:"context_keys,omitempty"` } func (x *AdRequest) Reset() { *x = AdRequest{} if protoimpl.UnsafeEnabled { mi := &file_demo_proto_msgTypes[29] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *AdRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*AdRequest) ProtoMessage() {} func (x *AdRequest) ProtoReflect() protoreflect.Message { mi := &file_demo_proto_msgTypes[29] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use AdRequest.ProtoReflect.Descriptor instead. func (*AdRequest) Descriptor() ([]byte, []int) { return file_demo_proto_rawDescGZIP(), []int{29} } func (x *AdRequest) GetContextKeys() []string { if x != nil { return x.ContextKeys } return nil } type AdResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Ads []*Ad `protobuf:"bytes,1,rep,name=ads,proto3" json:"ads,omitempty"` } func (x *AdResponse) Reset() { *x = AdResponse{} if protoimpl.UnsafeEnabled { mi := &file_demo_proto_msgTypes[30] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *AdResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*AdResponse) ProtoMessage() {} func (x *AdResponse) ProtoReflect() protoreflect.Message { mi := &file_demo_proto_msgTypes[30] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use AdResponse.ProtoReflect.Descriptor instead. func (*AdResponse) Descriptor() ([]byte, []int) { return file_demo_proto_rawDescGZIP(), []int{30} } func (x *AdResponse) GetAds() []*Ad { if x != nil { return x.Ads } return nil } type Ad struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // url to redirect to when an ad is clicked. RedirectUrl string `protobuf:"bytes,1,opt,name=redirect_url,json=redirectUrl,proto3" json:"redirect_url,omitempty"` // short advertisement text to display. Text string `protobuf:"bytes,2,opt,name=text,proto3" json:"text,omitempty"` } func (x *Ad) Reset() { *x = Ad{} if protoimpl.UnsafeEnabled { mi := &file_demo_proto_msgTypes[31] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Ad) String() string { return protoimpl.X.MessageStringOf(x) } func (*Ad) ProtoMessage() {} func (x *Ad) ProtoReflect() protoreflect.Message { mi := &file_demo_proto_msgTypes[31] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use Ad.ProtoReflect.Descriptor instead. func (*Ad) Descriptor() ([]byte, []int) { return file_demo_proto_rawDescGZIP(), []int{31} } func (x *Ad) GetRedirectUrl() string { if x != nil { return x.RedirectUrl } return "" } func (x *Ad) GetText() string { if x != nil { return x.Text } return "" } var File_demo_proto protoreflect.FileDescriptor var file_demo_proto_rawDesc = []byte{ 0x0a, 0x0a, 0x64, 0x65, 0x6d, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0b, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x22, 0x45, 0x0a, 0x08, 0x43, 0x61, 0x72, 0x74, 0x49, 0x74, 0x65, 0x6d, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x71, 0x75, 0x61, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x71, 0x75, 0x61, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x22, 0x54, 0x0a, 0x0e, 0x41, 0x64, 0x64, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x29, 0x0a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x43, 0x61, 0x72, 0x74, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x22, 0x2b, 0x0a, 0x10, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x43, 0x61, 0x72, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0x29, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x43, 0x61, 0x72, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0x4c, 0x0a, 0x04, 0x43, 0x61, 0x72, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x2b, 0x0a, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x43, 0x61, 0x72, 0x74, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x22, 0x07, 0x0a, 0x05, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x56, 0x0a, 0x1a, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x49, 0x64, 0x73, 0x22, 0x3e, 0x0a, 0x1b, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x49, 0x64, 0x73, 0x22, 0xba, 0x01, 0x0a, 0x07, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x69, 0x63, 0x74, 0x75, 0x72, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x70, 0x69, 0x63, 0x74, 0x75, 0x72, 0x65, 0x12, 0x2f, 0x0a, 0x09, 0x70, 0x72, 0x69, 0x63, 0x65, 0x5f, 0x75, 0x73, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x4d, 0x6f, 0x6e, 0x65, 0x79, 0x52, 0x08, 0x70, 0x72, 0x69, 0x63, 0x65, 0x55, 0x73, 0x64, 0x12, 0x1e, 0x0a, 0x0a, 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x69, 0x65, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x69, 0x65, 0x73, 0x22, 0x48, 0x0a, 0x14, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x30, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x73, 0x22, 0x23, 0x0a, 0x11, 0x47, 0x65, 0x74, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x22, 0x2d, 0x0a, 0x15, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x22, 0x48, 0x0a, 0x16, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2e, 0x0a, 0x07, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x52, 0x07, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x22, 0x6e, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x51, 0x75, 0x6f, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x2b, 0x0a, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x43, 0x61, 0x72, 0x74, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x22, 0x41, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x51, 0x75, 0x6f, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2d, 0x0a, 0x08, 0x63, 0x6f, 0x73, 0x74, 0x5f, 0x75, 0x73, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x4d, 0x6f, 0x6e, 0x65, 0x79, 0x52, 0x07, 0x63, 0x6f, 0x73, 0x74, 0x55, 0x73, 0x64, 0x22, 0x6f, 0x0a, 0x10, 0x53, 0x68, 0x69, 0x70, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x2b, 0x0a, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x43, 0x61, 0x72, 0x74, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x22, 0x34, 0x0a, 0x11, 0x53, 0x68, 0x69, 0x70, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x74, 0x72, 0x61, 0x63, 0x6b, 0x69, 0x6e, 0x67, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x74, 0x72, 0x61, 0x63, 0x6b, 0x69, 0x6e, 0x67, 0x49, 0x64, 0x22, 0x8f, 0x01, 0x0a, 0x07, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x25, 0x0a, 0x0e, 0x73, 0x74, 0x72, 0x65, 0x65, 0x74, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x73, 0x74, 0x72, 0x65, 0x65, 0x74, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x63, 0x69, 0x74, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x63, 0x69, 0x74, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x19, 0x0a, 0x08, 0x7a, 0x69, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x07, 0x7a, 0x69, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x22, 0x58, 0x0a, 0x05, 0x4d, 0x6f, 0x6e, 0x65, 0x79, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x75, 0x6e, 0x69, 0x74, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x75, 0x6e, 0x69, 0x74, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x6e, 0x61, 0x6e, 0x6f, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x6e, 0x61, 0x6e, 0x6f, 0x73, 0x22, 0x47, 0x0a, 0x1e, 0x47, 0x65, 0x74, 0x53, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x69, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0d, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x43, 0x6f, 0x64, 0x65, 0x73, 0x22, 0x5c, 0x0a, 0x19, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x43, 0x6f, 0x6e, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x26, 0x0a, 0x04, 0x66, 0x72, 0x6f, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x4d, 0x6f, 0x6e, 0x65, 0x79, 0x52, 0x04, 0x66, 0x72, 0x6f, 0x6d, 0x12, 0x17, 0x0a, 0x07, 0x74, 0x6f, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x74, 0x6f, 0x43, 0x6f, 0x64, 0x65, 0x22, 0xe6, 0x01, 0x0a, 0x0e, 0x43, 0x72, 0x65, 0x64, 0x69, 0x74, 0x43, 0x61, 0x72, 0x64, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x2c, 0x0a, 0x12, 0x63, 0x72, 0x65, 0x64, 0x69, 0x74, 0x5f, 0x63, 0x61, 0x72, 0x64, 0x5f, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x63, 0x72, 0x65, 0x64, 0x69, 0x74, 0x43, 0x61, 0x72, 0x64, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x26, 0x0a, 0x0f, 0x63, 0x72, 0x65, 0x64, 0x69, 0x74, 0x5f, 0x63, 0x61, 0x72, 0x64, 0x5f, 0x63, 0x76, 0x76, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0d, 0x63, 0x72, 0x65, 0x64, 0x69, 0x74, 0x43, 0x61, 0x72, 0x64, 0x43, 0x76, 0x76, 0x12, 0x3d, 0x0a, 0x1b, 0x63, 0x72, 0x65, 0x64, 0x69, 0x74, 0x5f, 0x63, 0x61, 0x72, 0x64, 0x5f, 0x65, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x79, 0x65, 0x61, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x18, 0x63, 0x72, 0x65, 0x64, 0x69, 0x74, 0x43, 0x61, 0x72, 0x64, 0x45, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x59, 0x65, 0x61, 0x72, 0x12, 0x3f, 0x0a, 0x1c, 0x63, 0x72, 0x65, 0x64, 0x69, 0x74, 0x5f, 0x63, 0x61, 0x72, 0x64, 0x5f, 0x65, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6d, 0x6f, 0x6e, 0x74, 0x68, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x19, 0x63, 0x72, 0x65, 0x64, 0x69, 0x74, 0x43, 0x61, 0x72, 0x64, 0x45, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x6f, 0x6e, 0x74, 0x68, 0x22, 0x79, 0x0a, 0x0d, 0x43, 0x68, 0x61, 0x72, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2a, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x4d, 0x6f, 0x6e, 0x65, 0x79, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x3c, 0x0a, 0x0b, 0x63, 0x72, 0x65, 0x64, 0x69, 0x74, 0x5f, 0x63, 0x61, 0x72, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x43, 0x72, 0x65, 0x64, 0x69, 0x74, 0x43, 0x61, 0x72, 0x64, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x0a, 0x63, 0x72, 0x65, 0x64, 0x69, 0x74, 0x43, 0x61, 0x72, 0x64, 0x22, 0x37, 0x0a, 0x0e, 0x43, 0x68, 0x61, 0x72, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x22, 0x5e, 0x0a, 0x09, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x49, 0x74, 0x65, 0x6d, 0x12, 0x29, 0x0a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x43, 0x61, 0x72, 0x74, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x12, 0x26, 0x0a, 0x04, 0x63, 0x6f, 0x73, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x4d, 0x6f, 0x6e, 0x65, 0x79, 0x52, 0x04, 0x63, 0x6f, 0x73, 0x74, 0x22, 0x82, 0x02, 0x0a, 0x0b, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x49, 0x64, 0x12, 0x30, 0x0a, 0x14, 0x73, 0x68, 0x69, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x5f, 0x74, 0x72, 0x61, 0x63, 0x6b, 0x69, 0x6e, 0x67, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x12, 0x73, 0x68, 0x69, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x54, 0x72, 0x61, 0x63, 0x6b, 0x69, 0x6e, 0x67, 0x49, 0x64, 0x12, 0x37, 0x0a, 0x0d, 0x73, 0x68, 0x69, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x6f, 0x73, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x4d, 0x6f, 0x6e, 0x65, 0x79, 0x52, 0x0c, 0x73, 0x68, 0x69, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x73, 0x74, 0x12, 0x3f, 0x0a, 0x10, 0x73, 0x68, 0x69, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, 0x0f, 0x73, 0x68, 0x69, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x2c, 0x0a, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x22, 0x64, 0x0a, 0x1c, 0x53, 0x65, 0x6e, 0x64, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x12, 0x2e, 0x0a, 0x05, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x05, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x22, 0xd5, 0x01, 0x0a, 0x11, 0x50, 0x6c, 0x61, 0x63, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x75, 0x73, 0x65, 0x72, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x12, 0x2e, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x12, 0x3c, 0x0a, 0x0b, 0x63, 0x72, 0x65, 0x64, 0x69, 0x74, 0x5f, 0x63, 0x61, 0x72, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x43, 0x72, 0x65, 0x64, 0x69, 0x74, 0x43, 0x61, 0x72, 0x64, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x0a, 0x63, 0x72, 0x65, 0x64, 0x69, 0x74, 0x43, 0x61, 0x72, 0x64, 0x22, 0x44, 0x0a, 0x12, 0x50, 0x6c, 0x61, 0x63, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2e, 0x0a, 0x05, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x05, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x22, 0x2e, 0x0a, 0x09, 0x41, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x5f, 0x6b, 0x65, 0x79, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x4b, 0x65, 0x79, 0x73, 0x22, 0x2f, 0x0a, 0x0a, 0x41, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x21, 0x0a, 0x03, 0x61, 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x41, 0x64, 0x52, 0x03, 0x61, 0x64, 0x73, 0x22, 0x3b, 0x0a, 0x02, 0x41, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x72, 0x65, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x72, 0x65, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x55, 0x72, 0x6c, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x65, 0x78, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x65, 0x78, 0x74, 0x32, 0xca, 0x01, 0x0a, 0x0b, 0x43, 0x61, 0x72, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x3c, 0x0a, 0x07, 0x41, 0x64, 0x64, 0x49, 0x74, 0x65, 0x6d, 0x12, 0x1b, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x41, 0x64, 0x64, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x3b, 0x0a, 0x07, 0x47, 0x65, 0x74, 0x43, 0x61, 0x72, 0x74, 0x12, 0x1b, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x61, 0x72, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x11, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x43, 0x61, 0x72, 0x74, 0x22, 0x00, 0x12, 0x40, 0x0a, 0x09, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x43, 0x61, 0x72, 0x74, 0x12, 0x1d, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x43, 0x61, 0x72, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x32, 0x83, 0x01, 0x0a, 0x15, 0x52, 0x65, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x6a, 0x0a, 0x13, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x27, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x32, 0x83, 0x02, 0x0a, 0x15, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x43, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x47, 0x0a, 0x0c, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x73, 0x12, 0x12, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x21, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x44, 0x0a, 0x0a, 0x47, 0x65, 0x74, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x12, 0x1e, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x47, 0x65, 0x74, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x22, 0x00, 0x12, 0x5b, 0x0a, 0x0e, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x73, 0x12, 0x22, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x32, 0xaa, 0x01, 0x0a, 0x0f, 0x53, 0x68, 0x69, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x49, 0x0a, 0x08, 0x47, 0x65, 0x74, 0x51, 0x75, 0x6f, 0x74, 0x65, 0x12, 0x1c, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x47, 0x65, 0x74, 0x51, 0x75, 0x6f, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x47, 0x65, 0x74, 0x51, 0x75, 0x6f, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x4c, 0x0a, 0x09, 0x53, 0x68, 0x69, 0x70, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x1d, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x53, 0x68, 0x69, 0x70, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x53, 0x68, 0x69, 0x70, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x32, 0xb7, 0x01, 0x0a, 0x0f, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x5b, 0x0a, 0x16, 0x47, 0x65, 0x74, 0x53, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x69, 0x65, 0x73, 0x12, 0x12, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x2b, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x69, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x47, 0x0a, 0x07, 0x43, 0x6f, 0x6e, 0x76, 0x65, 0x72, 0x74, 0x12, 0x26, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x43, 0x6f, 0x6e, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x4d, 0x6f, 0x6e, 0x65, 0x79, 0x22, 0x00, 0x32, 0x55, 0x0a, 0x0e, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x43, 0x0a, 0x06, 0x43, 0x68, 0x61, 0x72, 0x67, 0x65, 0x12, 0x1a, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x43, 0x68, 0x61, 0x72, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x43, 0x68, 0x61, 0x72, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x32, 0x68, 0x0a, 0x0c, 0x45, 0x6d, 0x61, 0x69, 0x6c, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x58, 0x0a, 0x15, 0x53, 0x65, 0x6e, 0x64, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x29, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x32, 0x62, 0x0a, 0x0f, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x6f, 0x75, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x4f, 0x0a, 0x0a, 0x50, 0x6c, 0x61, 0x63, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x1e, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x50, 0x6c, 0x61, 0x63, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x50, 0x6c, 0x61, 0x63, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x32, 0x48, 0x0a, 0x09, 0x41, 0x64, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x3b, 0x0a, 0x06, 0x47, 0x65, 0x74, 0x41, 0x64, 0x73, 0x12, 0x16, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x41, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x41, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x40, 0x5a, 0x3e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x47, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x43, 0x6c, 0x6f, 0x75, 0x64, 0x50, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x2f, 0x6d, 0x69, 0x73, 0x63, 0x72, 0x6f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2d, 0x64, 0x65, 0x6d, 0x6f, 0x2f, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( file_demo_proto_rawDescOnce sync.Once file_demo_proto_rawDescData = file_demo_proto_rawDesc ) func file_demo_proto_rawDescGZIP() []byte { file_demo_proto_rawDescOnce.Do(func() { file_demo_proto_rawDescData = protoimpl.X.CompressGZIP(file_demo_proto_rawDescData) }) return file_demo_proto_rawDescData } var file_demo_proto_msgTypes = make([]protoimpl.MessageInfo, 32) var file_demo_proto_goTypes = []any{ (*CartItem)(nil), // 0: hipstershop.CartItem (*AddItemRequest)(nil), // 1: hipstershop.AddItemRequest (*EmptyCartRequest)(nil), // 2: hipstershop.EmptyCartRequest (*GetCartRequest)(nil), // 3: hipstershop.GetCartRequest (*Cart)(nil), // 4: hipstershop.Cart (*Empty)(nil), // 5: hipstershop.Empty (*ListRecommendationsRequest)(nil), // 6: hipstershop.ListRecommendationsRequest (*ListRecommendationsResponse)(nil), // 7: hipstershop.ListRecommendationsResponse (*Product)(nil), // 8: hipstershop.Product (*ListProductsResponse)(nil), // 9: hipstershop.ListProductsResponse (*GetProductRequest)(nil), // 10: hipstershop.GetProductRequest (*SearchProductsRequest)(nil), // 11: hipstershop.SearchProductsRequest (*SearchProductsResponse)(nil), // 12: hipstershop.SearchProductsResponse (*GetQuoteRequest)(nil), // 13: hipstershop.GetQuoteRequest (*GetQuoteResponse)(nil), // 14: hipstershop.GetQuoteResponse (*ShipOrderRequest)(nil), // 15: hipstershop.ShipOrderRequest (*ShipOrderResponse)(nil), // 16: hipstershop.ShipOrderResponse (*Address)(nil), // 17: hipstershop.Address (*Money)(nil), // 18: hipstershop.Money (*GetSupportedCurrenciesResponse)(nil), // 19: hipstershop.GetSupportedCurrenciesResponse (*CurrencyConversionRequest)(nil), // 20: hipstershop.CurrencyConversionRequest (*CreditCardInfo)(nil), // 21: hipstershop.CreditCardInfo (*ChargeRequest)(nil), // 22: hipstershop.ChargeRequest (*ChargeResponse)(nil), // 23: hipstershop.ChargeResponse (*OrderItem)(nil), // 24: hipstershop.OrderItem (*OrderResult)(nil), // 25: hipstershop.OrderResult (*SendOrderConfirmationRequest)(nil), // 26: hipstershop.SendOrderConfirmationRequest (*PlaceOrderRequest)(nil), // 27: hipstershop.PlaceOrderRequest (*PlaceOrderResponse)(nil), // 28: hipstershop.PlaceOrderResponse (*AdRequest)(nil), // 29: hipstershop.AdRequest (*AdResponse)(nil), // 30: hipstershop.AdResponse (*Ad)(nil), // 31: hipstershop.Ad } var file_demo_proto_depIdxs = []int32{ 0, // 0: hipstershop.AddItemRequest.item:type_name -> hipstershop.CartItem 0, // 1: hipstershop.Cart.items:type_name -> hipstershop.CartItem 18, // 2: hipstershop.Product.price_usd:type_name -> hipstershop.Money 8, // 3: hipstershop.ListProductsResponse.products:type_name -> hipstershop.Product 8, // 4: hipstershop.SearchProductsResponse.results:type_name -> hipstershop.Product 17, // 5: hipstershop.GetQuoteRequest.address:type_name -> hipstershop.Address 0, // 6: hipstershop.GetQuoteRequest.items:type_name -> hipstershop.CartItem 18, // 7: hipstershop.GetQuoteResponse.cost_usd:type_name -> hipstershop.Money 17, // 8: hipstershop.ShipOrderRequest.address:type_name -> hipstershop.Address 0, // 9: hipstershop.ShipOrderRequest.items:type_name -> hipstershop.CartItem 18, // 10: hipstershop.CurrencyConversionRequest.from:type_name -> hipstershop.Money 18, // 11: hipstershop.ChargeRequest.amount:type_name -> hipstershop.Money 21, // 12: hipstershop.ChargeRequest.credit_card:type_name -> hipstershop.CreditCardInfo 0, // 13: hipstershop.OrderItem.item:type_name -> hipstershop.CartItem 18, // 14: hipstershop.OrderItem.cost:type_name -> hipstershop.Money 18, // 15: hipstershop.OrderResult.shipping_cost:type_name -> hipstershop.Money 17, // 16: hipstershop.OrderResult.shipping_address:type_name -> hipstershop.Address 24, // 17: hipstershop.OrderResult.items:type_name -> hipstershop.OrderItem 25, // 18: hipstershop.SendOrderConfirmationRequest.order:type_name -> hipstershop.OrderResult 17, // 19: hipstershop.PlaceOrderRequest.address:type_name -> hipstershop.Address 21, // 20: hipstershop.PlaceOrderRequest.credit_card:type_name -> hipstershop.CreditCardInfo 25, // 21: hipstershop.PlaceOrderResponse.order:type_name -> hipstershop.OrderResult 31, // 22: hipstershop.AdResponse.ads:type_name -> hipstershop.Ad 1, // 23: hipstershop.CartService.AddItem:input_type -> hipstershop.AddItemRequest 3, // 24: hipstershop.CartService.GetCart:input_type -> hipstershop.GetCartRequest 2, // 25: hipstershop.CartService.EmptyCart:input_type -> hipstershop.EmptyCartRequest 6, // 26: hipstershop.RecommendationService.ListRecommendations:input_type -> hipstershop.ListRecommendationsRequest 5, // 27: hipstershop.ProductCatalogService.ListProducts:input_type -> hipstershop.Empty 10, // 28: hipstershop.ProductCatalogService.GetProduct:input_type -> hipstershop.GetProductRequest 11, // 29: hipstershop.ProductCatalogService.SearchProducts:input_type -> hipstershop.SearchProductsRequest 13, // 30: hipstershop.ShippingService.GetQuote:input_type -> hipstershop.GetQuoteRequest 15, // 31: hipstershop.ShippingService.ShipOrder:input_type -> hipstershop.ShipOrderRequest 5, // 32: hipstershop.CurrencyService.GetSupportedCurrencies:input_type -> hipstershop.Empty 20, // 33: hipstershop.CurrencyService.Convert:input_type -> hipstershop.CurrencyConversionRequest 22, // 34: hipstershop.PaymentService.Charge:input_type -> hipstershop.ChargeRequest 26, // 35: hipstershop.EmailService.SendOrderConfirmation:input_type -> hipstershop.SendOrderConfirmationRequest 27, // 36: hipstershop.CheckoutService.PlaceOrder:input_type -> hipstershop.PlaceOrderRequest 29, // 37: hipstershop.AdService.GetAds:input_type -> hipstershop.AdRequest 5, // 38: hipstershop.CartService.AddItem:output_type -> hipstershop.Empty 4, // 39: hipstershop.CartService.GetCart:output_type -> hipstershop.Cart 5, // 40: hipstershop.CartService.EmptyCart:output_type -> hipstershop.Empty 7, // 41: hipstershop.RecommendationService.ListRecommendations:output_type -> hipstershop.ListRecommendationsResponse 9, // 42: hipstershop.ProductCatalogService.ListProducts:output_type -> hipstershop.ListProductsResponse 8, // 43: hipstershop.ProductCatalogService.GetProduct:output_type -> hipstershop.Product 12, // 44: hipstershop.ProductCatalogService.SearchProducts:output_type -> hipstershop.SearchProductsResponse 14, // 45: hipstershop.ShippingService.GetQuote:output_type -> hipstershop.GetQuoteResponse 16, // 46: hipstershop.ShippingService.ShipOrder:output_type -> hipstershop.ShipOrderResponse 19, // 47: hipstershop.CurrencyService.GetSupportedCurrencies:output_type -> hipstershop.GetSupportedCurrenciesResponse 18, // 48: hipstershop.CurrencyService.Convert:output_type -> hipstershop.Money 23, // 49: hipstershop.PaymentService.Charge:output_type -> hipstershop.ChargeResponse 5, // 50: hipstershop.EmailService.SendOrderConfirmation:output_type -> hipstershop.Empty 28, // 51: hipstershop.CheckoutService.PlaceOrder:output_type -> hipstershop.PlaceOrderResponse 30, // 52: hipstershop.AdService.GetAds:output_type -> hipstershop.AdResponse 38, // [38:53] is the sub-list for method output_type 23, // [23:38] is the sub-list for method input_type 23, // [23:23] is the sub-list for extension type_name 23, // [23:23] is the sub-list for extension extendee 0, // [0:23] is the sub-list for field type_name } func init() { file_demo_proto_init() } func file_demo_proto_init() { if File_demo_proto != nil { return } if !protoimpl.UnsafeEnabled { file_demo_proto_msgTypes[0].Exporter = func(v any, i int) any { switch v := v.(*CartItem); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_demo_proto_msgTypes[1].Exporter = func(v any, i int) any { switch v := v.(*AddItemRequest); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_demo_proto_msgTypes[2].Exporter = func(v any, i int) any { switch v := v.(*EmptyCartRequest); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_demo_proto_msgTypes[3].Exporter = func(v any, i int) any { switch v := v.(*GetCartRequest); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_demo_proto_msgTypes[4].Exporter = func(v any, i int) any { switch v := v.(*Cart); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_demo_proto_msgTypes[5].Exporter = func(v any, i int) any { switch v := v.(*Empty); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_demo_proto_msgTypes[6].Exporter = func(v any, i int) any { switch v := v.(*ListRecommendationsRequest); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_demo_proto_msgTypes[7].Exporter = func(v any, i int) any { switch v := v.(*ListRecommendationsResponse); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_demo_proto_msgTypes[8].Exporter = func(v any, i int) any { switch v := v.(*Product); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_demo_proto_msgTypes[9].Exporter = func(v any, i int) any { switch v := v.(*ListProductsResponse); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_demo_proto_msgTypes[10].Exporter = func(v any, i int) any { switch v := v.(*GetProductRequest); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_demo_proto_msgTypes[11].Exporter = func(v any, i int) any { switch v := v.(*SearchProductsRequest); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_demo_proto_msgTypes[12].Exporter = func(v any, i int) any { switch v := v.(*SearchProductsResponse); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_demo_proto_msgTypes[13].Exporter = func(v any, i int) any { switch v := v.(*GetQuoteRequest); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_demo_proto_msgTypes[14].Exporter = func(v any, i int) any { switch v := v.(*GetQuoteResponse); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_demo_proto_msgTypes[15].Exporter = func(v any, i int) any { switch v := v.(*ShipOrderRequest); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_demo_proto_msgTypes[16].Exporter = func(v any, i int) any { switch v := v.(*ShipOrderResponse); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_demo_proto_msgTypes[17].Exporter = func(v any, i int) any { switch v := v.(*Address); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_demo_proto_msgTypes[18].Exporter = func(v any, i int) any { switch v := v.(*Money); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_demo_proto_msgTypes[19].Exporter = func(v any, i int) any { switch v := v.(*GetSupportedCurrenciesResponse); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_demo_proto_msgTypes[20].Exporter = func(v any, i int) any { switch v := v.(*CurrencyConversionRequest); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_demo_proto_msgTypes[21].Exporter = func(v any, i int) any { switch v := v.(*CreditCardInfo); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_demo_proto_msgTypes[22].Exporter = func(v any, i int) any { switch v := v.(*ChargeRequest); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_demo_proto_msgTypes[23].Exporter = func(v any, i int) any { switch v := v.(*ChargeResponse); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_demo_proto_msgTypes[24].Exporter = func(v any, i int) any { switch v := v.(*OrderItem); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_demo_proto_msgTypes[25].Exporter = func(v any, i int) any { switch v := v.(*OrderResult); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_demo_proto_msgTypes[26].Exporter = func(v any, i int) any { switch v := v.(*SendOrderConfirmationRequest); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_demo_proto_msgTypes[27].Exporter = func(v any, i int) any { switch v := v.(*PlaceOrderRequest); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_demo_proto_msgTypes[28].Exporter = func(v any, i int) any { switch v := v.(*PlaceOrderResponse); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_demo_proto_msgTypes[29].Exporter = func(v any, i int) any { switch v := v.(*AdRequest); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_demo_proto_msgTypes[30].Exporter = func(v any, i int) any { switch v := v.(*AdResponse); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_demo_proto_msgTypes[31].Exporter = func(v any, i int) any { switch v := v.(*Ad); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_demo_proto_rawDesc, NumEnums: 0, NumMessages: 32, NumExtensions: 0, NumServices: 9, }, GoTypes: file_demo_proto_goTypes, DependencyIndexes: file_demo_proto_depIdxs, MessageInfos: file_demo_proto_msgTypes, }.Build() File_demo_proto = out.File file_demo_proto_rawDesc = nil file_demo_proto_goTypes = nil file_demo_proto_depIdxs = nil } ================================================ FILE: src/frontend/genproto/demo_grpc.pb.go ================================================ // Copyright 2020 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. // Code generated by protoc-gen-go-grpc. DO NOT EDIT. // versions: // - protoc-gen-go-grpc v1.5.1 // - protoc v3.6.1 // source: demo.proto package hipstershop import ( context "context" grpc "google.golang.org/grpc" codes "google.golang.org/grpc/codes" status "google.golang.org/grpc/status" ) // This is a compile-time assertion to ensure that this generated file // is compatible with the grpc package it is being compiled against. // Requires gRPC-Go v1.64.0 or later. const _ = grpc.SupportPackageIsVersion9 const ( CartService_AddItem_FullMethodName = "/hipstershop.CartService/AddItem" CartService_GetCart_FullMethodName = "/hipstershop.CartService/GetCart" CartService_EmptyCart_FullMethodName = "/hipstershop.CartService/EmptyCart" ) // CartServiceClient is the client API for CartService service. // // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. type CartServiceClient interface { AddItem(ctx context.Context, in *AddItemRequest, opts ...grpc.CallOption) (*Empty, error) GetCart(ctx context.Context, in *GetCartRequest, opts ...grpc.CallOption) (*Cart, error) EmptyCart(ctx context.Context, in *EmptyCartRequest, opts ...grpc.CallOption) (*Empty, error) } type cartServiceClient struct { cc grpc.ClientConnInterface } func NewCartServiceClient(cc grpc.ClientConnInterface) CartServiceClient { return &cartServiceClient{cc} } func (c *cartServiceClient) AddItem(ctx context.Context, in *AddItemRequest, opts ...grpc.CallOption) (*Empty, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(Empty) err := c.cc.Invoke(ctx, CartService_AddItem_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } return out, nil } func (c *cartServiceClient) GetCart(ctx context.Context, in *GetCartRequest, opts ...grpc.CallOption) (*Cart, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(Cart) err := c.cc.Invoke(ctx, CartService_GetCart_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } return out, nil } func (c *cartServiceClient) EmptyCart(ctx context.Context, in *EmptyCartRequest, opts ...grpc.CallOption) (*Empty, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(Empty) err := c.cc.Invoke(ctx, CartService_EmptyCart_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } return out, nil } // CartServiceServer is the server API for CartService service. // All implementations must embed UnimplementedCartServiceServer // for forward compatibility. type CartServiceServer interface { AddItem(context.Context, *AddItemRequest) (*Empty, error) GetCart(context.Context, *GetCartRequest) (*Cart, error) EmptyCart(context.Context, *EmptyCartRequest) (*Empty, error) mustEmbedUnimplementedCartServiceServer() } // UnimplementedCartServiceServer must be embedded to have // forward compatible implementations. // // NOTE: this should be embedded by value instead of pointer to avoid a nil // pointer dereference when methods are called. type UnimplementedCartServiceServer struct{} func (UnimplementedCartServiceServer) AddItem(context.Context, *AddItemRequest) (*Empty, error) { return nil, status.Errorf(codes.Unimplemented, "method AddItem not implemented") } func (UnimplementedCartServiceServer) GetCart(context.Context, *GetCartRequest) (*Cart, error) { return nil, status.Errorf(codes.Unimplemented, "method GetCart not implemented") } func (UnimplementedCartServiceServer) EmptyCart(context.Context, *EmptyCartRequest) (*Empty, error) { return nil, status.Errorf(codes.Unimplemented, "method EmptyCart not implemented") } func (UnimplementedCartServiceServer) mustEmbedUnimplementedCartServiceServer() {} func (UnimplementedCartServiceServer) testEmbeddedByValue() {} // UnsafeCartServiceServer may be embedded to opt out of forward compatibility for this service. // Use of this interface is not recommended, as added methods to CartServiceServer will // result in compilation errors. type UnsafeCartServiceServer interface { mustEmbedUnimplementedCartServiceServer() } func RegisterCartServiceServer(s grpc.ServiceRegistrar, srv CartServiceServer) { // If the following call pancis, it indicates UnimplementedCartServiceServer was // embedded by pointer and is nil. This will cause panics if an // unimplemented method is ever invoked, so we test this at initialization // time to prevent it from happening at runtime later due to I/O. if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { t.testEmbeddedByValue() } s.RegisterService(&CartService_ServiceDesc, srv) } func _CartService_AddItem_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(AddItemRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(CartServiceServer).AddItem(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: CartService_AddItem_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(CartServiceServer).AddItem(ctx, req.(*AddItemRequest)) } return interceptor(ctx, in, info, handler) } func _CartService_GetCart_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(GetCartRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(CartServiceServer).GetCart(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: CartService_GetCart_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(CartServiceServer).GetCart(ctx, req.(*GetCartRequest)) } return interceptor(ctx, in, info, handler) } func _CartService_EmptyCart_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(EmptyCartRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(CartServiceServer).EmptyCart(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: CartService_EmptyCart_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(CartServiceServer).EmptyCart(ctx, req.(*EmptyCartRequest)) } return interceptor(ctx, in, info, handler) } // CartService_ServiceDesc is the grpc.ServiceDesc for CartService service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) var CartService_ServiceDesc = grpc.ServiceDesc{ ServiceName: "hipstershop.CartService", HandlerType: (*CartServiceServer)(nil), Methods: []grpc.MethodDesc{ { MethodName: "AddItem", Handler: _CartService_AddItem_Handler, }, { MethodName: "GetCart", Handler: _CartService_GetCart_Handler, }, { MethodName: "EmptyCart", Handler: _CartService_EmptyCart_Handler, }, }, Streams: []grpc.StreamDesc{}, Metadata: "demo.proto", } const ( RecommendationService_ListRecommendations_FullMethodName = "/hipstershop.RecommendationService/ListRecommendations" ) // RecommendationServiceClient is the client API for RecommendationService service. // // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. type RecommendationServiceClient interface { ListRecommendations(ctx context.Context, in *ListRecommendationsRequest, opts ...grpc.CallOption) (*ListRecommendationsResponse, error) } type recommendationServiceClient struct { cc grpc.ClientConnInterface } func NewRecommendationServiceClient(cc grpc.ClientConnInterface) RecommendationServiceClient { return &recommendationServiceClient{cc} } func (c *recommendationServiceClient) ListRecommendations(ctx context.Context, in *ListRecommendationsRequest, opts ...grpc.CallOption) (*ListRecommendationsResponse, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(ListRecommendationsResponse) err := c.cc.Invoke(ctx, RecommendationService_ListRecommendations_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } return out, nil } // RecommendationServiceServer is the server API for RecommendationService service. // All implementations must embed UnimplementedRecommendationServiceServer // for forward compatibility. type RecommendationServiceServer interface { ListRecommendations(context.Context, *ListRecommendationsRequest) (*ListRecommendationsResponse, error) mustEmbedUnimplementedRecommendationServiceServer() } // UnimplementedRecommendationServiceServer must be embedded to have // forward compatible implementations. // // NOTE: this should be embedded by value instead of pointer to avoid a nil // pointer dereference when methods are called. type UnimplementedRecommendationServiceServer struct{} func (UnimplementedRecommendationServiceServer) ListRecommendations(context.Context, *ListRecommendationsRequest) (*ListRecommendationsResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method ListRecommendations not implemented") } func (UnimplementedRecommendationServiceServer) mustEmbedUnimplementedRecommendationServiceServer() {} func (UnimplementedRecommendationServiceServer) testEmbeddedByValue() {} // UnsafeRecommendationServiceServer may be embedded to opt out of forward compatibility for this service. // Use of this interface is not recommended, as added methods to RecommendationServiceServer will // result in compilation errors. type UnsafeRecommendationServiceServer interface { mustEmbedUnimplementedRecommendationServiceServer() } func RegisterRecommendationServiceServer(s grpc.ServiceRegistrar, srv RecommendationServiceServer) { // If the following call pancis, it indicates UnimplementedRecommendationServiceServer was // embedded by pointer and is nil. This will cause panics if an // unimplemented method is ever invoked, so we test this at initialization // time to prevent it from happening at runtime later due to I/O. if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { t.testEmbeddedByValue() } s.RegisterService(&RecommendationService_ServiceDesc, srv) } func _RecommendationService_ListRecommendations_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(ListRecommendationsRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(RecommendationServiceServer).ListRecommendations(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: RecommendationService_ListRecommendations_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(RecommendationServiceServer).ListRecommendations(ctx, req.(*ListRecommendationsRequest)) } return interceptor(ctx, in, info, handler) } // RecommendationService_ServiceDesc is the grpc.ServiceDesc for RecommendationService service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) var RecommendationService_ServiceDesc = grpc.ServiceDesc{ ServiceName: "hipstershop.RecommendationService", HandlerType: (*RecommendationServiceServer)(nil), Methods: []grpc.MethodDesc{ { MethodName: "ListRecommendations", Handler: _RecommendationService_ListRecommendations_Handler, }, }, Streams: []grpc.StreamDesc{}, Metadata: "demo.proto", } const ( ProductCatalogService_ListProducts_FullMethodName = "/hipstershop.ProductCatalogService/ListProducts" ProductCatalogService_GetProduct_FullMethodName = "/hipstershop.ProductCatalogService/GetProduct" ProductCatalogService_SearchProducts_FullMethodName = "/hipstershop.ProductCatalogService/SearchProducts" ) // ProductCatalogServiceClient is the client API for ProductCatalogService service. // // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. type ProductCatalogServiceClient interface { ListProducts(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*ListProductsResponse, error) GetProduct(ctx context.Context, in *GetProductRequest, opts ...grpc.CallOption) (*Product, error) SearchProducts(ctx context.Context, in *SearchProductsRequest, opts ...grpc.CallOption) (*SearchProductsResponse, error) } type productCatalogServiceClient struct { cc grpc.ClientConnInterface } func NewProductCatalogServiceClient(cc grpc.ClientConnInterface) ProductCatalogServiceClient { return &productCatalogServiceClient{cc} } func (c *productCatalogServiceClient) ListProducts(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*ListProductsResponse, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(ListProductsResponse) err := c.cc.Invoke(ctx, ProductCatalogService_ListProducts_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } return out, nil } func (c *productCatalogServiceClient) GetProduct(ctx context.Context, in *GetProductRequest, opts ...grpc.CallOption) (*Product, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(Product) err := c.cc.Invoke(ctx, ProductCatalogService_GetProduct_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } return out, nil } func (c *productCatalogServiceClient) SearchProducts(ctx context.Context, in *SearchProductsRequest, opts ...grpc.CallOption) (*SearchProductsResponse, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(SearchProductsResponse) err := c.cc.Invoke(ctx, ProductCatalogService_SearchProducts_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } return out, nil } // ProductCatalogServiceServer is the server API for ProductCatalogService service. // All implementations must embed UnimplementedProductCatalogServiceServer // for forward compatibility. type ProductCatalogServiceServer interface { ListProducts(context.Context, *Empty) (*ListProductsResponse, error) GetProduct(context.Context, *GetProductRequest) (*Product, error) SearchProducts(context.Context, *SearchProductsRequest) (*SearchProductsResponse, error) mustEmbedUnimplementedProductCatalogServiceServer() } // UnimplementedProductCatalogServiceServer must be embedded to have // forward compatible implementations. // // NOTE: this should be embedded by value instead of pointer to avoid a nil // pointer dereference when methods are called. type UnimplementedProductCatalogServiceServer struct{} func (UnimplementedProductCatalogServiceServer) ListProducts(context.Context, *Empty) (*ListProductsResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method ListProducts not implemented") } func (UnimplementedProductCatalogServiceServer) GetProduct(context.Context, *GetProductRequest) (*Product, error) { return nil, status.Errorf(codes.Unimplemented, "method GetProduct not implemented") } func (UnimplementedProductCatalogServiceServer) SearchProducts(context.Context, *SearchProductsRequest) (*SearchProductsResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method SearchProducts not implemented") } func (UnimplementedProductCatalogServiceServer) mustEmbedUnimplementedProductCatalogServiceServer() {} func (UnimplementedProductCatalogServiceServer) testEmbeddedByValue() {} // UnsafeProductCatalogServiceServer may be embedded to opt out of forward compatibility for this service. // Use of this interface is not recommended, as added methods to ProductCatalogServiceServer will // result in compilation errors. type UnsafeProductCatalogServiceServer interface { mustEmbedUnimplementedProductCatalogServiceServer() } func RegisterProductCatalogServiceServer(s grpc.ServiceRegistrar, srv ProductCatalogServiceServer) { // If the following call pancis, it indicates UnimplementedProductCatalogServiceServer was // embedded by pointer and is nil. This will cause panics if an // unimplemented method is ever invoked, so we test this at initialization // time to prevent it from happening at runtime later due to I/O. if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { t.testEmbeddedByValue() } s.RegisterService(&ProductCatalogService_ServiceDesc, srv) } func _ProductCatalogService_ListProducts_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(Empty) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(ProductCatalogServiceServer).ListProducts(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: ProductCatalogService_ListProducts_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(ProductCatalogServiceServer).ListProducts(ctx, req.(*Empty)) } return interceptor(ctx, in, info, handler) } func _ProductCatalogService_GetProduct_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(GetProductRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(ProductCatalogServiceServer).GetProduct(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: ProductCatalogService_GetProduct_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(ProductCatalogServiceServer).GetProduct(ctx, req.(*GetProductRequest)) } return interceptor(ctx, in, info, handler) } func _ProductCatalogService_SearchProducts_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(SearchProductsRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(ProductCatalogServiceServer).SearchProducts(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: ProductCatalogService_SearchProducts_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(ProductCatalogServiceServer).SearchProducts(ctx, req.(*SearchProductsRequest)) } return interceptor(ctx, in, info, handler) } // ProductCatalogService_ServiceDesc is the grpc.ServiceDesc for ProductCatalogService service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) var ProductCatalogService_ServiceDesc = grpc.ServiceDesc{ ServiceName: "hipstershop.ProductCatalogService", HandlerType: (*ProductCatalogServiceServer)(nil), Methods: []grpc.MethodDesc{ { MethodName: "ListProducts", Handler: _ProductCatalogService_ListProducts_Handler, }, { MethodName: "GetProduct", Handler: _ProductCatalogService_GetProduct_Handler, }, { MethodName: "SearchProducts", Handler: _ProductCatalogService_SearchProducts_Handler, }, }, Streams: []grpc.StreamDesc{}, Metadata: "demo.proto", } const ( ShippingService_GetQuote_FullMethodName = "/hipstershop.ShippingService/GetQuote" ShippingService_ShipOrder_FullMethodName = "/hipstershop.ShippingService/ShipOrder" ) // ShippingServiceClient is the client API for ShippingService service. // // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. type ShippingServiceClient interface { GetQuote(ctx context.Context, in *GetQuoteRequest, opts ...grpc.CallOption) (*GetQuoteResponse, error) ShipOrder(ctx context.Context, in *ShipOrderRequest, opts ...grpc.CallOption) (*ShipOrderResponse, error) } type shippingServiceClient struct { cc grpc.ClientConnInterface } func NewShippingServiceClient(cc grpc.ClientConnInterface) ShippingServiceClient { return &shippingServiceClient{cc} } func (c *shippingServiceClient) GetQuote(ctx context.Context, in *GetQuoteRequest, opts ...grpc.CallOption) (*GetQuoteResponse, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(GetQuoteResponse) err := c.cc.Invoke(ctx, ShippingService_GetQuote_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } return out, nil } func (c *shippingServiceClient) ShipOrder(ctx context.Context, in *ShipOrderRequest, opts ...grpc.CallOption) (*ShipOrderResponse, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(ShipOrderResponse) err := c.cc.Invoke(ctx, ShippingService_ShipOrder_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } return out, nil } // ShippingServiceServer is the server API for ShippingService service. // All implementations must embed UnimplementedShippingServiceServer // for forward compatibility. type ShippingServiceServer interface { GetQuote(context.Context, *GetQuoteRequest) (*GetQuoteResponse, error) ShipOrder(context.Context, *ShipOrderRequest) (*ShipOrderResponse, error) mustEmbedUnimplementedShippingServiceServer() } // UnimplementedShippingServiceServer must be embedded to have // forward compatible implementations. // // NOTE: this should be embedded by value instead of pointer to avoid a nil // pointer dereference when methods are called. type UnimplementedShippingServiceServer struct{} func (UnimplementedShippingServiceServer) GetQuote(context.Context, *GetQuoteRequest) (*GetQuoteResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method GetQuote not implemented") } func (UnimplementedShippingServiceServer) ShipOrder(context.Context, *ShipOrderRequest) (*ShipOrderResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method ShipOrder not implemented") } func (UnimplementedShippingServiceServer) mustEmbedUnimplementedShippingServiceServer() {} func (UnimplementedShippingServiceServer) testEmbeddedByValue() {} // UnsafeShippingServiceServer may be embedded to opt out of forward compatibility for this service. // Use of this interface is not recommended, as added methods to ShippingServiceServer will // result in compilation errors. type UnsafeShippingServiceServer interface { mustEmbedUnimplementedShippingServiceServer() } func RegisterShippingServiceServer(s grpc.ServiceRegistrar, srv ShippingServiceServer) { // If the following call pancis, it indicates UnimplementedShippingServiceServer was // embedded by pointer and is nil. This will cause panics if an // unimplemented method is ever invoked, so we test this at initialization // time to prevent it from happening at runtime later due to I/O. if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { t.testEmbeddedByValue() } s.RegisterService(&ShippingService_ServiceDesc, srv) } func _ShippingService_GetQuote_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(GetQuoteRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(ShippingServiceServer).GetQuote(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: ShippingService_GetQuote_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(ShippingServiceServer).GetQuote(ctx, req.(*GetQuoteRequest)) } return interceptor(ctx, in, info, handler) } func _ShippingService_ShipOrder_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(ShipOrderRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(ShippingServiceServer).ShipOrder(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: ShippingService_ShipOrder_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(ShippingServiceServer).ShipOrder(ctx, req.(*ShipOrderRequest)) } return interceptor(ctx, in, info, handler) } // ShippingService_ServiceDesc is the grpc.ServiceDesc for ShippingService service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) var ShippingService_ServiceDesc = grpc.ServiceDesc{ ServiceName: "hipstershop.ShippingService", HandlerType: (*ShippingServiceServer)(nil), Methods: []grpc.MethodDesc{ { MethodName: "GetQuote", Handler: _ShippingService_GetQuote_Handler, }, { MethodName: "ShipOrder", Handler: _ShippingService_ShipOrder_Handler, }, }, Streams: []grpc.StreamDesc{}, Metadata: "demo.proto", } const ( CurrencyService_GetSupportedCurrencies_FullMethodName = "/hipstershop.CurrencyService/GetSupportedCurrencies" CurrencyService_Convert_FullMethodName = "/hipstershop.CurrencyService/Convert" ) // CurrencyServiceClient is the client API for CurrencyService service. // // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. type CurrencyServiceClient interface { GetSupportedCurrencies(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*GetSupportedCurrenciesResponse, error) Convert(ctx context.Context, in *CurrencyConversionRequest, opts ...grpc.CallOption) (*Money, error) } type currencyServiceClient struct { cc grpc.ClientConnInterface } func NewCurrencyServiceClient(cc grpc.ClientConnInterface) CurrencyServiceClient { return ¤cyServiceClient{cc} } func (c *currencyServiceClient) GetSupportedCurrencies(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*GetSupportedCurrenciesResponse, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(GetSupportedCurrenciesResponse) err := c.cc.Invoke(ctx, CurrencyService_GetSupportedCurrencies_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } return out, nil } func (c *currencyServiceClient) Convert(ctx context.Context, in *CurrencyConversionRequest, opts ...grpc.CallOption) (*Money, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(Money) err := c.cc.Invoke(ctx, CurrencyService_Convert_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } return out, nil } // CurrencyServiceServer is the server API for CurrencyService service. // All implementations must embed UnimplementedCurrencyServiceServer // for forward compatibility. type CurrencyServiceServer interface { GetSupportedCurrencies(context.Context, *Empty) (*GetSupportedCurrenciesResponse, error) Convert(context.Context, *CurrencyConversionRequest) (*Money, error) mustEmbedUnimplementedCurrencyServiceServer() } // UnimplementedCurrencyServiceServer must be embedded to have // forward compatible implementations. // // NOTE: this should be embedded by value instead of pointer to avoid a nil // pointer dereference when methods are called. type UnimplementedCurrencyServiceServer struct{} func (UnimplementedCurrencyServiceServer) GetSupportedCurrencies(context.Context, *Empty) (*GetSupportedCurrenciesResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method GetSupportedCurrencies not implemented") } func (UnimplementedCurrencyServiceServer) Convert(context.Context, *CurrencyConversionRequest) (*Money, error) { return nil, status.Errorf(codes.Unimplemented, "method Convert not implemented") } func (UnimplementedCurrencyServiceServer) mustEmbedUnimplementedCurrencyServiceServer() {} func (UnimplementedCurrencyServiceServer) testEmbeddedByValue() {} // UnsafeCurrencyServiceServer may be embedded to opt out of forward compatibility for this service. // Use of this interface is not recommended, as added methods to CurrencyServiceServer will // result in compilation errors. type UnsafeCurrencyServiceServer interface { mustEmbedUnimplementedCurrencyServiceServer() } func RegisterCurrencyServiceServer(s grpc.ServiceRegistrar, srv CurrencyServiceServer) { // If the following call pancis, it indicates UnimplementedCurrencyServiceServer was // embedded by pointer and is nil. This will cause panics if an // unimplemented method is ever invoked, so we test this at initialization // time to prevent it from happening at runtime later due to I/O. if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { t.testEmbeddedByValue() } s.RegisterService(&CurrencyService_ServiceDesc, srv) } func _CurrencyService_GetSupportedCurrencies_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(Empty) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(CurrencyServiceServer).GetSupportedCurrencies(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: CurrencyService_GetSupportedCurrencies_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(CurrencyServiceServer).GetSupportedCurrencies(ctx, req.(*Empty)) } return interceptor(ctx, in, info, handler) } func _CurrencyService_Convert_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(CurrencyConversionRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(CurrencyServiceServer).Convert(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: CurrencyService_Convert_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(CurrencyServiceServer).Convert(ctx, req.(*CurrencyConversionRequest)) } return interceptor(ctx, in, info, handler) } // CurrencyService_ServiceDesc is the grpc.ServiceDesc for CurrencyService service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) var CurrencyService_ServiceDesc = grpc.ServiceDesc{ ServiceName: "hipstershop.CurrencyService", HandlerType: (*CurrencyServiceServer)(nil), Methods: []grpc.MethodDesc{ { MethodName: "GetSupportedCurrencies", Handler: _CurrencyService_GetSupportedCurrencies_Handler, }, { MethodName: "Convert", Handler: _CurrencyService_Convert_Handler, }, }, Streams: []grpc.StreamDesc{}, Metadata: "demo.proto", } const ( PaymentService_Charge_FullMethodName = "/hipstershop.PaymentService/Charge" ) // PaymentServiceClient is the client API for PaymentService service. // // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. type PaymentServiceClient interface { Charge(ctx context.Context, in *ChargeRequest, opts ...grpc.CallOption) (*ChargeResponse, error) } type paymentServiceClient struct { cc grpc.ClientConnInterface } func NewPaymentServiceClient(cc grpc.ClientConnInterface) PaymentServiceClient { return &paymentServiceClient{cc} } func (c *paymentServiceClient) Charge(ctx context.Context, in *ChargeRequest, opts ...grpc.CallOption) (*ChargeResponse, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(ChargeResponse) err := c.cc.Invoke(ctx, PaymentService_Charge_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } return out, nil } // PaymentServiceServer is the server API for PaymentService service. // All implementations must embed UnimplementedPaymentServiceServer // for forward compatibility. type PaymentServiceServer interface { Charge(context.Context, *ChargeRequest) (*ChargeResponse, error) mustEmbedUnimplementedPaymentServiceServer() } // UnimplementedPaymentServiceServer must be embedded to have // forward compatible implementations. // // NOTE: this should be embedded by value instead of pointer to avoid a nil // pointer dereference when methods are called. type UnimplementedPaymentServiceServer struct{} func (UnimplementedPaymentServiceServer) Charge(context.Context, *ChargeRequest) (*ChargeResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method Charge not implemented") } func (UnimplementedPaymentServiceServer) mustEmbedUnimplementedPaymentServiceServer() {} func (UnimplementedPaymentServiceServer) testEmbeddedByValue() {} // UnsafePaymentServiceServer may be embedded to opt out of forward compatibility for this service. // Use of this interface is not recommended, as added methods to PaymentServiceServer will // result in compilation errors. type UnsafePaymentServiceServer interface { mustEmbedUnimplementedPaymentServiceServer() } func RegisterPaymentServiceServer(s grpc.ServiceRegistrar, srv PaymentServiceServer) { // If the following call pancis, it indicates UnimplementedPaymentServiceServer was // embedded by pointer and is nil. This will cause panics if an // unimplemented method is ever invoked, so we test this at initialization // time to prevent it from happening at runtime later due to I/O. if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { t.testEmbeddedByValue() } s.RegisterService(&PaymentService_ServiceDesc, srv) } func _PaymentService_Charge_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(ChargeRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(PaymentServiceServer).Charge(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: PaymentService_Charge_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(PaymentServiceServer).Charge(ctx, req.(*ChargeRequest)) } return interceptor(ctx, in, info, handler) } // PaymentService_ServiceDesc is the grpc.ServiceDesc for PaymentService service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) var PaymentService_ServiceDesc = grpc.ServiceDesc{ ServiceName: "hipstershop.PaymentService", HandlerType: (*PaymentServiceServer)(nil), Methods: []grpc.MethodDesc{ { MethodName: "Charge", Handler: _PaymentService_Charge_Handler, }, }, Streams: []grpc.StreamDesc{}, Metadata: "demo.proto", } const ( EmailService_SendOrderConfirmation_FullMethodName = "/hipstershop.EmailService/SendOrderConfirmation" ) // EmailServiceClient is the client API for EmailService service. // // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. type EmailServiceClient interface { SendOrderConfirmation(ctx context.Context, in *SendOrderConfirmationRequest, opts ...grpc.CallOption) (*Empty, error) } type emailServiceClient struct { cc grpc.ClientConnInterface } func NewEmailServiceClient(cc grpc.ClientConnInterface) EmailServiceClient { return &emailServiceClient{cc} } func (c *emailServiceClient) SendOrderConfirmation(ctx context.Context, in *SendOrderConfirmationRequest, opts ...grpc.CallOption) (*Empty, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(Empty) err := c.cc.Invoke(ctx, EmailService_SendOrderConfirmation_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } return out, nil } // EmailServiceServer is the server API for EmailService service. // All implementations must embed UnimplementedEmailServiceServer // for forward compatibility. type EmailServiceServer interface { SendOrderConfirmation(context.Context, *SendOrderConfirmationRequest) (*Empty, error) mustEmbedUnimplementedEmailServiceServer() } // UnimplementedEmailServiceServer must be embedded to have // forward compatible implementations. // // NOTE: this should be embedded by value instead of pointer to avoid a nil // pointer dereference when methods are called. type UnimplementedEmailServiceServer struct{} func (UnimplementedEmailServiceServer) SendOrderConfirmation(context.Context, *SendOrderConfirmationRequest) (*Empty, error) { return nil, status.Errorf(codes.Unimplemented, "method SendOrderConfirmation not implemented") } func (UnimplementedEmailServiceServer) mustEmbedUnimplementedEmailServiceServer() {} func (UnimplementedEmailServiceServer) testEmbeddedByValue() {} // UnsafeEmailServiceServer may be embedded to opt out of forward compatibility for this service. // Use of this interface is not recommended, as added methods to EmailServiceServer will // result in compilation errors. type UnsafeEmailServiceServer interface { mustEmbedUnimplementedEmailServiceServer() } func RegisterEmailServiceServer(s grpc.ServiceRegistrar, srv EmailServiceServer) { // If the following call pancis, it indicates UnimplementedEmailServiceServer was // embedded by pointer and is nil. This will cause panics if an // unimplemented method is ever invoked, so we test this at initialization // time to prevent it from happening at runtime later due to I/O. if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { t.testEmbeddedByValue() } s.RegisterService(&EmailService_ServiceDesc, srv) } func _EmailService_SendOrderConfirmation_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(SendOrderConfirmationRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(EmailServiceServer).SendOrderConfirmation(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: EmailService_SendOrderConfirmation_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(EmailServiceServer).SendOrderConfirmation(ctx, req.(*SendOrderConfirmationRequest)) } return interceptor(ctx, in, info, handler) } // EmailService_ServiceDesc is the grpc.ServiceDesc for EmailService service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) var EmailService_ServiceDesc = grpc.ServiceDesc{ ServiceName: "hipstershop.EmailService", HandlerType: (*EmailServiceServer)(nil), Methods: []grpc.MethodDesc{ { MethodName: "SendOrderConfirmation", Handler: _EmailService_SendOrderConfirmation_Handler, }, }, Streams: []grpc.StreamDesc{}, Metadata: "demo.proto", } const ( CheckoutService_PlaceOrder_FullMethodName = "/hipstershop.CheckoutService/PlaceOrder" ) // CheckoutServiceClient is the client API for CheckoutService service. // // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. type CheckoutServiceClient interface { PlaceOrder(ctx context.Context, in *PlaceOrderRequest, opts ...grpc.CallOption) (*PlaceOrderResponse, error) } type checkoutServiceClient struct { cc grpc.ClientConnInterface } func NewCheckoutServiceClient(cc grpc.ClientConnInterface) CheckoutServiceClient { return &checkoutServiceClient{cc} } func (c *checkoutServiceClient) PlaceOrder(ctx context.Context, in *PlaceOrderRequest, opts ...grpc.CallOption) (*PlaceOrderResponse, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(PlaceOrderResponse) err := c.cc.Invoke(ctx, CheckoutService_PlaceOrder_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } return out, nil } // CheckoutServiceServer is the server API for CheckoutService service. // All implementations must embed UnimplementedCheckoutServiceServer // for forward compatibility. type CheckoutServiceServer interface { PlaceOrder(context.Context, *PlaceOrderRequest) (*PlaceOrderResponse, error) mustEmbedUnimplementedCheckoutServiceServer() } // UnimplementedCheckoutServiceServer must be embedded to have // forward compatible implementations. // // NOTE: this should be embedded by value instead of pointer to avoid a nil // pointer dereference when methods are called. type UnimplementedCheckoutServiceServer struct{} func (UnimplementedCheckoutServiceServer) PlaceOrder(context.Context, *PlaceOrderRequest) (*PlaceOrderResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method PlaceOrder not implemented") } func (UnimplementedCheckoutServiceServer) mustEmbedUnimplementedCheckoutServiceServer() {} func (UnimplementedCheckoutServiceServer) testEmbeddedByValue() {} // UnsafeCheckoutServiceServer may be embedded to opt out of forward compatibility for this service. // Use of this interface is not recommended, as added methods to CheckoutServiceServer will // result in compilation errors. type UnsafeCheckoutServiceServer interface { mustEmbedUnimplementedCheckoutServiceServer() } func RegisterCheckoutServiceServer(s grpc.ServiceRegistrar, srv CheckoutServiceServer) { // If the following call pancis, it indicates UnimplementedCheckoutServiceServer was // embedded by pointer and is nil. This will cause panics if an // unimplemented method is ever invoked, so we test this at initialization // time to prevent it from happening at runtime later due to I/O. if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { t.testEmbeddedByValue() } s.RegisterService(&CheckoutService_ServiceDesc, srv) } func _CheckoutService_PlaceOrder_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(PlaceOrderRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(CheckoutServiceServer).PlaceOrder(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: CheckoutService_PlaceOrder_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(CheckoutServiceServer).PlaceOrder(ctx, req.(*PlaceOrderRequest)) } return interceptor(ctx, in, info, handler) } // CheckoutService_ServiceDesc is the grpc.ServiceDesc for CheckoutService service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) var CheckoutService_ServiceDesc = grpc.ServiceDesc{ ServiceName: "hipstershop.CheckoutService", HandlerType: (*CheckoutServiceServer)(nil), Methods: []grpc.MethodDesc{ { MethodName: "PlaceOrder", Handler: _CheckoutService_PlaceOrder_Handler, }, }, Streams: []grpc.StreamDesc{}, Metadata: "demo.proto", } const ( AdService_GetAds_FullMethodName = "/hipstershop.AdService/GetAds" ) // AdServiceClient is the client API for AdService service. // // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. type AdServiceClient interface { GetAds(ctx context.Context, in *AdRequest, opts ...grpc.CallOption) (*AdResponse, error) } type adServiceClient struct { cc grpc.ClientConnInterface } func NewAdServiceClient(cc grpc.ClientConnInterface) AdServiceClient { return &adServiceClient{cc} } func (c *adServiceClient) GetAds(ctx context.Context, in *AdRequest, opts ...grpc.CallOption) (*AdResponse, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(AdResponse) err := c.cc.Invoke(ctx, AdService_GetAds_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } return out, nil } // AdServiceServer is the server API for AdService service. // All implementations must embed UnimplementedAdServiceServer // for forward compatibility. type AdServiceServer interface { GetAds(context.Context, *AdRequest) (*AdResponse, error) mustEmbedUnimplementedAdServiceServer() } // UnimplementedAdServiceServer must be embedded to have // forward compatible implementations. // // NOTE: this should be embedded by value instead of pointer to avoid a nil // pointer dereference when methods are called. type UnimplementedAdServiceServer struct{} func (UnimplementedAdServiceServer) GetAds(context.Context, *AdRequest) (*AdResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method GetAds not implemented") } func (UnimplementedAdServiceServer) mustEmbedUnimplementedAdServiceServer() {} func (UnimplementedAdServiceServer) testEmbeddedByValue() {} // UnsafeAdServiceServer may be embedded to opt out of forward compatibility for this service. // Use of this interface is not recommended, as added methods to AdServiceServer will // result in compilation errors. type UnsafeAdServiceServer interface { mustEmbedUnimplementedAdServiceServer() } func RegisterAdServiceServer(s grpc.ServiceRegistrar, srv AdServiceServer) { // If the following call pancis, it indicates UnimplementedAdServiceServer was // embedded by pointer and is nil. This will cause panics if an // unimplemented method is ever invoked, so we test this at initialization // time to prevent it from happening at runtime later due to I/O. if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { t.testEmbeddedByValue() } s.RegisterService(&AdService_ServiceDesc, srv) } func _AdService_GetAds_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(AdRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(AdServiceServer).GetAds(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: AdService_GetAds_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(AdServiceServer).GetAds(ctx, req.(*AdRequest)) } return interceptor(ctx, in, info, handler) } // AdService_ServiceDesc is the grpc.ServiceDesc for AdService service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) var AdService_ServiceDesc = grpc.ServiceDesc{ ServiceName: "hipstershop.AdService", HandlerType: (*AdServiceServer)(nil), Methods: []grpc.MethodDesc{ { MethodName: "GetAds", Handler: _AdService_GetAds_Handler, }, }, Streams: []grpc.StreamDesc{}, Metadata: "demo.proto", } ================================================ FILE: src/frontend/genproto.sh ================================================ #!/bin/bash -eu # # Copyright 2018 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 gke_frontend_genproto] PATH=$PATH:$(go env GOPATH)/bin protodir=../../protos outdir=./genproto protoc --proto_path=$protodir --go_out=./$outdir --go_opt=paths=source_relative --go-grpc_out=./$outdir --go-grpc_opt=paths=source_relative $protodir/demo.proto # [END gke_frontend_genproto] ================================================ FILE: src/frontend/go.mod ================================================ module github.com/GoogleCloudPlatform/microservices-demo/src/frontend go 1.25.0 toolchain go1.26.1 require ( cloud.google.com/go/compute/metadata v0.9.0 cloud.google.com/go/profiler v0.4.3 github.com/go-playground/validator/v10 v10.30.1 github.com/google/uuid v1.6.0 github.com/gorilla/mux v1.8.1 github.com/pkg/errors v0.9.1 github.com/sirupsen/logrus v1.9.4 go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0 go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0 go.opentelemetry.io/otel v1.42.0 go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.42.0 go.opentelemetry.io/otel/sdk v1.42.0 google.golang.org/grpc v1.79.2 google.golang.org/protobuf v1.36.11 ) require ( cloud.google.com/go v0.123.0 // indirect cloud.google.com/go/auth v0.17.0 // indirect cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect github.com/cenkalti/backoff/v5 v5.0.3 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/gabriel-vasile/mimetype v1.4.12 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect github.com/google/pprof v0.0.0-20251114195745-4902fdda35c8 // indirect github.com/google/s2a-go v0.1.9 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.7 // indirect github.com/googleapis/gax-go/v2 v2.15.0 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 // indirect github.com/leodido/go-urn v1.4.0 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.42.0 // indirect go.opentelemetry.io/otel/metric v1.42.0 // indirect go.opentelemetry.io/otel/trace v1.42.0 // indirect go.opentelemetry.io/proto/otlp v1.9.0 // indirect golang.org/x/crypto v0.48.0 // indirect golang.org/x/net v0.51.0 // indirect golang.org/x/oauth2 v0.35.0 // indirect golang.org/x/sync v0.19.0 // indirect golang.org/x/sys v0.41.0 // indirect golang.org/x/text v0.34.0 // indirect golang.org/x/time v0.14.0 // indirect google.golang.org/api v0.256.0 // indirect google.golang.org/genproto v0.0.0-20251124214823-79d6a2a48846 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 // indirect ) ================================================ FILE: src/frontend/go.sum ================================================ cel.dev/expr v0.24.0 h1:56OvJKSH3hDGL0ml5uSxZmz3/3Pq4tJ+fb1unVLAFcY= cel.dev/expr v0.24.0/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw= cloud.google.com/go v0.123.0 h1:2NAUJwPR47q+E35uaJeYoNhuNEM9kM8SjgRgdeOJUSE= cloud.google.com/go v0.123.0/go.mod h1:xBoMV08QcqUGuPW65Qfm1o9Y4zKZBpGS+7bImXLTAZU= cloud.google.com/go/auth v0.17.0 h1:74yCm7hCj2rUyyAocqnFzsAYXgJhrG26XCFimrc/Kz4= cloud.google.com/go/auth v0.17.0/go.mod h1:6wv/t5/6rOPAX4fJiRjKkJCvswLwdet7G8+UGXt7nCQ= cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc= cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c= cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs= cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10= cloud.google.com/go/iam v1.5.3 h1:+vMINPiDF2ognBJ97ABAYYwRgsaqxPbQDlMnbHMjolc= cloud.google.com/go/iam v1.5.3/go.mod h1:MR3v9oLkZCTlaqljW6Eb2d3HGDGK5/bDv93jhfISFvU= cloud.google.com/go/monitoring v1.24.3 h1:dde+gMNc0UhPZD1Azu6at2e79bfdztVDS5lvhOdsgaE= cloud.google.com/go/monitoring v1.24.3/go.mod h1:nYP6W0tm3N9H/bOw8am7t62YTzZY+zUeQ+Bi6+2eonI= cloud.google.com/go/profiler v0.4.3 h1:IY3QNKlr8VbXwGWHcZbJQsMA/83ZTH6uAHf8jYyj7OI= cloud.google.com/go/profiler v0.4.3/go.mod h1:3xFodugWfPIQZWFcXdUmfa+yTiiyQ8fWrdT+d2Sg4J0= cloud.google.com/go/storage v1.56.0 h1:iixmq2Fse2tqxMbWhLWC9HfBj1qdxqAmiK8/eqtsLxI= cloud.google.com/go/storage v1.56.0/go.mod h1:Tpuj6t4NweCLzlNbw9Z9iwxEkrSem20AetIeH/shgVU= github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0 h1:sBEjpZlNHzK1voKq9695PJSX2o5NEXl7/OL3coiIY0c= github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0/go.mod h1:P4WPRUkOhJC13W//jWpyfJNDAIpvRbAUIYLX/4jtlE0= github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.53.0 h1:owcC2UnmsZycprQ5RfRgjydWhuoxg71LUfyiQdijZuM= github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.53.0/go.mod h1:ZPpqegjbE99EPKsu3iUWV22A04wzGPcAY/ziSIQEEgs= github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.53.0 h1:Ron4zCA/yk6U7WOBXhTJcDpsUBG9npumK6xw2auFltQ= github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.53.0/go.mod h1:cSgYe11MCNYunTnRXrKiR/tHc0eoKjICUuWpNZoVCOo= github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cncf/xds/go v0.0.0-20251022180443-0feb69152e9f h1:Y8xYupdHxryycyPlc9Y+bSQAYZnetRJ70VMVKm5CKI0= github.com/cncf/xds/go v0.0.0-20251022180443-0feb69152e9f/go.mod h1:HlzOvOjVBOfTGSRXRyY0OiCS/3J1akRGQQpRO/7zyF4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/envoyproxy/go-control-plane v0.13.5-0.20251024222203-75eaa193e329 h1:K+fnvUM0VZ7ZFJf0n4L/BRlnsb9pL/GuDG6FqaH+PwM= github.com/envoyproxy/go-control-plane/envoy v1.35.0 h1:ixjkELDE+ru6idPxcHLj8LBVc2bFP7iBytj353BoHUo= github.com/envoyproxy/go-control-plane/envoy v1.35.0/go.mod h1:09qwbGVuSWWAyN5t/b3iyVfz5+z8QWGrzkoqm/8SbEs= github.com/envoyproxy/protoc-gen-validate v1.2.1 h1:DEo3O99U8j4hBFwbJfrz9VtgcDfUKS7KJ7spH3d86P8= github.com/envoyproxy/protoc-gen-validate v1.2.1/go.mod h1:d/C80l/jxXLdfEIhX1W2TmLfsJ31lvEjwamM4DxlWXU= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/gabriel-vasile/mimetype v1.4.11 h1:AQvxbp830wPhHTqc1u7nzoLT+ZFxGY7emj5DR5DYFik= github.com/gabriel-vasile/mimetype v1.4.11/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= github.com/gabriel-vasile/mimetype v1.4.12 h1:e9hWvmLYvtp846tLHam2o++qitpguFiYCKbn0w9jyqw= github.com/gabriel-vasile/mimetype v1.4.12/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= github.com/go-jose/go-jose/v4 v4.1.3 h1:CVLmWDhDVRa6Mi/IgCgaopNosCaHz7zrMeF9MlZRkrs= github.com/go-jose/go-jose/v4 v4.1.3/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= github.com/go-playground/validator/v10 v10.28.0 h1:Q7ibns33JjyW48gHkuFT91qX48KG0ktULL6FgHdG688= github.com/go-playground/validator/v10 v10.28.0/go.mod h1:GoI6I1SjPBh9p7ykNE/yj3fFYbyDOpwMn5KXd+m2hUU= github.com/go-playground/validator/v10 v10.30.0 h1:5YBPNs273uzsZJD1I8uiB4Aqg9sN6sMDVX3s6LxmhWU= github.com/go-playground/validator/v10 v10.30.0/go.mod h1:oSuBIQzuJxL//3MelwSLD5hc2Tu889bF0Idm9Dg26cM= github.com/go-playground/validator/v10 v10.30.1 h1:f3zDSN/zOma+w6+1Wswgd9fLkdwy06ntQJp0BBvFG0w= github.com/go-playground/validator/v10 v10.30.1/go.mod h1:oSuBIQzuJxL//3MelwSLD5hc2Tu889bF0Idm9Dg26cM= github.com/golang/mock v1.7.0-rc.1 h1:YojYx61/OLFsiv6Rw1Z96LpldJIy31o+UHmwAUMJ6/U= github.com/golang/mock v1.7.0-rc.1/go.mod h1:s42URUywIqd+OcERslBJvOjepvNymP31m3q8d/GkuRs= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/pprof v0.0.0-20251114195745-4902fdda35c8 h1:3DsUAV+VNEQa2CUVLxCY3f87278uWfIDhJnbdvDjvmE= github.com/google/pprof v0.0.0-20251114195745-4902fdda35c8/go.mod h1:I6V7YzU0XDpsHqbsyrghnFZLO1gwK6NPTNvmetQIk9U= github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0= github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/enterprise-certificate-proxy v0.3.7 h1:zrn2Ee/nWmHulBx5sAVrGgAa0f2/R35S4DJwfFaUPFQ= github.com/googleapis/enterprise-certificate-proxy v0.3.7/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA= github.com/googleapis/gax-go/v2 v2.15.0 h1:SyjDc1mGgZU5LncH8gimWo9lW1DtIfPibOG81vgd/bo= github.com/googleapis/gax-go/v2 v2.15.0/go.mod h1:zVVkkxAQHa1RQpg9z2AUCMnKhi0Qld9rcmyfL1OZhoc= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 h1:NmZ1PKzSTQbuGHw9DGPFomqkkLWMC+vZCkfs+FHv1Vg= github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3/go.mod h1:zQrxl1YP88HQlA6i9c63DSVPFklWpGX4OWAc9bFuaH4= github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 h1:HWRh5R2+9EifMyIHV7ZV+MIZqgz+PMpZ14Jynv3O2Zs= github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0/go.mod h1:JfhWUomR1baixubs02l85lZYYOm7LV6om4ceouMv45c= github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo= github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w= github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g= github.com/spiffe/go-spiffe/v2 v2.6.0 h1:l+DolpxNWYgruGQVV0xsfeya3CsC7m8iBzDnMpsbLuo= github.com/spiffe/go-spiffe/v2 v2.6.0/go.mod h1:gm2SeUoMZEtpnzPNs2Csc0D/gX33k1xIx7lEzqblHEs= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/contrib/detectors/gcp v1.38.0 h1:ZoYbqX7OaA/TAikspPl3ozPI6iY6LiIY9I8cUfm+pJs= go.opentelemetry.io/contrib/detectors/gcp v1.38.0/go.mod h1:SU+iU7nu5ud4oCb3LQOhIZ3nRLj6FNVrKgtflbaf2ts= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0 h1:YH4g8lQroajqUwWbq/tr2QX1JFmEXaDLgG+ew9bLMWo= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0/go.mod h1:fvPi2qXDqFs8M4B4fmJhE92TyQs9Ydjlg3RvfUp+NbQ= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.64.0 h1:RN3ifU8y4prNWeEnQp2kRRHz8UwonAEYZl8tUzHEXAk= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.64.0/go.mod h1:habDz3tEWiFANTo6oUE99EmaFUrCNYAAg3wiVmusm70= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0 h1:yI1/OhfEPy7J9eoa6Sj051C7n5dvpj0QX8g4sRchg04= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0/go.mod h1:NoUCKYWK+3ecatC4HjkRktREheMeEtrXoQxrqYFeHSc= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 h1:RbKq8BG0FI8OiXhBfcRtqqHcZcka+gU3cskNuf05R18= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0/go.mod h1:h06DGIukJOevXaj/xrNjhi/2098RZzcLTbc0jDAUbsg= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0 h1:ssfIgGNANqpVFCndZvcuyKbl0g+UAVcbBcqGkG28H0Y= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0/go.mod h1:GQ/474YrbE4Jx8gZ4q5I4hrhUzM6UPzyrqJYV2AqPoQ= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0 h1:OyrsyzuttWTSur2qN/Lm0m2a8yqyIjUVBZcxFPuXq2o= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0/go.mod h1:C2NGBr+kAB4bk3xtMXfZ94gqFDtg/GkI7e9zqGh5Beg= go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= go.opentelemetry.io/otel v1.42.0 h1:lSQGzTgVR3+sgJDAU/7/ZMjN9Z+vUip7leaqBKy4sho= go.opentelemetry.io/otel v1.42.0/go.mod h1:lJNsdRMxCUIWuMlVJWzecSMuNjE7dOYyWlqOXWkdqCc= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 h1:GqRJVj7UmLjCVyVJ3ZFLdPRmhDUp2zFmQe3RHIOsw24= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0/go.mod h1:ri3aaHSmCTVYu2AWv44YMauwAQc0aqI9gHKIcSbI1pU= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0 h1:f0cb2XPmrqn4XMy9PNliTgRKJgS5WcL/u0/WRYGz4t0= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0/go.mod h1:vnakAaFckOMiMtOIhFI2MNH4FYrZzXCYxmb1LlhoGz8= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.42.0 h1:THuZiwpQZuHPul65w4WcwEnkX2QIuMT+UFoOrygtoJw= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.42.0/go.mod h1:J2pvYM5NGHofZ2/Ru6zw/TNWnEQp5crgyDeSrYpXkAw= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 h1:lwI4Dc5leUqENgGuQImwLo4WnuXFPetmPpkLi2IrX54= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0/go.mod h1:Kz/oCE7z5wuyhPxsXDuaPteSWqjSBD5YaSdbxZYGbGk= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0 h1:in9O8ESIOlwJAEGTkkf34DesGRAc/Pn8qJ7k3r/42LM= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0/go.mod h1:Rp0EXBm5tfnv0WL+ARyO/PHBEaEAT8UUHQ6AGJcSq6c= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.42.0 h1:zWWrB1U6nqhS/k6zYB74CjRpuiitRtLLi68VcgmOEto= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.42.0/go.mod h1:2qXPNBX1OVRC0IwOnfo1ljoid+RD0QK3443EaqVlsOU= go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= go.opentelemetry.io/otel/metric v1.42.0 h1:2jXG+3oZLNXEPfNmnpxKDeZsFI5o4J+nz6xUlaFdF/4= go.opentelemetry.io/otel/metric v1.42.0/go.mod h1:RlUN/7vTU7Ao/diDkEpQpnz3/92J9ko05BIwxYa2SSI= go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E= go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg= go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= go.opentelemetry.io/otel/sdk v1.42.0 h1:LyC8+jqk6UJwdrI/8VydAq/hvkFKNHZVIWuslJXYsDo= go.opentelemetry.io/otel/sdk v1.42.0/go.mod h1:rGHCAxd9DAph0joO4W6OPwxjNTYWghRWmkHuGbayMts= go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM= go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= go.opentelemetry.io/otel/trace v1.42.0 h1:OUCgIPt+mzOnaUTpOQcBiM/PLQ/Op7oq6g4LenLmOYY= go.opentelemetry.io/otel/trace v1.42.0/go.mod h1:f3K9S+IFqnumBkKhRJMeaZeNk9epyhnCmQh/EysQCdc= go.opentelemetry.io/proto/otlp v1.9.0 h1:l706jCMITVouPOqEnii2fIAuO3IVGBRPV5ICjceRb/A= go.opentelemetry.io/proto/otlp v1.9.0/go.mod h1:xE+Cx5E/eEHw+ISFkwPLwCZefwVjY+pqKg1qcK03+/4= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q= golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4= golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU= golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0= golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts= golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos= golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo= golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y= golang.org/x/oauth2 v0.33.0 h1:4Q+qn+E5z8gPRJfmRy7C2gGG3T4jIprK6aSYgTXGRpo= golang.org/x/oauth2 v0.33.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/oauth2 v0.35.0 h1:Mv2mzuHuZuY2+bkyWXIHMfhNdJAdwW3FuWeCPYN5GVQ= golang.org/x/oauth2 v0.35.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k= golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU= golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY= golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk= golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA= golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= google.golang.org/api v0.256.0 h1:u6Khm8+F9sxbCTYNoBHg6/Hwv0N/i+V94MvkOSor6oI= google.golang.org/api v0.256.0/go.mod h1:KIgPhksXADEKJlnEoRa9qAII4rXcy40vfI8HRqcU964= google.golang.org/genproto v0.0.0-20251124214823-79d6a2a48846 h1:dDbsTLIK7EzwUq36kCSAsk0slouq/S0tWHeeGi97cD8= google.golang.org/genproto v0.0.0-20251124214823-79d6a2a48846/go.mod h1:PP0g88Dz3C7hRAfbQCQggeWAXjuqGsNPLE4s7jh0RGU= google.golang.org/genproto/googleapis/api v0.0.0-20251124214823-79d6a2a48846 h1:ZdyUkS9po3H7G0tuh955QVyyotWvOD4W0aEapeGeUYk= google.golang.org/genproto/googleapis/api v0.0.0-20251124214823-79d6a2a48846/go.mod h1:Fk4kyraUvqD7i5H6S43sj2W98fbZa75lpZz/eUyhfO0= google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 h1:fCvbg86sFXwdrl5LgVcTEvNC+2txB5mgROGmRL5mrls= google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:+rXWjjaukWZun3mLfjmVnQi18E1AsFbDN9QdJ5YXLto= google.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57 h1:JLQynH/LBHfCTSbDWl+py8C+Rg/k1OVH3xfcaiANuF0= google.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57/go.mod h1:kSJwQxqmFXeo79zOmbrALdflXQeAYcUbgS7PbpMknCY= google.golang.org/genproto/googleapis/rpc v0.0.0-20251124214823-79d6a2a48846 h1:Wgl1rcDNThT+Zn47YyCXOXyX/COgMTIdhJ717F0l4xk= google.golang.org/genproto/googleapis/rpc v0.0.0-20251124214823-79d6a2a48846/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 h1:gRkg/vSppuSQoDjxyiGfN4Upv/h/DQmIR10ZU8dh4Ww= google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 h1:ggcbiqK8WWh6l1dnltU4BgWGIGo+EVYxCaAPih/zQXQ= google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM= google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig= google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc= google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U= google.golang.org/grpc v1.79.2 h1:fRMD94s2tITpyJGtBBn7MkMseNpOZU8ZxgC3MMBaXRU= google.golang.org/grpc v1.79.2/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= ================================================ FILE: src/frontend/handlers.go ================================================ // Copyright 2018 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. package main import ( "context" "encoding/json" "fmt" "html/template" "io" "math/rand" "net" "net/http" "os" "strconv" "strings" "time" "github.com/gorilla/mux" "github.com/pkg/errors" "github.com/sirupsen/logrus" pb "github.com/GoogleCloudPlatform/microservices-demo/src/frontend/genproto" "github.com/GoogleCloudPlatform/microservices-demo/src/frontend/money" "github.com/GoogleCloudPlatform/microservices-demo/src/frontend/validator" ) type platformDetails struct { css string provider string } var ( frontendMessage = strings.TrimSpace(os.Getenv("FRONTEND_MESSAGE")) isCymbalBrand = "true" == strings.ToLower(os.Getenv("CYMBAL_BRANDING")) assistantEnabled = "true" == strings.ToLower(os.Getenv("ENABLE_ASSISTANT")) templates = template.Must(template.New(""). Funcs(template.FuncMap{ "renderMoney": renderMoney, "renderCurrencyLogo": renderCurrencyLogo, }).ParseGlob("templates/*.html")) plat platformDetails ) var validEnvs = []string{"local", "gcp", "azure", "aws", "onprem", "alibaba"} func (fe *frontendServer) homeHandler(w http.ResponseWriter, r *http.Request) { log := r.Context().Value(ctxKeyLog{}).(logrus.FieldLogger) log.WithField("currency", currentCurrency(r)).Info("home") currencies, err := fe.getCurrencies(r.Context()) if err != nil { renderHTTPError(log, r, w, errors.Wrap(err, "could not retrieve currencies"), http.StatusInternalServerError) return } products, err := fe.getProducts(r.Context()) if err != nil { renderHTTPError(log, r, w, errors.Wrap(err, "could not retrieve products"), http.StatusInternalServerError) return } cart, err := fe.getCart(r.Context(), sessionID(r)) if err != nil { renderHTTPError(log, r, w, errors.Wrap(err, "could not retrieve cart"), http.StatusInternalServerError) return } type productView struct { Item *pb.Product Price *pb.Money } ps := make([]productView, len(products)) for i, p := range products { price, err := fe.convertCurrency(r.Context(), p.GetPriceUsd(), currentCurrency(r)) if err != nil { renderHTTPError(log, r, w, errors.Wrapf(err, "failed to do currency conversion for product %s", p.GetId()), http.StatusInternalServerError) return } ps[i] = productView{p, price} } // Set ENV_PLATFORM (default to local if not set; use env var if set; otherwise detect GCP, which overrides env)_ var env = os.Getenv("ENV_PLATFORM") // Only override from env variable if set + valid env if env == "" || stringinSlice(validEnvs, env) == false { fmt.Println("env platform is either empty or invalid") env = "local" } // Autodetect GCP addrs, err := net.LookupHost("metadata.google.internal.") if err == nil && len(addrs) >= 0 { log.Debugf("Detected Google metadata server: %v, setting ENV_PLATFORM to GCP.", addrs) env = "gcp" } log.Debugf("ENV_PLATFORM is: %s", env) plat = platformDetails{} plat.setPlatformDetails(strings.ToLower(env)) if err := templates.ExecuteTemplate(w, "home", injectCommonTemplateData(r, map[string]interface{}{ "show_currency": true, "currencies": currencies, "products": ps, "cart_size": cartSize(cart), "banner_color": os.Getenv("BANNER_COLOR"), // illustrates canary deployments "ad": fe.chooseAd(r.Context(), []string{}, log), })); err != nil { log.Error(err) } } func (plat *platformDetails) setPlatformDetails(env string) { if env == "aws" { plat.provider = "AWS" plat.css = "aws-platform" } else if env == "onprem" { plat.provider = "On-Premises" plat.css = "onprem-platform" } else if env == "azure" { plat.provider = "Azure" plat.css = "azure-platform" } else if env == "gcp" { plat.provider = "Google Cloud" plat.css = "gcp-platform" } else if env == "alibaba" { plat.provider = "Alibaba Cloud" plat.css = "alibaba-platform" } else { plat.provider = "local" plat.css = "local" } } func (fe *frontendServer) productHandler(w http.ResponseWriter, r *http.Request) { log := r.Context().Value(ctxKeyLog{}).(logrus.FieldLogger) id := mux.Vars(r)["id"] if id == "" { renderHTTPError(log, r, w, errors.New("product id not specified"), http.StatusBadRequest) return } log.WithField("id", id).WithField("currency", currentCurrency(r)). Debug("serving product page") p, err := fe.getProduct(r.Context(), id) if err != nil { renderHTTPError(log, r, w, errors.Wrap(err, "could not retrieve product"), http.StatusInternalServerError) return } currencies, err := fe.getCurrencies(r.Context()) if err != nil { renderHTTPError(log, r, w, errors.Wrap(err, "could not retrieve currencies"), http.StatusInternalServerError) return } cart, err := fe.getCart(r.Context(), sessionID(r)) if err != nil { renderHTTPError(log, r, w, errors.Wrap(err, "could not retrieve cart"), http.StatusInternalServerError) return } price, err := fe.convertCurrency(r.Context(), p.GetPriceUsd(), currentCurrency(r)) if err != nil { renderHTTPError(log, r, w, errors.Wrap(err, "failed to convert currency"), http.StatusInternalServerError) return } // ignores the error retrieving recommendations since it is not critical recommendations, err := fe.getRecommendations(r.Context(), sessionID(r), []string{id}) if err != nil { log.WithField("error", err).Warn("failed to get product recommendations") } product := struct { Item *pb.Product Price *pb.Money }{p, price} // Fetch packaging info (weight/dimensions) of the product // The packaging service is an optional microservice you can run as part of a Google Cloud demo. var packagingInfo *PackagingInfo = nil if isPackagingServiceConfigured() { packagingInfo, err = httpGetPackagingInfo(id) if err != nil { fmt.Println("Failed to obtain product's packaging info:", err) } } if err := templates.ExecuteTemplate(w, "product", injectCommonTemplateData(r, map[string]interface{}{ "ad": fe.chooseAd(r.Context(), p.Categories, log), "show_currency": true, "currencies": currencies, "product": product, "recommendations": recommendations, "cart_size": cartSize(cart), "packagingInfo": packagingInfo, })); err != nil { log.Println(err) } } func (fe *frontendServer) addToCartHandler(w http.ResponseWriter, r *http.Request) { log := r.Context().Value(ctxKeyLog{}).(logrus.FieldLogger) quantity, _ := strconv.ParseUint(r.FormValue("quantity"), 10, 32) productID := r.FormValue("product_id") payload := validator.AddToCartPayload{ Quantity: quantity, ProductID: productID, } if err := payload.Validate(); err != nil { renderHTTPError(log, r, w, validator.ValidationErrorResponse(err), http.StatusUnprocessableEntity) return } log.WithField("product", payload.ProductID).WithField("quantity", payload.Quantity).Debug("adding to cart") p, err := fe.getProduct(r.Context(), payload.ProductID) if err != nil { renderHTTPError(log, r, w, errors.Wrap(err, "could not retrieve product"), http.StatusInternalServerError) return } if err := fe.insertCart(r.Context(), sessionID(r), p.GetId(), int32(payload.Quantity)); err != nil { renderHTTPError(log, r, w, errors.Wrap(err, "failed to add to cart"), http.StatusInternalServerError) return } w.Header().Set("location", baseUrl + "/cart") w.WriteHeader(http.StatusFound) } func (fe *frontendServer) emptyCartHandler(w http.ResponseWriter, r *http.Request) { log := r.Context().Value(ctxKeyLog{}).(logrus.FieldLogger) log.Debug("emptying cart") if err := fe.emptyCart(r.Context(), sessionID(r)); err != nil { renderHTTPError(log, r, w, errors.Wrap(err, "failed to empty cart"), http.StatusInternalServerError) return } w.Header().Set("location", baseUrl + "/") w.WriteHeader(http.StatusFound) } func (fe *frontendServer) viewCartHandler(w http.ResponseWriter, r *http.Request) { log := r.Context().Value(ctxKeyLog{}).(logrus.FieldLogger) log.Debug("view user cart") currencies, err := fe.getCurrencies(r.Context()) if err != nil { renderHTTPError(log, r, w, errors.Wrap(err, "could not retrieve currencies"), http.StatusInternalServerError) return } cart, err := fe.getCart(r.Context(), sessionID(r)) if err != nil { renderHTTPError(log, r, w, errors.Wrap(err, "could not retrieve cart"), http.StatusInternalServerError) return } // ignores the error retrieving recommendations since it is not critical recommendations, err := fe.getRecommendations(r.Context(), sessionID(r), cartIDs(cart)) if err != nil { log.WithField("error", err).Warn("failed to get product recommendations") } shippingCost, err := fe.getShippingQuote(r.Context(), cart, currentCurrency(r)) if err != nil { renderHTTPError(log, r, w, errors.Wrap(err, "failed to get shipping quote"), http.StatusInternalServerError) return } type cartItemView struct { Item *pb.Product Quantity int32 Price *pb.Money } items := make([]cartItemView, len(cart)) totalPrice := pb.Money{CurrencyCode: currentCurrency(r)} for i, item := range cart { p, err := fe.getProduct(r.Context(), item.GetProductId()) if err != nil { renderHTTPError(log, r, w, errors.Wrapf(err, "could not retrieve product #%s", item.GetProductId()), http.StatusInternalServerError) return } price, err := fe.convertCurrency(r.Context(), p.GetPriceUsd(), currentCurrency(r)) if err != nil { renderHTTPError(log, r, w, errors.Wrapf(err, "could not convert currency for product #%s", item.GetProductId()), http.StatusInternalServerError) return } multPrice := money.MultiplySlow(*price, uint32(item.GetQuantity())) items[i] = cartItemView{ Item: p, Quantity: item.GetQuantity(), Price: &multPrice} totalPrice = money.Must(money.Sum(totalPrice, multPrice)) } totalPrice = money.Must(money.Sum(totalPrice, *shippingCost)) year := time.Now().Year() if err := templates.ExecuteTemplate(w, "cart", injectCommonTemplateData(r, map[string]interface{}{ "currencies": currencies, "recommendations": recommendations, "cart_size": cartSize(cart), "shipping_cost": shippingCost, "show_currency": true, "total_cost": totalPrice, "items": items, "expiration_years": []int{year, year + 1, year + 2, year + 3, year + 4}, })); err != nil { log.Println(err) } } func (fe *frontendServer) placeOrderHandler(w http.ResponseWriter, r *http.Request) { log := r.Context().Value(ctxKeyLog{}).(logrus.FieldLogger) log.Debug("placing order") var ( email = r.FormValue("email") streetAddress = r.FormValue("street_address") zipCode, _ = strconv.ParseInt(r.FormValue("zip_code"), 10, 32) city = r.FormValue("city") state = r.FormValue("state") country = r.FormValue("country") ccNumber = r.FormValue("credit_card_number") ccMonth, _ = strconv.ParseInt(r.FormValue("credit_card_expiration_month"), 10, 32) ccYear, _ = strconv.ParseInt(r.FormValue("credit_card_expiration_year"), 10, 32) ccCVV, _ = strconv.ParseInt(r.FormValue("credit_card_cvv"), 10, 32) ) payload := validator.PlaceOrderPayload{ Email: email, StreetAddress: streetAddress, ZipCode: zipCode, City: city, State: state, Country: country, CcNumber: ccNumber, CcMonth: ccMonth, CcYear: ccYear, CcCVV: ccCVV, } if err := payload.Validate(); err != nil { renderHTTPError(log, r, w, validator.ValidationErrorResponse(err), http.StatusUnprocessableEntity) return } order, err := pb.NewCheckoutServiceClient(fe.checkoutSvcConn). PlaceOrder(r.Context(), &pb.PlaceOrderRequest{ Email: payload.Email, CreditCard: &pb.CreditCardInfo{ CreditCardNumber: payload.CcNumber, CreditCardExpirationMonth: int32(payload.CcMonth), CreditCardExpirationYear: int32(payload.CcYear), CreditCardCvv: int32(payload.CcCVV)}, UserId: sessionID(r), UserCurrency: currentCurrency(r), Address: &pb.Address{ StreetAddress: payload.StreetAddress, City: payload.City, State: payload.State, ZipCode: int32(payload.ZipCode), Country: payload.Country}, }) if err != nil { renderHTTPError(log, r, w, errors.Wrap(err, "failed to complete the order"), http.StatusInternalServerError) return } log.WithField("order", order.GetOrder().GetOrderId()).Info("order placed") order.GetOrder().GetItems() recommendations, _ := fe.getRecommendations(r.Context(), sessionID(r), nil) totalPaid := *order.GetOrder().GetShippingCost() for _, v := range order.GetOrder().GetItems() { multPrice := money.MultiplySlow(*v.GetCost(), uint32(v.GetItem().GetQuantity())) totalPaid = money.Must(money.Sum(totalPaid, multPrice)) } currencies, err := fe.getCurrencies(r.Context()) if err != nil { renderHTTPError(log, r, w, errors.Wrap(err, "could not retrieve currencies"), http.StatusInternalServerError) return } if err := templates.ExecuteTemplate(w, "order", injectCommonTemplateData(r, map[string]interface{}{ "show_currency": false, "currencies": currencies, "order": order.GetOrder(), "total_paid": &totalPaid, "recommendations": recommendations, })); err != nil { log.Println(err) } } func (fe *frontendServer) assistantHandler(w http.ResponseWriter, r *http.Request) { currencies, err := fe.getCurrencies(r.Context()) if err != nil { renderHTTPError(log, r, w, errors.Wrap(err, "could not retrieve currencies"), http.StatusInternalServerError) return } if err := templates.ExecuteTemplate(w, "assistant", injectCommonTemplateData(r, map[string]interface{}{ "show_currency": false, "currencies": currencies, })); err != nil { log.Println(err) } } func (fe *frontendServer) logoutHandler(w http.ResponseWriter, r *http.Request) { log := r.Context().Value(ctxKeyLog{}).(logrus.FieldLogger) log.Debug("logging out") for _, c := range r.Cookies() { c.Expires = time.Now().Add(-time.Hour * 24 * 365) c.MaxAge = -1 http.SetCookie(w, c) } w.Header().Set("Location", baseUrl + "/") w.WriteHeader(http.StatusFound) } func (fe *frontendServer) getProductByID(w http.ResponseWriter, r *http.Request) { id := mux.Vars(r)["ids"] if id == "" { return } p, err := fe.getProduct(r.Context(), id) if err != nil { return } jsonData, err := json.Marshal(p) if err != nil { fmt.Println(err) return } w.Write(jsonData) w.WriteHeader(http.StatusOK) } func (fe *frontendServer) chatBotHandler(w http.ResponseWriter, r *http.Request) { log := r.Context().Value(ctxKeyLog{}).(logrus.FieldLogger) type Response struct { Message string `json:"message"` } type LLMResponse struct { Content string `json:"content"` Details map[string]any `json:"details"` } var response LLMResponse url := "http://" + fe.shoppingAssistantSvcAddr req, err := http.NewRequest(http.MethodPost, url, r.Body) if err != nil { renderHTTPError(log, r, w, errors.Wrap(err, "failed to create request"), http.StatusInternalServerError) return } req.Header.Set("Content-Type", "application/json") req.Header.Set("Accept", "application/json") res, err := http.DefaultClient.Do(req) if err != nil { renderHTTPError(log, r, w, errors.Wrap(err, "failed to send request"), http.StatusInternalServerError) return } body, err := io.ReadAll(res.Body) if err != nil { renderHTTPError(log, r, w, errors.Wrap(err, "failed to read response"), http.StatusInternalServerError) return } fmt.Printf("%+v\n", body) fmt.Printf("%+v\n", res) err = json.Unmarshal(body, &response) if err != nil { renderHTTPError(log, r, w, errors.Wrap(err, "failed to unmarshal body"), http.StatusInternalServerError) return } // respond with the same message json.NewEncoder(w).Encode(Response{Message: response.Content}) w.WriteHeader(http.StatusOK) } func (fe *frontendServer) setCurrencyHandler(w http.ResponseWriter, r *http.Request) { log := r.Context().Value(ctxKeyLog{}).(logrus.FieldLogger) cur := r.FormValue("currency_code") payload := validator.SetCurrencyPayload{Currency: cur} if err := payload.Validate(); err != nil { renderHTTPError(log, r, w, validator.ValidationErrorResponse(err), http.StatusUnprocessableEntity) return } log.WithField("curr.new", payload.Currency).WithField("curr.old", currentCurrency(r)). Debug("setting currency") if payload.Currency != "" { http.SetCookie(w, &http.Cookie{ Name: cookieCurrency, Value: payload.Currency, MaxAge: cookieMaxAge, }) } referer := r.Header.Get("referer") if referer == "" { referer = baseUrl + "/" } w.Header().Set("Location", referer) w.WriteHeader(http.StatusFound) } // chooseAd queries for advertisements available and randomly chooses one, if // available. It ignores the error retrieving the ad since it is not critical. func (fe *frontendServer) chooseAd(ctx context.Context, ctxKeys []string, log logrus.FieldLogger) *pb.Ad { ads, err := fe.getAd(ctx, ctxKeys) if err != nil { log.WithField("error", err).Warn("failed to retrieve ads") return nil } return ads[rand.Intn(len(ads))] } func renderHTTPError(log logrus.FieldLogger, r *http.Request, w http.ResponseWriter, err error, code int) { log.WithField("error", err).Error("request error") errMsg := fmt.Sprintf("%+v", err) w.WriteHeader(code) if templateErr := templates.ExecuteTemplate(w, "error", injectCommonTemplateData(r, map[string]interface{}{ "error": errMsg, "status_code": code, "status": http.StatusText(code), })); templateErr != nil { log.Println(templateErr) } } func injectCommonTemplateData(r *http.Request, payload map[string]interface{}) map[string]interface{} { data := map[string]interface{}{ "session_id": sessionID(r), "request_id": r.Context().Value(ctxKeyRequestID{}), "user_currency": currentCurrency(r), "platform_css": plat.css, "platform_name": plat.provider, "is_cymbal_brand": isCymbalBrand, "assistant_enabled": assistantEnabled, "deploymentDetails": deploymentDetailsMap, "frontendMessage": frontendMessage, "currentYear": time.Now().Year(), "baseUrl": baseUrl, } for k, v := range payload { data[k] = v } return data } func currentCurrency(r *http.Request) string { c, _ := r.Cookie(cookieCurrency) if c != nil { return c.Value } return defaultCurrency } func sessionID(r *http.Request) string { v := r.Context().Value(ctxKeySessionID{}) if v != nil { return v.(string) } return "" } func cartIDs(c []*pb.CartItem) []string { out := make([]string, len(c)) for i, v := range c { out[i] = v.GetProductId() } return out } // get total # of items in cart func cartSize(c []*pb.CartItem) int { cartSize := 0 for _, item := range c { cartSize += int(item.GetQuantity()) } return cartSize } func renderMoney(money pb.Money) string { currencyLogo := renderCurrencyLogo(money.GetCurrencyCode()) return fmt.Sprintf("%s%d.%02d", currencyLogo, money.GetUnits(), money.GetNanos()/10000000) } func renderCurrencyLogo(currencyCode string) string { logos := map[string]string{ "USD": "$", "CAD": "$", "JPY": "¥", "EUR": "€", "TRY": "₺", "GBP": "£", } logo := "$" //default if val, ok := logos[currencyCode]; ok { logo = val } return logo } func stringinSlice(slice []string, val string) bool { for _, item := range slice { if item == val { return true } } return false } ================================================ FILE: src/frontend/main.go ================================================ // Copyright 2018 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. package main import ( "context" "fmt" "net/http" "os" "time" "cloud.google.com/go/profiler" "github.com/gorilla/mux" "github.com/pkg/errors" "github.com/sirupsen/logrus" "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc" "go.opentelemetry.io/otel/propagation" sdktrace "go.opentelemetry.io/otel/sdk/trace" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" ) const ( port = "8080" defaultCurrency = "USD" cookieMaxAge = 60 * 60 * 48 cookiePrefix = "shop_" cookieSessionID = cookiePrefix + "session-id" cookieCurrency = cookiePrefix + "currency" ) var ( whitelistedCurrencies = map[string]bool{ "USD": true, "EUR": true, "CAD": true, "JPY": true, "GBP": true, "TRY": true, } baseUrl = "" ) type ctxKeySessionID struct{} type frontendServer struct { productCatalogSvcAddr string productCatalogSvcConn *grpc.ClientConn currencySvcAddr string currencySvcConn *grpc.ClientConn cartSvcAddr string cartSvcConn *grpc.ClientConn recommendationSvcAddr string recommendationSvcConn *grpc.ClientConn checkoutSvcAddr string checkoutSvcConn *grpc.ClientConn shippingSvcAddr string shippingSvcConn *grpc.ClientConn adSvcAddr string adSvcConn *grpc.ClientConn collectorAddr string collectorConn *grpc.ClientConn shoppingAssistantSvcAddr string } func main() { ctx := context.Background() log := logrus.New() log.Level = logrus.DebugLevel log.Formatter = &logrus.JSONFormatter{ FieldMap: logrus.FieldMap{ logrus.FieldKeyTime: "timestamp", logrus.FieldKeyLevel: "severity", logrus.FieldKeyMsg: "message", }, TimestampFormat: time.RFC3339Nano, } log.Out = os.Stdout svc := new(frontendServer) otel.SetTextMapPropagator( propagation.NewCompositeTextMapPropagator( propagation.TraceContext{}, propagation.Baggage{})) baseUrl = os.Getenv("BASE_URL") if os.Getenv("ENABLE_TRACING") == "1" { log.Info("Tracing enabled.") initTracing(log, ctx, svc) } else { log.Info("Tracing disabled.") } if os.Getenv("ENABLE_PROFILER") == "1" { log.Info("Profiling enabled.") go initProfiling(log, "frontend", "1.0.0") } else { log.Info("Profiling disabled.") } srvPort := port if os.Getenv("PORT") != "" { srvPort = os.Getenv("PORT") } addr := os.Getenv("LISTEN_ADDR") mustMapEnv(&svc.productCatalogSvcAddr, "PRODUCT_CATALOG_SERVICE_ADDR") mustMapEnv(&svc.currencySvcAddr, "CURRENCY_SERVICE_ADDR") mustMapEnv(&svc.cartSvcAddr, "CART_SERVICE_ADDR") mustMapEnv(&svc.recommendationSvcAddr, "RECOMMENDATION_SERVICE_ADDR") mustMapEnv(&svc.checkoutSvcAddr, "CHECKOUT_SERVICE_ADDR") mustMapEnv(&svc.shippingSvcAddr, "SHIPPING_SERVICE_ADDR") mustMapEnv(&svc.adSvcAddr, "AD_SERVICE_ADDR") mustMapEnv(&svc.shoppingAssistantSvcAddr, "SHOPPING_ASSISTANT_SERVICE_ADDR") mustConnGRPC(ctx, &svc.currencySvcConn, svc.currencySvcAddr) mustConnGRPC(ctx, &svc.productCatalogSvcConn, svc.productCatalogSvcAddr) mustConnGRPC(ctx, &svc.cartSvcConn, svc.cartSvcAddr) mustConnGRPC(ctx, &svc.recommendationSvcConn, svc.recommendationSvcAddr) mustConnGRPC(ctx, &svc.shippingSvcConn, svc.shippingSvcAddr) mustConnGRPC(ctx, &svc.checkoutSvcConn, svc.checkoutSvcAddr) mustConnGRPC(ctx, &svc.adSvcConn, svc.adSvcAddr) r := mux.NewRouter() r.HandleFunc(baseUrl+"/", svc.homeHandler).Methods(http.MethodGet, http.MethodHead) r.HandleFunc(baseUrl+"/product/{id}", svc.productHandler).Methods(http.MethodGet, http.MethodHead) r.HandleFunc(baseUrl+"/cart", svc.viewCartHandler).Methods(http.MethodGet, http.MethodHead) r.HandleFunc(baseUrl+"/cart", svc.addToCartHandler).Methods(http.MethodPost) r.HandleFunc(baseUrl+"/cart/empty", svc.emptyCartHandler).Methods(http.MethodPost) r.HandleFunc(baseUrl+"/setCurrency", svc.setCurrencyHandler).Methods(http.MethodPost) r.HandleFunc(baseUrl+"/logout", svc.logoutHandler).Methods(http.MethodGet) r.HandleFunc(baseUrl+"/cart/checkout", svc.placeOrderHandler).Methods(http.MethodPost) r.HandleFunc(baseUrl+"/assistant", svc.assistantHandler).Methods(http.MethodGet) r.PathPrefix(baseUrl + "/static/").Handler(http.StripPrefix(baseUrl+"/static/", http.FileServer(http.Dir("./static/")))) r.HandleFunc(baseUrl+"/robots.txt", func(w http.ResponseWriter, _ *http.Request) { fmt.Fprint(w, "User-agent: *\nDisallow: /") }) r.HandleFunc(baseUrl+"/_healthz", func(w http.ResponseWriter, _ *http.Request) { fmt.Fprint(w, "ok") }) r.HandleFunc(baseUrl+"/product-meta/{ids}", svc.getProductByID).Methods(http.MethodGet) r.HandleFunc(baseUrl+"/bot", svc.chatBotHandler).Methods(http.MethodPost) var handler http.Handler = r handler = &logHandler{log: log, next: handler} // add logging handler = ensureSessionID(handler) // add session ID handler = otelhttp.NewHandler(handler, "frontend") // add OTel tracing log.Infof("starting server on %s:%s", addr, srvPort) log.Fatal(http.ListenAndServe(addr+":"+srvPort, handler)) } func initStats(log logrus.FieldLogger) { // TODO(arbrown) Implement OpenTelemtry stats } func initTracing(log logrus.FieldLogger, ctx context.Context, svc *frontendServer) (*sdktrace.TracerProvider, error) { mustMapEnv(&svc.collectorAddr, "COLLECTOR_SERVICE_ADDR") mustConnGRPC(ctx, &svc.collectorConn, svc.collectorAddr) exporter, err := otlptracegrpc.New( ctx, otlptracegrpc.WithGRPCConn(svc.collectorConn)) if err != nil { log.Warnf("warn: Failed to create trace exporter: %v", err) } tp := sdktrace.NewTracerProvider( sdktrace.WithBatcher(exporter), sdktrace.WithSampler(sdktrace.AlwaysSample())) otel.SetTracerProvider(tp) return tp, err } func initProfiling(log logrus.FieldLogger, service, version string) { // TODO(ahmetb) this method is duplicated in other microservices using Go // since they are not sharing packages. for i := 1; i <= 3; i++ { log = log.WithField("retry", i) if err := profiler.Start(profiler.Config{ Service: service, ServiceVersion: version, // ProjectID must be set if not running on GCP. // ProjectID: "my-project", }); err != nil { log.Warnf("warn: failed to start profiler: %+v", err) } else { log.Info("started Stackdriver profiler") return } d := time.Second * 10 * time.Duration(i) log.Debugf("sleeping %v to retry initializing Stackdriver profiler", d) time.Sleep(d) } log.Warn("warning: could not initialize Stackdriver profiler after retrying, giving up") } func mustMapEnv(target *string, envKey string) { v := os.Getenv(envKey) if v == "" { panic(fmt.Sprintf("environment variable %q not set", envKey)) } *target = v } func mustConnGRPC(ctx context.Context, conn **grpc.ClientConn, addr string) { var err error _, cancel := context.WithTimeout(ctx, time.Second*3) defer cancel() *conn, err = grpc.NewClient(addr, grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithStatsHandler(otelgrpc.NewClientHandler())) if err != nil { panic(errors.Wrapf(err, "grpc: failed to connect %s", addr)) } } ================================================ FILE: src/frontend/middleware.go ================================================ // Copyright 2018 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. package main import ( "context" "net/http" "time" "os" "github.com/google/uuid" "github.com/sirupsen/logrus" ) type ctxKeyLog struct{} type ctxKeyRequestID struct{} type logHandler struct { log *logrus.Logger next http.Handler } type responseRecorder struct { b int status int w http.ResponseWriter } func (r *responseRecorder) Header() http.Header { return r.w.Header() } func (r *responseRecorder) Write(p []byte) (int, error) { if r.status == 0 { r.status = http.StatusOK } n, err := r.w.Write(p) r.b += n return n, err } func (r *responseRecorder) WriteHeader(statusCode int) { r.status = statusCode r.w.WriteHeader(statusCode) } func (lh *logHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { ctx := r.Context() requestID, _ := uuid.NewRandom() ctx = context.WithValue(ctx, ctxKeyRequestID{}, requestID.String()) start := time.Now() rr := &responseRecorder{w: w} log := lh.log.WithFields(logrus.Fields{ "http.req.path": r.URL.Path, "http.req.method": r.Method, "http.req.id": requestID.String(), }) if v, ok := r.Context().Value(ctxKeySessionID{}).(string); ok { log = log.WithField("session", v) } log.Debug("request started") defer func() { log.WithFields(logrus.Fields{ "http.resp.took_ms": int64(time.Since(start) / time.Millisecond), "http.resp.status": rr.status, "http.resp.bytes": rr.b}).Debugf("request complete") }() ctx = context.WithValue(ctx, ctxKeyLog{}, log) r = r.WithContext(ctx) lh.next.ServeHTTP(rr, r) } func ensureSessionID(next http.Handler) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { var sessionID string c, err := r.Cookie(cookieSessionID) if err == http.ErrNoCookie { if os.Getenv("ENABLE_SINGLE_SHARED_SESSION") == "true" { // Hard coded user id, shared across sessions sessionID = "12345678-1234-1234-1234-123456789123" } else { u, _ := uuid.NewRandom() sessionID = u.String() } http.SetCookie(w, &http.Cookie{ Name: cookieSessionID, Value: sessionID, MaxAge: cookieMaxAge, }) } else if err != nil { return } else { sessionID = c.Value } ctx := context.WithValue(r.Context(), ctxKeySessionID{}, sessionID) r = r.WithContext(ctx) next.ServeHTTP(w, r) } } ================================================ FILE: src/frontend/money/money.go ================================================ // Copyright 2018 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. package money import ( "errors" pb "github.com/GoogleCloudPlatform/microservices-demo/src/frontend/genproto" ) const ( nanosMin = -999999999 nanosMax = +999999999 nanosMod = 1000000000 ) var ( ErrInvalidValue = errors.New("one of the specified money values is invalid") ErrMismatchingCurrency = errors.New("mismatching currency codes") ) // IsValid checks if specified value has a valid units/nanos signs and ranges. func IsValid(m pb.Money) bool { return signMatches(m) && validNanos(m.GetNanos()) } func signMatches(m pb.Money) bool { return m.GetNanos() == 0 || m.GetUnits() == 0 || (m.GetNanos() < 0) == (m.GetUnits() < 0) } func validNanos(nanos int32) bool { return nanosMin <= nanos && nanos <= nanosMax } // IsZero returns true if the specified money value is equal to zero. func IsZero(m pb.Money) bool { return m.GetUnits() == 0 && m.GetNanos() == 0 } // IsPositive returns true if the specified money value is valid and is // positive. func IsPositive(m pb.Money) bool { return IsValid(m) && m.GetUnits() > 0 || (m.GetUnits() == 0 && m.GetNanos() > 0) } // IsNegative returns true if the specified money value is valid and is // negative. func IsNegative(m pb.Money) bool { return IsValid(m) && m.GetUnits() < 0 || (m.GetUnits() == 0 && m.GetNanos() < 0) } // AreSameCurrency returns true if values l and r have a currency code and // they are the same values. func AreSameCurrency(l, r pb.Money) bool { return l.GetCurrencyCode() == r.GetCurrencyCode() && l.GetCurrencyCode() != "" } // AreEquals returns true if values l and r are the equal, including the // currency. This does not check validity of the provided values. func AreEquals(l, r pb.Money) bool { return l.GetCurrencyCode() == r.GetCurrencyCode() && l.GetUnits() == r.GetUnits() && l.GetNanos() == r.GetNanos() } // Negate returns the same amount with the sign negated. func Negate(m pb.Money) pb.Money { return pb.Money{ Units: -m.GetUnits(), Nanos: -m.GetNanos(), CurrencyCode: m.GetCurrencyCode()} } // Must panics if the given error is not nil. This can be used with other // functions like: "m := Must(Sum(a,b))". func Must(v pb.Money, err error) pb.Money { if err != nil { panic(err) } return v } // Sum adds two values. Returns an error if one of the values are invalid or // currency codes are not matching (unless currency code is unspecified for // both). func Sum(l, r pb.Money) (pb.Money, error) { if !IsValid(l) || !IsValid(r) { return pb.Money{}, ErrInvalidValue } else if l.GetCurrencyCode() != r.GetCurrencyCode() { return pb.Money{}, ErrMismatchingCurrency } units := l.GetUnits() + r.GetUnits() nanos := l.GetNanos() + r.GetNanos() if (units == 0 && nanos == 0) || (units > 0 && nanos >= 0) || (units < 0 && nanos <= 0) { // same sign units += int64(nanos / nanosMod) nanos = nanos % nanosMod } else { // different sign. nanos guaranteed to not to go over the limit if units > 0 { units-- nanos += nanosMod } else { units++ nanos -= nanosMod } } return pb.Money{ Units: units, Nanos: nanos, CurrencyCode: l.GetCurrencyCode()}, nil } // MultiplySlow is a slow multiplication operation done through adding the value // to itself n-1 times. func MultiplySlow(m pb.Money, n uint32) pb.Money { out := m for n > 1 { out = Must(Sum(out, m)) n-- } return out } ================================================ FILE: src/frontend/money/money_test.go ================================================ // Copyright 2018 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. package money import ( "fmt" "reflect" "testing" pb "github.com/GoogleCloudPlatform/microservices-demo/src/frontend/genproto" ) func mmc(u int64, n int32, c string) pb.Money { return pb.Money{Units: u, Nanos: n, CurrencyCode: c} } func mm(u int64, n int32) pb.Money { return mmc(u, n, "") } func TestIsValid(t *testing.T) { tests := []struct { name string in pb.Money want bool }{ {"valid -/-", mm(-981273891273, -999999999), true}, {"invalid -/+", mm(-981273891273, +999999999), false}, {"valid +/+", mm(981273891273, 999999999), true}, {"invalid +/-", mm(981273891273, -999999999), false}, {"invalid +/+overflow", mm(3, 1000000000), false}, {"invalid +/-overflow", mm(3, -1000000000), false}, {"invalid -/+overflow", mm(-3, 1000000000), false}, {"invalid -/-overflow", mm(-3, -1000000000), false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := IsValid(tt.in); got != tt.want { t.Errorf("IsValid(%v) = %v, want %v", tt.in, got, tt.want) } }) } } func TestIsZero(t *testing.T) { tests := []struct { name string in pb.Money want bool }{ {"zero", mm(0, 0), true}, {"not-zero (-/+)", mm(-1, +1), false}, {"not-zero (-/-)", mm(-1, -1), false}, {"not-zero (+/+)", mm(+1, +1), false}, {"not-zero (+/-)", mm(+1, -1), false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := IsZero(tt.in); got != tt.want { t.Errorf("IsZero(%v) = %v, want %v", tt.in, got, tt.want) } }) } } func TestIsPositive(t *testing.T) { tests := []struct { name string in pb.Money want bool }{ {"zero", mm(0, 0), false}, {"positive (+/+)", mm(+1, +1), true}, {"invalid (-/+)", mm(-1, +1), false}, {"negative (-/-)", mm(-1, -1), false}, {"invalid (+/-)", mm(+1, -1), false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := IsPositive(tt.in); got != tt.want { t.Errorf("IsPositive(%v) = %v, want %v", tt.in, got, tt.want) } }) } } func TestIsNegative(t *testing.T) { tests := []struct { name string in pb.Money want bool }{ {"zero", mm(0, 0), false}, {"positive (+/+)", mm(+1, +1), false}, {"invalid (-/+)", mm(-1, +1), false}, {"negative (-/-)", mm(-1, -1), true}, {"invalid (+/-)", mm(+1, -1), false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := IsNegative(tt.in); got != tt.want { t.Errorf("IsNegative(%v) = %v, want %v", tt.in, got, tt.want) } }) } } func TestAreSameCurrency(t *testing.T) { type args struct { l pb.Money r pb.Money } tests := []struct { name string args args want bool }{ {"both empty currency", args{mmc(1, 0, ""), mmc(2, 0, "")}, false}, {"left empty currency", args{mmc(1, 0, ""), mmc(2, 0, "USD")}, false}, {"right empty currency", args{mmc(1, 0, "USD"), mmc(2, 0, "")}, false}, {"mismatching", args{mmc(1, 0, "USD"), mmc(2, 0, "CAD")}, false}, {"matching", args{mmc(1, 0, "USD"), mmc(2, 0, "USD")}, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := AreSameCurrency(tt.args.l, tt.args.r); got != tt.want { t.Errorf("AreSameCurrency([%v],[%v]) = %v, want %v", tt.args.l, tt.args.r, got, tt.want) } }) } } func TestAreEquals(t *testing.T) { type args struct { l pb.Money r pb.Money } tests := []struct { name string args args want bool }{ {"equals", args{mmc(1, 2, "USD"), mmc(1, 2, "USD")}, true}, {"mismatching currency", args{mmc(1, 2, "USD"), mmc(1, 2, "CAD")}, false}, {"mismatching units", args{mmc(10, 20, "USD"), mmc(1, 20, "USD")}, false}, {"mismatching nanos", args{mmc(1, 2, "USD"), mmc(1, 20, "USD")}, false}, {"negated", args{mmc(1, 2, "USD"), mmc(-1, -2, "USD")}, false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := AreEquals(tt.args.l, tt.args.r); got != tt.want { t.Errorf("AreEquals([%v],[%v]) = %v, want %v", tt.args.l, tt.args.r, got, tt.want) } }) } } func TestNegate(t *testing.T) { tests := []struct { name string in pb.Money want pb.Money }{ {"zero", mm(0, 0), mm(0, 0)}, {"negative", mm(-1, -200), mm(1, 200)}, {"positive", mm(1, 200), mm(-1, -200)}, {"carries currency code", mmc(0, 0, "XXX"), mmc(0, 0, "XXX")}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := Negate(tt.in); !AreEquals(got, tt.want) { t.Errorf("Negate([%v]) = %v, want %v", tt.in, got, tt.want) } }) } } func TestMust_pass(t *testing.T) { v := Must(mm(2, 3), nil) if !AreEquals(v, mm(2, 3)) { t.Errorf("returned the wrong value: %v", v) } } func TestMust_panic(t *testing.T) { defer func() { if r := recover(); r != nil { t.Logf("panic captured: %v", r) } }() Must(mm(2, 3), fmt.Errorf("some error")) t.Fatal("this should not have executed due to the panic above") } func TestSum(t *testing.T) { type args struct { l pb.Money r pb.Money } tests := []struct { name string args args want pb.Money wantErr error }{ {"0+0=0", args{mm(0, 0), mm(0, 0)}, mm(0, 0), nil}, {"Error: currency code on left", args{mmc(0, 0, "XXX"), mm(0, 0)}, mm(0, 0), ErrMismatchingCurrency}, {"Error: currency code on right", args{mm(0, 0), mmc(0, 0, "YYY")}, mm(0, 0), ErrMismatchingCurrency}, {"Error: currency code mismatch", args{mmc(0, 0, "AAA"), mmc(0, 0, "BBB")}, mm(0, 0), ErrMismatchingCurrency}, {"Error: invalid +/-", args{mm(+1, -1), mm(0, 0)}, mm(0, 0), ErrInvalidValue}, {"Error: invalid -/+", args{mm(0, 0), mm(-1, +2)}, mm(0, 0), ErrInvalidValue}, {"Error: invalid nanos", args{mm(0, 1000000000), mm(1, 0)}, mm(0, 0), ErrInvalidValue}, {"both positive (no carry)", args{mm(2, 200000000), mm(2, 200000000)}, mm(4, 400000000), nil}, {"both positive (nanos=max)", args{mm(2, 111111111), mm(2, 888888888)}, mm(4, 999999999), nil}, {"both positive (carry)", args{mm(2, 200000000), mm(2, 900000000)}, mm(5, 100000000), nil}, {"both negative (no carry)", args{mm(-2, -200000000), mm(-2, -200000000)}, mm(-4, -400000000), nil}, {"both negative (carry)", args{mm(-2, -200000000), mm(-2, -900000000)}, mm(-5, -100000000), nil}, {"mixed (larger positive, just decimals)", args{mm(11, 0), mm(-2, 0)}, mm(9, 0), nil}, {"mixed (larger negative, just decimals)", args{mm(-11, 0), mm(2, 0)}, mm(-9, 0), nil}, {"mixed (larger positive, no borrow)", args{mm(11, 100000000), mm(-2, -100000000)}, mm(9, 0), nil}, {"mixed (larger positive, with borrow)", args{mm(11, 100000000), mm(-2, -9000000 /*.09*/)}, mm(9, 91000000 /*.091*/), nil}, {"mixed (larger negative, no borrow)", args{mm(-11, -100000000), mm(2, 100000000)}, mm(-9, 0), nil}, {"mixed (larger negative, with borrow)", args{mm(-11, -100000000), mm(2, 9000000 /*.09*/)}, mm(-9, -91000000 /*.091*/), nil}, {"0+negative", args{mm(0, 0), mm(-2, -100000000)}, mm(-2, -100000000), nil}, {"negative+0", args{mm(-2, -100000000), mm(0, 0)}, mm(-2, -100000000), nil}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := Sum(tt.args.l, tt.args.r) if err != tt.wantErr { t.Errorf("Sum([%v],[%v]): expected err=\"%v\" got=\"%v\"", tt.args.l, tt.args.r, tt.wantErr, err) } if !reflect.DeepEqual(got, tt.want) { t.Errorf("Sum([%v],[%v]) = %v, want %v", tt.args.l, tt.args.r, got, tt.want) } }) } } ================================================ FILE: src/frontend/packaging_info.go ================================================ // 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 // // 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. package main import ( "encoding/json" "fmt" "io/ioutil" "net/http" "os" ) /* As part of an optional Google Cloud demo, you can run an additional "packaging" microservice (HTTP server). This file contains code related to the frontend and the "packaging" microservice. */ var ( packagingServiceUrl string ) type PackagingInfo struct { Weight float32 `json:"weight"` Width float32 `json:"width"` Height float32 `json:"height"` Depth float32 `json:"depth"` } // init() is a special function in Golang that will run when this package is imported. func init() { packagingServiceUrl = os.Getenv("PACKAGING_SERVICE_URL") } func isPackagingServiceConfigured() bool { return packagingServiceUrl != "" } func httpGetPackagingInfo(productId string) (*PackagingInfo, error) { // Make the GET request url := packagingServiceUrl + "/" + productId fmt.Println("Requesting packaging info from URL: ", url) resp, err := http.Get(url) if err != nil { return nil, err } defer resp.Body.Close() // Check the response status code if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf("Unexpected status code: %d", resp.StatusCode) } // Read the JSON response body responseBody, err := ioutil.ReadAll(resp.Body) if err != nil { return nil, err } // Decode the JSON response into a PackagingInfo struct var packagingInfo PackagingInfo err = json.Unmarshal(responseBody, &packagingInfo) if err != nil { return nil, err } return &packagingInfo, nil } ================================================ FILE: src/frontend/rpc.go ================================================ // Copyright 2018 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. package main import ( "context" "time" pb "github.com/GoogleCloudPlatform/microservices-demo/src/frontend/genproto" "github.com/pkg/errors" ) const ( avoidNoopCurrencyConversionRPC = false ) func (fe *frontendServer) getCurrencies(ctx context.Context) ([]string, error) { currs, err := pb.NewCurrencyServiceClient(fe.currencySvcConn). GetSupportedCurrencies(ctx, &pb.Empty{}) if err != nil { return nil, err } var out []string for _, c := range currs.CurrencyCodes { if _, ok := whitelistedCurrencies[c]; ok { out = append(out, c) } } return out, nil } func (fe *frontendServer) getProducts(ctx context.Context) ([]*pb.Product, error) { resp, err := pb.NewProductCatalogServiceClient(fe.productCatalogSvcConn). ListProducts(ctx, &pb.Empty{}) return resp.GetProducts(), err } func (fe *frontendServer) getProduct(ctx context.Context, id string) (*pb.Product, error) { resp, err := pb.NewProductCatalogServiceClient(fe.productCatalogSvcConn). GetProduct(ctx, &pb.GetProductRequest{Id: id}) return resp, err } func (fe *frontendServer) getCart(ctx context.Context, userID string) ([]*pb.CartItem, error) { resp, err := pb.NewCartServiceClient(fe.cartSvcConn).GetCart(ctx, &pb.GetCartRequest{UserId: userID}) return resp.GetItems(), err } func (fe *frontendServer) emptyCart(ctx context.Context, userID string) error { _, err := pb.NewCartServiceClient(fe.cartSvcConn).EmptyCart(ctx, &pb.EmptyCartRequest{UserId: userID}) return err } func (fe *frontendServer) insertCart(ctx context.Context, userID, productID string, quantity int32) error { _, err := pb.NewCartServiceClient(fe.cartSvcConn).AddItem(ctx, &pb.AddItemRequest{ UserId: userID, Item: &pb.CartItem{ ProductId: productID, Quantity: quantity}, }) return err } func (fe *frontendServer) convertCurrency(ctx context.Context, money *pb.Money, currency string) (*pb.Money, error) { if avoidNoopCurrencyConversionRPC && money.GetCurrencyCode() == currency { return money, nil } return pb.NewCurrencyServiceClient(fe.currencySvcConn). Convert(ctx, &pb.CurrencyConversionRequest{ From: money, ToCode: currency}) } func (fe *frontendServer) getShippingQuote(ctx context.Context, items []*pb.CartItem, currency string) (*pb.Money, error) { quote, err := pb.NewShippingServiceClient(fe.shippingSvcConn).GetQuote(ctx, &pb.GetQuoteRequest{ Address: nil, Items: items}) if err != nil { return nil, err } localized, err := fe.convertCurrency(ctx, quote.GetCostUsd(), currency) return localized, errors.Wrap(err, "failed to convert currency for shipping cost") } func (fe *frontendServer) getRecommendations(ctx context.Context, userID string, productIDs []string) ([]*pb.Product, error) { resp, err := pb.NewRecommendationServiceClient(fe.recommendationSvcConn).ListRecommendations(ctx, &pb.ListRecommendationsRequest{UserId: userID, ProductIds: productIDs}) if err != nil { return nil, err } out := make([]*pb.Product, len(resp.GetProductIds())) for i, v := range resp.GetProductIds() { p, err := fe.getProduct(ctx, v) if err != nil { return nil, errors.Wrapf(err, "failed to get recommended product info (#%s)", v) } out[i] = p } if len(out) > 4 { out = out[:4] // take only first four to fit the UI } return out, err } func (fe *frontendServer) getAd(ctx context.Context, ctxKeys []string) ([]*pb.Ad, error) { ctx, cancel := context.WithTimeout(ctx, time.Millisecond*100) defer cancel() resp, err := pb.NewAdServiceClient(fe.adSvcConn).GetAds(ctx, &pb.AdRequest{ ContextKeys: ctxKeys, }) return resp.GetAds(), errors.Wrap(err, "failed to get ads") } ================================================ FILE: src/frontend/static/images/credits.txt ================================================ folded-clothes-on-white-chair.jpg,,https://unsplash.com/photos/fr0J5-GIVyg folded-clothes-on-white-chair-wide.jpg,,https://unsplash.com/photos/fr0J5-GIVyg ================================================ FILE: src/frontend/static/styles/bot.css ================================================ .chat-modal { width: 100%; height: 85vh; margin-top: 50px; background-color: #EEE; border-radius: 16px; box-shadow: 0px 4px 4px rgba(0, 0, 0, 0.25); overflow: auto; display: block; } @keyframes scale-in { 0% { scale: 0; } 100% { scale: 1; } } .bot-messages { overflow: auto; height: calc(100% - 100px); scroll-snap-align: end; } .bot-message { position: relative; margin: 16px; padding: 16px; margin-right: 20%; border-radius: 16px; background-color: white; min-height: 55px; } .bot-message-loading { -webkit-mask:linear-gradient(-60deg,#000 30%,#0005,#000 70%) right/300% 100%; animation: shimmer 1.5s infinite; } .user-message { position: relative; margin: 16px; margin-left: 20%; padding: 16px; border-radius: 16px; background-color: var(--blue); } .bot-input { display: flex; position: absolute; bottom: 0; width: -webkit-fill-available; margin: 16px; margin-right: 32px; padding: 16px; border-radius: 16px; background-color: white; } .bot-input-file-button { padding-top: 5px; } .bot-input-text { border: none; border-bottom: 1px solid #9AA0A6; padding: 0 0 8px 16px; outline: none; color: #1E2021; width: -webkit-fill-available; } .user-message-text { color: white; } .user-image-div { position: relative; width: 100%; height: 150px; } .user-image { position: absolute; right: 0; border-radius: 16px; margin-right: 16px; height: 150px; } .bot-input-button { display: inline-block; border: solid 1px var(--blue); padding: 8px 16px; outline: none; font-size: 14px; border-radius: 22px; cursor: pointer; background-color: var(--blue); color: white; } .bot-input-button:disabled, button[disabled]{ border: 1px solid #999999; background-color: #cccccc; color: #666666; } .bot-products { margin-left: 16px; margin-right: 20%; } .bot-product { height: 150px; margin-bottom: 16px; display: flex; align-items: center; border-radius: 16px; background-color: white; color: black; } .bot-product-img { height: 150px; border-top-left-radius: 16px; border-bottom-left-radius: 16px; margin-right: 16px; } .bot-product-description { float: right; width: calc(100% - 180px); } @keyframes typing { 0% { opacity: 0; } 50% { opacity: 1; } 100% { opacity: 0; } } .shimmer { color: grey; display:inline-block; -webkit-mask:linear-gradient(-60deg,#000 30%,#0005,#000 70%) right/300% 100%; background-repeat: no-repeat; animation: shimmer 2.5s infinite; font-size: 50px; max-width:200px; filter: invert(21%) sepia(100%) saturate(7414%) hue-rotate(210deg) brightness(50%) contrast(117%); } @keyframes shimmer { 100% {-webkit-mask-position:left} } ================================================ FILE: src/frontend/static/styles/cart.css ================================================ /** * Copyright 2020 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. */ .cart-sections { padding-bottom: 120px; padding-top: 56px; background-color: #F9F9F9; } .cart-sections h3 { font-size: 36px; font-weight: normal; } .cart-sections a.cymbal-button-primary:hover { text-decoration: none; color: white; } /* Empty Cart Section */ .empty-cart-section { max-width: 458px; margin: auto; text-align: center; } .empty-cart-section a { display: inline-block; /* So margin-top works. */ margin-top: 32px; } .empty-cart-section a:hover { color: white; text-decoration: none; } /* Cart Summary Section */ .cart-summary-empty-cart-button { margin-right: 10px; } .cart-summary-item-row, .cart-summary-shipping-row, .cart-summary-total-row { padding-bottom: 24px; padding-top: 24px; border-top: solid 1px rgba(154, 160, 166, 0.5); } .cart-summary-item-row img { border-radius: 20% 0 20% 20%; } .cart-summary-item-row-item-id-row { font-size: 12px; color: #5C6063; } .cart-summary-item-row h4 { font-size: 18px; font-weight: normal; } /* Stick item quantity and cost to the bottom (for wider screens). */ @media (min-width: 768px) { .cart-summary-item-row .row:last-child { position: absolute; bottom: 0px; width: 100%; } } /* Item cost (price). */ .cart-summary-item-row .row:last-child strong { font-weight: 500; } .cart-summary-total-row { font-size: 28px; } /* Cart Checkout Form */ .cart-checkout-form h3 { margin-bottom: 0; } .payment-method-heading { margin-top: 36px; } /* "Place Order" button */ .cart-checkout-form .cymbal-button-primary { margin-top: 36px; } ================================================ FILE: src/frontend/static/styles/order.css ================================================ /** * Copyright 2020 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. */ .order { background: #F9F9F9; } .order-complete-section { max-width: 487px; padding-top: 56px; padding-bottom: 120px; } .order-complete-section h3 { margin: 0; font-size: 36px; font-weight: normal; } .order-complete-section p { margin-top: 8px; } .order-complete-section .padding-y-24 { padding-bottom: 24px; padding-top: 24px; } .order-complete-section .border-bottom-solid { border-bottom: 1px solid rgba(154, 160, 166, 0.5); } .order-complete-section .cymbal-button-primary { margin-top: 24px; } .order-complete-section a.cymbal-button-primary:hover { text-decoration: none; color: white; } ================================================ FILE: src/frontend/static/styles/styles.css ================================================ /** * Copyright 2020 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. */ /* General */ html, body { height: 100%; } body { color: #111111; font-family: 'DM Sans', sans-serif; display: flex; flex-direction: column; } /* Header */ header { background-color: #853B5C; color: white; } /* This allows the sub-navbar (white strip containing logo) to be as wide as the browser window. */ header > div:nth-child(2).navbar.sub-navbar { padding-left: 0; padding-right: 0; } header > div:nth-child(2) > .container { max-width: none; } header .cart-link { position: relative; display: block; margin-left: 25px; display: flex; flex-flow: column; align-items: center; justify-content: center; } header .cart-size-circle { display: flex; align-items: center; justify-content: center; position: absolute; top: 24px; left: 11px; width: 16px; height: 16px; font-size: 11px; border-radius: 4px 4px 0 4px; color: white; background-color: #853B5C; } header .navbar { padding-top: 5px; padding-bottom: 5px; } header .h-free-shipping { font-size: 14px; } header .h-controls { display: flex; justify-content: flex-end; } header .h-control { display: flex; align-items: center; font-size: 12px; position: relative; margin-left: 40px; color: #605f64; } header .h-control:first-child { margin-left: 0; } header .h-control input { border: none; padding: 0 31px 0 31px; width: 250px; height: 24px; flex-shrink: 0; background-color: #f2f2f2; display: flex; align-items: center; } header .h-control input:focus { outline: 0; border: 0; box-shadow: 0; } header .icon { width: 20px; height: 20px; } header .icon.search-icon { width: 12px; height: 13px; position: absolute; left: 10px; } /* The currency drop-down. */ header img.currency-icon, header span.currency-icon { position: relative; left: 35px; top: -1px; width: 20px; display: inline-block; height: 20px; } header span.currency-icon { font-size: 16px; text-align: center; } header .h-control select { display: flex; align-items: center; background: transparent; border-radius: 0; border: 1px solid #acacac; width: 130px; height: 40px; flex-shrink: 0; padding: 1px 0 0 45px; font-size: 16px; border-radius: 8px; } header .icon.arrow { position: absolute; right: 25px; width: 10px; height: 5px; } header .h-control::-webkit-input-placeholder { /* Chrome/Opera/Safari */ font-size: 12px; color: #605f64; } header .h-control::-moz-placeholder { /* Firefox 19+ */ font-size: 12px; color: #605f64; } header .h-control :-ms-input-placeholder { /* IE 10+ */ font-size: 12px; color: #605f64; } header .h-control :-moz-placeholder { /* Firefox 18- */ font-size: 12px; color: #605f64; } header .navbar.sub-navbar { height: 60px; background-color: white; font-size: 15px; color: #b4b2bb; padding-top: 0; padding-bottom: 0; box-shadow: 0px 0px 4px rgba(0, 0, 0, 0.25); z-index: 1; /* Need this to see the box-shadow on the home page. */ } header .navbar.sub-navbar > .container { padding-left: 26px; padding-right: 26px; } header .top-left-logo { height: 40px; } header .top-left-logo-cymbal { height: 30px; } header .navbar.sub-navbar .navbar-brand { padding: 0; } header .navbar.sub-navbar a { color: #b4b2bb; } header .navbar.sub-navbar nav a { margin: 0 10px; } header .navbar.sub-navbar .controls { display: flex; height: 60px; } header .navbar.sub-navbar .controls a img { width: 20px; height: 20px; margin-bottom: 3px; } /* Footer */ footer.py-5 { flex-shrink: 0; padding: 0 !important; } footer .footer-top { padding: 60px 0px; background-color: #570D2E; color: white; } footer .footer-top a { color: white; text-decoration: underline; } /* The

containing the session-id. */ footer .footer-top p:nth-child(3) { margin-top: 56px; } footer .footer-top .footer-social, footer .footer-top .footer-app, footer .footer-links, footer .footer-top .social, footer .footer-top .app { display: block; align-items: center; } footer .footer-top .footer-social { padding: 31px; } footer .footer-top .footer-social h4 { margin-bottom: 0; } footer .footer-top .footer-social div { width: 50%; } /* Home */ main { flex: 1 0 auto; background-color: #F9F9F9; } @media (min-width: 992px) { .home .container-fluid { height: calc(100vh - 91px); /* 91px is the height of the top/header bars. */ } .home .container-fluid > .row > .col-4 { height: calc(100vh - 91px); } .home .container-fluid > .row > .col-lg-8 { height: calc(100vh - 91px); overflow-y: scroll; } .px-10-percent { padding-left: 10%; padding-right: 10%; } } .home-mobile-hero-banner { height: 200px; background: url(/static/images/folded-clothes-on-white-chair-wide.jpg) no-repeat top center; background-size: cover; } .home-desktop-left-image { background: url(/static/images/folded-clothes-on-white-chair.jpg) no-repeat center; background-size: cover; } .hot-products-row h3 { margin-bottom: 32px; margin-top: 56px; font-size: 36px; font-weight: normal; } .hot-products-row { padding-bottom: 70px; padding-left: 10%; padding-right: 10%; } .hot-product-card { margin-bottom: 52px; padding-left: 16px; padding-right: 16px; } .hot-product-card img { width: 100%; height: auto; border-radius: 20% 0 20% 20%; } .hot-product-card-name { margin-top: 8px; font-size: 18px; } .hot-product-card-price { font-size: 14px; } .hot-product-card > a:first-child { position: relative; display: block; } .hot-product-card-img-overlay { position: absolute; height: 100%; width: 100%; top: 0; left: 0; border-radius: 20% 0 20% 20%; background-color: transparent; } .hot-product-card:hover .hot-product-card-img-overlay { background-color: rgba(71, 0, 29, 0.2); } /* This chunk ensures the left/right padding of the footer is similar to that of the hot-products-row. */ .home-desktop-footer-row { padding-left: 9%; padding-right: 9%; background-color: #570D2E; width: 100%; margin: 0; } /* Ad */ .ad { position: relative; background-color: #FF9A9B; font-size: 24px; text-align: center; } /* "Ad" text. */ .ad strong { position: absolute; top: 6px; left: 12px; font-size: 14px; font-weight: normal; } .ad a { color: black; } /* Product */ .h-product { margin-top: 56px; margin-bottom: 112px; max-width: 1200px; background-color: #F9F9F9; } .h-product > .row { align-items: flex-end; } .h-product .product-image { width: 100%; border-radius: 20% 20% 0 20%; } .h-product .product-price { font-size: 28px; } .h-product .product-info .product-wrapper { margin-left: 15px; } .h-product .product-info h2 { margin-bottom: 16px; margin-top: 16px; font-size: 56px; line-height: 1.14; font-weight: normal; color: #111111; } .h-product .product-packaging { margin: 0 0 15px 0; } .h-product .product-packaging h3 { font-size: 20px; } .h-product .product-packaging span { display: inline-block; margin: 0 10px 0 0; } .h-product .input-group-text, .h-product .btn.btn-info { font-size: 18px; line-height: 1.89; letter-spacing: 3.6px; text-align: center; color: #111111; border-radius: 0; } .product-quantity-dropdown { position: relative; width: 100px; } .product-quantity-dropdown select { width: 100%; height: 45px; border: 1px solid #acacac; padding: 10px 16px; border-radius: 8px; } .product-quantity-dropdown img { position: absolute; right: 25px; top: 20px; width: 10px; height: 5px; } .h-product .cymbal-button-primary { margin-top: 16px; } /* Platform Banner */ .local, .aws-platform, .onprem-platform, .azure-platform, .alibaba-platform, .gcp-platform { position: fixed; top: 0; left: 0; width: 10px; height: 100vh; color: white; font-size: 24px; z-index: 999; } .aws-platform, .aws-platform .platform-flag { background-color: #ff9900; } .onprem-platform, .onprem-platform .platform-flag { background-color: #34A853; } .gcp-platform, .gcp-platform .platform-flag { background-color: #4285f4; } .azure-platform, .azure-platform .platform-flag { background-color: #f35426; } .alibaba-platform, .alibaba-platform .platform-flag { background-color: #ffC300; } .local, .local .platform-flag { background-color: #2c0678; } .platform-flag { position: absolute; top: 98px; left: 0; width: 190px; height: 50px; display: flex; justify-content: center; align-items: center; } /* Recommendation */ .recommendations { background: #F9F9F9; padding-bottom: 55px; } .recommendations .container { max-width: 1174px; } @media (max-width: 992px) { .recommendations .container { max-width: none; } } .recommendations h2 { border-top: solid 1px; padding: 40px 0; font-weight: normal; text-align: center; } .recommendations h5 { margin-top: 8px; font-weight: normal; font-size: 18px; } .recommendations img { height: 100%; width: 100%; border-radius: 20% 0 20% 20%; } select { -webkit-appearance: none; -webkit-border-radius: 0px; } /* Cymbal */ /* If we ever decide to create a separate Cymbal CSS library for Cymbal components, the rules below could be extracted. */ .cymbal-button-primary, .cymbal-button-secondary { display: inline-block; border: solid 1px #CE0631; padding: 8px 16px; outline: none; font-size: 14px; border-radius: 22px; cursor: pointer; } .cymbal-button-primary:focus, .cymbal-button-secondary:focus { outline: none; /* To override browser (Chrome) default blue outline. */ } .cymbal-button-primary { background-color: #CE0631; color: white; } .cymbal-button-primary:active, .cymbal-button-primary:focus, .cymbal-button-primary:hover { border: solid 1px #7b031d; background-color: #7b031d; box-shadow: 0px 2px 2px 0px rgb(0 0 0 / 30%); } .cymbal-button-primary:active { box-shadow: 0px 3px 6px 0px rgb(0 0 0 / 30%); } .cymbal-button-secondary { background: none; color: #CE0631; } .cymbal-button-secondary:active, .cymbal-button-secondary:focus, .cymbal-button-secondary:hover { color: #7b031d; border: solid 1px #7b031d; } .cymbal-button-secondary:active { background-color: #f5ccd5; } .cymbal-form-field { position: relative; margin-top: 24px; } .cymbal-form-field label { width: 100%; margin: 0; padding: 8px 16px 0 16px; font-size: 12px; line-height: 1.8em; /* Without this, there might be a 1px gap underneath. */ font-weight: normal; border-radius: 4px 4px 0px 0px; color: #5C6063; background-color: white; } .cymbal-form-field input[type='email'], .cymbal-form-field input[type='password'], .cymbal-form-field select, .cymbal-form-field input[type='text'] { width: 100%; border: none; border-bottom: 1px solid #9AA0A6; padding: 0 16px 8px 16px; outline: none; color: #1E2021; } .cymbal-form-field .cymbal-dropdown-chevron { position: absolute; right: 25px; width: 10px; height: 5px; } ================================================ FILE: src/frontend/templates/ad.html ================================================ {{ define "text_ad" }}

{{ end }} ================================================ FILE: src/frontend/templates/assistant.html ================================================ {{ define "assistant" }} {{ template "header" . }}
{{$.platform_name}}

Hi, I'm the Cymbal Shops assistant. I can help you with your shopping experience.

What can I help you with?

{{ end }} ================================================ FILE: src/frontend/templates/cart.html ================================================ {{ define "cart" }} {{ template "header" . }}
{{$.platform_name}}
{{ if eq (len $.items) 0 }}

Your shopping cart is empty!

Items you add to your shopping cart will appear here.

Continue Shopping
{{ else }}

Cart ({{ $.cart_size }})

{{ range $.items }}

{{ .Item.Name }}

SKU #{{ .Item.Id }}
Quantity: {{ .Quantity }}
{{ renderMoney .Price }}
{{ end }}
Shipping
{{ renderMoney .shipping_cost }}
Total
{{ renderMoney .total_cost }}

Shipping Address

Payment Method

{{ end }}
{{ if $.recommendations }} {{ template "recommendations" $ }} {{ end }} {{ template "footer" . }} {{ end }} ================================================ FILE: src/frontend/templates/error.html ================================================ {{ define "error" }} {{ template "header" . }}
{{$.platform_name}}

Uh, oh!

Something has failed. Below are some details for debugging.

HTTP Status: {{.status_code}} {{.status}}

                    {{- .error -}}
                
{{ template "footer" . }} {{ end }} ================================================ FILE: src/frontend/templates/footer.html ================================================ {{ define "footer" }}
{{ end }} ================================================ FILE: src/frontend/templates/header.html ================================================ {{ define "header" }} {{ if $.is_cymbal_brand }} Cymbal Shops {{ else }} Online Boutique {{ end }} {{ if $.is_cymbal_brand }} {{ else }} {{ end }}
{{ if $.frontendMessage }} {{ end }}
{{end}} ================================================ FILE: src/frontend/templates/home.html ================================================ {{ define "home" }} {{ template "header" . }}
{{$.platform_name}}

Hot Products

{{ range $.products }}
{{ .Item.Name }}
{{ renderMoney .Price }}
{{ end }}
{{ template "footer" . }}
{{ end }} ================================================ FILE: src/frontend/templates/order.html ================================================ {{ define "order" }} {{ template "header" . }}
{{$.platform_name}}

Your order is complete!

We've sent you a confirmation email.

Confirmation #
{{.order.OrderId}}
Tracking #
{{.order.ShippingTrackingId}}
Total Paid
{{renderMoney .total_paid}}
{{ if $.recommendations }} {{ template "recommendations" $ }} {{ end }}
{{ template "footer" . }} {{ end }} ================================================ FILE: src/frontend/templates/product.html ================================================ {{ define "product" }} {{ template "header" . }}
{{$.platform_name}}

{{ $.product.Item.Name }}

{{ renderMoney $.product.Price }}

{{ $.product.Item.Description }}

{{ if $.packagingInfo }}

Packaging

Weight: {{ if $.packagingInfo.Weight }}{{ $.packagingInfo.Weight }}lb{{ else }}n/a{{ end }} Width: {{ if $.packagingInfo.Width }}{{ $.packagingInfo.Width }}cm{{ else }}n/a{{ end }} Height: {{ if $.packagingInfo.Height }}{{ $.packagingInfo.Height }}cm{{ else }}n/a{{ end }} Depth: {{ if $.packagingInfo.Depth }}{{ $.packagingInfo.Depth }}cm{{ else }}n/a{{ end }}
{{ end }}
{{ if $.recommendations}} {{ template "recommendations" $ }} {{ end }}
{{ if $.ad }}{{ template "text_ad" $ }}{{ end }}
{{ template "footer" . }} {{ end }} ================================================ FILE: src/frontend/templates/recommendations.html ================================================ {{ define "recommendations" }}

You May Also Like

{{ range .recommendations }}
{{ .Name }}
{{ end }}
{{ end }} ================================================ FILE: src/frontend/validator/validator.go ================================================ // 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. package validator import ( "errors" "fmt" "github.com/go-playground/validator/v10" ) var validate *validator.Validate // init() is a special function that will run when this package is imported. // It instantiates a SINGLE instance of *validator.Validate with the added // benefit of caching struct info and validations. func init() { validate = validator.New(validator.WithRequiredStructEnabled()) } type Payload interface { Validate() error } type AddToCartPayload struct { Quantity uint64 `validate:"required,gte=1,lte=10"` ProductID string `validate:"required"` } type PlaceOrderPayload struct { Email string `validate:"required,email"` StreetAddress string `validate:"required,max=512"` ZipCode int64 `validate:"required"` City string `validate:"required,max=128"` State string `validate:"required,max=128"` Country string `validate:"required,max=128"` CcNumber string `validate:"required,credit_card"` CcMonth int64 `validate:"required,gte=1,lte=12"` CcYear int64 `validate:"required"` CcCVV int64 `validate:"required"` } type SetCurrencyPayload struct { Currency string `validate:"required,iso4217"` } // Implementations of the 'Payload' interface. func (ad *AddToCartPayload) Validate() error { return validate.Struct(ad) } func (po *PlaceOrderPayload) Validate() error { return validate.Struct(po) } func (sc *SetCurrencyPayload) Validate() error { return validate.Struct(sc) } // Reusable error response function. func ValidationErrorResponse(err error) error { validationErrs, ok := err.(validator.ValidationErrors) if !ok { return errors.New("invalid validation error format") } var msg string for _, err := range validationErrs { msg += fmt.Sprintf("Field '%s' is invalid: %s\n", err.Field(), err.Tag()) } return fmt.Errorf("%s", msg) } ================================================ FILE: src/frontend/validator/validator_test.go ================================================ // 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. package validator import ( "strings" "testing" ) func TestPlaceOrderPassesValidation(t *testing.T) { tests := []struct { name string email string streetAddress string zipCode int64 city string state string country string ccNumber string ccMonth int64 ccYear int64 ccCVV int64 }{ {"valid", "test@example.com", "12345 example street", 10004, "New York", "New York", "United States", "5272940000751666", 4, 2024, 584}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { payload := PlaceOrderPayload{ Email: tt.email, StreetAddress: tt.streetAddress, ZipCode: tt.zipCode, City: tt.city, State: tt.state, Country: tt.country, CcNumber: tt.ccNumber, CcMonth: tt.ccMonth, CcYear: tt.ccYear, CcCVV: tt.ccCVV, } if err := payload.Validate(); err != nil { t.Errorf("want validation on %v, got %v", payload, err) } }) } } func TestPlaceOrderFailsValidation(t *testing.T) { tests := []struct { name string email string streetAddress string zipCode int64 city string state string country string ccNumber string ccMonth int64 ccYear int64 ccCVV int64 }{ {"invalid email", "test@example", "12345 example street", 10004, "New York", "New York", "United States", "5272940000751666", 4, 2024, 584}, {"invalid address (too long)", "test@example.com", strings.Repeat("12345 example street", 513), 10004, "New York", "New York", "United States", "5272940000751666", 4, 2024, 584}, {"invalid zip code", "test@example.com", "12345 example street", 0, "New York", "New York", "United States", "5272940000751666", 4, 2024, 584}, {"invalid city", "test@example.com", "12345 example street", 10004, "", "New York", "United States", "5272940000751666", 4, 2024, 584}, {"invalid state", "test@example.com", "12345 example street", 10004, "New York", "", "United States", "5272940000751666", 4, 2024, 584}, {"invalid country", "test@example.com", "12345 example street", 10004, "New York", "New York", "", "5272940000751666", 4, 2024, 584}, {"invalid ccNumber", "test@example.com", "12345 example street", 10004, "New York", "New York", "United States", "5272940000", 4, 2024, 584}, {"invalid ccMonth (month < 1)", "test@example.com", "12345 example street", 10004, "New York", "New York", "United States", "5272940000751666", 0, 2024, 584}, {"invalid ccMonth (month > 12)", "test@example.com", "12345 example street", 10004, "New York", "New York", "United States", "5272940000751666", 13, 2024, 584}, {"invalid ccYear (not provided)", "test@example.com", "12345 example street", 10004, "New York", "New York", "United States", "5272940000751666", 12, 0, 584}, {"invalid ccCVV (not provided)", "test@example.com", "12345 example street", 10004, "New York", "New York", "United States", "5272940000751666", 12, 2024, 0}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { payload := PlaceOrderPayload{ Email: tt.email, StreetAddress: tt.streetAddress, ZipCode: tt.zipCode, City: tt.city, State: tt.state, Country: tt.country, CcNumber: tt.ccNumber, CcMonth: tt.ccMonth, CcYear: tt.ccYear, CcCVV: tt.ccCVV, } if err := payload.Validate(); err == nil { t.Errorf("want validation on %v, got %v", payload, err) } }) } } func TestAddToCartPassesValidation(t *testing.T) { tests := []struct { name string quantity uint64 productID string }{ {"valid min quantity and product id", 1, "OLJCESPC7Z"}, {"valid max quantity and product id", 10, "OLJCESPC7Z"}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { payload := AddToCartPayload{Quantity: tt.quantity, ProductID: tt.productID} if err := payload.Validate(); err != nil { t.Errorf("want validation on %v, got %v", payload, err) } }) } } func TestAddToCartFailsValidation(t *testing.T) { tests := []struct { name string quantity uint64 productID string }{ {"invalid min quantity", 0, "OLJCESPC7Z"}, {"invalid max quantity", 11, "OLJCESPC7Z"}, {"invalid product id", 1, ""}, {"invalid quantity and product id", 0, ""}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { payload := AddToCartPayload{Quantity: tt.quantity, ProductID: tt.productID} if err := payload.Validate(); err == nil { t.Errorf("want validation on %v, got %v", payload, err) } }) } } func TestSetCurrencyPassesValidation(t *testing.T) { tests := []struct { name string currency string }{ {"valid currency (EUR)", "EUR"}, {"valid currency (USD)", "USD"}, {"valid currency (JPY)", "JPY"}, {"valid currency (GBP)", "GBP"}, {"valid currency (TRY)", "TRY"}, {"valid currency (CAD)", "CAD"}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { payload := SetCurrencyPayload{Currency: tt.currency} if err := payload.Validate(); err != nil { t.Errorf("want validation on %v, got %v", payload, err) } }) } } func TestSetCurrencyFailsValidation(t *testing.T) { tests := []struct { name string currency string }{ {"invalid currency", "ABC"}, {"invalid currency (symbol)", "$"}, {"invalid (no currency)", ""}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { payload := SetCurrencyPayload{Currency: tt.currency} if err := payload.Validate(); err == nil { t.Errorf("want validation on %v, got %v", payload, err) } }) } } ================================================ FILE: src/loadgenerator/Dockerfile ================================================ # Copyright 2020 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. # Define a default value so it's not empty if the builder fails to provide it ARG BUILDPLATFORM=linux/amd64 FROM --platform=$BUILDPLATFORM python:3.14.3-alpine@sha256:faee120f7885a06fcc9677922331391fa690d911c020abb9e8025ff3d908e510 AS base FROM base AS builder ENV PYTHONDONTWRITEBYTECODE=1 ENV PYTHONUNBUFFERED=1 RUN apk update \ && apk add --no-cache g++ linux-headers \ && rm -rf /var/cache/apk/* COPY requirements.txt . RUN pip install --prefix="/install" -r requirements.txt FROM base ENV PYTHONDONTWRITEBYTECODE=1 ENV PYTHONUNBUFFERED=1 RUN apk update \ && apk add --no-cache libstdc++ \ && rm -rf /var/cache/apk/* WORKDIR /loadgen COPY --from=builder /install /usr/local # Add application code. COPY locustfile.py . # enable gevent support in debugger ENV GEVENT_SUPPORT=True ENTRYPOINT locust --host="http://${FRONTEND_ADDR}" --headless -u "${USERS:-10}" -r "${RATE:-1}" 2>&1 ================================================ FILE: src/loadgenerator/locustfile.py ================================================ #!/usr/bin/python # # Copyright 2018 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. import random from locust import FastHttpUser, TaskSet, between from faker import Faker import datetime fake = Faker() products = [ '0PUK6V6EV0', '1YMWWN1N4O', '2ZYFJ3GM2N', '66VCHSJNUP', '6E92ZMYYFZ', '9SIQT8TOJO', 'L9ECAV7KIM', 'LS4PSXUNUM', 'OLJCESPC7Z'] def index(l): l.client.get("/") def setCurrency(l): currencies = ['EUR', 'USD', 'JPY', 'CAD', 'GBP', 'TRY'] l.client.post("/setCurrency", {'currency_code': random.choice(currencies)}) def browseProduct(l): l.client.get("/product/" + random.choice(products)) def viewCart(l): l.client.get("/cart") def addToCart(l): product = random.choice(products) l.client.get("/product/" + product) l.client.post("/cart", { 'product_id': product, 'quantity': random.randint(1,10)}) def empty_cart(l): l.client.post('/cart/empty') def checkout(l): addToCart(l) current_year = datetime.datetime.now().year+1 l.client.post("/cart/checkout", { 'email': fake.email(), 'street_address': fake.street_address(), 'zip_code': fake.zipcode(), 'city': fake.city(), 'state': fake.state_abbr(), 'country': fake.country(), 'credit_card_number': fake.credit_card_number(card_type="visa"), 'credit_card_expiration_month': random.randint(1, 12), 'credit_card_expiration_year': random.randint(current_year, current_year + 70), 'credit_card_cvv': f"{random.randint(100, 999)}", }) def logout(l): l.client.get('/logout') class UserBehavior(TaskSet): def on_start(self): index(self) tasks = {index: 1, setCurrency: 2, browseProduct: 10, addToCart: 2, viewCart: 3, checkout: 1} class WebsiteUser(FastHttpUser): tasks = [UserBehavior] wait_time = between(1, 10) ================================================ FILE: src/loadgenerator/requirements.in ================================================ locust==2.43.0 faker==40.1.0 ================================================ FILE: src/loadgenerator/requirements.txt ================================================ # This file was autogenerated by uv via the following command: # uv pip compile requirements.in -o requirements.txt bidict==0.23.1 # via python-socketio blinker==1.9.0 # via flask brotli==1.2.0 # via geventhttpclient certifi==2025.8.3 # via # geventhttpclient # requests charset-normalizer==3.4.3 # via requests click==8.3.0 # via flask configargparse==1.7.1 # via locust faker==40.1.0 # via -r requirements.in flask==3.1.3 # via # flask-cors # flask-login # locust flask-cors==6.0.1 # via locust flask-login==0.6.3 # via locust gevent==25.9.1 # via # geventhttpclient # locust geventhttpclient==2.3.4 # via locust greenlet==3.2.4 # via gevent h11==0.16.0 # via wsproto idna==3.10 # via requests iniconfig==2.1.0 # via pytest itsdangerous==2.2.0 # via flask jinja2==3.1.6 # via flask locust==2.43.0 # via -r requirements.in markupsafe==3.0.2 # via # flask # jinja2 # werkzeug msgpack==1.1.1 # via locust packaging==25.0 # via pytest pluggy==1.6.0 # via pytest psutil==7.1.0 # via locust pygments==2.19.2 # via pytest pytest==8.4.2 # via locust python-engineio==4.12.2 # via # locust # python-socketio python-socketio[client]==5.13.0 # via locust pyzmq==27.1.0 # via locust requests==2.32.5 # via # locust # python-socketio simple-websocket==1.1.0 # via python-engineio tzdata==2025.2 # via faker urllib3==2.6.3 # via # geventhttpclient # requests websocket-client==1.8.0 # via python-socketio werkzeug==3.1.6 # via # flask # flask-cors # flask-login # locust wsproto==1.2.0 # via simple-websocket zope-event==6.0 # via gevent zope-interface==8.0 # via gevent ================================================ FILE: src/paymentservice/.dockerignore ================================================ node_modules ================================================ FILE: src/paymentservice/Dockerfile ================================================ # Copyright 2020 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. # Define a default value so it's not empty if the builder fails to provide it ARG BUILDPLATFORM=linux/amd64 FROM --platform=$BUILDPLATFORM node:20.20.1-alpine@sha256:b88333c42c23fbd91596ebd7fd10de239cedab9617de04142dde7315e3bc0afa AS builder # Some packages (e.g. @google-cloud/profiler) require additional # deps for post-install scripts RUN apk add --update --no-cache \ python3 \ make \ g++ WORKDIR /usr/src/app COPY package*.json ./ RUN npm install --only=production FROM alpine:3.23.3@sha256:25109184c71bdad752c8312a8623239686a9a2071e8825f20acb8f2198c3f659 RUN apk add --no-cache nodejs WORKDIR /usr/src/app COPY --from=builder /usr/src/app/node_modules ./node_modules COPY . . EXPOSE 50051 ENTRYPOINT [ "node", "index.js" ] ================================================ FILE: src/paymentservice/charge.js ================================================ // Copyright 2018 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. const cardValidator = require('simple-card-validator'); const { v4: uuidv4 } = require('uuid'); const pino = require('pino'); const logger = pino({ name: 'paymentservice-charge', messageKey: 'message', formatters: { level (logLevelString, logLevelNum) { return { severity: logLevelString } } } }); class CreditCardError extends Error { constructor (message) { super(message); this.code = 400; // Invalid argument error } } class InvalidCreditCard extends CreditCardError { constructor (cardType) { super(`Credit card info is invalid`); } } class UnacceptedCreditCard extends CreditCardError { constructor (cardType) { super(`Sorry, we cannot process ${cardType} credit cards. Only VISA or MasterCard is accepted.`); } } class ExpiredCreditCard extends CreditCardError { constructor (number, month, year) { super(`Your credit card (ending ${number.substr(-4)}) expired on ${month}/${year}`); } } /** * Verifies the credit card number and (pretend) charges the card. * * @param {*} request * @return transaction_id - a random uuid. */ module.exports = function charge (request) { const { amount, credit_card: creditCard } = request; const cardNumber = creditCard.credit_card_number; const cardInfo = cardValidator(cardNumber); const { card_type: cardType, valid } = cardInfo.getCardDetails(); if (!valid) { throw new InvalidCreditCard(); } // Only VISA and mastercard is accepted, other card types (AMEX, dinersclub) will // throw UnacceptedCreditCard error. if (!(cardType === 'visa' || cardType === 'mastercard')) { throw new UnacceptedCreditCard(cardType); } // Also validate expiration is > today. const currentMonth = new Date().getMonth() + 1; const currentYear = new Date().getFullYear(); const { credit_card_expiration_year: year, credit_card_expiration_month: month } = creditCard; if ((currentYear * 12 + currentMonth) > (year * 12 + month)) { throw new ExpiredCreditCard(cardNumber.replace('-', ''), month, year); } logger.info(`Transaction processed: ${cardType} ending ${cardNumber.substr(-4)} \ Amount: ${amount.currency_code}${amount.units}.${amount.nanos}`); return { transaction_id: uuidv4() }; }; ================================================ FILE: src/paymentservice/genproto.sh ================================================ #!/bin/bash -eu # # Copyright 2018 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 gke_paymentservice_genproto] # protos are loaded dynamically for node, simply copies over the proto. mkdir -p proto cp -r ../../protos/* ./proto # [END gke_paymentservice_genproto] ================================================ FILE: src/paymentservice/index.js ================================================ /* * Copyright 2018 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. */ 'use strict'; const logger = require('./logger') if (process.env.DISABLE_PROFILER) { logger.info("Profiler disabled.") } else { logger.info("Profiler enabled.") require('@google-cloud/profiler').start({ serviceContext: { service: 'paymentservice', version: '1.0.0' } }); } if (process.env.ENABLE_TRACING == "1") { logger.info("Tracing enabled.") const { resourceFromAttributes } = require('@opentelemetry/resources'); const { ATTR_SERVICE_NAME }= require('@opentelemetry/semantic-conventions'); const { GrpcInstrumentation } = require('@opentelemetry/instrumentation-grpc'); const { registerInstrumentations } = require('@opentelemetry/instrumentation'); const opentelemetry = require('@opentelemetry/sdk-node'); const { OTLPTraceExporter } = require('@opentelemetry/exporter-otlp-grpc'); const collectorUrl = process.env.COLLECTOR_SERVICE_ADDR; const traceExporter = new OTLPTraceExporter({url: collectorUrl}); const sdk = new opentelemetry.NodeSDK({ resource: resourceFromAttributes({ [ATTR_SERVICE_NAME]: process.env.OTEL_SERVICE_NAME || 'paymentservice', }), traceExporter: traceExporter, }); registerInstrumentations({ instrumentations: [new GrpcInstrumentation()] }); sdk.start() } else { logger.info("Tracing disabled.") } const path = require('path'); const HipsterShopServer = require('./server'); const PORT = process.env['PORT']; const PROTO_PATH = path.join(__dirname, '/proto/'); const server = new HipsterShopServer(PROTO_PATH, PORT); server.listen(); ================================================ FILE: src/paymentservice/logger.js ================================================ /* * 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. */ const pino = require('pino'); module.exports = pino({ name: 'paymentservice-server', messageKey: 'message', formatters: { level (logLevelString, logLevelNum) { return { severity: logLevelString } } } }); ================================================ FILE: src/paymentservice/package.json ================================================ { "name": "paymentservice", "version": "0.0.1", "description": "Payment Microservice demo", "repository": "https://github.com/GoogleCloudPlatform/microservices-demo", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "author": "Jonathan Lui", "license": "ISC", "dependencies": { "@google-cloud/profiler": "6.0.4", "@grpc/grpc-js": "1.14.3", "@grpc/proto-loader": "0.8.0", "@opentelemetry/api": "1.9.0", "@opentelemetry/exporter-otlp-grpc": "0.26.0", "@opentelemetry/instrumentation-grpc": "0.213.0", "@opentelemetry/sdk-trace-base": "2.6.0", "@opentelemetry/sdk-node": "0.213.0", "@opentelemetry/resources": "2.6.0", "@opentelemetry/semantic-conventions": "1.40.0", "pino": "10.3.1", "simple-card-validator": "^1.1.0", "uuid": "^13.0.0" } } ================================================ FILE: src/paymentservice/proto/demo.proto ================================================ // Copyright 2020 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. syntax = "proto3"; package hipstershop; // -----------------Cart service----------------- service CartService { rpc AddItem(AddItemRequest) returns (Empty) {} rpc GetCart(GetCartRequest) returns (Cart) {} rpc EmptyCart(EmptyCartRequest) returns (Empty) {} } message CartItem { string product_id = 1; int32 quantity = 2; } message AddItemRequest { string user_id = 1; CartItem item = 2; } message EmptyCartRequest { string user_id = 1; } message GetCartRequest { string user_id = 1; } message Cart { string user_id = 1; repeated CartItem items = 2; } message Empty {} // ---------------Recommendation service---------- service RecommendationService { rpc ListRecommendations(ListRecommendationsRequest) returns (ListRecommendationsResponse){} } message ListRecommendationsRequest { string user_id = 1; repeated string product_ids = 2; } message ListRecommendationsResponse { repeated string product_ids = 1; } // ---------------Product Catalog---------------- service ProductCatalogService { rpc ListProducts(Empty) returns (ListProductsResponse) {} rpc GetProduct(GetProductRequest) returns (Product) {} rpc SearchProducts(SearchProductsRequest) returns (SearchProductsResponse) {} } message Product { string id = 1; string name = 2; string description = 3; string picture = 4; Money price_usd = 5; // Categories such as "clothing" or "kitchen" that can be used to look up // other related products. repeated string categories = 6; } message ListProductsResponse { repeated Product products = 1; } message GetProductRequest { string id = 1; } message SearchProductsRequest { string query = 1; } message SearchProductsResponse { repeated Product results = 1; } // ---------------Shipping Service---------- service ShippingService { rpc GetQuote(GetQuoteRequest) returns (GetQuoteResponse) {} rpc ShipOrder(ShipOrderRequest) returns (ShipOrderResponse) {} } message GetQuoteRequest { Address address = 1; repeated CartItem items = 2; } message GetQuoteResponse { Money cost_usd = 1; } message ShipOrderRequest { Address address = 1; repeated CartItem items = 2; } message ShipOrderResponse { string tracking_id = 1; } message Address { string street_address = 1; string city = 2; string state = 3; string country = 4; int32 zip_code = 5; } // -----------------Currency service----------------- service CurrencyService { rpc GetSupportedCurrencies(Empty) returns (GetSupportedCurrenciesResponse) {} rpc Convert(CurrencyConversionRequest) returns (Money) {} } // Represents an amount of money with its currency type. message Money { // The 3-letter currency code defined in ISO 4217. string currency_code = 1; // The whole units of the amount. // For example if `currencyCode` is `"USD"`, then 1 unit is one US dollar. int64 units = 2; // Number of nano (10^-9) units of the amount. // The value must be between -999,999,999 and +999,999,999 inclusive. // If `units` is positive, `nanos` must be positive or zero. // If `units` is zero, `nanos` can be positive, zero, or negative. // If `units` is negative, `nanos` must be negative or zero. // For example $-1.75 is represented as `units`=-1 and `nanos`=-750,000,000. int32 nanos = 3; } message GetSupportedCurrenciesResponse { // The 3-letter currency code defined in ISO 4217. repeated string currency_codes = 1; } message CurrencyConversionRequest { Money from = 1; // The 3-letter currency code defined in ISO 4217. string to_code = 2; } // -------------Payment service----------------- service PaymentService { rpc Charge(ChargeRequest) returns (ChargeResponse) {} } message CreditCardInfo { string credit_card_number = 1; int32 credit_card_cvv = 2; int32 credit_card_expiration_year = 3; int32 credit_card_expiration_month = 4; } message ChargeRequest { Money amount = 1; CreditCardInfo credit_card = 2; } message ChargeResponse { string transaction_id = 1; } // -------------Email service----------------- service EmailService { rpc SendOrderConfirmation(SendOrderConfirmationRequest) returns (Empty) {} } message OrderItem { CartItem item = 1; Money cost = 2; } message OrderResult { string order_id = 1; string shipping_tracking_id = 2; Money shipping_cost = 3; Address shipping_address = 4; repeated OrderItem items = 5; } message SendOrderConfirmationRequest { string email = 1; OrderResult order = 2; } // -------------Checkout service----------------- service CheckoutService { rpc PlaceOrder(PlaceOrderRequest) returns (PlaceOrderResponse) {} } message PlaceOrderRequest { string user_id = 1; string user_currency = 2; Address address = 3; string email = 5; CreditCardInfo credit_card = 6; } message PlaceOrderResponse { OrderResult order = 1; } // ------------Ad service------------------ service AdService { rpc GetAds(AdRequest) returns (AdResponse) {} } message AdRequest { // List of important key words from the current page describing the context. repeated string context_keys = 1; } message AdResponse { repeated Ad ads = 1; } message Ad { // url to redirect to when an ad is clicked. string redirect_url = 1; // short advertisement text to display. string text = 2; } ================================================ FILE: src/paymentservice/proto/grpc/health/v1/health.proto ================================================ // Copyright 2015 The gRPC Authors // // 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. // The canonical version of this proto can be found at // https://github.com/grpc/grpc-proto/blob/master/grpc/health/v1/health.proto syntax = "proto3"; package grpc.health.v1; option csharp_namespace = "Grpc.Health.V1"; option go_package = "google.golang.org/grpc/health/grpc_health_v1"; option java_multiple_files = true; option java_outer_classname = "HealthProto"; option java_package = "io.grpc.health.v1"; message HealthCheckRequest { string service = 1; } message HealthCheckResponse { enum ServingStatus { UNKNOWN = 0; SERVING = 1; NOT_SERVING = 2; } ServingStatus status = 1; } service Health { rpc Check(HealthCheckRequest) returns (HealthCheckResponse); } ================================================ FILE: src/paymentservice/server.js ================================================ // Copyright 2018 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. const path = require('path'); const grpc = require('@grpc/grpc-js'); const protoLoader = require('@grpc/proto-loader'); const charge = require('./charge'); const logger = require('./logger') class HipsterShopServer { constructor(protoRoot, port = HipsterShopServer.PORT) { this.port = port; this.packages = { hipsterShop: this.loadProto(path.join(protoRoot, 'demo.proto')), health: this.loadProto(path.join(protoRoot, 'grpc/health/v1/health.proto')) }; this.server = new grpc.Server(); this.loadAllProtos(protoRoot); } /** * Handler for PaymentService.Charge. * @param {*} call { ChargeRequest } * @param {*} callback fn(err, ChargeResponse) */ static ChargeServiceHandler(call, callback) { try { logger.info(`PaymentService#Charge invoked with request ${JSON.stringify(call.request)}`); const response = charge(call.request); callback(null, response); } catch (err) { console.warn(err); callback(err); } } static CheckHandler(call, callback) { callback(null, { status: 'SERVING' }); } listen() { const server = this.server const port = this.port server.bindAsync( `[::]:${port}`, grpc.ServerCredentials.createInsecure(), function () { logger.info(`PaymentService gRPC server started on port ${port}`); server.start(); } ); } loadProto(path) { const packageDefinition = protoLoader.loadSync( path, { keepCase: true, longs: String, enums: String, defaults: true, oneofs: true } ); return grpc.loadPackageDefinition(packageDefinition); } loadAllProtos(protoRoot) { const hipsterShopPackage = this.packages.hipsterShop.hipstershop; const healthPackage = this.packages.health.grpc.health.v1; this.server.addService( hipsterShopPackage.PaymentService.service, { charge: HipsterShopServer.ChargeServiceHandler.bind(this) } ); this.server.addService( healthPackage.Health.service, { check: HipsterShopServer.CheckHandler.bind(this) } ); } } HipsterShopServer.PORT = process.env.PORT; module.exports = HipsterShopServer; ================================================ FILE: src/productcatalogservice/.dockerignore ================================================ vendor/ ================================================ FILE: src/productcatalogservice/Dockerfile ================================================ # Copyright 2020 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. # Define a default value so it's not empty if the builder fails to provide it ARG BUILDPLATFORM=linux/amd64 FROM --platform=$BUILDPLATFORM golang:1.26.1-alpine@sha256:2389ebfa5b7f43eeafbd6be0c3700cc46690ef842ad962f6c5bd6be49ed82039 AS builder ARG TARGETOS=linux ARG TARGETARCH=amd64 WORKDIR /src # restore dependencies COPY go.mod go.sum ./ RUN go mod download COPY . . # Skaffold passes in debug-oriented compiler flags ARG SKAFFOLD_GO_GCFLAGS RUN GOOS=${TARGETOS} GOARCH=${TARGETARCH} CGO_ENABLED=0 go build -ldflags="-s -w" -gcflags="${SKAFFOLD_GO_GCFLAGS}" -o /productcatalogservice . FROM gcr.io/distroless/static WORKDIR /src COPY --from=builder /productcatalogservice ./server COPY products.json . # Definition of this variable is used by 'skaffold debug' to identify a golang binary. # Default behavior - a failure prints a stack trace for the current goroutine. # See https://golang.org/pkg/runtime/ ENV GOTRACEBACK=single EXPOSE 3550 ENTRYPOINT ["/src/server"] ================================================ FILE: src/productcatalogservice/README.md ================================================ # productcatalogservice Run the following command to restore dependencies to `vendor/` directory: go mod vendor ## Dynamic catalog reloading / artificial delay This service has a "dynamic catalog reloading" feature that is purposefully not well implemented. The goal of this feature is to allow you to modify the `products.json` file and have the changes be picked up without having to restart the service. However, this feature is bugged: the catalog is actually reloaded on each request, introducing a noticeable delay in the frontend. This delay will also show up in profiling tools: the `parseCatalog` function will take more than 80% of the CPU time. You can trigger this feature (and the delay) by sending a `USR1` signal and remove it (if needed) by sending a `USR2` signal: ``` # Trigger bug kubectl exec \ $(kubectl get pods -l app=productcatalogservice -o jsonpath='{.items[0].metadata.name}') \ -c server -- kill -USR1 1 # Remove bug kubectl exec \ $(kubectl get pods -l app=productcatalogservice -o jsonpath='{.items[0].metadata.name}') \ -c server -- kill -USR2 1 ``` ## Latency injection This service has an `EXTRA_LATENCY` environment variable. This will inject a sleep for the specified [time.Duration](https://golang.org/pkg/time/#ParseDuration) on every call to to the server. For example, use `EXTRA_LATENCY="5.5s"` to sleep for 5.5 seconds on every request. ================================================ FILE: src/productcatalogservice/catalog_loader.go ================================================ // 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 // // 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. package main import ( "bytes" "context" "fmt" "net" "os" "strings" "cloud.google.com/go/alloydbconn" secretmanager "cloud.google.com/go/secretmanager/apiv1" "cloud.google.com/go/secretmanager/apiv1/secretmanagerpb" pb "github.com/GoogleCloudPlatform/microservices-demo/src/productcatalogservice/genproto" "github.com/golang/protobuf/jsonpb" "github.com/jackc/pgx/v5/pgxpool" ) func loadCatalog(catalog *pb.ListProductsResponse) error { catalogMutex.Lock() defer catalogMutex.Unlock() if os.Getenv("ALLOYDB_CLUSTER_NAME") != "" { return loadCatalogFromAlloyDB(catalog) } return loadCatalogFromLocalFile(catalog) } func loadCatalogFromLocalFile(catalog *pb.ListProductsResponse) error { log.Info("loading catalog from local products.json file...") catalogJSON, err := os.ReadFile("products.json") if err != nil { log.Warnf("failed to open product catalog json file: %v", err) return err } if err := jsonpb.Unmarshal(bytes.NewReader(catalogJSON), catalog); err != nil { log.Warnf("failed to parse the catalog JSON: %v", err) return err } log.Info("successfully parsed product catalog json") return nil } func getSecretPayload(project, secret, version string) (string, error) { ctx := context.Background() client, err := secretmanager.NewClient(ctx) if err != nil { log.Warnf("failed to create SecretManager client: %v", err) return "", err } defer client.Close() req := &secretmanagerpb.AccessSecretVersionRequest{ Name: fmt.Sprintf("projects/%s/secrets/%s/versions/%s", project, secret, version), } // Call the API. result, err := client.AccessSecretVersion(ctx, req) if err != nil { log.Warnf("failed to access SecretVersion: %v", err) return "", err } return string(result.Payload.Data), nil } func loadCatalogFromAlloyDB(catalog *pb.ListProductsResponse) error { log.Info("loading catalog from AlloyDB...") projectID := os.Getenv("PROJECT_ID") region := os.Getenv("REGION") pgClusterName := os.Getenv("ALLOYDB_CLUSTER_NAME") pgInstanceName := os.Getenv("ALLOYDB_INSTANCE_NAME") pgDatabaseName := os.Getenv("ALLOYDB_DATABASE_NAME") pgTableName := os.Getenv("ALLOYDB_TABLE_NAME") pgSecretName := os.Getenv("ALLOYDB_SECRET_NAME") pgPassword, err := getSecretPayload(projectID, pgSecretName, "latest") if err != nil { return err } dialer, err := alloydbconn.NewDialer(context.Background()) if err != nil { log.Warnf("failed to set-up dialer connection: %v", err) return err } cleanup := func() error { return dialer.Close() } defer cleanup() dsn := fmt.Sprintf( "user=%s password=%s dbname=%s sslmode=disable", "postgres", pgPassword, pgDatabaseName, ) config, err := pgxpool.ParseConfig(dsn) if err != nil { log.Warnf("failed to parse DSN config: %v", err) return err } pgInstanceURI := fmt.Sprintf("projects/%s/locations/%s/clusters/%s/instances/%s", projectID, region, pgClusterName, pgInstanceName) config.ConnConfig.DialFunc = func(ctx context.Context, _ string, _ string) (net.Conn, error) { return dialer.Dial(ctx, pgInstanceURI) } pool, err := pgxpool.NewWithConfig(context.Background(), config) if err != nil { log.Warnf("failed to set-up pgx pool: %v", err) return err } defer pool.Close() query := "SELECT id, name, description, picture, price_usd_currency_code, price_usd_units, price_usd_nanos, categories FROM " + pgTableName rows, err := pool.Query(context.Background(), query) if err != nil { log.Warnf("failed to query database: %v", err) return err } defer rows.Close() catalog.Products = catalog.Products[:0] for rows.Next() { product := &pb.Product{} product.PriceUsd = &pb.Money{} var categories string err = rows.Scan(&product.Id, &product.Name, &product.Description, &product.Picture, &product.PriceUsd.CurrencyCode, &product.PriceUsd.Units, &product.PriceUsd.Nanos, &categories) if err != nil { log.Warnf("failed to scan query result row: %v", err) return err } categories = strings.ToLower(categories) product.Categories = strings.Split(categories, ",") catalog.Products = append(catalog.Products, product) } log.Info("successfully parsed product catalog from AlloyDB") return nil } ================================================ FILE: src/productcatalogservice/genproto/demo.pb.go ================================================ // Copyright 2020 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. // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.34.2 // protoc v3.6.1 // source: demo.proto package hipstershop import ( protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" sync "sync" ) const ( // Verify that this generated code is sufficiently up-to-date. _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) // Verify that runtime/protoimpl is sufficiently up-to-date. _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) type CartItem struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields ProductId string `protobuf:"bytes,1,opt,name=product_id,json=productId,proto3" json:"product_id,omitempty"` Quantity int32 `protobuf:"varint,2,opt,name=quantity,proto3" json:"quantity,omitempty"` } func (x *CartItem) Reset() { *x = CartItem{} if protoimpl.UnsafeEnabled { mi := &file_demo_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *CartItem) String() string { return protoimpl.X.MessageStringOf(x) } func (*CartItem) ProtoMessage() {} func (x *CartItem) ProtoReflect() protoreflect.Message { mi := &file_demo_proto_msgTypes[0] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use CartItem.ProtoReflect.Descriptor instead. func (*CartItem) Descriptor() ([]byte, []int) { return file_demo_proto_rawDescGZIP(), []int{0} } func (x *CartItem) GetProductId() string { if x != nil { return x.ProductId } return "" } func (x *CartItem) GetQuantity() int32 { if x != nil { return x.Quantity } return 0 } type AddItemRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields UserId string `protobuf:"bytes,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` Item *CartItem `protobuf:"bytes,2,opt,name=item,proto3" json:"item,omitempty"` } func (x *AddItemRequest) Reset() { *x = AddItemRequest{} if protoimpl.UnsafeEnabled { mi := &file_demo_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *AddItemRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*AddItemRequest) ProtoMessage() {} func (x *AddItemRequest) ProtoReflect() protoreflect.Message { mi := &file_demo_proto_msgTypes[1] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use AddItemRequest.ProtoReflect.Descriptor instead. func (*AddItemRequest) Descriptor() ([]byte, []int) { return file_demo_proto_rawDescGZIP(), []int{1} } func (x *AddItemRequest) GetUserId() string { if x != nil { return x.UserId } return "" } func (x *AddItemRequest) GetItem() *CartItem { if x != nil { return x.Item } return nil } type EmptyCartRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields UserId string `protobuf:"bytes,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` } func (x *EmptyCartRequest) Reset() { *x = EmptyCartRequest{} if protoimpl.UnsafeEnabled { mi := &file_demo_proto_msgTypes[2] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *EmptyCartRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*EmptyCartRequest) ProtoMessage() {} func (x *EmptyCartRequest) ProtoReflect() protoreflect.Message { mi := &file_demo_proto_msgTypes[2] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use EmptyCartRequest.ProtoReflect.Descriptor instead. func (*EmptyCartRequest) Descriptor() ([]byte, []int) { return file_demo_proto_rawDescGZIP(), []int{2} } func (x *EmptyCartRequest) GetUserId() string { if x != nil { return x.UserId } return "" } type GetCartRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields UserId string `protobuf:"bytes,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` } func (x *GetCartRequest) Reset() { *x = GetCartRequest{} if protoimpl.UnsafeEnabled { mi := &file_demo_proto_msgTypes[3] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *GetCartRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*GetCartRequest) ProtoMessage() {} func (x *GetCartRequest) ProtoReflect() protoreflect.Message { mi := &file_demo_proto_msgTypes[3] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use GetCartRequest.ProtoReflect.Descriptor instead. func (*GetCartRequest) Descriptor() ([]byte, []int) { return file_demo_proto_rawDescGZIP(), []int{3} } func (x *GetCartRequest) GetUserId() string { if x != nil { return x.UserId } return "" } type Cart struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields UserId string `protobuf:"bytes,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` Items []*CartItem `protobuf:"bytes,2,rep,name=items,proto3" json:"items,omitempty"` } func (x *Cart) Reset() { *x = Cart{} if protoimpl.UnsafeEnabled { mi := &file_demo_proto_msgTypes[4] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Cart) String() string { return protoimpl.X.MessageStringOf(x) } func (*Cart) ProtoMessage() {} func (x *Cart) ProtoReflect() protoreflect.Message { mi := &file_demo_proto_msgTypes[4] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use Cart.ProtoReflect.Descriptor instead. func (*Cart) Descriptor() ([]byte, []int) { return file_demo_proto_rawDescGZIP(), []int{4} } func (x *Cart) GetUserId() string { if x != nil { return x.UserId } return "" } func (x *Cart) GetItems() []*CartItem { if x != nil { return x.Items } return nil } type Empty struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields } func (x *Empty) Reset() { *x = Empty{} if protoimpl.UnsafeEnabled { mi := &file_demo_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Empty) String() string { return protoimpl.X.MessageStringOf(x) } func (*Empty) ProtoMessage() {} func (x *Empty) ProtoReflect() protoreflect.Message { mi := &file_demo_proto_msgTypes[5] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use Empty.ProtoReflect.Descriptor instead. func (*Empty) Descriptor() ([]byte, []int) { return file_demo_proto_rawDescGZIP(), []int{5} } type ListRecommendationsRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields UserId string `protobuf:"bytes,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` ProductIds []string `protobuf:"bytes,2,rep,name=product_ids,json=productIds,proto3" json:"product_ids,omitempty"` } func (x *ListRecommendationsRequest) Reset() { *x = ListRecommendationsRequest{} if protoimpl.UnsafeEnabled { mi := &file_demo_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *ListRecommendationsRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*ListRecommendationsRequest) ProtoMessage() {} func (x *ListRecommendationsRequest) ProtoReflect() protoreflect.Message { mi := &file_demo_proto_msgTypes[6] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use ListRecommendationsRequest.ProtoReflect.Descriptor instead. func (*ListRecommendationsRequest) Descriptor() ([]byte, []int) { return file_demo_proto_rawDescGZIP(), []int{6} } func (x *ListRecommendationsRequest) GetUserId() string { if x != nil { return x.UserId } return "" } func (x *ListRecommendationsRequest) GetProductIds() []string { if x != nil { return x.ProductIds } return nil } type ListRecommendationsResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields ProductIds []string `protobuf:"bytes,1,rep,name=product_ids,json=productIds,proto3" json:"product_ids,omitempty"` } func (x *ListRecommendationsResponse) Reset() { *x = ListRecommendationsResponse{} if protoimpl.UnsafeEnabled { mi := &file_demo_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *ListRecommendationsResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*ListRecommendationsResponse) ProtoMessage() {} func (x *ListRecommendationsResponse) ProtoReflect() protoreflect.Message { mi := &file_demo_proto_msgTypes[7] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use ListRecommendationsResponse.ProtoReflect.Descriptor instead. func (*ListRecommendationsResponse) Descriptor() ([]byte, []int) { return file_demo_proto_rawDescGZIP(), []int{7} } func (x *ListRecommendationsResponse) GetProductIds() []string { if x != nil { return x.ProductIds } return nil } type Product struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` Description string `protobuf:"bytes,3,opt,name=description,proto3" json:"description,omitempty"` Picture string `protobuf:"bytes,4,opt,name=picture,proto3" json:"picture,omitempty"` PriceUsd *Money `protobuf:"bytes,5,opt,name=price_usd,json=priceUsd,proto3" json:"price_usd,omitempty"` // Categories such as "clothing" or "kitchen" that can be used to look up // other related products. Categories []string `protobuf:"bytes,6,rep,name=categories,proto3" json:"categories,omitempty"` } func (x *Product) Reset() { *x = Product{} if protoimpl.UnsafeEnabled { mi := &file_demo_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Product) String() string { return protoimpl.X.MessageStringOf(x) } func (*Product) ProtoMessage() {} func (x *Product) ProtoReflect() protoreflect.Message { mi := &file_demo_proto_msgTypes[8] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use Product.ProtoReflect.Descriptor instead. func (*Product) Descriptor() ([]byte, []int) { return file_demo_proto_rawDescGZIP(), []int{8} } func (x *Product) GetId() string { if x != nil { return x.Id } return "" } func (x *Product) GetName() string { if x != nil { return x.Name } return "" } func (x *Product) GetDescription() string { if x != nil { return x.Description } return "" } func (x *Product) GetPicture() string { if x != nil { return x.Picture } return "" } func (x *Product) GetPriceUsd() *Money { if x != nil { return x.PriceUsd } return nil } func (x *Product) GetCategories() []string { if x != nil { return x.Categories } return nil } type ListProductsResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Products []*Product `protobuf:"bytes,1,rep,name=products,proto3" json:"products,omitempty"` } func (x *ListProductsResponse) Reset() { *x = ListProductsResponse{} if protoimpl.UnsafeEnabled { mi := &file_demo_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *ListProductsResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*ListProductsResponse) ProtoMessage() {} func (x *ListProductsResponse) ProtoReflect() protoreflect.Message { mi := &file_demo_proto_msgTypes[9] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use ListProductsResponse.ProtoReflect.Descriptor instead. func (*ListProductsResponse) Descriptor() ([]byte, []int) { return file_demo_proto_rawDescGZIP(), []int{9} } func (x *ListProductsResponse) GetProducts() []*Product { if x != nil { return x.Products } return nil } type GetProductRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` } func (x *GetProductRequest) Reset() { *x = GetProductRequest{} if protoimpl.UnsafeEnabled { mi := &file_demo_proto_msgTypes[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *GetProductRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*GetProductRequest) ProtoMessage() {} func (x *GetProductRequest) ProtoReflect() protoreflect.Message { mi := &file_demo_proto_msgTypes[10] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use GetProductRequest.ProtoReflect.Descriptor instead. func (*GetProductRequest) Descriptor() ([]byte, []int) { return file_demo_proto_rawDescGZIP(), []int{10} } func (x *GetProductRequest) GetId() string { if x != nil { return x.Id } return "" } type SearchProductsRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Query string `protobuf:"bytes,1,opt,name=query,proto3" json:"query,omitempty"` } func (x *SearchProductsRequest) Reset() { *x = SearchProductsRequest{} if protoimpl.UnsafeEnabled { mi := &file_demo_proto_msgTypes[11] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *SearchProductsRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*SearchProductsRequest) ProtoMessage() {} func (x *SearchProductsRequest) ProtoReflect() protoreflect.Message { mi := &file_demo_proto_msgTypes[11] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use SearchProductsRequest.ProtoReflect.Descriptor instead. func (*SearchProductsRequest) Descriptor() ([]byte, []int) { return file_demo_proto_rawDescGZIP(), []int{11} } func (x *SearchProductsRequest) GetQuery() string { if x != nil { return x.Query } return "" } type SearchProductsResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Results []*Product `protobuf:"bytes,1,rep,name=results,proto3" json:"results,omitempty"` } func (x *SearchProductsResponse) Reset() { *x = SearchProductsResponse{} if protoimpl.UnsafeEnabled { mi := &file_demo_proto_msgTypes[12] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *SearchProductsResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*SearchProductsResponse) ProtoMessage() {} func (x *SearchProductsResponse) ProtoReflect() protoreflect.Message { mi := &file_demo_proto_msgTypes[12] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use SearchProductsResponse.ProtoReflect.Descriptor instead. func (*SearchProductsResponse) Descriptor() ([]byte, []int) { return file_demo_proto_rawDescGZIP(), []int{12} } func (x *SearchProductsResponse) GetResults() []*Product { if x != nil { return x.Results } return nil } type GetQuoteRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Address *Address `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"` Items []*CartItem `protobuf:"bytes,2,rep,name=items,proto3" json:"items,omitempty"` } func (x *GetQuoteRequest) Reset() { *x = GetQuoteRequest{} if protoimpl.UnsafeEnabled { mi := &file_demo_proto_msgTypes[13] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *GetQuoteRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*GetQuoteRequest) ProtoMessage() {} func (x *GetQuoteRequest) ProtoReflect() protoreflect.Message { mi := &file_demo_proto_msgTypes[13] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use GetQuoteRequest.ProtoReflect.Descriptor instead. func (*GetQuoteRequest) Descriptor() ([]byte, []int) { return file_demo_proto_rawDescGZIP(), []int{13} } func (x *GetQuoteRequest) GetAddress() *Address { if x != nil { return x.Address } return nil } func (x *GetQuoteRequest) GetItems() []*CartItem { if x != nil { return x.Items } return nil } type GetQuoteResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields CostUsd *Money `protobuf:"bytes,1,opt,name=cost_usd,json=costUsd,proto3" json:"cost_usd,omitempty"` } func (x *GetQuoteResponse) Reset() { *x = GetQuoteResponse{} if protoimpl.UnsafeEnabled { mi := &file_demo_proto_msgTypes[14] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *GetQuoteResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*GetQuoteResponse) ProtoMessage() {} func (x *GetQuoteResponse) ProtoReflect() protoreflect.Message { mi := &file_demo_proto_msgTypes[14] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use GetQuoteResponse.ProtoReflect.Descriptor instead. func (*GetQuoteResponse) Descriptor() ([]byte, []int) { return file_demo_proto_rawDescGZIP(), []int{14} } func (x *GetQuoteResponse) GetCostUsd() *Money { if x != nil { return x.CostUsd } return nil } type ShipOrderRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Address *Address `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"` Items []*CartItem `protobuf:"bytes,2,rep,name=items,proto3" json:"items,omitempty"` } func (x *ShipOrderRequest) Reset() { *x = ShipOrderRequest{} if protoimpl.UnsafeEnabled { mi := &file_demo_proto_msgTypes[15] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *ShipOrderRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*ShipOrderRequest) ProtoMessage() {} func (x *ShipOrderRequest) ProtoReflect() protoreflect.Message { mi := &file_demo_proto_msgTypes[15] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use ShipOrderRequest.ProtoReflect.Descriptor instead. func (*ShipOrderRequest) Descriptor() ([]byte, []int) { return file_demo_proto_rawDescGZIP(), []int{15} } func (x *ShipOrderRequest) GetAddress() *Address { if x != nil { return x.Address } return nil } func (x *ShipOrderRequest) GetItems() []*CartItem { if x != nil { return x.Items } return nil } type ShipOrderResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields TrackingId string `protobuf:"bytes,1,opt,name=tracking_id,json=trackingId,proto3" json:"tracking_id,omitempty"` } func (x *ShipOrderResponse) Reset() { *x = ShipOrderResponse{} if protoimpl.UnsafeEnabled { mi := &file_demo_proto_msgTypes[16] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *ShipOrderResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*ShipOrderResponse) ProtoMessage() {} func (x *ShipOrderResponse) ProtoReflect() protoreflect.Message { mi := &file_demo_proto_msgTypes[16] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use ShipOrderResponse.ProtoReflect.Descriptor instead. func (*ShipOrderResponse) Descriptor() ([]byte, []int) { return file_demo_proto_rawDescGZIP(), []int{16} } func (x *ShipOrderResponse) GetTrackingId() string { if x != nil { return x.TrackingId } return "" } type Address struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields StreetAddress string `protobuf:"bytes,1,opt,name=street_address,json=streetAddress,proto3" json:"street_address,omitempty"` City string `protobuf:"bytes,2,opt,name=city,proto3" json:"city,omitempty"` State string `protobuf:"bytes,3,opt,name=state,proto3" json:"state,omitempty"` Country string `protobuf:"bytes,4,opt,name=country,proto3" json:"country,omitempty"` ZipCode int32 `protobuf:"varint,5,opt,name=zip_code,json=zipCode,proto3" json:"zip_code,omitempty"` } func (x *Address) Reset() { *x = Address{} if protoimpl.UnsafeEnabled { mi := &file_demo_proto_msgTypes[17] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Address) String() string { return protoimpl.X.MessageStringOf(x) } func (*Address) ProtoMessage() {} func (x *Address) ProtoReflect() protoreflect.Message { mi := &file_demo_proto_msgTypes[17] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use Address.ProtoReflect.Descriptor instead. func (*Address) Descriptor() ([]byte, []int) { return file_demo_proto_rawDescGZIP(), []int{17} } func (x *Address) GetStreetAddress() string { if x != nil { return x.StreetAddress } return "" } func (x *Address) GetCity() string { if x != nil { return x.City } return "" } func (x *Address) GetState() string { if x != nil { return x.State } return "" } func (x *Address) GetCountry() string { if x != nil { return x.Country } return "" } func (x *Address) GetZipCode() int32 { if x != nil { return x.ZipCode } return 0 } // Represents an amount of money with its currency type. type Money struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // The 3-letter currency code defined in ISO 4217. CurrencyCode string `protobuf:"bytes,1,opt,name=currency_code,json=currencyCode,proto3" json:"currency_code,omitempty"` // The whole units of the amount. // For example if `currencyCode` is `"USD"`, then 1 unit is one US dollar. Units int64 `protobuf:"varint,2,opt,name=units,proto3" json:"units,omitempty"` // Number of nano (10^-9) units of the amount. // The value must be between -999,999,999 and +999,999,999 inclusive. // If `units` is positive, `nanos` must be positive or zero. // If `units` is zero, `nanos` can be positive, zero, or negative. // If `units` is negative, `nanos` must be negative or zero. // For example $-1.75 is represented as `units`=-1 and `nanos`=-750,000,000. Nanos int32 `protobuf:"varint,3,opt,name=nanos,proto3" json:"nanos,omitempty"` } func (x *Money) Reset() { *x = Money{} if protoimpl.UnsafeEnabled { mi := &file_demo_proto_msgTypes[18] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Money) String() string { return protoimpl.X.MessageStringOf(x) } func (*Money) ProtoMessage() {} func (x *Money) ProtoReflect() protoreflect.Message { mi := &file_demo_proto_msgTypes[18] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use Money.ProtoReflect.Descriptor instead. func (*Money) Descriptor() ([]byte, []int) { return file_demo_proto_rawDescGZIP(), []int{18} } func (x *Money) GetCurrencyCode() string { if x != nil { return x.CurrencyCode } return "" } func (x *Money) GetUnits() int64 { if x != nil { return x.Units } return 0 } func (x *Money) GetNanos() int32 { if x != nil { return x.Nanos } return 0 } type GetSupportedCurrenciesResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // The 3-letter currency code defined in ISO 4217. CurrencyCodes []string `protobuf:"bytes,1,rep,name=currency_codes,json=currencyCodes,proto3" json:"currency_codes,omitempty"` } func (x *GetSupportedCurrenciesResponse) Reset() { *x = GetSupportedCurrenciesResponse{} if protoimpl.UnsafeEnabled { mi := &file_demo_proto_msgTypes[19] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *GetSupportedCurrenciesResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*GetSupportedCurrenciesResponse) ProtoMessage() {} func (x *GetSupportedCurrenciesResponse) ProtoReflect() protoreflect.Message { mi := &file_demo_proto_msgTypes[19] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use GetSupportedCurrenciesResponse.ProtoReflect.Descriptor instead. func (*GetSupportedCurrenciesResponse) Descriptor() ([]byte, []int) { return file_demo_proto_rawDescGZIP(), []int{19} } func (x *GetSupportedCurrenciesResponse) GetCurrencyCodes() []string { if x != nil { return x.CurrencyCodes } return nil } type CurrencyConversionRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields From *Money `protobuf:"bytes,1,opt,name=from,proto3" json:"from,omitempty"` // The 3-letter currency code defined in ISO 4217. ToCode string `protobuf:"bytes,2,opt,name=to_code,json=toCode,proto3" json:"to_code,omitempty"` } func (x *CurrencyConversionRequest) Reset() { *x = CurrencyConversionRequest{} if protoimpl.UnsafeEnabled { mi := &file_demo_proto_msgTypes[20] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *CurrencyConversionRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*CurrencyConversionRequest) ProtoMessage() {} func (x *CurrencyConversionRequest) ProtoReflect() protoreflect.Message { mi := &file_demo_proto_msgTypes[20] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use CurrencyConversionRequest.ProtoReflect.Descriptor instead. func (*CurrencyConversionRequest) Descriptor() ([]byte, []int) { return file_demo_proto_rawDescGZIP(), []int{20} } func (x *CurrencyConversionRequest) GetFrom() *Money { if x != nil { return x.From } return nil } func (x *CurrencyConversionRequest) GetToCode() string { if x != nil { return x.ToCode } return "" } type CreditCardInfo struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields CreditCardNumber string `protobuf:"bytes,1,opt,name=credit_card_number,json=creditCardNumber,proto3" json:"credit_card_number,omitempty"` CreditCardCvv int32 `protobuf:"varint,2,opt,name=credit_card_cvv,json=creditCardCvv,proto3" json:"credit_card_cvv,omitempty"` CreditCardExpirationYear int32 `protobuf:"varint,3,opt,name=credit_card_expiration_year,json=creditCardExpirationYear,proto3" json:"credit_card_expiration_year,omitempty"` CreditCardExpirationMonth int32 `protobuf:"varint,4,opt,name=credit_card_expiration_month,json=creditCardExpirationMonth,proto3" json:"credit_card_expiration_month,omitempty"` } func (x *CreditCardInfo) Reset() { *x = CreditCardInfo{} if protoimpl.UnsafeEnabled { mi := &file_demo_proto_msgTypes[21] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *CreditCardInfo) String() string { return protoimpl.X.MessageStringOf(x) } func (*CreditCardInfo) ProtoMessage() {} func (x *CreditCardInfo) ProtoReflect() protoreflect.Message { mi := &file_demo_proto_msgTypes[21] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use CreditCardInfo.ProtoReflect.Descriptor instead. func (*CreditCardInfo) Descriptor() ([]byte, []int) { return file_demo_proto_rawDescGZIP(), []int{21} } func (x *CreditCardInfo) GetCreditCardNumber() string { if x != nil { return x.CreditCardNumber } return "" } func (x *CreditCardInfo) GetCreditCardCvv() int32 { if x != nil { return x.CreditCardCvv } return 0 } func (x *CreditCardInfo) GetCreditCardExpirationYear() int32 { if x != nil { return x.CreditCardExpirationYear } return 0 } func (x *CreditCardInfo) GetCreditCardExpirationMonth() int32 { if x != nil { return x.CreditCardExpirationMonth } return 0 } type ChargeRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Amount *Money `protobuf:"bytes,1,opt,name=amount,proto3" json:"amount,omitempty"` CreditCard *CreditCardInfo `protobuf:"bytes,2,opt,name=credit_card,json=creditCard,proto3" json:"credit_card,omitempty"` } func (x *ChargeRequest) Reset() { *x = ChargeRequest{} if protoimpl.UnsafeEnabled { mi := &file_demo_proto_msgTypes[22] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *ChargeRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*ChargeRequest) ProtoMessage() {} func (x *ChargeRequest) ProtoReflect() protoreflect.Message { mi := &file_demo_proto_msgTypes[22] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use ChargeRequest.ProtoReflect.Descriptor instead. func (*ChargeRequest) Descriptor() ([]byte, []int) { return file_demo_proto_rawDescGZIP(), []int{22} } func (x *ChargeRequest) GetAmount() *Money { if x != nil { return x.Amount } return nil } func (x *ChargeRequest) GetCreditCard() *CreditCardInfo { if x != nil { return x.CreditCard } return nil } type ChargeResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields TransactionId string `protobuf:"bytes,1,opt,name=transaction_id,json=transactionId,proto3" json:"transaction_id,omitempty"` } func (x *ChargeResponse) Reset() { *x = ChargeResponse{} if protoimpl.UnsafeEnabled { mi := &file_demo_proto_msgTypes[23] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *ChargeResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*ChargeResponse) ProtoMessage() {} func (x *ChargeResponse) ProtoReflect() protoreflect.Message { mi := &file_demo_proto_msgTypes[23] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use ChargeResponse.ProtoReflect.Descriptor instead. func (*ChargeResponse) Descriptor() ([]byte, []int) { return file_demo_proto_rawDescGZIP(), []int{23} } func (x *ChargeResponse) GetTransactionId() string { if x != nil { return x.TransactionId } return "" } type OrderItem struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Item *CartItem `protobuf:"bytes,1,opt,name=item,proto3" json:"item,omitempty"` Cost *Money `protobuf:"bytes,2,opt,name=cost,proto3" json:"cost,omitempty"` } func (x *OrderItem) Reset() { *x = OrderItem{} if protoimpl.UnsafeEnabled { mi := &file_demo_proto_msgTypes[24] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *OrderItem) String() string { return protoimpl.X.MessageStringOf(x) } func (*OrderItem) ProtoMessage() {} func (x *OrderItem) ProtoReflect() protoreflect.Message { mi := &file_demo_proto_msgTypes[24] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use OrderItem.ProtoReflect.Descriptor instead. func (*OrderItem) Descriptor() ([]byte, []int) { return file_demo_proto_rawDescGZIP(), []int{24} } func (x *OrderItem) GetItem() *CartItem { if x != nil { return x.Item } return nil } func (x *OrderItem) GetCost() *Money { if x != nil { return x.Cost } return nil } type OrderResult struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields OrderId string `protobuf:"bytes,1,opt,name=order_id,json=orderId,proto3" json:"order_id,omitempty"` ShippingTrackingId string `protobuf:"bytes,2,opt,name=shipping_tracking_id,json=shippingTrackingId,proto3" json:"shipping_tracking_id,omitempty"` ShippingCost *Money `protobuf:"bytes,3,opt,name=shipping_cost,json=shippingCost,proto3" json:"shipping_cost,omitempty"` ShippingAddress *Address `protobuf:"bytes,4,opt,name=shipping_address,json=shippingAddress,proto3" json:"shipping_address,omitempty"` Items []*OrderItem `protobuf:"bytes,5,rep,name=items,proto3" json:"items,omitempty"` } func (x *OrderResult) Reset() { *x = OrderResult{} if protoimpl.UnsafeEnabled { mi := &file_demo_proto_msgTypes[25] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *OrderResult) String() string { return protoimpl.X.MessageStringOf(x) } func (*OrderResult) ProtoMessage() {} func (x *OrderResult) ProtoReflect() protoreflect.Message { mi := &file_demo_proto_msgTypes[25] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use OrderResult.ProtoReflect.Descriptor instead. func (*OrderResult) Descriptor() ([]byte, []int) { return file_demo_proto_rawDescGZIP(), []int{25} } func (x *OrderResult) GetOrderId() string { if x != nil { return x.OrderId } return "" } func (x *OrderResult) GetShippingTrackingId() string { if x != nil { return x.ShippingTrackingId } return "" } func (x *OrderResult) GetShippingCost() *Money { if x != nil { return x.ShippingCost } return nil } func (x *OrderResult) GetShippingAddress() *Address { if x != nil { return x.ShippingAddress } return nil } func (x *OrderResult) GetItems() []*OrderItem { if x != nil { return x.Items } return nil } type SendOrderConfirmationRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Email string `protobuf:"bytes,1,opt,name=email,proto3" json:"email,omitempty"` Order *OrderResult `protobuf:"bytes,2,opt,name=order,proto3" json:"order,omitempty"` } func (x *SendOrderConfirmationRequest) Reset() { *x = SendOrderConfirmationRequest{} if protoimpl.UnsafeEnabled { mi := &file_demo_proto_msgTypes[26] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *SendOrderConfirmationRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*SendOrderConfirmationRequest) ProtoMessage() {} func (x *SendOrderConfirmationRequest) ProtoReflect() protoreflect.Message { mi := &file_demo_proto_msgTypes[26] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use SendOrderConfirmationRequest.ProtoReflect.Descriptor instead. func (*SendOrderConfirmationRequest) Descriptor() ([]byte, []int) { return file_demo_proto_rawDescGZIP(), []int{26} } func (x *SendOrderConfirmationRequest) GetEmail() string { if x != nil { return x.Email } return "" } func (x *SendOrderConfirmationRequest) GetOrder() *OrderResult { if x != nil { return x.Order } return nil } type PlaceOrderRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields UserId string `protobuf:"bytes,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` UserCurrency string `protobuf:"bytes,2,opt,name=user_currency,json=userCurrency,proto3" json:"user_currency,omitempty"` Address *Address `protobuf:"bytes,3,opt,name=address,proto3" json:"address,omitempty"` Email string `protobuf:"bytes,5,opt,name=email,proto3" json:"email,omitempty"` CreditCard *CreditCardInfo `protobuf:"bytes,6,opt,name=credit_card,json=creditCard,proto3" json:"credit_card,omitempty"` } func (x *PlaceOrderRequest) Reset() { *x = PlaceOrderRequest{} if protoimpl.UnsafeEnabled { mi := &file_demo_proto_msgTypes[27] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *PlaceOrderRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*PlaceOrderRequest) ProtoMessage() {} func (x *PlaceOrderRequest) ProtoReflect() protoreflect.Message { mi := &file_demo_proto_msgTypes[27] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use PlaceOrderRequest.ProtoReflect.Descriptor instead. func (*PlaceOrderRequest) Descriptor() ([]byte, []int) { return file_demo_proto_rawDescGZIP(), []int{27} } func (x *PlaceOrderRequest) GetUserId() string { if x != nil { return x.UserId } return "" } func (x *PlaceOrderRequest) GetUserCurrency() string { if x != nil { return x.UserCurrency } return "" } func (x *PlaceOrderRequest) GetAddress() *Address { if x != nil { return x.Address } return nil } func (x *PlaceOrderRequest) GetEmail() string { if x != nil { return x.Email } return "" } func (x *PlaceOrderRequest) GetCreditCard() *CreditCardInfo { if x != nil { return x.CreditCard } return nil } type PlaceOrderResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Order *OrderResult `protobuf:"bytes,1,opt,name=order,proto3" json:"order,omitempty"` } func (x *PlaceOrderResponse) Reset() { *x = PlaceOrderResponse{} if protoimpl.UnsafeEnabled { mi := &file_demo_proto_msgTypes[28] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *PlaceOrderResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*PlaceOrderResponse) ProtoMessage() {} func (x *PlaceOrderResponse) ProtoReflect() protoreflect.Message { mi := &file_demo_proto_msgTypes[28] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use PlaceOrderResponse.ProtoReflect.Descriptor instead. func (*PlaceOrderResponse) Descriptor() ([]byte, []int) { return file_demo_proto_rawDescGZIP(), []int{28} } func (x *PlaceOrderResponse) GetOrder() *OrderResult { if x != nil { return x.Order } return nil } type AdRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // List of important key words from the current page describing the context. ContextKeys []string `protobuf:"bytes,1,rep,name=context_keys,json=contextKeys,proto3" json:"context_keys,omitempty"` } func (x *AdRequest) Reset() { *x = AdRequest{} if protoimpl.UnsafeEnabled { mi := &file_demo_proto_msgTypes[29] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *AdRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*AdRequest) ProtoMessage() {} func (x *AdRequest) ProtoReflect() protoreflect.Message { mi := &file_demo_proto_msgTypes[29] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use AdRequest.ProtoReflect.Descriptor instead. func (*AdRequest) Descriptor() ([]byte, []int) { return file_demo_proto_rawDescGZIP(), []int{29} } func (x *AdRequest) GetContextKeys() []string { if x != nil { return x.ContextKeys } return nil } type AdResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Ads []*Ad `protobuf:"bytes,1,rep,name=ads,proto3" json:"ads,omitempty"` } func (x *AdResponse) Reset() { *x = AdResponse{} if protoimpl.UnsafeEnabled { mi := &file_demo_proto_msgTypes[30] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *AdResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*AdResponse) ProtoMessage() {} func (x *AdResponse) ProtoReflect() protoreflect.Message { mi := &file_demo_proto_msgTypes[30] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use AdResponse.ProtoReflect.Descriptor instead. func (*AdResponse) Descriptor() ([]byte, []int) { return file_demo_proto_rawDescGZIP(), []int{30} } func (x *AdResponse) GetAds() []*Ad { if x != nil { return x.Ads } return nil } type Ad struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // url to redirect to when an ad is clicked. RedirectUrl string `protobuf:"bytes,1,opt,name=redirect_url,json=redirectUrl,proto3" json:"redirect_url,omitempty"` // short advertisement text to display. Text string `protobuf:"bytes,2,opt,name=text,proto3" json:"text,omitempty"` } func (x *Ad) Reset() { *x = Ad{} if protoimpl.UnsafeEnabled { mi := &file_demo_proto_msgTypes[31] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Ad) String() string { return protoimpl.X.MessageStringOf(x) } func (*Ad) ProtoMessage() {} func (x *Ad) ProtoReflect() protoreflect.Message { mi := &file_demo_proto_msgTypes[31] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use Ad.ProtoReflect.Descriptor instead. func (*Ad) Descriptor() ([]byte, []int) { return file_demo_proto_rawDescGZIP(), []int{31} } func (x *Ad) GetRedirectUrl() string { if x != nil { return x.RedirectUrl } return "" } func (x *Ad) GetText() string { if x != nil { return x.Text } return "" } var File_demo_proto protoreflect.FileDescriptor var file_demo_proto_rawDesc = []byte{ 0x0a, 0x0a, 0x64, 0x65, 0x6d, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0b, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x22, 0x45, 0x0a, 0x08, 0x43, 0x61, 0x72, 0x74, 0x49, 0x74, 0x65, 0x6d, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x71, 0x75, 0x61, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x71, 0x75, 0x61, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x22, 0x54, 0x0a, 0x0e, 0x41, 0x64, 0x64, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x29, 0x0a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x43, 0x61, 0x72, 0x74, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x22, 0x2b, 0x0a, 0x10, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x43, 0x61, 0x72, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0x29, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x43, 0x61, 0x72, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0x4c, 0x0a, 0x04, 0x43, 0x61, 0x72, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x2b, 0x0a, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x43, 0x61, 0x72, 0x74, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x22, 0x07, 0x0a, 0x05, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x56, 0x0a, 0x1a, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x49, 0x64, 0x73, 0x22, 0x3e, 0x0a, 0x1b, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x49, 0x64, 0x73, 0x22, 0xba, 0x01, 0x0a, 0x07, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x69, 0x63, 0x74, 0x75, 0x72, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x70, 0x69, 0x63, 0x74, 0x75, 0x72, 0x65, 0x12, 0x2f, 0x0a, 0x09, 0x70, 0x72, 0x69, 0x63, 0x65, 0x5f, 0x75, 0x73, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x4d, 0x6f, 0x6e, 0x65, 0x79, 0x52, 0x08, 0x70, 0x72, 0x69, 0x63, 0x65, 0x55, 0x73, 0x64, 0x12, 0x1e, 0x0a, 0x0a, 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x69, 0x65, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x69, 0x65, 0x73, 0x22, 0x48, 0x0a, 0x14, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x30, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x73, 0x22, 0x23, 0x0a, 0x11, 0x47, 0x65, 0x74, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x22, 0x2d, 0x0a, 0x15, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x22, 0x48, 0x0a, 0x16, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2e, 0x0a, 0x07, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x52, 0x07, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x22, 0x6e, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x51, 0x75, 0x6f, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x2b, 0x0a, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x43, 0x61, 0x72, 0x74, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x22, 0x41, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x51, 0x75, 0x6f, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2d, 0x0a, 0x08, 0x63, 0x6f, 0x73, 0x74, 0x5f, 0x75, 0x73, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x4d, 0x6f, 0x6e, 0x65, 0x79, 0x52, 0x07, 0x63, 0x6f, 0x73, 0x74, 0x55, 0x73, 0x64, 0x22, 0x6f, 0x0a, 0x10, 0x53, 0x68, 0x69, 0x70, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x2b, 0x0a, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x43, 0x61, 0x72, 0x74, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x22, 0x34, 0x0a, 0x11, 0x53, 0x68, 0x69, 0x70, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x74, 0x72, 0x61, 0x63, 0x6b, 0x69, 0x6e, 0x67, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x74, 0x72, 0x61, 0x63, 0x6b, 0x69, 0x6e, 0x67, 0x49, 0x64, 0x22, 0x8f, 0x01, 0x0a, 0x07, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x25, 0x0a, 0x0e, 0x73, 0x74, 0x72, 0x65, 0x65, 0x74, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x73, 0x74, 0x72, 0x65, 0x65, 0x74, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x63, 0x69, 0x74, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x63, 0x69, 0x74, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x19, 0x0a, 0x08, 0x7a, 0x69, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x07, 0x7a, 0x69, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x22, 0x58, 0x0a, 0x05, 0x4d, 0x6f, 0x6e, 0x65, 0x79, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x75, 0x6e, 0x69, 0x74, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x75, 0x6e, 0x69, 0x74, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x6e, 0x61, 0x6e, 0x6f, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x6e, 0x61, 0x6e, 0x6f, 0x73, 0x22, 0x47, 0x0a, 0x1e, 0x47, 0x65, 0x74, 0x53, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x69, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0d, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x43, 0x6f, 0x64, 0x65, 0x73, 0x22, 0x5c, 0x0a, 0x19, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x43, 0x6f, 0x6e, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x26, 0x0a, 0x04, 0x66, 0x72, 0x6f, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x4d, 0x6f, 0x6e, 0x65, 0x79, 0x52, 0x04, 0x66, 0x72, 0x6f, 0x6d, 0x12, 0x17, 0x0a, 0x07, 0x74, 0x6f, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x74, 0x6f, 0x43, 0x6f, 0x64, 0x65, 0x22, 0xe6, 0x01, 0x0a, 0x0e, 0x43, 0x72, 0x65, 0x64, 0x69, 0x74, 0x43, 0x61, 0x72, 0x64, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x2c, 0x0a, 0x12, 0x63, 0x72, 0x65, 0x64, 0x69, 0x74, 0x5f, 0x63, 0x61, 0x72, 0x64, 0x5f, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x63, 0x72, 0x65, 0x64, 0x69, 0x74, 0x43, 0x61, 0x72, 0x64, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x26, 0x0a, 0x0f, 0x63, 0x72, 0x65, 0x64, 0x69, 0x74, 0x5f, 0x63, 0x61, 0x72, 0x64, 0x5f, 0x63, 0x76, 0x76, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0d, 0x63, 0x72, 0x65, 0x64, 0x69, 0x74, 0x43, 0x61, 0x72, 0x64, 0x43, 0x76, 0x76, 0x12, 0x3d, 0x0a, 0x1b, 0x63, 0x72, 0x65, 0x64, 0x69, 0x74, 0x5f, 0x63, 0x61, 0x72, 0x64, 0x5f, 0x65, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x79, 0x65, 0x61, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x18, 0x63, 0x72, 0x65, 0x64, 0x69, 0x74, 0x43, 0x61, 0x72, 0x64, 0x45, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x59, 0x65, 0x61, 0x72, 0x12, 0x3f, 0x0a, 0x1c, 0x63, 0x72, 0x65, 0x64, 0x69, 0x74, 0x5f, 0x63, 0x61, 0x72, 0x64, 0x5f, 0x65, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6d, 0x6f, 0x6e, 0x74, 0x68, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x19, 0x63, 0x72, 0x65, 0x64, 0x69, 0x74, 0x43, 0x61, 0x72, 0x64, 0x45, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x6f, 0x6e, 0x74, 0x68, 0x22, 0x79, 0x0a, 0x0d, 0x43, 0x68, 0x61, 0x72, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2a, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x4d, 0x6f, 0x6e, 0x65, 0x79, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x3c, 0x0a, 0x0b, 0x63, 0x72, 0x65, 0x64, 0x69, 0x74, 0x5f, 0x63, 0x61, 0x72, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x43, 0x72, 0x65, 0x64, 0x69, 0x74, 0x43, 0x61, 0x72, 0x64, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x0a, 0x63, 0x72, 0x65, 0x64, 0x69, 0x74, 0x43, 0x61, 0x72, 0x64, 0x22, 0x37, 0x0a, 0x0e, 0x43, 0x68, 0x61, 0x72, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x22, 0x5e, 0x0a, 0x09, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x49, 0x74, 0x65, 0x6d, 0x12, 0x29, 0x0a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x43, 0x61, 0x72, 0x74, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x12, 0x26, 0x0a, 0x04, 0x63, 0x6f, 0x73, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x4d, 0x6f, 0x6e, 0x65, 0x79, 0x52, 0x04, 0x63, 0x6f, 0x73, 0x74, 0x22, 0x82, 0x02, 0x0a, 0x0b, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x49, 0x64, 0x12, 0x30, 0x0a, 0x14, 0x73, 0x68, 0x69, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x5f, 0x74, 0x72, 0x61, 0x63, 0x6b, 0x69, 0x6e, 0x67, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x12, 0x73, 0x68, 0x69, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x54, 0x72, 0x61, 0x63, 0x6b, 0x69, 0x6e, 0x67, 0x49, 0x64, 0x12, 0x37, 0x0a, 0x0d, 0x73, 0x68, 0x69, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x6f, 0x73, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x4d, 0x6f, 0x6e, 0x65, 0x79, 0x52, 0x0c, 0x73, 0x68, 0x69, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x73, 0x74, 0x12, 0x3f, 0x0a, 0x10, 0x73, 0x68, 0x69, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, 0x0f, 0x73, 0x68, 0x69, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x2c, 0x0a, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x22, 0x64, 0x0a, 0x1c, 0x53, 0x65, 0x6e, 0x64, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x12, 0x2e, 0x0a, 0x05, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x05, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x22, 0xd5, 0x01, 0x0a, 0x11, 0x50, 0x6c, 0x61, 0x63, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x75, 0x73, 0x65, 0x72, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x12, 0x2e, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x12, 0x3c, 0x0a, 0x0b, 0x63, 0x72, 0x65, 0x64, 0x69, 0x74, 0x5f, 0x63, 0x61, 0x72, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x43, 0x72, 0x65, 0x64, 0x69, 0x74, 0x43, 0x61, 0x72, 0x64, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x0a, 0x63, 0x72, 0x65, 0x64, 0x69, 0x74, 0x43, 0x61, 0x72, 0x64, 0x22, 0x44, 0x0a, 0x12, 0x50, 0x6c, 0x61, 0x63, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2e, 0x0a, 0x05, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x05, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x22, 0x2e, 0x0a, 0x09, 0x41, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x5f, 0x6b, 0x65, 0x79, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x4b, 0x65, 0x79, 0x73, 0x22, 0x2f, 0x0a, 0x0a, 0x41, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x21, 0x0a, 0x03, 0x61, 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x41, 0x64, 0x52, 0x03, 0x61, 0x64, 0x73, 0x22, 0x3b, 0x0a, 0x02, 0x41, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x72, 0x65, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x72, 0x65, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x55, 0x72, 0x6c, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x65, 0x78, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x65, 0x78, 0x74, 0x32, 0xca, 0x01, 0x0a, 0x0b, 0x43, 0x61, 0x72, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x3c, 0x0a, 0x07, 0x41, 0x64, 0x64, 0x49, 0x74, 0x65, 0x6d, 0x12, 0x1b, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x41, 0x64, 0x64, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x3b, 0x0a, 0x07, 0x47, 0x65, 0x74, 0x43, 0x61, 0x72, 0x74, 0x12, 0x1b, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x61, 0x72, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x11, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x43, 0x61, 0x72, 0x74, 0x22, 0x00, 0x12, 0x40, 0x0a, 0x09, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x43, 0x61, 0x72, 0x74, 0x12, 0x1d, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x43, 0x61, 0x72, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x32, 0x83, 0x01, 0x0a, 0x15, 0x52, 0x65, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x6a, 0x0a, 0x13, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x27, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x32, 0x83, 0x02, 0x0a, 0x15, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x43, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x47, 0x0a, 0x0c, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x73, 0x12, 0x12, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x21, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x44, 0x0a, 0x0a, 0x47, 0x65, 0x74, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x12, 0x1e, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x47, 0x65, 0x74, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x22, 0x00, 0x12, 0x5b, 0x0a, 0x0e, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x73, 0x12, 0x22, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x32, 0xaa, 0x01, 0x0a, 0x0f, 0x53, 0x68, 0x69, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x49, 0x0a, 0x08, 0x47, 0x65, 0x74, 0x51, 0x75, 0x6f, 0x74, 0x65, 0x12, 0x1c, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x47, 0x65, 0x74, 0x51, 0x75, 0x6f, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x47, 0x65, 0x74, 0x51, 0x75, 0x6f, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x4c, 0x0a, 0x09, 0x53, 0x68, 0x69, 0x70, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x1d, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x53, 0x68, 0x69, 0x70, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x53, 0x68, 0x69, 0x70, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x32, 0xb7, 0x01, 0x0a, 0x0f, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x5b, 0x0a, 0x16, 0x47, 0x65, 0x74, 0x53, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x69, 0x65, 0x73, 0x12, 0x12, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x2b, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x69, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x47, 0x0a, 0x07, 0x43, 0x6f, 0x6e, 0x76, 0x65, 0x72, 0x74, 0x12, 0x26, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x43, 0x6f, 0x6e, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x4d, 0x6f, 0x6e, 0x65, 0x79, 0x22, 0x00, 0x32, 0x55, 0x0a, 0x0e, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x43, 0x0a, 0x06, 0x43, 0x68, 0x61, 0x72, 0x67, 0x65, 0x12, 0x1a, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x43, 0x68, 0x61, 0x72, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x43, 0x68, 0x61, 0x72, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x32, 0x68, 0x0a, 0x0c, 0x45, 0x6d, 0x61, 0x69, 0x6c, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x58, 0x0a, 0x15, 0x53, 0x65, 0x6e, 0x64, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x29, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x32, 0x62, 0x0a, 0x0f, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x6f, 0x75, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x4f, 0x0a, 0x0a, 0x50, 0x6c, 0x61, 0x63, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x1e, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x50, 0x6c, 0x61, 0x63, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x50, 0x6c, 0x61, 0x63, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x32, 0x48, 0x0a, 0x09, 0x41, 0x64, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x3b, 0x0a, 0x06, 0x47, 0x65, 0x74, 0x41, 0x64, 0x73, 0x12, 0x16, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x41, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x41, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x40, 0x5a, 0x3e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x47, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x43, 0x6c, 0x6f, 0x75, 0x64, 0x50, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x2f, 0x6d, 0x69, 0x73, 0x63, 0x72, 0x6f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2d, 0x64, 0x65, 0x6d, 0x6f, 0x2f, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( file_demo_proto_rawDescOnce sync.Once file_demo_proto_rawDescData = file_demo_proto_rawDesc ) func file_demo_proto_rawDescGZIP() []byte { file_demo_proto_rawDescOnce.Do(func() { file_demo_proto_rawDescData = protoimpl.X.CompressGZIP(file_demo_proto_rawDescData) }) return file_demo_proto_rawDescData } var file_demo_proto_msgTypes = make([]protoimpl.MessageInfo, 32) var file_demo_proto_goTypes = []any{ (*CartItem)(nil), // 0: hipstershop.CartItem (*AddItemRequest)(nil), // 1: hipstershop.AddItemRequest (*EmptyCartRequest)(nil), // 2: hipstershop.EmptyCartRequest (*GetCartRequest)(nil), // 3: hipstershop.GetCartRequest (*Cart)(nil), // 4: hipstershop.Cart (*Empty)(nil), // 5: hipstershop.Empty (*ListRecommendationsRequest)(nil), // 6: hipstershop.ListRecommendationsRequest (*ListRecommendationsResponse)(nil), // 7: hipstershop.ListRecommendationsResponse (*Product)(nil), // 8: hipstershop.Product (*ListProductsResponse)(nil), // 9: hipstershop.ListProductsResponse (*GetProductRequest)(nil), // 10: hipstershop.GetProductRequest (*SearchProductsRequest)(nil), // 11: hipstershop.SearchProductsRequest (*SearchProductsResponse)(nil), // 12: hipstershop.SearchProductsResponse (*GetQuoteRequest)(nil), // 13: hipstershop.GetQuoteRequest (*GetQuoteResponse)(nil), // 14: hipstershop.GetQuoteResponse (*ShipOrderRequest)(nil), // 15: hipstershop.ShipOrderRequest (*ShipOrderResponse)(nil), // 16: hipstershop.ShipOrderResponse (*Address)(nil), // 17: hipstershop.Address (*Money)(nil), // 18: hipstershop.Money (*GetSupportedCurrenciesResponse)(nil), // 19: hipstershop.GetSupportedCurrenciesResponse (*CurrencyConversionRequest)(nil), // 20: hipstershop.CurrencyConversionRequest (*CreditCardInfo)(nil), // 21: hipstershop.CreditCardInfo (*ChargeRequest)(nil), // 22: hipstershop.ChargeRequest (*ChargeResponse)(nil), // 23: hipstershop.ChargeResponse (*OrderItem)(nil), // 24: hipstershop.OrderItem (*OrderResult)(nil), // 25: hipstershop.OrderResult (*SendOrderConfirmationRequest)(nil), // 26: hipstershop.SendOrderConfirmationRequest (*PlaceOrderRequest)(nil), // 27: hipstershop.PlaceOrderRequest (*PlaceOrderResponse)(nil), // 28: hipstershop.PlaceOrderResponse (*AdRequest)(nil), // 29: hipstershop.AdRequest (*AdResponse)(nil), // 30: hipstershop.AdResponse (*Ad)(nil), // 31: hipstershop.Ad } var file_demo_proto_depIdxs = []int32{ 0, // 0: hipstershop.AddItemRequest.item:type_name -> hipstershop.CartItem 0, // 1: hipstershop.Cart.items:type_name -> hipstershop.CartItem 18, // 2: hipstershop.Product.price_usd:type_name -> hipstershop.Money 8, // 3: hipstershop.ListProductsResponse.products:type_name -> hipstershop.Product 8, // 4: hipstershop.SearchProductsResponse.results:type_name -> hipstershop.Product 17, // 5: hipstershop.GetQuoteRequest.address:type_name -> hipstershop.Address 0, // 6: hipstershop.GetQuoteRequest.items:type_name -> hipstershop.CartItem 18, // 7: hipstershop.GetQuoteResponse.cost_usd:type_name -> hipstershop.Money 17, // 8: hipstershop.ShipOrderRequest.address:type_name -> hipstershop.Address 0, // 9: hipstershop.ShipOrderRequest.items:type_name -> hipstershop.CartItem 18, // 10: hipstershop.CurrencyConversionRequest.from:type_name -> hipstershop.Money 18, // 11: hipstershop.ChargeRequest.amount:type_name -> hipstershop.Money 21, // 12: hipstershop.ChargeRequest.credit_card:type_name -> hipstershop.CreditCardInfo 0, // 13: hipstershop.OrderItem.item:type_name -> hipstershop.CartItem 18, // 14: hipstershop.OrderItem.cost:type_name -> hipstershop.Money 18, // 15: hipstershop.OrderResult.shipping_cost:type_name -> hipstershop.Money 17, // 16: hipstershop.OrderResult.shipping_address:type_name -> hipstershop.Address 24, // 17: hipstershop.OrderResult.items:type_name -> hipstershop.OrderItem 25, // 18: hipstershop.SendOrderConfirmationRequest.order:type_name -> hipstershop.OrderResult 17, // 19: hipstershop.PlaceOrderRequest.address:type_name -> hipstershop.Address 21, // 20: hipstershop.PlaceOrderRequest.credit_card:type_name -> hipstershop.CreditCardInfo 25, // 21: hipstershop.PlaceOrderResponse.order:type_name -> hipstershop.OrderResult 31, // 22: hipstershop.AdResponse.ads:type_name -> hipstershop.Ad 1, // 23: hipstershop.CartService.AddItem:input_type -> hipstershop.AddItemRequest 3, // 24: hipstershop.CartService.GetCart:input_type -> hipstershop.GetCartRequest 2, // 25: hipstershop.CartService.EmptyCart:input_type -> hipstershop.EmptyCartRequest 6, // 26: hipstershop.RecommendationService.ListRecommendations:input_type -> hipstershop.ListRecommendationsRequest 5, // 27: hipstershop.ProductCatalogService.ListProducts:input_type -> hipstershop.Empty 10, // 28: hipstershop.ProductCatalogService.GetProduct:input_type -> hipstershop.GetProductRequest 11, // 29: hipstershop.ProductCatalogService.SearchProducts:input_type -> hipstershop.SearchProductsRequest 13, // 30: hipstershop.ShippingService.GetQuote:input_type -> hipstershop.GetQuoteRequest 15, // 31: hipstershop.ShippingService.ShipOrder:input_type -> hipstershop.ShipOrderRequest 5, // 32: hipstershop.CurrencyService.GetSupportedCurrencies:input_type -> hipstershop.Empty 20, // 33: hipstershop.CurrencyService.Convert:input_type -> hipstershop.CurrencyConversionRequest 22, // 34: hipstershop.PaymentService.Charge:input_type -> hipstershop.ChargeRequest 26, // 35: hipstershop.EmailService.SendOrderConfirmation:input_type -> hipstershop.SendOrderConfirmationRequest 27, // 36: hipstershop.CheckoutService.PlaceOrder:input_type -> hipstershop.PlaceOrderRequest 29, // 37: hipstershop.AdService.GetAds:input_type -> hipstershop.AdRequest 5, // 38: hipstershop.CartService.AddItem:output_type -> hipstershop.Empty 4, // 39: hipstershop.CartService.GetCart:output_type -> hipstershop.Cart 5, // 40: hipstershop.CartService.EmptyCart:output_type -> hipstershop.Empty 7, // 41: hipstershop.RecommendationService.ListRecommendations:output_type -> hipstershop.ListRecommendationsResponse 9, // 42: hipstershop.ProductCatalogService.ListProducts:output_type -> hipstershop.ListProductsResponse 8, // 43: hipstershop.ProductCatalogService.GetProduct:output_type -> hipstershop.Product 12, // 44: hipstershop.ProductCatalogService.SearchProducts:output_type -> hipstershop.SearchProductsResponse 14, // 45: hipstershop.ShippingService.GetQuote:output_type -> hipstershop.GetQuoteResponse 16, // 46: hipstershop.ShippingService.ShipOrder:output_type -> hipstershop.ShipOrderResponse 19, // 47: hipstershop.CurrencyService.GetSupportedCurrencies:output_type -> hipstershop.GetSupportedCurrenciesResponse 18, // 48: hipstershop.CurrencyService.Convert:output_type -> hipstershop.Money 23, // 49: hipstershop.PaymentService.Charge:output_type -> hipstershop.ChargeResponse 5, // 50: hipstershop.EmailService.SendOrderConfirmation:output_type -> hipstershop.Empty 28, // 51: hipstershop.CheckoutService.PlaceOrder:output_type -> hipstershop.PlaceOrderResponse 30, // 52: hipstershop.AdService.GetAds:output_type -> hipstershop.AdResponse 38, // [38:53] is the sub-list for method output_type 23, // [23:38] is the sub-list for method input_type 23, // [23:23] is the sub-list for extension type_name 23, // [23:23] is the sub-list for extension extendee 0, // [0:23] is the sub-list for field type_name } func init() { file_demo_proto_init() } func file_demo_proto_init() { if File_demo_proto != nil { return } if !protoimpl.UnsafeEnabled { file_demo_proto_msgTypes[0].Exporter = func(v any, i int) any { switch v := v.(*CartItem); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_demo_proto_msgTypes[1].Exporter = func(v any, i int) any { switch v := v.(*AddItemRequest); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_demo_proto_msgTypes[2].Exporter = func(v any, i int) any { switch v := v.(*EmptyCartRequest); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_demo_proto_msgTypes[3].Exporter = func(v any, i int) any { switch v := v.(*GetCartRequest); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_demo_proto_msgTypes[4].Exporter = func(v any, i int) any { switch v := v.(*Cart); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_demo_proto_msgTypes[5].Exporter = func(v any, i int) any { switch v := v.(*Empty); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_demo_proto_msgTypes[6].Exporter = func(v any, i int) any { switch v := v.(*ListRecommendationsRequest); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_demo_proto_msgTypes[7].Exporter = func(v any, i int) any { switch v := v.(*ListRecommendationsResponse); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_demo_proto_msgTypes[8].Exporter = func(v any, i int) any { switch v := v.(*Product); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_demo_proto_msgTypes[9].Exporter = func(v any, i int) any { switch v := v.(*ListProductsResponse); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_demo_proto_msgTypes[10].Exporter = func(v any, i int) any { switch v := v.(*GetProductRequest); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_demo_proto_msgTypes[11].Exporter = func(v any, i int) any { switch v := v.(*SearchProductsRequest); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_demo_proto_msgTypes[12].Exporter = func(v any, i int) any { switch v := v.(*SearchProductsResponse); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_demo_proto_msgTypes[13].Exporter = func(v any, i int) any { switch v := v.(*GetQuoteRequest); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_demo_proto_msgTypes[14].Exporter = func(v any, i int) any { switch v := v.(*GetQuoteResponse); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_demo_proto_msgTypes[15].Exporter = func(v any, i int) any { switch v := v.(*ShipOrderRequest); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_demo_proto_msgTypes[16].Exporter = func(v any, i int) any { switch v := v.(*ShipOrderResponse); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_demo_proto_msgTypes[17].Exporter = func(v any, i int) any { switch v := v.(*Address); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_demo_proto_msgTypes[18].Exporter = func(v any, i int) any { switch v := v.(*Money); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_demo_proto_msgTypes[19].Exporter = func(v any, i int) any { switch v := v.(*GetSupportedCurrenciesResponse); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_demo_proto_msgTypes[20].Exporter = func(v any, i int) any { switch v := v.(*CurrencyConversionRequest); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_demo_proto_msgTypes[21].Exporter = func(v any, i int) any { switch v := v.(*CreditCardInfo); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_demo_proto_msgTypes[22].Exporter = func(v any, i int) any { switch v := v.(*ChargeRequest); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_demo_proto_msgTypes[23].Exporter = func(v any, i int) any { switch v := v.(*ChargeResponse); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_demo_proto_msgTypes[24].Exporter = func(v any, i int) any { switch v := v.(*OrderItem); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_demo_proto_msgTypes[25].Exporter = func(v any, i int) any { switch v := v.(*OrderResult); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_demo_proto_msgTypes[26].Exporter = func(v any, i int) any { switch v := v.(*SendOrderConfirmationRequest); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_demo_proto_msgTypes[27].Exporter = func(v any, i int) any { switch v := v.(*PlaceOrderRequest); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_demo_proto_msgTypes[28].Exporter = func(v any, i int) any { switch v := v.(*PlaceOrderResponse); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_demo_proto_msgTypes[29].Exporter = func(v any, i int) any { switch v := v.(*AdRequest); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_demo_proto_msgTypes[30].Exporter = func(v any, i int) any { switch v := v.(*AdResponse); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_demo_proto_msgTypes[31].Exporter = func(v any, i int) any { switch v := v.(*Ad); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_demo_proto_rawDesc, NumEnums: 0, NumMessages: 32, NumExtensions: 0, NumServices: 9, }, GoTypes: file_demo_proto_goTypes, DependencyIndexes: file_demo_proto_depIdxs, MessageInfos: file_demo_proto_msgTypes, }.Build() File_demo_proto = out.File file_demo_proto_rawDesc = nil file_demo_proto_goTypes = nil file_demo_proto_depIdxs = nil } ================================================ FILE: src/productcatalogservice/genproto/demo_grpc.pb.go ================================================ // Copyright 2020 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. // Code generated by protoc-gen-go-grpc. DO NOT EDIT. // versions: // - protoc-gen-go-grpc v1.5.1 // - protoc v3.6.1 // source: demo.proto package hipstershop import ( context "context" grpc "google.golang.org/grpc" codes "google.golang.org/grpc/codes" status "google.golang.org/grpc/status" ) // This is a compile-time assertion to ensure that this generated file // is compatible with the grpc package it is being compiled against. // Requires gRPC-Go v1.64.0 or later. const _ = grpc.SupportPackageIsVersion9 const ( CartService_AddItem_FullMethodName = "/hipstershop.CartService/AddItem" CartService_GetCart_FullMethodName = "/hipstershop.CartService/GetCart" CartService_EmptyCart_FullMethodName = "/hipstershop.CartService/EmptyCart" ) // CartServiceClient is the client API for CartService service. // // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. type CartServiceClient interface { AddItem(ctx context.Context, in *AddItemRequest, opts ...grpc.CallOption) (*Empty, error) GetCart(ctx context.Context, in *GetCartRequest, opts ...grpc.CallOption) (*Cart, error) EmptyCart(ctx context.Context, in *EmptyCartRequest, opts ...grpc.CallOption) (*Empty, error) } type cartServiceClient struct { cc grpc.ClientConnInterface } func NewCartServiceClient(cc grpc.ClientConnInterface) CartServiceClient { return &cartServiceClient{cc} } func (c *cartServiceClient) AddItem(ctx context.Context, in *AddItemRequest, opts ...grpc.CallOption) (*Empty, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(Empty) err := c.cc.Invoke(ctx, CartService_AddItem_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } return out, nil } func (c *cartServiceClient) GetCart(ctx context.Context, in *GetCartRequest, opts ...grpc.CallOption) (*Cart, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(Cart) err := c.cc.Invoke(ctx, CartService_GetCart_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } return out, nil } func (c *cartServiceClient) EmptyCart(ctx context.Context, in *EmptyCartRequest, opts ...grpc.CallOption) (*Empty, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(Empty) err := c.cc.Invoke(ctx, CartService_EmptyCart_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } return out, nil } // CartServiceServer is the server API for CartService service. // All implementations must embed UnimplementedCartServiceServer // for forward compatibility. type CartServiceServer interface { AddItem(context.Context, *AddItemRequest) (*Empty, error) GetCart(context.Context, *GetCartRequest) (*Cart, error) EmptyCart(context.Context, *EmptyCartRequest) (*Empty, error) mustEmbedUnimplementedCartServiceServer() } // UnimplementedCartServiceServer must be embedded to have // forward compatible implementations. // // NOTE: this should be embedded by value instead of pointer to avoid a nil // pointer dereference when methods are called. type UnimplementedCartServiceServer struct{} func (UnimplementedCartServiceServer) AddItem(context.Context, *AddItemRequest) (*Empty, error) { return nil, status.Errorf(codes.Unimplemented, "method AddItem not implemented") } func (UnimplementedCartServiceServer) GetCart(context.Context, *GetCartRequest) (*Cart, error) { return nil, status.Errorf(codes.Unimplemented, "method GetCart not implemented") } func (UnimplementedCartServiceServer) EmptyCart(context.Context, *EmptyCartRequest) (*Empty, error) { return nil, status.Errorf(codes.Unimplemented, "method EmptyCart not implemented") } func (UnimplementedCartServiceServer) mustEmbedUnimplementedCartServiceServer() {} func (UnimplementedCartServiceServer) testEmbeddedByValue() {} // UnsafeCartServiceServer may be embedded to opt out of forward compatibility for this service. // Use of this interface is not recommended, as added methods to CartServiceServer will // result in compilation errors. type UnsafeCartServiceServer interface { mustEmbedUnimplementedCartServiceServer() } func RegisterCartServiceServer(s grpc.ServiceRegistrar, srv CartServiceServer) { // If the following call pancis, it indicates UnimplementedCartServiceServer was // embedded by pointer and is nil. This will cause panics if an // unimplemented method is ever invoked, so we test this at initialization // time to prevent it from happening at runtime later due to I/O. if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { t.testEmbeddedByValue() } s.RegisterService(&CartService_ServiceDesc, srv) } func _CartService_AddItem_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(AddItemRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(CartServiceServer).AddItem(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: CartService_AddItem_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(CartServiceServer).AddItem(ctx, req.(*AddItemRequest)) } return interceptor(ctx, in, info, handler) } func _CartService_GetCart_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(GetCartRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(CartServiceServer).GetCart(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: CartService_GetCart_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(CartServiceServer).GetCart(ctx, req.(*GetCartRequest)) } return interceptor(ctx, in, info, handler) } func _CartService_EmptyCart_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(EmptyCartRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(CartServiceServer).EmptyCart(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: CartService_EmptyCart_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(CartServiceServer).EmptyCart(ctx, req.(*EmptyCartRequest)) } return interceptor(ctx, in, info, handler) } // CartService_ServiceDesc is the grpc.ServiceDesc for CartService service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) var CartService_ServiceDesc = grpc.ServiceDesc{ ServiceName: "hipstershop.CartService", HandlerType: (*CartServiceServer)(nil), Methods: []grpc.MethodDesc{ { MethodName: "AddItem", Handler: _CartService_AddItem_Handler, }, { MethodName: "GetCart", Handler: _CartService_GetCart_Handler, }, { MethodName: "EmptyCart", Handler: _CartService_EmptyCart_Handler, }, }, Streams: []grpc.StreamDesc{}, Metadata: "demo.proto", } const ( RecommendationService_ListRecommendations_FullMethodName = "/hipstershop.RecommendationService/ListRecommendations" ) // RecommendationServiceClient is the client API for RecommendationService service. // // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. type RecommendationServiceClient interface { ListRecommendations(ctx context.Context, in *ListRecommendationsRequest, opts ...grpc.CallOption) (*ListRecommendationsResponse, error) } type recommendationServiceClient struct { cc grpc.ClientConnInterface } func NewRecommendationServiceClient(cc grpc.ClientConnInterface) RecommendationServiceClient { return &recommendationServiceClient{cc} } func (c *recommendationServiceClient) ListRecommendations(ctx context.Context, in *ListRecommendationsRequest, opts ...grpc.CallOption) (*ListRecommendationsResponse, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(ListRecommendationsResponse) err := c.cc.Invoke(ctx, RecommendationService_ListRecommendations_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } return out, nil } // RecommendationServiceServer is the server API for RecommendationService service. // All implementations must embed UnimplementedRecommendationServiceServer // for forward compatibility. type RecommendationServiceServer interface { ListRecommendations(context.Context, *ListRecommendationsRequest) (*ListRecommendationsResponse, error) mustEmbedUnimplementedRecommendationServiceServer() } // UnimplementedRecommendationServiceServer must be embedded to have // forward compatible implementations. // // NOTE: this should be embedded by value instead of pointer to avoid a nil // pointer dereference when methods are called. type UnimplementedRecommendationServiceServer struct{} func (UnimplementedRecommendationServiceServer) ListRecommendations(context.Context, *ListRecommendationsRequest) (*ListRecommendationsResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method ListRecommendations not implemented") } func (UnimplementedRecommendationServiceServer) mustEmbedUnimplementedRecommendationServiceServer() {} func (UnimplementedRecommendationServiceServer) testEmbeddedByValue() {} // UnsafeRecommendationServiceServer may be embedded to opt out of forward compatibility for this service. // Use of this interface is not recommended, as added methods to RecommendationServiceServer will // result in compilation errors. type UnsafeRecommendationServiceServer interface { mustEmbedUnimplementedRecommendationServiceServer() } func RegisterRecommendationServiceServer(s grpc.ServiceRegistrar, srv RecommendationServiceServer) { // If the following call pancis, it indicates UnimplementedRecommendationServiceServer was // embedded by pointer and is nil. This will cause panics if an // unimplemented method is ever invoked, so we test this at initialization // time to prevent it from happening at runtime later due to I/O. if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { t.testEmbeddedByValue() } s.RegisterService(&RecommendationService_ServiceDesc, srv) } func _RecommendationService_ListRecommendations_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(ListRecommendationsRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(RecommendationServiceServer).ListRecommendations(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: RecommendationService_ListRecommendations_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(RecommendationServiceServer).ListRecommendations(ctx, req.(*ListRecommendationsRequest)) } return interceptor(ctx, in, info, handler) } // RecommendationService_ServiceDesc is the grpc.ServiceDesc for RecommendationService service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) var RecommendationService_ServiceDesc = grpc.ServiceDesc{ ServiceName: "hipstershop.RecommendationService", HandlerType: (*RecommendationServiceServer)(nil), Methods: []grpc.MethodDesc{ { MethodName: "ListRecommendations", Handler: _RecommendationService_ListRecommendations_Handler, }, }, Streams: []grpc.StreamDesc{}, Metadata: "demo.proto", } const ( ProductCatalogService_ListProducts_FullMethodName = "/hipstershop.ProductCatalogService/ListProducts" ProductCatalogService_GetProduct_FullMethodName = "/hipstershop.ProductCatalogService/GetProduct" ProductCatalogService_SearchProducts_FullMethodName = "/hipstershop.ProductCatalogService/SearchProducts" ) // ProductCatalogServiceClient is the client API for ProductCatalogService service. // // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. type ProductCatalogServiceClient interface { ListProducts(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*ListProductsResponse, error) GetProduct(ctx context.Context, in *GetProductRequest, opts ...grpc.CallOption) (*Product, error) SearchProducts(ctx context.Context, in *SearchProductsRequest, opts ...grpc.CallOption) (*SearchProductsResponse, error) } type productCatalogServiceClient struct { cc grpc.ClientConnInterface } func NewProductCatalogServiceClient(cc grpc.ClientConnInterface) ProductCatalogServiceClient { return &productCatalogServiceClient{cc} } func (c *productCatalogServiceClient) ListProducts(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*ListProductsResponse, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(ListProductsResponse) err := c.cc.Invoke(ctx, ProductCatalogService_ListProducts_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } return out, nil } func (c *productCatalogServiceClient) GetProduct(ctx context.Context, in *GetProductRequest, opts ...grpc.CallOption) (*Product, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(Product) err := c.cc.Invoke(ctx, ProductCatalogService_GetProduct_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } return out, nil } func (c *productCatalogServiceClient) SearchProducts(ctx context.Context, in *SearchProductsRequest, opts ...grpc.CallOption) (*SearchProductsResponse, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(SearchProductsResponse) err := c.cc.Invoke(ctx, ProductCatalogService_SearchProducts_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } return out, nil } // ProductCatalogServiceServer is the server API for ProductCatalogService service. // All implementations must embed UnimplementedProductCatalogServiceServer // for forward compatibility. type ProductCatalogServiceServer interface { ListProducts(context.Context, *Empty) (*ListProductsResponse, error) GetProduct(context.Context, *GetProductRequest) (*Product, error) SearchProducts(context.Context, *SearchProductsRequest) (*SearchProductsResponse, error) mustEmbedUnimplementedProductCatalogServiceServer() } // UnimplementedProductCatalogServiceServer must be embedded to have // forward compatible implementations. // // NOTE: this should be embedded by value instead of pointer to avoid a nil // pointer dereference when methods are called. type UnimplementedProductCatalogServiceServer struct{} func (UnimplementedProductCatalogServiceServer) ListProducts(context.Context, *Empty) (*ListProductsResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method ListProducts not implemented") } func (UnimplementedProductCatalogServiceServer) GetProduct(context.Context, *GetProductRequest) (*Product, error) { return nil, status.Errorf(codes.Unimplemented, "method GetProduct not implemented") } func (UnimplementedProductCatalogServiceServer) SearchProducts(context.Context, *SearchProductsRequest) (*SearchProductsResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method SearchProducts not implemented") } func (UnimplementedProductCatalogServiceServer) mustEmbedUnimplementedProductCatalogServiceServer() {} func (UnimplementedProductCatalogServiceServer) testEmbeddedByValue() {} // UnsafeProductCatalogServiceServer may be embedded to opt out of forward compatibility for this service. // Use of this interface is not recommended, as added methods to ProductCatalogServiceServer will // result in compilation errors. type UnsafeProductCatalogServiceServer interface { mustEmbedUnimplementedProductCatalogServiceServer() } func RegisterProductCatalogServiceServer(s grpc.ServiceRegistrar, srv ProductCatalogServiceServer) { // If the following call pancis, it indicates UnimplementedProductCatalogServiceServer was // embedded by pointer and is nil. This will cause panics if an // unimplemented method is ever invoked, so we test this at initialization // time to prevent it from happening at runtime later due to I/O. if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { t.testEmbeddedByValue() } s.RegisterService(&ProductCatalogService_ServiceDesc, srv) } func _ProductCatalogService_ListProducts_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(Empty) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(ProductCatalogServiceServer).ListProducts(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: ProductCatalogService_ListProducts_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(ProductCatalogServiceServer).ListProducts(ctx, req.(*Empty)) } return interceptor(ctx, in, info, handler) } func _ProductCatalogService_GetProduct_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(GetProductRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(ProductCatalogServiceServer).GetProduct(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: ProductCatalogService_GetProduct_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(ProductCatalogServiceServer).GetProduct(ctx, req.(*GetProductRequest)) } return interceptor(ctx, in, info, handler) } func _ProductCatalogService_SearchProducts_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(SearchProductsRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(ProductCatalogServiceServer).SearchProducts(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: ProductCatalogService_SearchProducts_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(ProductCatalogServiceServer).SearchProducts(ctx, req.(*SearchProductsRequest)) } return interceptor(ctx, in, info, handler) } // ProductCatalogService_ServiceDesc is the grpc.ServiceDesc for ProductCatalogService service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) var ProductCatalogService_ServiceDesc = grpc.ServiceDesc{ ServiceName: "hipstershop.ProductCatalogService", HandlerType: (*ProductCatalogServiceServer)(nil), Methods: []grpc.MethodDesc{ { MethodName: "ListProducts", Handler: _ProductCatalogService_ListProducts_Handler, }, { MethodName: "GetProduct", Handler: _ProductCatalogService_GetProduct_Handler, }, { MethodName: "SearchProducts", Handler: _ProductCatalogService_SearchProducts_Handler, }, }, Streams: []grpc.StreamDesc{}, Metadata: "demo.proto", } const ( ShippingService_GetQuote_FullMethodName = "/hipstershop.ShippingService/GetQuote" ShippingService_ShipOrder_FullMethodName = "/hipstershop.ShippingService/ShipOrder" ) // ShippingServiceClient is the client API for ShippingService service. // // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. type ShippingServiceClient interface { GetQuote(ctx context.Context, in *GetQuoteRequest, opts ...grpc.CallOption) (*GetQuoteResponse, error) ShipOrder(ctx context.Context, in *ShipOrderRequest, opts ...grpc.CallOption) (*ShipOrderResponse, error) } type shippingServiceClient struct { cc grpc.ClientConnInterface } func NewShippingServiceClient(cc grpc.ClientConnInterface) ShippingServiceClient { return &shippingServiceClient{cc} } func (c *shippingServiceClient) GetQuote(ctx context.Context, in *GetQuoteRequest, opts ...grpc.CallOption) (*GetQuoteResponse, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(GetQuoteResponse) err := c.cc.Invoke(ctx, ShippingService_GetQuote_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } return out, nil } func (c *shippingServiceClient) ShipOrder(ctx context.Context, in *ShipOrderRequest, opts ...grpc.CallOption) (*ShipOrderResponse, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(ShipOrderResponse) err := c.cc.Invoke(ctx, ShippingService_ShipOrder_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } return out, nil } // ShippingServiceServer is the server API for ShippingService service. // All implementations must embed UnimplementedShippingServiceServer // for forward compatibility. type ShippingServiceServer interface { GetQuote(context.Context, *GetQuoteRequest) (*GetQuoteResponse, error) ShipOrder(context.Context, *ShipOrderRequest) (*ShipOrderResponse, error) mustEmbedUnimplementedShippingServiceServer() } // UnimplementedShippingServiceServer must be embedded to have // forward compatible implementations. // // NOTE: this should be embedded by value instead of pointer to avoid a nil // pointer dereference when methods are called. type UnimplementedShippingServiceServer struct{} func (UnimplementedShippingServiceServer) GetQuote(context.Context, *GetQuoteRequest) (*GetQuoteResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method GetQuote not implemented") } func (UnimplementedShippingServiceServer) ShipOrder(context.Context, *ShipOrderRequest) (*ShipOrderResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method ShipOrder not implemented") } func (UnimplementedShippingServiceServer) mustEmbedUnimplementedShippingServiceServer() {} func (UnimplementedShippingServiceServer) testEmbeddedByValue() {} // UnsafeShippingServiceServer may be embedded to opt out of forward compatibility for this service. // Use of this interface is not recommended, as added methods to ShippingServiceServer will // result in compilation errors. type UnsafeShippingServiceServer interface { mustEmbedUnimplementedShippingServiceServer() } func RegisterShippingServiceServer(s grpc.ServiceRegistrar, srv ShippingServiceServer) { // If the following call pancis, it indicates UnimplementedShippingServiceServer was // embedded by pointer and is nil. This will cause panics if an // unimplemented method is ever invoked, so we test this at initialization // time to prevent it from happening at runtime later due to I/O. if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { t.testEmbeddedByValue() } s.RegisterService(&ShippingService_ServiceDesc, srv) } func _ShippingService_GetQuote_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(GetQuoteRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(ShippingServiceServer).GetQuote(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: ShippingService_GetQuote_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(ShippingServiceServer).GetQuote(ctx, req.(*GetQuoteRequest)) } return interceptor(ctx, in, info, handler) } func _ShippingService_ShipOrder_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(ShipOrderRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(ShippingServiceServer).ShipOrder(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: ShippingService_ShipOrder_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(ShippingServiceServer).ShipOrder(ctx, req.(*ShipOrderRequest)) } return interceptor(ctx, in, info, handler) } // ShippingService_ServiceDesc is the grpc.ServiceDesc for ShippingService service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) var ShippingService_ServiceDesc = grpc.ServiceDesc{ ServiceName: "hipstershop.ShippingService", HandlerType: (*ShippingServiceServer)(nil), Methods: []grpc.MethodDesc{ { MethodName: "GetQuote", Handler: _ShippingService_GetQuote_Handler, }, { MethodName: "ShipOrder", Handler: _ShippingService_ShipOrder_Handler, }, }, Streams: []grpc.StreamDesc{}, Metadata: "demo.proto", } const ( CurrencyService_GetSupportedCurrencies_FullMethodName = "/hipstershop.CurrencyService/GetSupportedCurrencies" CurrencyService_Convert_FullMethodName = "/hipstershop.CurrencyService/Convert" ) // CurrencyServiceClient is the client API for CurrencyService service. // // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. type CurrencyServiceClient interface { GetSupportedCurrencies(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*GetSupportedCurrenciesResponse, error) Convert(ctx context.Context, in *CurrencyConversionRequest, opts ...grpc.CallOption) (*Money, error) } type currencyServiceClient struct { cc grpc.ClientConnInterface } func NewCurrencyServiceClient(cc grpc.ClientConnInterface) CurrencyServiceClient { return ¤cyServiceClient{cc} } func (c *currencyServiceClient) GetSupportedCurrencies(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*GetSupportedCurrenciesResponse, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(GetSupportedCurrenciesResponse) err := c.cc.Invoke(ctx, CurrencyService_GetSupportedCurrencies_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } return out, nil } func (c *currencyServiceClient) Convert(ctx context.Context, in *CurrencyConversionRequest, opts ...grpc.CallOption) (*Money, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(Money) err := c.cc.Invoke(ctx, CurrencyService_Convert_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } return out, nil } // CurrencyServiceServer is the server API for CurrencyService service. // All implementations must embed UnimplementedCurrencyServiceServer // for forward compatibility. type CurrencyServiceServer interface { GetSupportedCurrencies(context.Context, *Empty) (*GetSupportedCurrenciesResponse, error) Convert(context.Context, *CurrencyConversionRequest) (*Money, error) mustEmbedUnimplementedCurrencyServiceServer() } // UnimplementedCurrencyServiceServer must be embedded to have // forward compatible implementations. // // NOTE: this should be embedded by value instead of pointer to avoid a nil // pointer dereference when methods are called. type UnimplementedCurrencyServiceServer struct{} func (UnimplementedCurrencyServiceServer) GetSupportedCurrencies(context.Context, *Empty) (*GetSupportedCurrenciesResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method GetSupportedCurrencies not implemented") } func (UnimplementedCurrencyServiceServer) Convert(context.Context, *CurrencyConversionRequest) (*Money, error) { return nil, status.Errorf(codes.Unimplemented, "method Convert not implemented") } func (UnimplementedCurrencyServiceServer) mustEmbedUnimplementedCurrencyServiceServer() {} func (UnimplementedCurrencyServiceServer) testEmbeddedByValue() {} // UnsafeCurrencyServiceServer may be embedded to opt out of forward compatibility for this service. // Use of this interface is not recommended, as added methods to CurrencyServiceServer will // result in compilation errors. type UnsafeCurrencyServiceServer interface { mustEmbedUnimplementedCurrencyServiceServer() } func RegisterCurrencyServiceServer(s grpc.ServiceRegistrar, srv CurrencyServiceServer) { // If the following call pancis, it indicates UnimplementedCurrencyServiceServer was // embedded by pointer and is nil. This will cause panics if an // unimplemented method is ever invoked, so we test this at initialization // time to prevent it from happening at runtime later due to I/O. if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { t.testEmbeddedByValue() } s.RegisterService(&CurrencyService_ServiceDesc, srv) } func _CurrencyService_GetSupportedCurrencies_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(Empty) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(CurrencyServiceServer).GetSupportedCurrencies(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: CurrencyService_GetSupportedCurrencies_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(CurrencyServiceServer).GetSupportedCurrencies(ctx, req.(*Empty)) } return interceptor(ctx, in, info, handler) } func _CurrencyService_Convert_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(CurrencyConversionRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(CurrencyServiceServer).Convert(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: CurrencyService_Convert_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(CurrencyServiceServer).Convert(ctx, req.(*CurrencyConversionRequest)) } return interceptor(ctx, in, info, handler) } // CurrencyService_ServiceDesc is the grpc.ServiceDesc for CurrencyService service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) var CurrencyService_ServiceDesc = grpc.ServiceDesc{ ServiceName: "hipstershop.CurrencyService", HandlerType: (*CurrencyServiceServer)(nil), Methods: []grpc.MethodDesc{ { MethodName: "GetSupportedCurrencies", Handler: _CurrencyService_GetSupportedCurrencies_Handler, }, { MethodName: "Convert", Handler: _CurrencyService_Convert_Handler, }, }, Streams: []grpc.StreamDesc{}, Metadata: "demo.proto", } const ( PaymentService_Charge_FullMethodName = "/hipstershop.PaymentService/Charge" ) // PaymentServiceClient is the client API for PaymentService service. // // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. type PaymentServiceClient interface { Charge(ctx context.Context, in *ChargeRequest, opts ...grpc.CallOption) (*ChargeResponse, error) } type paymentServiceClient struct { cc grpc.ClientConnInterface } func NewPaymentServiceClient(cc grpc.ClientConnInterface) PaymentServiceClient { return &paymentServiceClient{cc} } func (c *paymentServiceClient) Charge(ctx context.Context, in *ChargeRequest, opts ...grpc.CallOption) (*ChargeResponse, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(ChargeResponse) err := c.cc.Invoke(ctx, PaymentService_Charge_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } return out, nil } // PaymentServiceServer is the server API for PaymentService service. // All implementations must embed UnimplementedPaymentServiceServer // for forward compatibility. type PaymentServiceServer interface { Charge(context.Context, *ChargeRequest) (*ChargeResponse, error) mustEmbedUnimplementedPaymentServiceServer() } // UnimplementedPaymentServiceServer must be embedded to have // forward compatible implementations. // // NOTE: this should be embedded by value instead of pointer to avoid a nil // pointer dereference when methods are called. type UnimplementedPaymentServiceServer struct{} func (UnimplementedPaymentServiceServer) Charge(context.Context, *ChargeRequest) (*ChargeResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method Charge not implemented") } func (UnimplementedPaymentServiceServer) mustEmbedUnimplementedPaymentServiceServer() {} func (UnimplementedPaymentServiceServer) testEmbeddedByValue() {} // UnsafePaymentServiceServer may be embedded to opt out of forward compatibility for this service. // Use of this interface is not recommended, as added methods to PaymentServiceServer will // result in compilation errors. type UnsafePaymentServiceServer interface { mustEmbedUnimplementedPaymentServiceServer() } func RegisterPaymentServiceServer(s grpc.ServiceRegistrar, srv PaymentServiceServer) { // If the following call pancis, it indicates UnimplementedPaymentServiceServer was // embedded by pointer and is nil. This will cause panics if an // unimplemented method is ever invoked, so we test this at initialization // time to prevent it from happening at runtime later due to I/O. if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { t.testEmbeddedByValue() } s.RegisterService(&PaymentService_ServiceDesc, srv) } func _PaymentService_Charge_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(ChargeRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(PaymentServiceServer).Charge(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: PaymentService_Charge_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(PaymentServiceServer).Charge(ctx, req.(*ChargeRequest)) } return interceptor(ctx, in, info, handler) } // PaymentService_ServiceDesc is the grpc.ServiceDesc for PaymentService service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) var PaymentService_ServiceDesc = grpc.ServiceDesc{ ServiceName: "hipstershop.PaymentService", HandlerType: (*PaymentServiceServer)(nil), Methods: []grpc.MethodDesc{ { MethodName: "Charge", Handler: _PaymentService_Charge_Handler, }, }, Streams: []grpc.StreamDesc{}, Metadata: "demo.proto", } const ( EmailService_SendOrderConfirmation_FullMethodName = "/hipstershop.EmailService/SendOrderConfirmation" ) // EmailServiceClient is the client API for EmailService service. // // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. type EmailServiceClient interface { SendOrderConfirmation(ctx context.Context, in *SendOrderConfirmationRequest, opts ...grpc.CallOption) (*Empty, error) } type emailServiceClient struct { cc grpc.ClientConnInterface } func NewEmailServiceClient(cc grpc.ClientConnInterface) EmailServiceClient { return &emailServiceClient{cc} } func (c *emailServiceClient) SendOrderConfirmation(ctx context.Context, in *SendOrderConfirmationRequest, opts ...grpc.CallOption) (*Empty, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(Empty) err := c.cc.Invoke(ctx, EmailService_SendOrderConfirmation_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } return out, nil } // EmailServiceServer is the server API for EmailService service. // All implementations must embed UnimplementedEmailServiceServer // for forward compatibility. type EmailServiceServer interface { SendOrderConfirmation(context.Context, *SendOrderConfirmationRequest) (*Empty, error) mustEmbedUnimplementedEmailServiceServer() } // UnimplementedEmailServiceServer must be embedded to have // forward compatible implementations. // // NOTE: this should be embedded by value instead of pointer to avoid a nil // pointer dereference when methods are called. type UnimplementedEmailServiceServer struct{} func (UnimplementedEmailServiceServer) SendOrderConfirmation(context.Context, *SendOrderConfirmationRequest) (*Empty, error) { return nil, status.Errorf(codes.Unimplemented, "method SendOrderConfirmation not implemented") } func (UnimplementedEmailServiceServer) mustEmbedUnimplementedEmailServiceServer() {} func (UnimplementedEmailServiceServer) testEmbeddedByValue() {} // UnsafeEmailServiceServer may be embedded to opt out of forward compatibility for this service. // Use of this interface is not recommended, as added methods to EmailServiceServer will // result in compilation errors. type UnsafeEmailServiceServer interface { mustEmbedUnimplementedEmailServiceServer() } func RegisterEmailServiceServer(s grpc.ServiceRegistrar, srv EmailServiceServer) { // If the following call pancis, it indicates UnimplementedEmailServiceServer was // embedded by pointer and is nil. This will cause panics if an // unimplemented method is ever invoked, so we test this at initialization // time to prevent it from happening at runtime later due to I/O. if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { t.testEmbeddedByValue() } s.RegisterService(&EmailService_ServiceDesc, srv) } func _EmailService_SendOrderConfirmation_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(SendOrderConfirmationRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(EmailServiceServer).SendOrderConfirmation(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: EmailService_SendOrderConfirmation_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(EmailServiceServer).SendOrderConfirmation(ctx, req.(*SendOrderConfirmationRequest)) } return interceptor(ctx, in, info, handler) } // EmailService_ServiceDesc is the grpc.ServiceDesc for EmailService service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) var EmailService_ServiceDesc = grpc.ServiceDesc{ ServiceName: "hipstershop.EmailService", HandlerType: (*EmailServiceServer)(nil), Methods: []grpc.MethodDesc{ { MethodName: "SendOrderConfirmation", Handler: _EmailService_SendOrderConfirmation_Handler, }, }, Streams: []grpc.StreamDesc{}, Metadata: "demo.proto", } const ( CheckoutService_PlaceOrder_FullMethodName = "/hipstershop.CheckoutService/PlaceOrder" ) // CheckoutServiceClient is the client API for CheckoutService service. // // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. type CheckoutServiceClient interface { PlaceOrder(ctx context.Context, in *PlaceOrderRequest, opts ...grpc.CallOption) (*PlaceOrderResponse, error) } type checkoutServiceClient struct { cc grpc.ClientConnInterface } func NewCheckoutServiceClient(cc grpc.ClientConnInterface) CheckoutServiceClient { return &checkoutServiceClient{cc} } func (c *checkoutServiceClient) PlaceOrder(ctx context.Context, in *PlaceOrderRequest, opts ...grpc.CallOption) (*PlaceOrderResponse, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(PlaceOrderResponse) err := c.cc.Invoke(ctx, CheckoutService_PlaceOrder_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } return out, nil } // CheckoutServiceServer is the server API for CheckoutService service. // All implementations must embed UnimplementedCheckoutServiceServer // for forward compatibility. type CheckoutServiceServer interface { PlaceOrder(context.Context, *PlaceOrderRequest) (*PlaceOrderResponse, error) mustEmbedUnimplementedCheckoutServiceServer() } // UnimplementedCheckoutServiceServer must be embedded to have // forward compatible implementations. // // NOTE: this should be embedded by value instead of pointer to avoid a nil // pointer dereference when methods are called. type UnimplementedCheckoutServiceServer struct{} func (UnimplementedCheckoutServiceServer) PlaceOrder(context.Context, *PlaceOrderRequest) (*PlaceOrderResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method PlaceOrder not implemented") } func (UnimplementedCheckoutServiceServer) mustEmbedUnimplementedCheckoutServiceServer() {} func (UnimplementedCheckoutServiceServer) testEmbeddedByValue() {} // UnsafeCheckoutServiceServer may be embedded to opt out of forward compatibility for this service. // Use of this interface is not recommended, as added methods to CheckoutServiceServer will // result in compilation errors. type UnsafeCheckoutServiceServer interface { mustEmbedUnimplementedCheckoutServiceServer() } func RegisterCheckoutServiceServer(s grpc.ServiceRegistrar, srv CheckoutServiceServer) { // If the following call pancis, it indicates UnimplementedCheckoutServiceServer was // embedded by pointer and is nil. This will cause panics if an // unimplemented method is ever invoked, so we test this at initialization // time to prevent it from happening at runtime later due to I/O. if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { t.testEmbeddedByValue() } s.RegisterService(&CheckoutService_ServiceDesc, srv) } func _CheckoutService_PlaceOrder_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(PlaceOrderRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(CheckoutServiceServer).PlaceOrder(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: CheckoutService_PlaceOrder_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(CheckoutServiceServer).PlaceOrder(ctx, req.(*PlaceOrderRequest)) } return interceptor(ctx, in, info, handler) } // CheckoutService_ServiceDesc is the grpc.ServiceDesc for CheckoutService service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) var CheckoutService_ServiceDesc = grpc.ServiceDesc{ ServiceName: "hipstershop.CheckoutService", HandlerType: (*CheckoutServiceServer)(nil), Methods: []grpc.MethodDesc{ { MethodName: "PlaceOrder", Handler: _CheckoutService_PlaceOrder_Handler, }, }, Streams: []grpc.StreamDesc{}, Metadata: "demo.proto", } const ( AdService_GetAds_FullMethodName = "/hipstershop.AdService/GetAds" ) // AdServiceClient is the client API for AdService service. // // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. type AdServiceClient interface { GetAds(ctx context.Context, in *AdRequest, opts ...grpc.CallOption) (*AdResponse, error) } type adServiceClient struct { cc grpc.ClientConnInterface } func NewAdServiceClient(cc grpc.ClientConnInterface) AdServiceClient { return &adServiceClient{cc} } func (c *adServiceClient) GetAds(ctx context.Context, in *AdRequest, opts ...grpc.CallOption) (*AdResponse, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(AdResponse) err := c.cc.Invoke(ctx, AdService_GetAds_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } return out, nil } // AdServiceServer is the server API for AdService service. // All implementations must embed UnimplementedAdServiceServer // for forward compatibility. type AdServiceServer interface { GetAds(context.Context, *AdRequest) (*AdResponse, error) mustEmbedUnimplementedAdServiceServer() } // UnimplementedAdServiceServer must be embedded to have // forward compatible implementations. // // NOTE: this should be embedded by value instead of pointer to avoid a nil // pointer dereference when methods are called. type UnimplementedAdServiceServer struct{} func (UnimplementedAdServiceServer) GetAds(context.Context, *AdRequest) (*AdResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method GetAds not implemented") } func (UnimplementedAdServiceServer) mustEmbedUnimplementedAdServiceServer() {} func (UnimplementedAdServiceServer) testEmbeddedByValue() {} // UnsafeAdServiceServer may be embedded to opt out of forward compatibility for this service. // Use of this interface is not recommended, as added methods to AdServiceServer will // result in compilation errors. type UnsafeAdServiceServer interface { mustEmbedUnimplementedAdServiceServer() } func RegisterAdServiceServer(s grpc.ServiceRegistrar, srv AdServiceServer) { // If the following call pancis, it indicates UnimplementedAdServiceServer was // embedded by pointer and is nil. This will cause panics if an // unimplemented method is ever invoked, so we test this at initialization // time to prevent it from happening at runtime later due to I/O. if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { t.testEmbeddedByValue() } s.RegisterService(&AdService_ServiceDesc, srv) } func _AdService_GetAds_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(AdRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(AdServiceServer).GetAds(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: AdService_GetAds_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(AdServiceServer).GetAds(ctx, req.(*AdRequest)) } return interceptor(ctx, in, info, handler) } // AdService_ServiceDesc is the grpc.ServiceDesc for AdService service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) var AdService_ServiceDesc = grpc.ServiceDesc{ ServiceName: "hipstershop.AdService", HandlerType: (*AdServiceServer)(nil), Methods: []grpc.MethodDesc{ { MethodName: "GetAds", Handler: _AdService_GetAds_Handler, }, }, Streams: []grpc.StreamDesc{}, Metadata: "demo.proto", } ================================================ FILE: src/productcatalogservice/genproto.sh ================================================ #!/bin/bash -eu # # Copyright 2018 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 gke_productcatalogservice_genproto] PATH=$PATH:$(go env GOPATH)/bin protodir=../../protos outdir=./genproto protoc --proto_path=$protodir --go_out=./$outdir --go_opt=paths=source_relative --go-grpc_out=./$outdir --go-grpc_opt=paths=source_relative $protodir/demo.proto # [END gke_productcatalogservice_genproto] ================================================ FILE: src/productcatalogservice/go.mod ================================================ module github.com/GoogleCloudPlatform/microservices-demo/src/productcatalogservice go 1.25.0 toolchain go1.26.1 require ( cloud.google.com/go/alloydbconn v1.17.3 cloud.google.com/go/profiler v0.4.3 cloud.google.com/go/secretmanager v1.16.0 github.com/golang/protobuf v1.5.4 github.com/jackc/pgx/v5 v5.8.0 github.com/pkg/errors v0.9.1 github.com/sirupsen/logrus v1.9.4 go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0 go.opentelemetry.io/otel v1.42.0 go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.42.0 go.opentelemetry.io/otel/sdk v1.42.0 google.golang.org/grpc v1.79.2 google.golang.org/protobuf v1.36.11 ) require ( cloud.google.com/go v0.123.0 // indirect cloud.google.com/go/alloydb v1.20.0 // indirect cloud.google.com/go/auth v0.18.1 // indirect cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect cloud.google.com/go/compute/metadata v0.9.0 // indirect cloud.google.com/go/iam v1.5.3 // indirect cloud.google.com/go/longrunning v0.7.0 // indirect cloud.google.com/go/monitoring v1.24.3 // indirect github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.55.0 // indirect github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.55.0 // indirect github.com/cenkalti/backoff/v5 v5.0.3 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect github.com/google/pprof v0.0.0-20251114195745-4902fdda35c8 // indirect github.com/google/s2a-go v0.1.9 // indirect github.com/google/uuid v1.6.0 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.11 // indirect github.com/googleapis/gax-go/v2 v2.16.0 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect github.com/jackc/puddle/v2 v2.2.2 // indirect go.opencensus.io v0.24.0 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.42.0 // indirect go.opentelemetry.io/otel/metric v1.42.0 // indirect go.opentelemetry.io/otel/sdk/metric v1.42.0 // indirect go.opentelemetry.io/otel/trace v1.42.0 // indirect go.opentelemetry.io/proto/otlp v1.9.0 // indirect golang.org/x/crypto v0.48.0 // indirect golang.org/x/net v0.51.0 // indirect golang.org/x/oauth2 v0.35.0 // indirect golang.org/x/sync v0.19.0 // indirect golang.org/x/sys v0.41.0 // indirect golang.org/x/text v0.34.0 // indirect golang.org/x/time v0.14.0 // indirect google.golang.org/api v0.265.0 // indirect google.golang.org/genproto v0.0.0-20251202230838-ff82c1b0f217 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 // indirect ) ================================================ FILE: src/productcatalogservice/go.sum ================================================ cel.dev/expr v0.24.0 h1:56OvJKSH3hDGL0ml5uSxZmz3/3Pq4tJ+fb1unVLAFcY= cel.dev/expr v0.24.0/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.123.0 h1:2NAUJwPR47q+E35uaJeYoNhuNEM9kM8SjgRgdeOJUSE= cloud.google.com/go v0.123.0/go.mod h1:xBoMV08QcqUGuPW65Qfm1o9Y4zKZBpGS+7bImXLTAZU= cloud.google.com/go/alloydb v1.19.0 h1:JVcIM43+ndcjdXXvOFWkLKjhF6kREQKAEtRxpsaKIQo= cloud.google.com/go/alloydb v1.19.0/go.mod h1:WQYERzf1+LwX5zCbmeyza4DNalaBiCN8P04POkZ/vds= cloud.google.com/go/alloydb v1.20.0 h1:p9SbcJhdi6s39SAIpz4lpJJTkfboSQUCwDd7go0bJ6o= cloud.google.com/go/alloydb v1.20.0/go.mod h1:ZwDNJOn6Z2uzzwDIlS6ctpM/EgMD1LqPzI+/ynJNaIU= cloud.google.com/go/alloydbconn v1.17.0 h1:3tmudS+MI4g2jkdVyJ6EeaGHEHvBtJ+iHMsu3LJX91s= cloud.google.com/go/alloydbconn v1.17.0/go.mod h1:XUHWgUfH5fhzZ50vIK31KBOecerzpH6NBAwEoG7fic4= cloud.google.com/go/alloydbconn v1.17.1 h1:S1wj+0KN6x89+VnRLMZjvCiVNBH6WSj5dP/7PGZ/dzE= cloud.google.com/go/alloydbconn v1.17.1/go.mod h1:vzlHhKPy1epUXIJSPPOq94NK1yFSXZ0t1uQlv8mC/ZA= cloud.google.com/go/alloydbconn v1.17.2 h1:VvJ+hxcs0EHI9VnQh65D8VwpjV3hFS6GT6YMo/5oC9s= cloud.google.com/go/alloydbconn v1.17.2/go.mod h1:VpdGJOWemdCfuNgQM7DKCAcr2H0sl+eU6V/IuEcjeuU= cloud.google.com/go/alloydbconn v1.17.3 h1:fz4JB72A4Q0fVfv52bDppRJtwiyoLp85xvVGibk/zB4= cloud.google.com/go/alloydbconn v1.17.3/go.mod h1:YOq1U+7SeiXaQUKTlZi2QR6XadKis9x6yDRiEQIrbeo= cloud.google.com/go/auth v0.17.0 h1:74yCm7hCj2rUyyAocqnFzsAYXgJhrG26XCFimrc/Kz4= cloud.google.com/go/auth v0.17.0/go.mod h1:6wv/t5/6rOPAX4fJiRjKkJCvswLwdet7G8+UGXt7nCQ= cloud.google.com/go/auth v0.18.0 h1:wnqy5hrv7p3k7cShwAU/Br3nzod7fxoqG+k0VZ+/Pk0= cloud.google.com/go/auth v0.18.0/go.mod h1:wwkPM1AgE1f2u6dG443MiWoD8C3BtOywNsUMcUTVDRo= cloud.google.com/go/auth v0.18.1 h1:IwTEx92GFUo2pJ6Qea0EU3zYvKnTAeRCODxfA/G5UWs= cloud.google.com/go/auth v0.18.1/go.mod h1:GfTYoS9G3CWpRA3Va9doKN9mjPGRS+v41jmZAhBzbrA= cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc= cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c= cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs= cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10= cloud.google.com/go/iam v1.5.3 h1:+vMINPiDF2ognBJ97ABAYYwRgsaqxPbQDlMnbHMjolc= cloud.google.com/go/iam v1.5.3/go.mod h1:MR3v9oLkZCTlaqljW6Eb2d3HGDGK5/bDv93jhfISFvU= cloud.google.com/go/logging v1.13.1 h1:O7LvmO0kGLaHY/gq8cV7T0dyp6zJhYAOtZPX4TF3QtY= cloud.google.com/go/logging v1.13.1/go.mod h1:XAQkfkMBxQRjQek96WLPNze7vsOmay9H5PqfsNYDqvw= cloud.google.com/go/longrunning v0.7.0 h1:FV0+SYF1RIj59gyoWDRi45GiYUMM3K1qO51qoboQT1E= cloud.google.com/go/longrunning v0.7.0/go.mod h1:ySn2yXmjbK9Ba0zsQqunhDkYi0+9rlXIwnoAf+h+TPY= cloud.google.com/go/monitoring v1.24.3 h1:dde+gMNc0UhPZD1Azu6at2e79bfdztVDS5lvhOdsgaE= cloud.google.com/go/monitoring v1.24.3/go.mod h1:nYP6W0tm3N9H/bOw8am7t62YTzZY+zUeQ+Bi6+2eonI= cloud.google.com/go/profiler v0.4.3 h1:IY3QNKlr8VbXwGWHcZbJQsMA/83ZTH6uAHf8jYyj7OI= cloud.google.com/go/profiler v0.4.3/go.mod h1:3xFodugWfPIQZWFcXdUmfa+yTiiyQ8fWrdT+d2Sg4J0= cloud.google.com/go/secretmanager v1.16.0 h1:19QT7ZsLJ8FSP1k+4esQvuCD7npMJml6hYzilxVyT+k= cloud.google.com/go/secretmanager v1.16.0/go.mod h1://C/e4I8D26SDTz1f3TQcddhcmiC3rMEl0S1Cakvs3Q= cloud.google.com/go/storage v1.56.0 h1:iixmq2Fse2tqxMbWhLWC9HfBj1qdxqAmiK8/eqtsLxI= cloud.google.com/go/storage v1.56.0/go.mod h1:Tpuj6t4NweCLzlNbw9Z9iwxEkrSem20AetIeH/shgVU= cloud.google.com/go/trace v1.11.7 h1:kDNDX8JkaAG3R2nq1lIdkb7FCSi1rCmsEtKVsty7p+U= cloud.google.com/go/trace v1.11.7/go.mod h1:TNn9d5V3fQVf6s4SCveVMIBS2LJUqo73GACmq/Tky0s= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0 h1:sBEjpZlNHzK1voKq9695PJSX2o5NEXl7/OL3coiIY0c= github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0/go.mod h1:P4WPRUkOhJC13W//jWpyfJNDAIpvRbAUIYLX/4jtlE0= github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.54.0 h1:lhhYARPUu3LmHysQ/igznQphfzynnqI3D75oUyw1HXk= github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.54.0/go.mod h1:l9rva3ApbBpEJxSNYnwT9N4CDLrWgtq3u8736C5hyJw= github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.55.0 h1:UnDZ/zFfG1JhH/DqxIZYU/1CUAlTUScoXD/LcM2Ykk8= github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.55.0/go.mod h1:IA1C1U7jO/ENqm/vhi7V9YYpBsp+IMyqNrEN94N7tVc= github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.54.0 h1:xfK3bbi6F2RDtaZFtUdKO3osOBIhNb+xTs8lFW6yx9o= github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.54.0/go.mod h1:vB2GH9GAYYJTO3mEn8oYwzEdhlayZIdQz6zdzgUIRvA= github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.54.0 h1:s0WlVbf9qpvkh1c/uDAPElam0WrL7fHRIidgZJ7UqZI= github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.54.0/go.mod h1:Mf6O40IAyB9zR/1J8nGDDPirZQQPbYJni8Yisy7NTMc= github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.55.0 h1:0s6TxfCu2KHkkZPnBfsQ2y5qia0jl3MMrmBhu3nCOYk= github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.55.0/go.mod h1:Mf6O40IAyB9zR/1J8nGDDPirZQQPbYJni8Yisy7NTMc= github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/xds/go v0.0.0-20251022180443-0feb69152e9f h1:Y8xYupdHxryycyPlc9Y+bSQAYZnetRJ70VMVKm5CKI0= github.com/cncf/xds/go v0.0.0-20251022180443-0feb69152e9f/go.mod h1:HlzOvOjVBOfTGSRXRyY0OiCS/3J1akRGQQpRO/7zyF4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/go-control-plane v0.13.5-0.20251024222203-75eaa193e329 h1:K+fnvUM0VZ7ZFJf0n4L/BRlnsb9pL/GuDG6FqaH+PwM= github.com/envoyproxy/go-control-plane/envoy v1.35.0 h1:ixjkELDE+ru6idPxcHLj8LBVc2bFP7iBytj353BoHUo= github.com/envoyproxy/go-control-plane/envoy v1.35.0/go.mod h1:09qwbGVuSWWAyN5t/b3iyVfz5+z8QWGrzkoqm/8SbEs= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/envoyproxy/protoc-gen-validate v1.2.1 h1:DEo3O99U8j4hBFwbJfrz9VtgcDfUKS7KJ7spH3d86P8= github.com/envoyproxy/protoc-gen-validate v1.2.1/go.mod h1:d/C80l/jxXLdfEIhX1W2TmLfsJ31lvEjwamM4DxlWXU= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/go-jose/go-jose/v4 v4.1.3 h1:CVLmWDhDVRa6Mi/IgCgaopNosCaHz7zrMeF9MlZRkrs= github.com/go-jose/go-jose/v4 v4.1.3/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ= github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.7.0-rc.1 h1:YojYx61/OLFsiv6Rw1Z96LpldJIy31o+UHmwAUMJ6/U= github.com/golang/mock v1.7.0-rc.1/go.mod h1:s42URUywIqd+OcERslBJvOjepvNymP31m3q8d/GkuRs= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/pprof v0.0.0-20251114195745-4902fdda35c8 h1:3DsUAV+VNEQa2CUVLxCY3f87278uWfIDhJnbdvDjvmE= github.com/google/pprof v0.0.0-20251114195745-4902fdda35c8/go.mod h1:I6V7YzU0XDpsHqbsyrghnFZLO1gwK6NPTNvmetQIk9U= github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0= github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/enterprise-certificate-proxy v0.3.7 h1:zrn2Ee/nWmHulBx5sAVrGgAa0f2/R35S4DJwfFaUPFQ= github.com/googleapis/enterprise-certificate-proxy v0.3.7/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA= github.com/googleapis/enterprise-certificate-proxy v0.3.11 h1:vAe81Msw+8tKUxi2Dqh/NZMz7475yUvmRIkXr4oN2ao= github.com/googleapis/enterprise-certificate-proxy v0.3.11/go.mod h1:RFV7MUdlb7AgEq2v7FmMCfeSMCllAzWxFgRdusoGks8= github.com/googleapis/gax-go/v2 v2.15.0 h1:SyjDc1mGgZU5LncH8gimWo9lW1DtIfPibOG81vgd/bo= github.com/googleapis/gax-go/v2 v2.15.0/go.mod h1:zVVkkxAQHa1RQpg9z2AUCMnKhi0Qld9rcmyfL1OZhoc= github.com/googleapis/gax-go/v2 v2.16.0 h1:iHbQmKLLZrexmb0OSsNGTeSTS0HO4YvFOG8g5E4Zd0Y= github.com/googleapis/gax-go/v2 v2.16.0/go.mod h1:o1vfQjjNZn4+dPnRdl/4ZD7S9414Y4xA+a/6Icj6l14= github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 h1:NmZ1PKzSTQbuGHw9DGPFomqkkLWMC+vZCkfs+FHv1Vg= github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3/go.mod h1:zQrxl1YP88HQlA6i9c63DSVPFklWpGX4OWAc9bFuaH4= github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 h1:HWRh5R2+9EifMyIHV7ZV+MIZqgz+PMpZ14Jynv3O2Zs= github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0/go.mod h1:JfhWUomR1baixubs02l85lZYYOm7LV6om4ceouMv45c= github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8= github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= github.com/jackc/pgconn v1.14.3 h1:bVoTr12EGANZz66nZPkMInAV/KHD2TxH9npjXXgiB3w= github.com/jackc/pgconn v1.14.3/go.mod h1:RZbme4uasqzybK2RK5c65VsHxoyaml09lx3tXOcO/VM= github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE= github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgproto3/v2 v2.3.3 h1:1HLSx5H+tXR9pW3in3zaztoEwQYRC9SQaYUHjTSUOag= github.com/jackc/pgproto3/v2 v2.3.3/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= github.com/jackc/pgtype v1.14.0 h1:y+xUdabmyMkJLyApYuPj38mW+aAIqCe5uuBB51rH3Vw= github.com/jackc/pgtype v1.14.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4= github.com/jackc/pgx/v4 v4.18.3 h1:dE2/TrEsGX3RBprb3qryqSV9Y60iZN1C6i8IrmW9/BA= github.com/jackc/pgx/v4 v4.18.3/go.mod h1:Ey4Oru5tH5sB6tV7hDmfWFahwF15Eb7DNXlRKx2CkVw= github.com/jackc/pgx/v5 v5.7.6 h1:rWQc5FwZSPX58r1OQmkuaNicxdmExaEz5A2DO2hUuTk= github.com/jackc/pgx/v5 v5.7.6/go.mod h1:aruU7o91Tc2q2cFp5h4uP3f6ztExVpyVv88Xl/8Vl8M= github.com/jackc/pgx/v5 v5.8.0 h1:TYPDoleBBme0xGSAX3/+NujXXtpZn9HBONkQC7IEZSo= github.com/jackc/pgx/v5 v5.8.0/go.mod h1:QVeDInX2m9VyzvNeiCJVjCkNFqzsNb43204HshNSZKw= github.com/jackc/puddle v1.3.0 h1:eHK/5clGOatcjX3oWGBO/MpxpbHzSwud5EWTSCI+MX0= github.com/jackc/puddle v1.3.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo= github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w= github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g= github.com/spiffe/go-spiffe/v2 v2.6.0 h1:l+DolpxNWYgruGQVV0xsfeya3CsC7m8iBzDnMpsbLuo= github.com/spiffe/go-spiffe/v2 v2.6.0/go.mod h1:gm2SeUoMZEtpnzPNs2Csc0D/gX33k1xIx7lEzqblHEs= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/contrib/detectors/gcp v1.38.0 h1:ZoYbqX7OaA/TAikspPl3ozPI6iY6LiIY9I8cUfm+pJs= go.opentelemetry.io/contrib/detectors/gcp v1.38.0/go.mod h1:SU+iU7nu5ud4oCb3LQOhIZ3nRLj6FNVrKgtflbaf2ts= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0 h1:YH4g8lQroajqUwWbq/tr2QX1JFmEXaDLgG+ew9bLMWo= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0/go.mod h1:fvPi2qXDqFs8M4B4fmJhE92TyQs9Ydjlg3RvfUp+NbQ= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.64.0 h1:RN3ifU8y4prNWeEnQp2kRRHz8UwonAEYZl8tUzHEXAk= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.64.0/go.mod h1:habDz3tEWiFANTo6oUE99EmaFUrCNYAAg3wiVmusm70= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0 h1:yI1/OhfEPy7J9eoa6Sj051C7n5dvpj0QX8g4sRchg04= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0/go.mod h1:NoUCKYWK+3ecatC4HjkRktREheMeEtrXoQxrqYFeHSc= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 h1:RbKq8BG0FI8OiXhBfcRtqqHcZcka+gU3cskNuf05R18= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0/go.mod h1:h06DGIukJOevXaj/xrNjhi/2098RZzcLTbc0jDAUbsg= go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= go.opentelemetry.io/otel v1.42.0 h1:lSQGzTgVR3+sgJDAU/7/ZMjN9Z+vUip7leaqBKy4sho= go.opentelemetry.io/otel v1.42.0/go.mod h1:lJNsdRMxCUIWuMlVJWzecSMuNjE7dOYyWlqOXWkdqCc= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 h1:GqRJVj7UmLjCVyVJ3ZFLdPRmhDUp2zFmQe3RHIOsw24= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0/go.mod h1:ri3aaHSmCTVYu2AWv44YMauwAQc0aqI9gHKIcSbI1pU= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0 h1:f0cb2XPmrqn4XMy9PNliTgRKJgS5WcL/u0/WRYGz4t0= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0/go.mod h1:vnakAaFckOMiMtOIhFI2MNH4FYrZzXCYxmb1LlhoGz8= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.42.0 h1:THuZiwpQZuHPul65w4WcwEnkX2QIuMT+UFoOrygtoJw= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.42.0/go.mod h1:J2pvYM5NGHofZ2/Ru6zw/TNWnEQp5crgyDeSrYpXkAw= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 h1:lwI4Dc5leUqENgGuQImwLo4WnuXFPetmPpkLi2IrX54= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0/go.mod h1:Kz/oCE7z5wuyhPxsXDuaPteSWqjSBD5YaSdbxZYGbGk= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0 h1:in9O8ESIOlwJAEGTkkf34DesGRAc/Pn8qJ7k3r/42LM= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0/go.mod h1:Rp0EXBm5tfnv0WL+ARyO/PHBEaEAT8UUHQ6AGJcSq6c= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.42.0 h1:zWWrB1U6nqhS/k6zYB74CjRpuiitRtLLi68VcgmOEto= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.42.0/go.mod h1:2qXPNBX1OVRC0IwOnfo1ljoid+RD0QK3443EaqVlsOU= go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= go.opentelemetry.io/otel/metric v1.42.0 h1:2jXG+3oZLNXEPfNmnpxKDeZsFI5o4J+nz6xUlaFdF/4= go.opentelemetry.io/otel/metric v1.42.0/go.mod h1:RlUN/7vTU7Ao/diDkEpQpnz3/92J9ko05BIwxYa2SSI= go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E= go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg= go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= go.opentelemetry.io/otel/sdk v1.42.0 h1:LyC8+jqk6UJwdrI/8VydAq/hvkFKNHZVIWuslJXYsDo= go.opentelemetry.io/otel/sdk v1.42.0/go.mod h1:rGHCAxd9DAph0joO4W6OPwxjNTYWghRWmkHuGbayMts= go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM= go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= go.opentelemetry.io/otel/sdk/metric v1.42.0 h1:D/1QR46Clz6ajyZ3G8SgNlTJKBdGp84q9RKCAZ3YGuA= go.opentelemetry.io/otel/sdk/metric v1.42.0/go.mod h1:Ua6AAlDKdZ7tdvaQKfSmnFTdHx37+J4ba8MwVCYM5hc= go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= go.opentelemetry.io/otel/trace v1.42.0 h1:OUCgIPt+mzOnaUTpOQcBiM/PLQ/Op7oq6g4LenLmOYY= go.opentelemetry.io/otel/trace v1.42.0/go.mod h1:f3K9S+IFqnumBkKhRJMeaZeNk9epyhnCmQh/EysQCdc= go.opentelemetry.io/proto/otlp v1.9.0 h1:l706jCMITVouPOqEnii2fIAuO3IVGBRPV5ICjceRb/A= go.opentelemetry.io/proto/otlp v1.9.0/go.mod h1:xE+Cx5E/eEHw+ISFkwPLwCZefwVjY+pqKg1qcK03+/4= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q= golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4= golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8= golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A= golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts= golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o= golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8= golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo= golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.33.0 h1:4Q+qn+E5z8gPRJfmRy7C2gGG3T4jIprK6aSYgTXGRpo= golang.org/x/oauth2 v0.33.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw= golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/oauth2 v0.35.0 h1:Mv2mzuHuZuY2+bkyWXIHMfhNdJAdwW3FuWeCPYN5GVQ= golang.org/x/oauth2 v0.35.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k= golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk= golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA= golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= google.golang.org/api v0.256.0 h1:u6Khm8+F9sxbCTYNoBHg6/Hwv0N/i+V94MvkOSor6oI= google.golang.org/api v0.256.0/go.mod h1:KIgPhksXADEKJlnEoRa9qAII4rXcy40vfI8HRqcU964= google.golang.org/api v0.257.0 h1:8Y0lzvHlZps53PEaw+G29SsQIkuKrumGWs9puiexNAA= google.golang.org/api v0.257.0/go.mod h1:4eJrr+vbVaZSqs7vovFd1Jb/A6ml6iw2e6FBYf3GAO4= google.golang.org/api v0.259.0 h1:90TaGVIxScrh1Vn/XI2426kRpBqHwWIzVBzJsVZ5XrQ= google.golang.org/api v0.259.0/go.mod h1:LC2ISWGWbRoyQVpxGntWwLWN/vLNxxKBK9KuJRI8Te4= google.golang.org/api v0.265.0 h1:FZvfUdI8nfmuNrE34aOWFPmLC+qRBEiNm3JdivTvAAU= google.golang.org/api v0.265.0/go.mod h1:uAvfEl3SLUj/7n6k+lJutcswVojHPp2Sp08jWCu8hLY= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20251124214823-79d6a2a48846 h1:dDbsTLIK7EzwUq36kCSAsk0slouq/S0tWHeeGi97cD8= google.golang.org/genproto v0.0.0-20251124214823-79d6a2a48846/go.mod h1:PP0g88Dz3C7hRAfbQCQggeWAXjuqGsNPLE4s7jh0RGU= google.golang.org/genproto v0.0.0-20251202230838-ff82c1b0f217 h1:GvESR9BIyHUahIb0NcTum6itIWtdoglGX+rnGxm2934= google.golang.org/genproto v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:yJ2HH4EHEDTd3JiLmhds6NkJ17ITVYOdV3m3VKOnws0= google.golang.org/genproto/googleapis/api v0.0.0-20251124214823-79d6a2a48846 h1:ZdyUkS9po3H7G0tuh955QVyyotWvOD4W0aEapeGeUYk= google.golang.org/genproto/googleapis/api v0.0.0-20251124214823-79d6a2a48846/go.mod h1:Fk4kyraUvqD7i5H6S43sj2W98fbZa75lpZz/eUyhfO0= google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 h1:fCvbg86sFXwdrl5LgVcTEvNC+2txB5mgROGmRL5mrls= google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:+rXWjjaukWZun3mLfjmVnQi18E1AsFbDN9QdJ5YXLto= google.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57 h1:JLQynH/LBHfCTSbDWl+py8C+Rg/k1OVH3xfcaiANuF0= google.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57/go.mod h1:kSJwQxqmFXeo79zOmbrALdflXQeAYcUbgS7PbpMknCY= google.golang.org/genproto/googleapis/rpc v0.0.0-20251124214823-79d6a2a48846 h1:Wgl1rcDNThT+Zn47YyCXOXyX/COgMTIdhJ717F0l4xk= google.golang.org/genproto/googleapis/rpc v0.0.0-20251124214823-79d6a2a48846/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 h1:gRkg/vSppuSQoDjxyiGfN4Upv/h/DQmIR10ZU8dh4Ww= google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= google.golang.org/genproto/googleapis/rpc v0.0.0-20260112192933-99fd39fd28a9 h1:IY6/YYRrFUk0JPp0xOVctvFIVuRnjccihY5kxf5g0TE= google.golang.org/genproto/googleapis/rpc v0.0.0-20260112192933-99fd39fd28a9/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 h1:ggcbiqK8WWh6l1dnltU4BgWGIGo+EVYxCaAPih/zQXQ= google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM= google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig= google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc= google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U= google.golang.org/grpc v1.79.2 h1:fRMD94s2tITpyJGtBBn7MkMseNpOZU8ZxgC3MMBaXRU= google.golang.org/grpc v1.79.2/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= ================================================ FILE: src/productcatalogservice/product_catalog.go ================================================ // 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 // // 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. package main import ( "context" "strings" "time" pb "github.com/GoogleCloudPlatform/microservices-demo/src/productcatalogservice/genproto" "google.golang.org/grpc/codes" healthpb "google.golang.org/grpc/health/grpc_health_v1" "google.golang.org/grpc/status" ) type productCatalog struct { pb.UnimplementedProductCatalogServiceServer catalog pb.ListProductsResponse } func (p *productCatalog) Check(ctx context.Context, req *healthpb.HealthCheckRequest) (*healthpb.HealthCheckResponse, error) { return &healthpb.HealthCheckResponse{Status: healthpb.HealthCheckResponse_SERVING}, nil } func (p *productCatalog) Watch(req *healthpb.HealthCheckRequest, ws healthpb.Health_WatchServer) error { return status.Errorf(codes.Unimplemented, "health check via Watch not implemented") } func (p *productCatalog) ListProducts(context.Context, *pb.Empty) (*pb.ListProductsResponse, error) { time.Sleep(extraLatency) return &pb.ListProductsResponse{Products: p.parseCatalog()}, nil } func (p *productCatalog) GetProduct(ctx context.Context, req *pb.GetProductRequest) (*pb.Product, error) { time.Sleep(extraLatency) var found *pb.Product for i := 0; i < len(p.parseCatalog()); i++ { if req.Id == p.parseCatalog()[i].Id { found = p.parseCatalog()[i] } } if found == nil { return nil, status.Errorf(codes.NotFound, "no product with ID %s", req.Id) } return found, nil } func (p *productCatalog) SearchProducts(ctx context.Context, req *pb.SearchProductsRequest) (*pb.SearchProductsResponse, error) { time.Sleep(extraLatency) var ps []*pb.Product for _, product := range p.parseCatalog() { if strings.Contains(strings.ToLower(product.Name), strings.ToLower(req.Query)) || strings.Contains(strings.ToLower(product.Description), strings.ToLower(req.Query)) { ps = append(ps, product) } } return &pb.SearchProductsResponse{Results: ps}, nil } func (p *productCatalog) parseCatalog() []*pb.Product { if reloadCatalog || len(p.catalog.Products) == 0 { err := loadCatalog(&p.catalog) if err != nil { return []*pb.Product{} } } return p.catalog.Products } ================================================ FILE: src/productcatalogservice/product_catalog_test.go ================================================ // 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 // // 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. package main import ( "context" "os" "testing" pb "github.com/GoogleCloudPlatform/microservices-demo/src/productcatalogservice/genproto" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" ) var ( mockProductCatalog *productCatalog ) func TestMain(m *testing.M) { mockProductCatalog = &productCatalog{ catalog: pb.ListProductsResponse{ Products: []*pb.Product{}, }, } mockProductCatalog.catalog.Products = append(mockProductCatalog.catalog.Products, &pb.Product{ Id: "abc001", Name: "Product Alpha One", }) mockProductCatalog.catalog.Products = append(mockProductCatalog.catalog.Products, &pb.Product{ Id: "abc002", Name: "Product Delta", }) mockProductCatalog.catalog.Products = append(mockProductCatalog.catalog.Products, &pb.Product{ Id: "abc003", Name: "Product Alpha Two", }) mockProductCatalog.catalog.Products = append(mockProductCatalog.catalog.Products, &pb.Product{ Id: "abc004", Name: "Product Gamma", }) os.Exit(m.Run()) } func TestGetProductExists(t *testing.T) { product, err := mockProductCatalog.GetProduct(context.Background(), &pb.GetProductRequest{Id: "abc003"}, ) if err != nil { t.Fatal(err) } if got, want := product.Name, "Product Alpha Two"; got != want { t.Errorf("got %s, want %s", got, want) } } func TestGetProductNotFound(t *testing.T) { _, err := mockProductCatalog.GetProduct(context.Background(), &pb.GetProductRequest{Id: "abc005"}, ) if got, want := status.Code(err), codes.NotFound; got != want { t.Errorf("got %s, want %s", got, want) } } func TestListProducts(t *testing.T) { products, err := mockProductCatalog.ListProducts(context.Background(), &pb.Empty{}, ) if err != nil { t.Fatal(err) } if got, want := len(products.Products), 4; got != want { t.Errorf("got %d, want %d", got, want) } } func TestSearchProducts(t *testing.T) { products, err := mockProductCatalog.SearchProducts(context.Background(), &pb.SearchProductsRequest{Query: "alpha"}, ) if err != nil { t.Fatal(err) } if got, want := len(products.Results), 2; got != want { t.Errorf("got %d, want %d", got, want) } } ================================================ FILE: src/productcatalogservice/products.json ================================================ { "products": [ { "id": "OLJCESPC7Z", "name": "Sunglasses", "description": "Add a modern touch to your outfits with these sleek aviator sunglasses.", "picture": "/static/img/products/sunglasses.jpg", "priceUsd": { "currencyCode": "USD", "units": 19, "nanos": 990000000 }, "categories": ["accessories"] }, { "id": "66VCHSJNUP", "name": "Tank Top", "description": "Perfectly cropped cotton tank, with a scooped neckline.", "picture": "/static/img/products/tank-top.jpg", "priceUsd": { "currencyCode": "USD", "units": 18, "nanos": 990000000 }, "categories": ["clothing", "tops"] }, { "id": "1YMWWN1N4O", "name": "Watch", "description": "This gold-tone stainless steel watch will work with most of your outfits.", "picture": "/static/img/products/watch.jpg", "priceUsd": { "currencyCode": "USD", "units": 109, "nanos": 990000000 }, "categories": ["accessories"] }, { "id": "L9ECAV7KIM", "name": "Loafers", "description": "A neat addition to your summer wardrobe.", "picture": "/static/img/products/loafers.jpg", "priceUsd": { "currencyCode": "USD", "units": 89, "nanos": 990000000 }, "categories": ["footwear"] }, { "id": "2ZYFJ3GM2N", "name": "Hairdryer", "description": "This lightweight hairdryer has 3 heat and speed settings. It's perfect for travel.", "picture": "/static/img/products/hairdryer.jpg", "priceUsd": { "currencyCode": "USD", "units": 24, "nanos": 990000000 }, "categories": ["hair", "beauty"] }, { "id": "0PUK6V6EV0", "name": "Candle Holder", "description": "This small but intricate candle holder is an excellent gift.", "picture": "/static/img/products/candle-holder.jpg", "priceUsd": { "currencyCode": "USD", "units": 18, "nanos": 990000000 }, "categories": ["decor", "home"] }, { "id": "LS4PSXUNUM", "name": "Salt & Pepper Shakers", "description": "Add some flavor to your kitchen.", "picture": "/static/img/products/salt-and-pepper-shakers.jpg", "priceUsd": { "currencyCode": "USD", "units": 18, "nanos": 490000000 }, "categories": ["kitchen"] }, { "id": "9SIQT8TOJO", "name": "Bamboo Glass Jar", "description": "This bamboo glass jar can hold 57 oz (1.7 l) and is perfect for any kitchen.", "picture": "/static/img/products/bamboo-glass-jar.jpg", "priceUsd": { "currencyCode": "USD", "units": 5, "nanos": 490000000 }, "categories": ["kitchen"] }, { "id": "6E92ZMYYFZ", "name": "Mug", "description": "A simple mug with a mustard interior.", "picture": "/static/img/products/mug.jpg", "priceUsd": { "currencyCode": "USD", "units": 8, "nanos": 990000000 }, "categories": ["kitchen"] } ] } ================================================ FILE: src/productcatalogservice/server.go ================================================ // Copyright 2018 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. package main import ( "context" "flag" "fmt" "net" "os" "os/signal" "sync" "syscall" "time" pb "github.com/GoogleCloudPlatform/microservices-demo/src/productcatalogservice/genproto" "google.golang.org/grpc/credentials/insecure" "google.golang.org/grpc/health" healthpb "google.golang.org/grpc/health/grpc_health_v1" "cloud.google.com/go/profiler" "github.com/pkg/errors" "github.com/sirupsen/logrus" "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc" "go.opentelemetry.io/otel/propagation" sdktrace "go.opentelemetry.io/otel/sdk/trace" "google.golang.org/grpc" ) var ( catalogMutex *sync.Mutex log *logrus.Logger extraLatency time.Duration port = "3550" reloadCatalog bool ) func init() { log = logrus.New() log.Formatter = &logrus.JSONFormatter{ FieldMap: logrus.FieldMap{ logrus.FieldKeyTime: "timestamp", logrus.FieldKeyLevel: "severity", logrus.FieldKeyMsg: "message", }, TimestampFormat: time.RFC3339Nano, } log.Out = os.Stdout catalogMutex = &sync.Mutex{} } func main() { if os.Getenv("ENABLE_TRACING") == "1" { err := initTracing() if err != nil { log.Warnf("warn: failed to start tracer: %+v", err) } } else { log.Info("Tracing disabled.") } if os.Getenv("DISABLE_PROFILER") == "" { log.Info("Profiling enabled.") go initProfiling("productcatalogservice", "1.0.0") } else { log.Info("Profiling disabled.") } flag.Parse() // set injected latency if s := os.Getenv("EXTRA_LATENCY"); s != "" { v, err := time.ParseDuration(s) if err != nil { log.Fatalf("failed to parse EXTRA_LATENCY (%s) as time.Duration: %+v", v, err) } extraLatency = v log.Infof("extra latency enabled (duration: %v)", extraLatency) } else { extraLatency = time.Duration(0) } sigs := make(chan os.Signal, 1) signal.Notify(sigs, syscall.SIGUSR1, syscall.SIGUSR2) go func() { for { sig := <-sigs log.Printf("Received signal: %s", sig) if sig == syscall.SIGUSR1 { reloadCatalog = true log.Infof("Enable catalog reloading") } else { reloadCatalog = false log.Infof("Disable catalog reloading") } } }() if os.Getenv("PORT") != "" { port = os.Getenv("PORT") } log.Infof("starting grpc server at :%s", port) run(port) select {} } func run(port string) string { listener, err := net.Listen("tcp", fmt.Sprintf(":%s", port)) if err != nil { log.Fatal(err) } // Propagate trace context otel.SetTextMapPropagator( propagation.NewCompositeTextMapPropagator( propagation.TraceContext{}, propagation.Baggage{})) var srv *grpc.Server srv = grpc.NewServer( grpc.StatsHandler(otelgrpc.NewServerHandler())) svc := &productCatalog{} err = loadCatalog(&svc.catalog) if err != nil { log.Fatalf("could not parse product catalog: %v", err) } pb.RegisterProductCatalogServiceServer(srv, svc) healthcheck := health.NewServer() healthpb.RegisterHealthServer(srv, healthcheck) go srv.Serve(listener) return listener.Addr().String() } func initStats() { // TODO(drewbr) Implement OpenTelemetry stats } func initTracing() error { var ( collectorAddr string collectorConn *grpc.ClientConn ) ctx := context.Background() mustMapEnv(&collectorAddr, "COLLECTOR_SERVICE_ADDR") mustConnGRPC(ctx, &collectorConn, collectorAddr) exporter, err := otlptracegrpc.New( ctx, otlptracegrpc.WithGRPCConn(collectorConn)) if err != nil { log.Warnf("warn: Failed to create trace exporter: %v", err) } tp := sdktrace.NewTracerProvider( sdktrace.WithBatcher(exporter), sdktrace.WithSampler(sdktrace.AlwaysSample())) otel.SetTracerProvider(tp) return err } func initProfiling(service, version string) { for i := 1; i <= 3; i++ { if err := profiler.Start(profiler.Config{ Service: service, ServiceVersion: version, // ProjectID must be set if not running on GCP. // ProjectID: "my-project", }); err != nil { log.Warnf("failed to start profiler: %+v", err) } else { log.Info("started Stackdriver profiler") return } d := time.Second * 10 * time.Duration(i) log.Infof("sleeping %v to retry initializing Stackdriver profiler", d) time.Sleep(d) } log.Warn("could not initialize Stackdriver profiler after retrying, giving up") } func mustMapEnv(target *string, envKey string) { v := os.Getenv(envKey) if v == "" { panic(fmt.Sprintf("environment variable %q not set", envKey)) } *target = v } func mustConnGRPC(ctx context.Context, conn **grpc.ClientConn, addr string) { var err error _, cancel := context.WithTimeout(ctx, time.Second*3) defer cancel() *conn, err = grpc.NewClient(addr, grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithStatsHandler(otelgrpc.NewClientHandler())) if err != nil { panic(errors.Wrapf(err, "grpc: failed to connect %s", addr)) } } ================================================ FILE: src/recommendationservice/Dockerfile ================================================ # Copyright 2020 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. # Define a default value so it's not empty if the builder fails to provide it ARG BUILDPLATFORM=linux/amd64 FROM --platform=$BUILDPLATFORM python:3.14.3-alpine@sha256:faee120f7885a06fcc9677922331391fa690d911c020abb9e8025ff3d908e510 AS base FROM base AS builder ENV PYTHONDONTWRITEBYTECODE=1 ENV PYTHONUNBUFFERED=1 RUN apk update \ && apk add --no-cache g++ linux-headers \ && rm -rf /var/cache/apk/* # get packages COPY requirements.txt . RUN pip install -r requirements.txt FROM base ENV PYTHONDONTWRITEBYTECODE=1 ENV PYTHONUNBUFFERED=1 RUN apk update \ && apk add --no-cache libstdc++ \ && rm -rf /var/cache/apk/* # get packages WORKDIR /recommendationservice # Grab packages from builder COPY --from=builder /usr/local/lib/python3.14/ /usr/local/lib/python3.14/ # Add the application COPY . . # set listen port ENV PORT="8080" EXPOSE 8080 ENTRYPOINT ["python", "recommendation_server.py"] ================================================ FILE: src/recommendationservice/client.py ================================================ #!/usr/bin/python # # Copyright 2018 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. import sys import grpc import demo_pb2 import demo_pb2_grpc from logger import getJSONLogger logger = getJSONLogger('recommendationservice-server') if __name__ == "__main__": # get port if len(sys.argv) > 1: port = sys.argv[1] else: port = "8080" # set up server stub channel = grpc.insecure_channel('localhost:'+port) stub = demo_pb2_grpc.RecommendationServiceStub(channel) # form request request = demo_pb2.ListRecommendationsRequest(user_id="test", product_ids=["test"]) # make call to server response = stub.ListRecommendations(request) logger.info(response) ================================================ FILE: src/recommendationservice/demo_pb2.py ================================================ #!/usr/bin/python # # Copyright 2018 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. # -*- coding: utf-8 -*- # Generated by the protocol buffer compiler. DO NOT EDIT! # source: demo.proto """Generated protocol buffer code.""" from google.protobuf.internal import builder as _builder from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool from google.protobuf import symbol_database as _symbol_database # @@protoc_insertion_point(imports) _sym_db = _symbol_database.Default() DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\ndemo.proto\x12\x0bhipstershop\"0\n\x08\x43\x61rtItem\x12\x12\n\nproduct_id\x18\x01 \x01(\t\x12\x10\n\x08quantity\x18\x02 \x01(\x05\"F\n\x0e\x41\x64\x64ItemRequest\x12\x0f\n\x07user_id\x18\x01 \x01(\t\x12#\n\x04item\x18\x02 \x01(\x0b\x32\x15.hipstershop.CartItem\"#\n\x10\x45mptyCartRequest\x12\x0f\n\x07user_id\x18\x01 \x01(\t\"!\n\x0eGetCartRequest\x12\x0f\n\x07user_id\x18\x01 \x01(\t\"=\n\x04\x43\x61rt\x12\x0f\n\x07user_id\x18\x01 \x01(\t\x12$\n\x05items\x18\x02 \x03(\x0b\x32\x15.hipstershop.CartItem\"\x07\n\x05\x45mpty\"B\n\x1aListRecommendationsRequest\x12\x0f\n\x07user_id\x18\x01 \x01(\t\x12\x13\n\x0bproduct_ids\x18\x02 \x03(\t\"2\n\x1bListRecommendationsResponse\x12\x13\n\x0bproduct_ids\x18\x01 \x03(\t\"\x84\x01\n\x07Product\x12\n\n\x02id\x18\x01 \x01(\t\x12\x0c\n\x04name\x18\x02 \x01(\t\x12\x13\n\x0b\x64\x65scription\x18\x03 \x01(\t\x12\x0f\n\x07picture\x18\x04 \x01(\t\x12%\n\tprice_usd\x18\x05 \x01(\x0b\x32\x12.hipstershop.Money\x12\x12\n\ncategories\x18\x06 \x03(\t\">\n\x14ListProductsResponse\x12&\n\x08products\x18\x01 \x03(\x0b\x32\x14.hipstershop.Product\"\x1f\n\x11GetProductRequest\x12\n\n\x02id\x18\x01 \x01(\t\"&\n\x15SearchProductsRequest\x12\r\n\x05query\x18\x01 \x01(\t\"?\n\x16SearchProductsResponse\x12%\n\x07results\x18\x01 \x03(\x0b\x32\x14.hipstershop.Product\"^\n\x0fGetQuoteRequest\x12%\n\x07\x61\x64\x64ress\x18\x01 \x01(\x0b\x32\x14.hipstershop.Address\x12$\n\x05items\x18\x02 \x03(\x0b\x32\x15.hipstershop.CartItem\"8\n\x10GetQuoteResponse\x12$\n\x08\x63ost_usd\x18\x01 \x01(\x0b\x32\x12.hipstershop.Money\"_\n\x10ShipOrderRequest\x12%\n\x07\x61\x64\x64ress\x18\x01 \x01(\x0b\x32\x14.hipstershop.Address\x12$\n\x05items\x18\x02 \x03(\x0b\x32\x15.hipstershop.CartItem\"(\n\x11ShipOrderResponse\x12\x13\n\x0btracking_id\x18\x01 \x01(\t\"a\n\x07\x41\x64\x64ress\x12\x16\n\x0estreet_address\x18\x01 \x01(\t\x12\x0c\n\x04\x63ity\x18\x02 \x01(\t\x12\r\n\x05state\x18\x03 \x01(\t\x12\x0f\n\x07\x63ountry\x18\x04 \x01(\t\x12\x10\n\x08zip_code\x18\x05 \x01(\x05\"<\n\x05Money\x12\x15\n\rcurrency_code\x18\x01 \x01(\t\x12\r\n\x05units\x18\x02 \x01(\x03\x12\r\n\x05nanos\x18\x03 \x01(\x05\"8\n\x1eGetSupportedCurrenciesResponse\x12\x16\n\x0e\x63urrency_codes\x18\x01 \x03(\t\"N\n\x19\x43urrencyConversionRequest\x12 \n\x04\x66rom\x18\x01 \x01(\x0b\x32\x12.hipstershop.Money\x12\x0f\n\x07to_code\x18\x02 \x01(\t\"\x90\x01\n\x0e\x43reditCardInfo\x12\x1a\n\x12\x63redit_card_number\x18\x01 \x01(\t\x12\x17\n\x0f\x63redit_card_cvv\x18\x02 \x01(\x05\x12#\n\x1b\x63redit_card_expiration_year\x18\x03 \x01(\x05\x12$\n\x1c\x63redit_card_expiration_month\x18\x04 \x01(\x05\"e\n\rChargeRequest\x12\"\n\x06\x61mount\x18\x01 \x01(\x0b\x32\x12.hipstershop.Money\x12\x30\n\x0b\x63redit_card\x18\x02 \x01(\x0b\x32\x1b.hipstershop.CreditCardInfo\"(\n\x0e\x43hargeResponse\x12\x16\n\x0etransaction_id\x18\x01 \x01(\t\"R\n\tOrderItem\x12#\n\x04item\x18\x01 \x01(\x0b\x32\x15.hipstershop.CartItem\x12 \n\x04\x63ost\x18\x02 \x01(\x0b\x32\x12.hipstershop.Money\"\xbf\x01\n\x0bOrderResult\x12\x10\n\x08order_id\x18\x01 \x01(\t\x12\x1c\n\x14shipping_tracking_id\x18\x02 \x01(\t\x12)\n\rshipping_cost\x18\x03 \x01(\x0b\x32\x12.hipstershop.Money\x12.\n\x10shipping_address\x18\x04 \x01(\x0b\x32\x14.hipstershop.Address\x12%\n\x05items\x18\x05 \x03(\x0b\x32\x16.hipstershop.OrderItem\"V\n\x1cSendOrderConfirmationRequest\x12\r\n\x05\x65mail\x18\x01 \x01(\t\x12\'\n\x05order\x18\x02 \x01(\x0b\x32\x18.hipstershop.OrderResult\"\xa3\x01\n\x11PlaceOrderRequest\x12\x0f\n\x07user_id\x18\x01 \x01(\t\x12\x15\n\ruser_currency\x18\x02 \x01(\t\x12%\n\x07\x61\x64\x64ress\x18\x03 \x01(\x0b\x32\x14.hipstershop.Address\x12\r\n\x05\x65mail\x18\x05 \x01(\t\x12\x30\n\x0b\x63redit_card\x18\x06 \x01(\x0b\x32\x1b.hipstershop.CreditCardInfo\"=\n\x12PlaceOrderResponse\x12\'\n\x05order\x18\x01 \x01(\x0b\x32\x18.hipstershop.OrderResult\"!\n\tAdRequest\x12\x14\n\x0c\x63ontext_keys\x18\x01 \x03(\t\"*\n\nAdResponse\x12\x1c\n\x03\x61\x64s\x18\x01 \x03(\x0b\x32\x0f.hipstershop.Ad\"(\n\x02\x41\x64\x12\x14\n\x0credirect_url\x18\x01 \x01(\t\x12\x0c\n\x04text\x18\x02 \x01(\t2\xca\x01\n\x0b\x43\x61rtService\x12<\n\x07\x41\x64\x64Item\x12\x1b.hipstershop.AddItemRequest\x1a\x12.hipstershop.Empty\"\x00\x12;\n\x07GetCart\x12\x1b.hipstershop.GetCartRequest\x1a\x11.hipstershop.Cart\"\x00\x12@\n\tEmptyCart\x12\x1d.hipstershop.EmptyCartRequest\x1a\x12.hipstershop.Empty\"\x00\x32\x83\x01\n\x15RecommendationService\x12j\n\x13ListRecommendations\x12\'.hipstershop.ListRecommendationsRequest\x1a(.hipstershop.ListRecommendationsResponse\"\x00\x32\x83\x02\n\x15ProductCatalogService\x12G\n\x0cListProducts\x12\x12.hipstershop.Empty\x1a!.hipstershop.ListProductsResponse\"\x00\x12\x44\n\nGetProduct\x12\x1e.hipstershop.GetProductRequest\x1a\x14.hipstershop.Product\"\x00\x12[\n\x0eSearchProducts\x12\".hipstershop.SearchProductsRequest\x1a#.hipstershop.SearchProductsResponse\"\x00\x32\xaa\x01\n\x0fShippingService\x12I\n\x08GetQuote\x12\x1c.hipstershop.GetQuoteRequest\x1a\x1d.hipstershop.GetQuoteResponse\"\x00\x12L\n\tShipOrder\x12\x1d.hipstershop.ShipOrderRequest\x1a\x1e.hipstershop.ShipOrderResponse\"\x00\x32\xb7\x01\n\x0f\x43urrencyService\x12[\n\x16GetSupportedCurrencies\x12\x12.hipstershop.Empty\x1a+.hipstershop.GetSupportedCurrenciesResponse\"\x00\x12G\n\x07\x43onvert\x12&.hipstershop.CurrencyConversionRequest\x1a\x12.hipstershop.Money\"\x00\x32U\n\x0ePaymentService\x12\x43\n\x06\x43harge\x12\x1a.hipstershop.ChargeRequest\x1a\x1b.hipstershop.ChargeResponse\"\x00\x32h\n\x0c\x45mailService\x12X\n\x15SendOrderConfirmation\x12).hipstershop.SendOrderConfirmationRequest\x1a\x12.hipstershop.Empty\"\x00\x32\x62\n\x0f\x43heckoutService\x12O\n\nPlaceOrder\x12\x1e.hipstershop.PlaceOrderRequest\x1a\x1f.hipstershop.PlaceOrderResponse\"\x00\x32H\n\tAdService\x12;\n\x06GetAds\x12\x16.hipstershop.AdRequest\x1a\x17.hipstershop.AdResponse\"\x00\x62\x06proto3') _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals()) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'demo_pb2', globals()) if _descriptor._USE_C_DESCRIPTORS == False: DESCRIPTOR._options = None _CARTITEM._serialized_start=27 _CARTITEM._serialized_end=75 _ADDITEMREQUEST._serialized_start=77 _ADDITEMREQUEST._serialized_end=147 _EMPTYCARTREQUEST._serialized_start=149 _EMPTYCARTREQUEST._serialized_end=184 _GETCARTREQUEST._serialized_start=186 _GETCARTREQUEST._serialized_end=219 _CART._serialized_start=221 _CART._serialized_end=282 _EMPTY._serialized_start=284 _EMPTY._serialized_end=291 _LISTRECOMMENDATIONSREQUEST._serialized_start=293 _LISTRECOMMENDATIONSREQUEST._serialized_end=359 _LISTRECOMMENDATIONSRESPONSE._serialized_start=361 _LISTRECOMMENDATIONSRESPONSE._serialized_end=411 _PRODUCT._serialized_start=414 _PRODUCT._serialized_end=546 _LISTPRODUCTSRESPONSE._serialized_start=548 _LISTPRODUCTSRESPONSE._serialized_end=610 _GETPRODUCTREQUEST._serialized_start=612 _GETPRODUCTREQUEST._serialized_end=643 _SEARCHPRODUCTSREQUEST._serialized_start=645 _SEARCHPRODUCTSREQUEST._serialized_end=683 _SEARCHPRODUCTSRESPONSE._serialized_start=685 _SEARCHPRODUCTSRESPONSE._serialized_end=748 _GETQUOTEREQUEST._serialized_start=750 _GETQUOTEREQUEST._serialized_end=844 _GETQUOTERESPONSE._serialized_start=846 _GETQUOTERESPONSE._serialized_end=902 _SHIPORDERREQUEST._serialized_start=904 _SHIPORDERREQUEST._serialized_end=999 _SHIPORDERRESPONSE._serialized_start=1001 _SHIPORDERRESPONSE._serialized_end=1041 _ADDRESS._serialized_start=1043 _ADDRESS._serialized_end=1140 _MONEY._serialized_start=1142 _MONEY._serialized_end=1202 _GETSUPPORTEDCURRENCIESRESPONSE._serialized_start=1204 _GETSUPPORTEDCURRENCIESRESPONSE._serialized_end=1260 _CURRENCYCONVERSIONREQUEST._serialized_start=1262 _CURRENCYCONVERSIONREQUEST._serialized_end=1340 _CREDITCARDINFO._serialized_start=1343 _CREDITCARDINFO._serialized_end=1487 _CHARGEREQUEST._serialized_start=1489 _CHARGEREQUEST._serialized_end=1590 _CHARGERESPONSE._serialized_start=1592 _CHARGERESPONSE._serialized_end=1632 _ORDERITEM._serialized_start=1634 _ORDERITEM._serialized_end=1716 _ORDERRESULT._serialized_start=1719 _ORDERRESULT._serialized_end=1910 _SENDORDERCONFIRMATIONREQUEST._serialized_start=1912 _SENDORDERCONFIRMATIONREQUEST._serialized_end=1998 _PLACEORDERREQUEST._serialized_start=2001 _PLACEORDERREQUEST._serialized_end=2164 _PLACEORDERRESPONSE._serialized_start=2166 _PLACEORDERRESPONSE._serialized_end=2227 _ADREQUEST._serialized_start=2229 _ADREQUEST._serialized_end=2262 _ADRESPONSE._serialized_start=2264 _ADRESPONSE._serialized_end=2306 _AD._serialized_start=2308 _AD._serialized_end=2348 _CARTSERVICE._serialized_start=2351 _CARTSERVICE._serialized_end=2553 _RECOMMENDATIONSERVICE._serialized_start=2556 _RECOMMENDATIONSERVICE._serialized_end=2687 _PRODUCTCATALOGSERVICE._serialized_start=2690 _PRODUCTCATALOGSERVICE._serialized_end=2949 _SHIPPINGSERVICE._serialized_start=2952 _SHIPPINGSERVICE._serialized_end=3122 _CURRENCYSERVICE._serialized_start=3125 _CURRENCYSERVICE._serialized_end=3308 _PAYMENTSERVICE._serialized_start=3310 _PAYMENTSERVICE._serialized_end=3395 _EMAILSERVICE._serialized_start=3397 _EMAILSERVICE._serialized_end=3501 _CHECKOUTSERVICE._serialized_start=3503 _CHECKOUTSERVICE._serialized_end=3601 _ADSERVICE._serialized_start=3603 _ADSERVICE._serialized_end=3675 # @@protoc_insertion_point(module_scope) ================================================ FILE: src/recommendationservice/demo_pb2_grpc.py ================================================ #!/usr/bin/python # # Copyright 2018 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. # Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! """Client and server classes corresponding to protobuf-defined services.""" import grpc import demo_pb2 as demo__pb2 class CartServiceStub(object): """-----------------Cart service----------------- """ def __init__(self, channel): """Constructor. Args: channel: A grpc.Channel. """ self.AddItem = channel.unary_unary( '/hipstershop.CartService/AddItem', request_serializer=demo__pb2.AddItemRequest.SerializeToString, response_deserializer=demo__pb2.Empty.FromString, ) self.GetCart = channel.unary_unary( '/hipstershop.CartService/GetCart', request_serializer=demo__pb2.GetCartRequest.SerializeToString, response_deserializer=demo__pb2.Cart.FromString, ) self.EmptyCart = channel.unary_unary( '/hipstershop.CartService/EmptyCart', request_serializer=demo__pb2.EmptyCartRequest.SerializeToString, response_deserializer=demo__pb2.Empty.FromString, ) class CartServiceServicer(object): """-----------------Cart service----------------- """ def AddItem(self, request, context): """Missing associated documentation comment in .proto file.""" context.set_code(grpc.StatusCode.UNIMPLEMENTED) context.set_details('Method not implemented!') raise NotImplementedError('Method not implemented!') def GetCart(self, request, context): """Missing associated documentation comment in .proto file.""" context.set_code(grpc.StatusCode.UNIMPLEMENTED) context.set_details('Method not implemented!') raise NotImplementedError('Method not implemented!') def EmptyCart(self, request, context): """Missing associated documentation comment in .proto file.""" context.set_code(grpc.StatusCode.UNIMPLEMENTED) context.set_details('Method not implemented!') raise NotImplementedError('Method not implemented!') def add_CartServiceServicer_to_server(servicer, server): rpc_method_handlers = { 'AddItem': grpc.unary_unary_rpc_method_handler( servicer.AddItem, request_deserializer=demo__pb2.AddItemRequest.FromString, response_serializer=demo__pb2.Empty.SerializeToString, ), 'GetCart': grpc.unary_unary_rpc_method_handler( servicer.GetCart, request_deserializer=demo__pb2.GetCartRequest.FromString, response_serializer=demo__pb2.Cart.SerializeToString, ), 'EmptyCart': grpc.unary_unary_rpc_method_handler( servicer.EmptyCart, request_deserializer=demo__pb2.EmptyCartRequest.FromString, response_serializer=demo__pb2.Empty.SerializeToString, ), } generic_handler = grpc.method_handlers_generic_handler( 'hipstershop.CartService', rpc_method_handlers) server.add_generic_rpc_handlers((generic_handler,)) # This class is part of an EXPERIMENTAL API. class CartService(object): """-----------------Cart service----------------- """ @staticmethod def AddItem(request, target, options=(), channel_credentials=None, call_credentials=None, insecure=False, compression=None, wait_for_ready=None, timeout=None, metadata=None): return grpc.experimental.unary_unary(request, target, '/hipstershop.CartService/AddItem', demo__pb2.AddItemRequest.SerializeToString, demo__pb2.Empty.FromString, options, channel_credentials, insecure, call_credentials, compression, wait_for_ready, timeout, metadata) @staticmethod def GetCart(request, target, options=(), channel_credentials=None, call_credentials=None, insecure=False, compression=None, wait_for_ready=None, timeout=None, metadata=None): return grpc.experimental.unary_unary(request, target, '/hipstershop.CartService/GetCart', demo__pb2.GetCartRequest.SerializeToString, demo__pb2.Cart.FromString, options, channel_credentials, insecure, call_credentials, compression, wait_for_ready, timeout, metadata) @staticmethod def EmptyCart(request, target, options=(), channel_credentials=None, call_credentials=None, insecure=False, compression=None, wait_for_ready=None, timeout=None, metadata=None): return grpc.experimental.unary_unary(request, target, '/hipstershop.CartService/EmptyCart', demo__pb2.EmptyCartRequest.SerializeToString, demo__pb2.Empty.FromString, options, channel_credentials, insecure, call_credentials, compression, wait_for_ready, timeout, metadata) class RecommendationServiceStub(object): """---------------Recommendation service---------- """ def __init__(self, channel): """Constructor. Args: channel: A grpc.Channel. """ self.ListRecommendations = channel.unary_unary( '/hipstershop.RecommendationService/ListRecommendations', request_serializer=demo__pb2.ListRecommendationsRequest.SerializeToString, response_deserializer=demo__pb2.ListRecommendationsResponse.FromString, ) class RecommendationServiceServicer(object): """---------------Recommendation service---------- """ def ListRecommendations(self, request, context): """Missing associated documentation comment in .proto file.""" context.set_code(grpc.StatusCode.UNIMPLEMENTED) context.set_details('Method not implemented!') raise NotImplementedError('Method not implemented!') def add_RecommendationServiceServicer_to_server(servicer, server): rpc_method_handlers = { 'ListRecommendations': grpc.unary_unary_rpc_method_handler( servicer.ListRecommendations, request_deserializer=demo__pb2.ListRecommendationsRequest.FromString, response_serializer=demo__pb2.ListRecommendationsResponse.SerializeToString, ), } generic_handler = grpc.method_handlers_generic_handler( 'hipstershop.RecommendationService', rpc_method_handlers) server.add_generic_rpc_handlers((generic_handler,)) # This class is part of an EXPERIMENTAL API. class RecommendationService(object): """---------------Recommendation service---------- """ @staticmethod def ListRecommendations(request, target, options=(), channel_credentials=None, call_credentials=None, insecure=False, compression=None, wait_for_ready=None, timeout=None, metadata=None): return grpc.experimental.unary_unary(request, target, '/hipstershop.RecommendationService/ListRecommendations', demo__pb2.ListRecommendationsRequest.SerializeToString, demo__pb2.ListRecommendationsResponse.FromString, options, channel_credentials, insecure, call_credentials, compression, wait_for_ready, timeout, metadata) class ProductCatalogServiceStub(object): """---------------Product Catalog---------------- """ def __init__(self, channel): """Constructor. Args: channel: A grpc.Channel. """ self.ListProducts = channel.unary_unary( '/hipstershop.ProductCatalogService/ListProducts', request_serializer=demo__pb2.Empty.SerializeToString, response_deserializer=demo__pb2.ListProductsResponse.FromString, ) self.GetProduct = channel.unary_unary( '/hipstershop.ProductCatalogService/GetProduct', request_serializer=demo__pb2.GetProductRequest.SerializeToString, response_deserializer=demo__pb2.Product.FromString, ) self.SearchProducts = channel.unary_unary( '/hipstershop.ProductCatalogService/SearchProducts', request_serializer=demo__pb2.SearchProductsRequest.SerializeToString, response_deserializer=demo__pb2.SearchProductsResponse.FromString, ) class ProductCatalogServiceServicer(object): """---------------Product Catalog---------------- """ def ListProducts(self, request, context): """Missing associated documentation comment in .proto file.""" context.set_code(grpc.StatusCode.UNIMPLEMENTED) context.set_details('Method not implemented!') raise NotImplementedError('Method not implemented!') def GetProduct(self, request, context): """Missing associated documentation comment in .proto file.""" context.set_code(grpc.StatusCode.UNIMPLEMENTED) context.set_details('Method not implemented!') raise NotImplementedError('Method not implemented!') def SearchProducts(self, request, context): """Missing associated documentation comment in .proto file.""" context.set_code(grpc.StatusCode.UNIMPLEMENTED) context.set_details('Method not implemented!') raise NotImplementedError('Method not implemented!') def add_ProductCatalogServiceServicer_to_server(servicer, server): rpc_method_handlers = { 'ListProducts': grpc.unary_unary_rpc_method_handler( servicer.ListProducts, request_deserializer=demo__pb2.Empty.FromString, response_serializer=demo__pb2.ListProductsResponse.SerializeToString, ), 'GetProduct': grpc.unary_unary_rpc_method_handler( servicer.GetProduct, request_deserializer=demo__pb2.GetProductRequest.FromString, response_serializer=demo__pb2.Product.SerializeToString, ), 'SearchProducts': grpc.unary_unary_rpc_method_handler( servicer.SearchProducts, request_deserializer=demo__pb2.SearchProductsRequest.FromString, response_serializer=demo__pb2.SearchProductsResponse.SerializeToString, ), } generic_handler = grpc.method_handlers_generic_handler( 'hipstershop.ProductCatalogService', rpc_method_handlers) server.add_generic_rpc_handlers((generic_handler,)) # This class is part of an EXPERIMENTAL API. class ProductCatalogService(object): """---------------Product Catalog---------------- """ @staticmethod def ListProducts(request, target, options=(), channel_credentials=None, call_credentials=None, insecure=False, compression=None, wait_for_ready=None, timeout=None, metadata=None): return grpc.experimental.unary_unary(request, target, '/hipstershop.ProductCatalogService/ListProducts', demo__pb2.Empty.SerializeToString, demo__pb2.ListProductsResponse.FromString, options, channel_credentials, insecure, call_credentials, compression, wait_for_ready, timeout, metadata) @staticmethod def GetProduct(request, target, options=(), channel_credentials=None, call_credentials=None, insecure=False, compression=None, wait_for_ready=None, timeout=None, metadata=None): return grpc.experimental.unary_unary(request, target, '/hipstershop.ProductCatalogService/GetProduct', demo__pb2.GetProductRequest.SerializeToString, demo__pb2.Product.FromString, options, channel_credentials, insecure, call_credentials, compression, wait_for_ready, timeout, metadata) @staticmethod def SearchProducts(request, target, options=(), channel_credentials=None, call_credentials=None, insecure=False, compression=None, wait_for_ready=None, timeout=None, metadata=None): return grpc.experimental.unary_unary(request, target, '/hipstershop.ProductCatalogService/SearchProducts', demo__pb2.SearchProductsRequest.SerializeToString, demo__pb2.SearchProductsResponse.FromString, options, channel_credentials, insecure, call_credentials, compression, wait_for_ready, timeout, metadata) class ShippingServiceStub(object): """---------------Shipping Service---------- """ def __init__(self, channel): """Constructor. Args: channel: A grpc.Channel. """ self.GetQuote = channel.unary_unary( '/hipstershop.ShippingService/GetQuote', request_serializer=demo__pb2.GetQuoteRequest.SerializeToString, response_deserializer=demo__pb2.GetQuoteResponse.FromString, ) self.ShipOrder = channel.unary_unary( '/hipstershop.ShippingService/ShipOrder', request_serializer=demo__pb2.ShipOrderRequest.SerializeToString, response_deserializer=demo__pb2.ShipOrderResponse.FromString, ) class ShippingServiceServicer(object): """---------------Shipping Service---------- """ def GetQuote(self, request, context): """Missing associated documentation comment in .proto file.""" context.set_code(grpc.StatusCode.UNIMPLEMENTED) context.set_details('Method not implemented!') raise NotImplementedError('Method not implemented!') def ShipOrder(self, request, context): """Missing associated documentation comment in .proto file.""" context.set_code(grpc.StatusCode.UNIMPLEMENTED) context.set_details('Method not implemented!') raise NotImplementedError('Method not implemented!') def add_ShippingServiceServicer_to_server(servicer, server): rpc_method_handlers = { 'GetQuote': grpc.unary_unary_rpc_method_handler( servicer.GetQuote, request_deserializer=demo__pb2.GetQuoteRequest.FromString, response_serializer=demo__pb2.GetQuoteResponse.SerializeToString, ), 'ShipOrder': grpc.unary_unary_rpc_method_handler( servicer.ShipOrder, request_deserializer=demo__pb2.ShipOrderRequest.FromString, response_serializer=demo__pb2.ShipOrderResponse.SerializeToString, ), } generic_handler = grpc.method_handlers_generic_handler( 'hipstershop.ShippingService', rpc_method_handlers) server.add_generic_rpc_handlers((generic_handler,)) # This class is part of an EXPERIMENTAL API. class ShippingService(object): """---------------Shipping Service---------- """ @staticmethod def GetQuote(request, target, options=(), channel_credentials=None, call_credentials=None, insecure=False, compression=None, wait_for_ready=None, timeout=None, metadata=None): return grpc.experimental.unary_unary(request, target, '/hipstershop.ShippingService/GetQuote', demo__pb2.GetQuoteRequest.SerializeToString, demo__pb2.GetQuoteResponse.FromString, options, channel_credentials, insecure, call_credentials, compression, wait_for_ready, timeout, metadata) @staticmethod def ShipOrder(request, target, options=(), channel_credentials=None, call_credentials=None, insecure=False, compression=None, wait_for_ready=None, timeout=None, metadata=None): return grpc.experimental.unary_unary(request, target, '/hipstershop.ShippingService/ShipOrder', demo__pb2.ShipOrderRequest.SerializeToString, demo__pb2.ShipOrderResponse.FromString, options, channel_credentials, insecure, call_credentials, compression, wait_for_ready, timeout, metadata) class CurrencyServiceStub(object): """-----------------Currency service----------------- """ def __init__(self, channel): """Constructor. Args: channel: A grpc.Channel. """ self.GetSupportedCurrencies = channel.unary_unary( '/hipstershop.CurrencyService/GetSupportedCurrencies', request_serializer=demo__pb2.Empty.SerializeToString, response_deserializer=demo__pb2.GetSupportedCurrenciesResponse.FromString, ) self.Convert = channel.unary_unary( '/hipstershop.CurrencyService/Convert', request_serializer=demo__pb2.CurrencyConversionRequest.SerializeToString, response_deserializer=demo__pb2.Money.FromString, ) class CurrencyServiceServicer(object): """-----------------Currency service----------------- """ def GetSupportedCurrencies(self, request, context): """Missing associated documentation comment in .proto file.""" context.set_code(grpc.StatusCode.UNIMPLEMENTED) context.set_details('Method not implemented!') raise NotImplementedError('Method not implemented!') def Convert(self, request, context): """Missing associated documentation comment in .proto file.""" context.set_code(grpc.StatusCode.UNIMPLEMENTED) context.set_details('Method not implemented!') raise NotImplementedError('Method not implemented!') def add_CurrencyServiceServicer_to_server(servicer, server): rpc_method_handlers = { 'GetSupportedCurrencies': grpc.unary_unary_rpc_method_handler( servicer.GetSupportedCurrencies, request_deserializer=demo__pb2.Empty.FromString, response_serializer=demo__pb2.GetSupportedCurrenciesResponse.SerializeToString, ), 'Convert': grpc.unary_unary_rpc_method_handler( servicer.Convert, request_deserializer=demo__pb2.CurrencyConversionRequest.FromString, response_serializer=demo__pb2.Money.SerializeToString, ), } generic_handler = grpc.method_handlers_generic_handler( 'hipstershop.CurrencyService', rpc_method_handlers) server.add_generic_rpc_handlers((generic_handler,)) # This class is part of an EXPERIMENTAL API. class CurrencyService(object): """-----------------Currency service----------------- """ @staticmethod def GetSupportedCurrencies(request, target, options=(), channel_credentials=None, call_credentials=None, insecure=False, compression=None, wait_for_ready=None, timeout=None, metadata=None): return grpc.experimental.unary_unary(request, target, '/hipstershop.CurrencyService/GetSupportedCurrencies', demo__pb2.Empty.SerializeToString, demo__pb2.GetSupportedCurrenciesResponse.FromString, options, channel_credentials, insecure, call_credentials, compression, wait_for_ready, timeout, metadata) @staticmethod def Convert(request, target, options=(), channel_credentials=None, call_credentials=None, insecure=False, compression=None, wait_for_ready=None, timeout=None, metadata=None): return grpc.experimental.unary_unary(request, target, '/hipstershop.CurrencyService/Convert', demo__pb2.CurrencyConversionRequest.SerializeToString, demo__pb2.Money.FromString, options, channel_credentials, insecure, call_credentials, compression, wait_for_ready, timeout, metadata) class PaymentServiceStub(object): """-------------Payment service----------------- """ def __init__(self, channel): """Constructor. Args: channel: A grpc.Channel. """ self.Charge = channel.unary_unary( '/hipstershop.PaymentService/Charge', request_serializer=demo__pb2.ChargeRequest.SerializeToString, response_deserializer=demo__pb2.ChargeResponse.FromString, ) class PaymentServiceServicer(object): """-------------Payment service----------------- """ def Charge(self, request, context): """Missing associated documentation comment in .proto file.""" context.set_code(grpc.StatusCode.UNIMPLEMENTED) context.set_details('Method not implemented!') raise NotImplementedError('Method not implemented!') def add_PaymentServiceServicer_to_server(servicer, server): rpc_method_handlers = { 'Charge': grpc.unary_unary_rpc_method_handler( servicer.Charge, request_deserializer=demo__pb2.ChargeRequest.FromString, response_serializer=demo__pb2.ChargeResponse.SerializeToString, ), } generic_handler = grpc.method_handlers_generic_handler( 'hipstershop.PaymentService', rpc_method_handlers) server.add_generic_rpc_handlers((generic_handler,)) # This class is part of an EXPERIMENTAL API. class PaymentService(object): """-------------Payment service----------------- """ @staticmethod def Charge(request, target, options=(), channel_credentials=None, call_credentials=None, insecure=False, compression=None, wait_for_ready=None, timeout=None, metadata=None): return grpc.experimental.unary_unary(request, target, '/hipstershop.PaymentService/Charge', demo__pb2.ChargeRequest.SerializeToString, demo__pb2.ChargeResponse.FromString, options, channel_credentials, insecure, call_credentials, compression, wait_for_ready, timeout, metadata) class EmailServiceStub(object): """-------------Email service----------------- """ def __init__(self, channel): """Constructor. Args: channel: A grpc.Channel. """ self.SendOrderConfirmation = channel.unary_unary( '/hipstershop.EmailService/SendOrderConfirmation', request_serializer=demo__pb2.SendOrderConfirmationRequest.SerializeToString, response_deserializer=demo__pb2.Empty.FromString, ) class EmailServiceServicer(object): """-------------Email service----------------- """ def SendOrderConfirmation(self, request, context): """Missing associated documentation comment in .proto file.""" context.set_code(grpc.StatusCode.UNIMPLEMENTED) context.set_details('Method not implemented!') raise NotImplementedError('Method not implemented!') def add_EmailServiceServicer_to_server(servicer, server): rpc_method_handlers = { 'SendOrderConfirmation': grpc.unary_unary_rpc_method_handler( servicer.SendOrderConfirmation, request_deserializer=demo__pb2.SendOrderConfirmationRequest.FromString, response_serializer=demo__pb2.Empty.SerializeToString, ), } generic_handler = grpc.method_handlers_generic_handler( 'hipstershop.EmailService', rpc_method_handlers) server.add_generic_rpc_handlers((generic_handler,)) # This class is part of an EXPERIMENTAL API. class EmailService(object): """-------------Email service----------------- """ @staticmethod def SendOrderConfirmation(request, target, options=(), channel_credentials=None, call_credentials=None, insecure=False, compression=None, wait_for_ready=None, timeout=None, metadata=None): return grpc.experimental.unary_unary(request, target, '/hipstershop.EmailService/SendOrderConfirmation', demo__pb2.SendOrderConfirmationRequest.SerializeToString, demo__pb2.Empty.FromString, options, channel_credentials, insecure, call_credentials, compression, wait_for_ready, timeout, metadata) class CheckoutServiceStub(object): """-------------Checkout service----------------- """ def __init__(self, channel): """Constructor. Args: channel: A grpc.Channel. """ self.PlaceOrder = channel.unary_unary( '/hipstershop.CheckoutService/PlaceOrder', request_serializer=demo__pb2.PlaceOrderRequest.SerializeToString, response_deserializer=demo__pb2.PlaceOrderResponse.FromString, ) class CheckoutServiceServicer(object): """-------------Checkout service----------------- """ def PlaceOrder(self, request, context): """Missing associated documentation comment in .proto file.""" context.set_code(grpc.StatusCode.UNIMPLEMENTED) context.set_details('Method not implemented!') raise NotImplementedError('Method not implemented!') def add_CheckoutServiceServicer_to_server(servicer, server): rpc_method_handlers = { 'PlaceOrder': grpc.unary_unary_rpc_method_handler( servicer.PlaceOrder, request_deserializer=demo__pb2.PlaceOrderRequest.FromString, response_serializer=demo__pb2.PlaceOrderResponse.SerializeToString, ), } generic_handler = grpc.method_handlers_generic_handler( 'hipstershop.CheckoutService', rpc_method_handlers) server.add_generic_rpc_handlers((generic_handler,)) # This class is part of an EXPERIMENTAL API. class CheckoutService(object): """-------------Checkout service----------------- """ @staticmethod def PlaceOrder(request, target, options=(), channel_credentials=None, call_credentials=None, insecure=False, compression=None, wait_for_ready=None, timeout=None, metadata=None): return grpc.experimental.unary_unary(request, target, '/hipstershop.CheckoutService/PlaceOrder', demo__pb2.PlaceOrderRequest.SerializeToString, demo__pb2.PlaceOrderResponse.FromString, options, channel_credentials, insecure, call_credentials, compression, wait_for_ready, timeout, metadata) class AdServiceStub(object): """------------Ad service------------------ """ def __init__(self, channel): """Constructor. Args: channel: A grpc.Channel. """ self.GetAds = channel.unary_unary( '/hipstershop.AdService/GetAds', request_serializer=demo__pb2.AdRequest.SerializeToString, response_deserializer=demo__pb2.AdResponse.FromString, ) class AdServiceServicer(object): """------------Ad service------------------ """ def GetAds(self, request, context): """Missing associated documentation comment in .proto file.""" context.set_code(grpc.StatusCode.UNIMPLEMENTED) context.set_details('Method not implemented!') raise NotImplementedError('Method not implemented!') def add_AdServiceServicer_to_server(servicer, server): rpc_method_handlers = { 'GetAds': grpc.unary_unary_rpc_method_handler( servicer.GetAds, request_deserializer=demo__pb2.AdRequest.FromString, response_serializer=demo__pb2.AdResponse.SerializeToString, ), } generic_handler = grpc.method_handlers_generic_handler( 'hipstershop.AdService', rpc_method_handlers) server.add_generic_rpc_handlers((generic_handler,)) # This class is part of an EXPERIMENTAL API. class AdService(object): """------------Ad service------------------ """ @staticmethod def GetAds(request, target, options=(), channel_credentials=None, call_credentials=None, insecure=False, compression=None, wait_for_ready=None, timeout=None, metadata=None): return grpc.experimental.unary_unary(request, target, '/hipstershop.AdService/GetAds', demo__pb2.AdRequest.SerializeToString, demo__pb2.AdResponse.FromString, options, channel_credentials, insecure, call_credentials, compression, wait_for_ready, timeout, metadata) ================================================ FILE: src/recommendationservice/genproto.sh ================================================ #!/bin/bash -eu # # Copyright 2018 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 gke_recommendationservice_genproto] # script to compile python protos # # requires gRPC tools: # pip install -r requirements.txt python -m grpc_tools.protoc -I../../protos --python_out=. --grpc_python_out=. ../../protos/demo.proto # [END gke_recommendationservice_genproto] ================================================ FILE: src/recommendationservice/logger.py ================================================ #!/usr/bin/python # # Copyright 2018 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. import logging import sys from pythonjsonlogger import jsonlogger # TODO(yoshifumi) this class is duplicated since other Python services are # not sharing the modules for logging. class CustomJsonFormatter(jsonlogger.JsonFormatter): def add_fields(self, log_record, record, message_dict): super(CustomJsonFormatter, self).add_fields(log_record, record, message_dict) if not log_record.get('timestamp'): log_record['timestamp'] = record.created if log_record.get('severity'): log_record['severity'] = log_record['severity'].upper() else: log_record['severity'] = record.levelname def getJSONLogger(name): logger = logging.getLogger(name) handler = logging.StreamHandler(sys.stdout) formatter = CustomJsonFormatter('%(timestamp)s %(severity)s %(name)s %(message)s') handler.setFormatter(formatter) logger.addHandler(handler) logger.setLevel(logging.INFO) logger.propagate = False return logger ================================================ FILE: src/recommendationservice/recommendation_server.py ================================================ #!/usr/bin/python # # Copyright 2018 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. import os import random import time import traceback from concurrent import futures # @TODO: Temporarily removed in https://github.com/GoogleCloudPlatform/microservices-demo/pull/3196 # import googlecloudprofiler from google.auth.exceptions import DefaultCredentialsError import grpc import demo_pb2 import demo_pb2_grpc from grpc_health.v1 import health_pb2 from grpc_health.v1 import health_pb2_grpc from opentelemetry import trace from opentelemetry.instrumentation.grpc import GrpcInstrumentorClient, GrpcInstrumentorServer from opentelemetry.sdk.trace import TracerProvider from opentelemetry.sdk.trace.export import BatchSpanProcessor from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter from logger import getJSONLogger logger = getJSONLogger('recommendationservice-server') def initStackdriverProfiling(): project_id = None try: project_id = os.environ["GCP_PROJECT_ID"] except KeyError: # Environment variable not set pass # @TODO: Temporarily removed in https://github.com/GoogleCloudPlatform/microservices-demo/pull/3196 # for retry in range(1,4): # try: # if project_id: # googlecloudprofiler.start(service='recommendation_server', service_version='1.0.0', verbose=0, project_id=project_id) # else: # googlecloudprofiler.start(service='recommendation_server', service_version='1.0.0', verbose=0) # logger.info("Successfully started Stackdriver Profiler.") # return # except (BaseException) as exc: # logger.info("Unable to start Stackdriver Profiler Python agent. " + str(exc)) # if (retry < 4): # logger.info("Sleeping %d seconds to retry Stackdriver Profiler agent initialization"%(retry*10)) # time.sleep (1) # else: # logger.warning("Could not initialize Stackdriver Profiler after retrying, giving up") return class RecommendationService(demo_pb2_grpc.RecommendationServiceServicer): def ListRecommendations(self, request, context): max_responses = 5 # fetch list of products from product catalog stub cat_response = product_catalog_stub.ListProducts(demo_pb2.Empty()) product_ids = [x.id for x in cat_response.products] filtered_products = list(set(product_ids)-set(request.product_ids)) num_products = len(filtered_products) num_return = min(max_responses, num_products) # sample list of indicies to return indices = random.sample(range(num_products), num_return) # fetch product ids from indices prod_list = [filtered_products[i] for i in indices] logger.info("[Recv ListRecommendations] product_ids={}".format(prod_list)) # build and return response response = demo_pb2.ListRecommendationsResponse() response.product_ids.extend(prod_list) return response def Check(self, request, context): return health_pb2.HealthCheckResponse( status=health_pb2.HealthCheckResponse.SERVING) def Watch(self, request, context): return health_pb2.HealthCheckResponse( status=health_pb2.HealthCheckResponse.UNIMPLEMENTED) if __name__ == "__main__": logger.info("initializing recommendationservice") try: if "DISABLE_PROFILER" in os.environ: raise KeyError() else: logger.info("Profiler enabled.") initStackdriverProfiling() except KeyError: logger.info("Profiler disabled.") try: grpc_client_instrumentor = GrpcInstrumentorClient() grpc_client_instrumentor.instrument() grpc_server_instrumentor = GrpcInstrumentorServer() grpc_server_instrumentor.instrument() if os.environ["ENABLE_TRACING"] == "1": trace.set_tracer_provider(TracerProvider()) otel_endpoint = os.getenv("COLLECTOR_SERVICE_ADDR", "localhost:4317") trace.get_tracer_provider().add_span_processor( BatchSpanProcessor( OTLPSpanExporter( endpoint = otel_endpoint, insecure = True ) ) ) except (KeyError, DefaultCredentialsError): logger.info("Tracing disabled.") except Exception as e: logger.warn(f"Exception on Cloud Trace setup: {traceback.format_exc()}, tracing disabled.") port = os.environ.get('PORT', "8080") catalog_addr = os.environ.get('PRODUCT_CATALOG_SERVICE_ADDR', '') if catalog_addr == "": raise Exception('PRODUCT_CATALOG_SERVICE_ADDR environment variable not set') logger.info("product catalog address: " + catalog_addr) channel = grpc.insecure_channel(catalog_addr) product_catalog_stub = demo_pb2_grpc.ProductCatalogServiceStub(channel) # create gRPC server server = grpc.server(futures.ThreadPoolExecutor(max_workers=10)) # add class to gRPC server service = RecommendationService() demo_pb2_grpc.add_RecommendationServiceServicer_to_server(service, server) health_pb2_grpc.add_HealthServicer_to_server(service, server) # start server logger.info("listening on port: " + port) server.add_insecure_port('[::]:'+port) server.start() # keep alive try: while True: time.sleep(10000) except KeyboardInterrupt: server.stop(0) ================================================ FILE: src/recommendationservice/requirements.in ================================================ google-api-core==2.28.1 grpcio-health-checking==1.76.0 python-json-logger==4.0.0 requests==2.32.5 rsa==4.9.1 opentelemetry-distro==0.60b1 opentelemetry-instrumentation-grpc==0.60b1 opentelemetry-exporter-otlp-proto-grpc==1.39.1 ================================================ FILE: src/recommendationservice/requirements.txt ================================================ # This file was autogenerated by uv via the following command: # uv pip compile requirements.in -o requirements.txt cachetools==5.3.2 # via google-auth certifi==2024.7.4 # via requests charset-normalizer==3.3.2 # via requests google-api-core==2.28.1 # via -r requirements.in google-auth==2.23.4 # via google-api-core googleapis-common-protos==1.72.0 # via # google-api-core # opentelemetry-exporter-otlp-proto-grpc grpcio==1.76.0 # via # grpcio-health-checking # opentelemetry-exporter-otlp-proto-grpc grpcio-health-checking==1.76.0 # via -r requirements.in idna==3.7 # via requests importlib-metadata==6.8.0 # via opentelemetry-api opentelemetry-api==1.39.1 # via # opentelemetry-distro # opentelemetry-exporter-otlp-proto-grpc # opentelemetry-instrumentation # opentelemetry-instrumentation-grpc # opentelemetry-sdk # opentelemetry-semantic-conventions opentelemetry-distro==0.60b1 # via -r requirements.in opentelemetry-exporter-otlp-proto-common==1.39.1 # via opentelemetry-exporter-otlp-proto-grpc opentelemetry-exporter-otlp-proto-grpc==1.39.1 # via -r requirements.in opentelemetry-instrumentation==0.60b1 # via # opentelemetry-distro # opentelemetry-instrumentation-grpc opentelemetry-instrumentation-grpc==0.60b1 # via -r requirements.in opentelemetry-proto==1.39.1 # via # opentelemetry-exporter-otlp-proto-common # opentelemetry-exporter-otlp-proto-grpc opentelemetry-sdk==1.39.1 # via # opentelemetry-distro # opentelemetry-exporter-otlp-proto-grpc opentelemetry-semantic-conventions==0.60b1 # via # opentelemetry-instrumentation # opentelemetry-instrumentation-grpc # opentelemetry-sdk packaging==25.0 # via opentelemetry-instrumentation proto-plus==1.27.0 # via google-api-core protobuf==6.33.5 # via # google-api-core # googleapis-common-protos # grpcio-health-checking # opentelemetry-proto # proto-plus pyasn1==0.5.0 # via # pyasn1-modules # rsa pyasn1-modules==0.3.0 # via google-auth python-json-logger==4.0.0 # via -r requirements.in requests==2.32.5 # via # -r requirements.in # google-api-core rsa==4.9.1 # via # -r requirements.in # google-auth typing-extensions==4.15.0 # via # grpcio # opentelemetry-api # opentelemetry-exporter-otlp-proto-grpc # opentelemetry-sdk # opentelemetry-semantic-conventions urllib3==2.6.3 # via requests wrapt==1.16.0 # via # opentelemetry-instrumentation # opentelemetry-instrumentation-grpc zipp==3.19.1 # via importlib-metadata ================================================ FILE: src/shippingservice/.dockerignore ================================================ vendor/ ================================================ FILE: src/shippingservice/Dockerfile ================================================ # Copyright 2020 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. # Define a default value so it's not empty if the builder fails to provide it ARG BUILDPLATFORM=linux/amd64 FROM --platform=$BUILDPLATFORM golang:1.26.1-alpine@sha256:2389ebfa5b7f43eeafbd6be0c3700cc46690ef842ad962f6c5bd6be49ed82039 AS builder ARG TARGETOS=linux ARG TARGETARCH=amd64 WORKDIR /src # restore dependencies COPY go.mod go.sum ./ RUN go mod download COPY . . # Skaffold passes in debug-oriented compiler flags ARG SKAFFOLD_GO_GCFLAGS RUN GOOS=${TARGETOS} GOARCH=${TARGETARCH} CGO_ENABLED=0 go build -ldflags="-s -w" -gcflags="${SKAFFOLD_GO_GCFLAGS}" -o /go/bin/shippingservice . FROM gcr.io/distroless/static WORKDIR /src COPY --from=builder /go/bin/shippingservice /src/shippingservice ENV APP_PORT=50051 # Definition of this variable is used by 'skaffold debug' to identify a golang binary. # Default behavior - a failure prints a stack trace for the current goroutine. # See https://golang.org/pkg/runtime/ ENV GOTRACEBACK=single EXPOSE 50051 ENTRYPOINT ["/src/shippingservice"] ================================================ FILE: src/shippingservice/README.md ================================================ # Shipping Service The Shipping service provides price quote, tracking IDs, and the impression of order fulfillment & shipping processes. ## Local Run the following command to restore dependencies to `vendor/` directory: dep ensure --vendor-only ## Build From `src/shippingservice`, run: ``` docker build ./ ``` ## Test ``` go test . ``` ================================================ FILE: src/shippingservice/genproto/demo.pb.go ================================================ // Copyright 2020 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. // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.34.2 // protoc v3.6.1 // source: demo.proto package hipstershop import ( protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" sync "sync" ) const ( // Verify that this generated code is sufficiently up-to-date. _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) // Verify that runtime/protoimpl is sufficiently up-to-date. _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) type CartItem struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields ProductId string `protobuf:"bytes,1,opt,name=product_id,json=productId,proto3" json:"product_id,omitempty"` Quantity int32 `protobuf:"varint,2,opt,name=quantity,proto3" json:"quantity,omitempty"` } func (x *CartItem) Reset() { *x = CartItem{} if protoimpl.UnsafeEnabled { mi := &file_demo_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *CartItem) String() string { return protoimpl.X.MessageStringOf(x) } func (*CartItem) ProtoMessage() {} func (x *CartItem) ProtoReflect() protoreflect.Message { mi := &file_demo_proto_msgTypes[0] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use CartItem.ProtoReflect.Descriptor instead. func (*CartItem) Descriptor() ([]byte, []int) { return file_demo_proto_rawDescGZIP(), []int{0} } func (x *CartItem) GetProductId() string { if x != nil { return x.ProductId } return "" } func (x *CartItem) GetQuantity() int32 { if x != nil { return x.Quantity } return 0 } type AddItemRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields UserId string `protobuf:"bytes,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` Item *CartItem `protobuf:"bytes,2,opt,name=item,proto3" json:"item,omitempty"` } func (x *AddItemRequest) Reset() { *x = AddItemRequest{} if protoimpl.UnsafeEnabled { mi := &file_demo_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *AddItemRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*AddItemRequest) ProtoMessage() {} func (x *AddItemRequest) ProtoReflect() protoreflect.Message { mi := &file_demo_proto_msgTypes[1] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use AddItemRequest.ProtoReflect.Descriptor instead. func (*AddItemRequest) Descriptor() ([]byte, []int) { return file_demo_proto_rawDescGZIP(), []int{1} } func (x *AddItemRequest) GetUserId() string { if x != nil { return x.UserId } return "" } func (x *AddItemRequest) GetItem() *CartItem { if x != nil { return x.Item } return nil } type EmptyCartRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields UserId string `protobuf:"bytes,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` } func (x *EmptyCartRequest) Reset() { *x = EmptyCartRequest{} if protoimpl.UnsafeEnabled { mi := &file_demo_proto_msgTypes[2] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *EmptyCartRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*EmptyCartRequest) ProtoMessage() {} func (x *EmptyCartRequest) ProtoReflect() protoreflect.Message { mi := &file_demo_proto_msgTypes[2] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use EmptyCartRequest.ProtoReflect.Descriptor instead. func (*EmptyCartRequest) Descriptor() ([]byte, []int) { return file_demo_proto_rawDescGZIP(), []int{2} } func (x *EmptyCartRequest) GetUserId() string { if x != nil { return x.UserId } return "" } type GetCartRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields UserId string `protobuf:"bytes,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` } func (x *GetCartRequest) Reset() { *x = GetCartRequest{} if protoimpl.UnsafeEnabled { mi := &file_demo_proto_msgTypes[3] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *GetCartRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*GetCartRequest) ProtoMessage() {} func (x *GetCartRequest) ProtoReflect() protoreflect.Message { mi := &file_demo_proto_msgTypes[3] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use GetCartRequest.ProtoReflect.Descriptor instead. func (*GetCartRequest) Descriptor() ([]byte, []int) { return file_demo_proto_rawDescGZIP(), []int{3} } func (x *GetCartRequest) GetUserId() string { if x != nil { return x.UserId } return "" } type Cart struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields UserId string `protobuf:"bytes,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` Items []*CartItem `protobuf:"bytes,2,rep,name=items,proto3" json:"items,omitempty"` } func (x *Cart) Reset() { *x = Cart{} if protoimpl.UnsafeEnabled { mi := &file_demo_proto_msgTypes[4] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Cart) String() string { return protoimpl.X.MessageStringOf(x) } func (*Cart) ProtoMessage() {} func (x *Cart) ProtoReflect() protoreflect.Message { mi := &file_demo_proto_msgTypes[4] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use Cart.ProtoReflect.Descriptor instead. func (*Cart) Descriptor() ([]byte, []int) { return file_demo_proto_rawDescGZIP(), []int{4} } func (x *Cart) GetUserId() string { if x != nil { return x.UserId } return "" } func (x *Cart) GetItems() []*CartItem { if x != nil { return x.Items } return nil } type Empty struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields } func (x *Empty) Reset() { *x = Empty{} if protoimpl.UnsafeEnabled { mi := &file_demo_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Empty) String() string { return protoimpl.X.MessageStringOf(x) } func (*Empty) ProtoMessage() {} func (x *Empty) ProtoReflect() protoreflect.Message { mi := &file_demo_proto_msgTypes[5] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use Empty.ProtoReflect.Descriptor instead. func (*Empty) Descriptor() ([]byte, []int) { return file_demo_proto_rawDescGZIP(), []int{5} } type ListRecommendationsRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields UserId string `protobuf:"bytes,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` ProductIds []string `protobuf:"bytes,2,rep,name=product_ids,json=productIds,proto3" json:"product_ids,omitempty"` } func (x *ListRecommendationsRequest) Reset() { *x = ListRecommendationsRequest{} if protoimpl.UnsafeEnabled { mi := &file_demo_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *ListRecommendationsRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*ListRecommendationsRequest) ProtoMessage() {} func (x *ListRecommendationsRequest) ProtoReflect() protoreflect.Message { mi := &file_demo_proto_msgTypes[6] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use ListRecommendationsRequest.ProtoReflect.Descriptor instead. func (*ListRecommendationsRequest) Descriptor() ([]byte, []int) { return file_demo_proto_rawDescGZIP(), []int{6} } func (x *ListRecommendationsRequest) GetUserId() string { if x != nil { return x.UserId } return "" } func (x *ListRecommendationsRequest) GetProductIds() []string { if x != nil { return x.ProductIds } return nil } type ListRecommendationsResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields ProductIds []string `protobuf:"bytes,1,rep,name=product_ids,json=productIds,proto3" json:"product_ids,omitempty"` } func (x *ListRecommendationsResponse) Reset() { *x = ListRecommendationsResponse{} if protoimpl.UnsafeEnabled { mi := &file_demo_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *ListRecommendationsResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*ListRecommendationsResponse) ProtoMessage() {} func (x *ListRecommendationsResponse) ProtoReflect() protoreflect.Message { mi := &file_demo_proto_msgTypes[7] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use ListRecommendationsResponse.ProtoReflect.Descriptor instead. func (*ListRecommendationsResponse) Descriptor() ([]byte, []int) { return file_demo_proto_rawDescGZIP(), []int{7} } func (x *ListRecommendationsResponse) GetProductIds() []string { if x != nil { return x.ProductIds } return nil } type Product struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` Description string `protobuf:"bytes,3,opt,name=description,proto3" json:"description,omitempty"` Picture string `protobuf:"bytes,4,opt,name=picture,proto3" json:"picture,omitempty"` PriceUsd *Money `protobuf:"bytes,5,opt,name=price_usd,json=priceUsd,proto3" json:"price_usd,omitempty"` // Categories such as "clothing" or "kitchen" that can be used to look up // other related products. Categories []string `protobuf:"bytes,6,rep,name=categories,proto3" json:"categories,omitempty"` } func (x *Product) Reset() { *x = Product{} if protoimpl.UnsafeEnabled { mi := &file_demo_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Product) String() string { return protoimpl.X.MessageStringOf(x) } func (*Product) ProtoMessage() {} func (x *Product) ProtoReflect() protoreflect.Message { mi := &file_demo_proto_msgTypes[8] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use Product.ProtoReflect.Descriptor instead. func (*Product) Descriptor() ([]byte, []int) { return file_demo_proto_rawDescGZIP(), []int{8} } func (x *Product) GetId() string { if x != nil { return x.Id } return "" } func (x *Product) GetName() string { if x != nil { return x.Name } return "" } func (x *Product) GetDescription() string { if x != nil { return x.Description } return "" } func (x *Product) GetPicture() string { if x != nil { return x.Picture } return "" } func (x *Product) GetPriceUsd() *Money { if x != nil { return x.PriceUsd } return nil } func (x *Product) GetCategories() []string { if x != nil { return x.Categories } return nil } type ListProductsResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Products []*Product `protobuf:"bytes,1,rep,name=products,proto3" json:"products,omitempty"` } func (x *ListProductsResponse) Reset() { *x = ListProductsResponse{} if protoimpl.UnsafeEnabled { mi := &file_demo_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *ListProductsResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*ListProductsResponse) ProtoMessage() {} func (x *ListProductsResponse) ProtoReflect() protoreflect.Message { mi := &file_demo_proto_msgTypes[9] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use ListProductsResponse.ProtoReflect.Descriptor instead. func (*ListProductsResponse) Descriptor() ([]byte, []int) { return file_demo_proto_rawDescGZIP(), []int{9} } func (x *ListProductsResponse) GetProducts() []*Product { if x != nil { return x.Products } return nil } type GetProductRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` } func (x *GetProductRequest) Reset() { *x = GetProductRequest{} if protoimpl.UnsafeEnabled { mi := &file_demo_proto_msgTypes[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *GetProductRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*GetProductRequest) ProtoMessage() {} func (x *GetProductRequest) ProtoReflect() protoreflect.Message { mi := &file_demo_proto_msgTypes[10] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use GetProductRequest.ProtoReflect.Descriptor instead. func (*GetProductRequest) Descriptor() ([]byte, []int) { return file_demo_proto_rawDescGZIP(), []int{10} } func (x *GetProductRequest) GetId() string { if x != nil { return x.Id } return "" } type SearchProductsRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Query string `protobuf:"bytes,1,opt,name=query,proto3" json:"query,omitempty"` } func (x *SearchProductsRequest) Reset() { *x = SearchProductsRequest{} if protoimpl.UnsafeEnabled { mi := &file_demo_proto_msgTypes[11] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *SearchProductsRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*SearchProductsRequest) ProtoMessage() {} func (x *SearchProductsRequest) ProtoReflect() protoreflect.Message { mi := &file_demo_proto_msgTypes[11] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use SearchProductsRequest.ProtoReflect.Descriptor instead. func (*SearchProductsRequest) Descriptor() ([]byte, []int) { return file_demo_proto_rawDescGZIP(), []int{11} } func (x *SearchProductsRequest) GetQuery() string { if x != nil { return x.Query } return "" } type SearchProductsResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Results []*Product `protobuf:"bytes,1,rep,name=results,proto3" json:"results,omitempty"` } func (x *SearchProductsResponse) Reset() { *x = SearchProductsResponse{} if protoimpl.UnsafeEnabled { mi := &file_demo_proto_msgTypes[12] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *SearchProductsResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*SearchProductsResponse) ProtoMessage() {} func (x *SearchProductsResponse) ProtoReflect() protoreflect.Message { mi := &file_demo_proto_msgTypes[12] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use SearchProductsResponse.ProtoReflect.Descriptor instead. func (*SearchProductsResponse) Descriptor() ([]byte, []int) { return file_demo_proto_rawDescGZIP(), []int{12} } func (x *SearchProductsResponse) GetResults() []*Product { if x != nil { return x.Results } return nil } type GetQuoteRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Address *Address `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"` Items []*CartItem `protobuf:"bytes,2,rep,name=items,proto3" json:"items,omitempty"` } func (x *GetQuoteRequest) Reset() { *x = GetQuoteRequest{} if protoimpl.UnsafeEnabled { mi := &file_demo_proto_msgTypes[13] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *GetQuoteRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*GetQuoteRequest) ProtoMessage() {} func (x *GetQuoteRequest) ProtoReflect() protoreflect.Message { mi := &file_demo_proto_msgTypes[13] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use GetQuoteRequest.ProtoReflect.Descriptor instead. func (*GetQuoteRequest) Descriptor() ([]byte, []int) { return file_demo_proto_rawDescGZIP(), []int{13} } func (x *GetQuoteRequest) GetAddress() *Address { if x != nil { return x.Address } return nil } func (x *GetQuoteRequest) GetItems() []*CartItem { if x != nil { return x.Items } return nil } type GetQuoteResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields CostUsd *Money `protobuf:"bytes,1,opt,name=cost_usd,json=costUsd,proto3" json:"cost_usd,omitempty"` } func (x *GetQuoteResponse) Reset() { *x = GetQuoteResponse{} if protoimpl.UnsafeEnabled { mi := &file_demo_proto_msgTypes[14] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *GetQuoteResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*GetQuoteResponse) ProtoMessage() {} func (x *GetQuoteResponse) ProtoReflect() protoreflect.Message { mi := &file_demo_proto_msgTypes[14] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use GetQuoteResponse.ProtoReflect.Descriptor instead. func (*GetQuoteResponse) Descriptor() ([]byte, []int) { return file_demo_proto_rawDescGZIP(), []int{14} } func (x *GetQuoteResponse) GetCostUsd() *Money { if x != nil { return x.CostUsd } return nil } type ShipOrderRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Address *Address `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"` Items []*CartItem `protobuf:"bytes,2,rep,name=items,proto3" json:"items,omitempty"` } func (x *ShipOrderRequest) Reset() { *x = ShipOrderRequest{} if protoimpl.UnsafeEnabled { mi := &file_demo_proto_msgTypes[15] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *ShipOrderRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*ShipOrderRequest) ProtoMessage() {} func (x *ShipOrderRequest) ProtoReflect() protoreflect.Message { mi := &file_demo_proto_msgTypes[15] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use ShipOrderRequest.ProtoReflect.Descriptor instead. func (*ShipOrderRequest) Descriptor() ([]byte, []int) { return file_demo_proto_rawDescGZIP(), []int{15} } func (x *ShipOrderRequest) GetAddress() *Address { if x != nil { return x.Address } return nil } func (x *ShipOrderRequest) GetItems() []*CartItem { if x != nil { return x.Items } return nil } type ShipOrderResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields TrackingId string `protobuf:"bytes,1,opt,name=tracking_id,json=trackingId,proto3" json:"tracking_id,omitempty"` } func (x *ShipOrderResponse) Reset() { *x = ShipOrderResponse{} if protoimpl.UnsafeEnabled { mi := &file_demo_proto_msgTypes[16] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *ShipOrderResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*ShipOrderResponse) ProtoMessage() {} func (x *ShipOrderResponse) ProtoReflect() protoreflect.Message { mi := &file_demo_proto_msgTypes[16] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use ShipOrderResponse.ProtoReflect.Descriptor instead. func (*ShipOrderResponse) Descriptor() ([]byte, []int) { return file_demo_proto_rawDescGZIP(), []int{16} } func (x *ShipOrderResponse) GetTrackingId() string { if x != nil { return x.TrackingId } return "" } type Address struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields StreetAddress string `protobuf:"bytes,1,opt,name=street_address,json=streetAddress,proto3" json:"street_address,omitempty"` City string `protobuf:"bytes,2,opt,name=city,proto3" json:"city,omitempty"` State string `protobuf:"bytes,3,opt,name=state,proto3" json:"state,omitempty"` Country string `protobuf:"bytes,4,opt,name=country,proto3" json:"country,omitempty"` ZipCode int32 `protobuf:"varint,5,opt,name=zip_code,json=zipCode,proto3" json:"zip_code,omitempty"` } func (x *Address) Reset() { *x = Address{} if protoimpl.UnsafeEnabled { mi := &file_demo_proto_msgTypes[17] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Address) String() string { return protoimpl.X.MessageStringOf(x) } func (*Address) ProtoMessage() {} func (x *Address) ProtoReflect() protoreflect.Message { mi := &file_demo_proto_msgTypes[17] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use Address.ProtoReflect.Descriptor instead. func (*Address) Descriptor() ([]byte, []int) { return file_demo_proto_rawDescGZIP(), []int{17} } func (x *Address) GetStreetAddress() string { if x != nil { return x.StreetAddress } return "" } func (x *Address) GetCity() string { if x != nil { return x.City } return "" } func (x *Address) GetState() string { if x != nil { return x.State } return "" } func (x *Address) GetCountry() string { if x != nil { return x.Country } return "" } func (x *Address) GetZipCode() int32 { if x != nil { return x.ZipCode } return 0 } // Represents an amount of money with its currency type. type Money struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // The 3-letter currency code defined in ISO 4217. CurrencyCode string `protobuf:"bytes,1,opt,name=currency_code,json=currencyCode,proto3" json:"currency_code,omitempty"` // The whole units of the amount. // For example if `currencyCode` is `"USD"`, then 1 unit is one US dollar. Units int64 `protobuf:"varint,2,opt,name=units,proto3" json:"units,omitempty"` // Number of nano (10^-9) units of the amount. // The value must be between -999,999,999 and +999,999,999 inclusive. // If `units` is positive, `nanos` must be positive or zero. // If `units` is zero, `nanos` can be positive, zero, or negative. // If `units` is negative, `nanos` must be negative or zero. // For example $-1.75 is represented as `units`=-1 and `nanos`=-750,000,000. Nanos int32 `protobuf:"varint,3,opt,name=nanos,proto3" json:"nanos,omitempty"` } func (x *Money) Reset() { *x = Money{} if protoimpl.UnsafeEnabled { mi := &file_demo_proto_msgTypes[18] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Money) String() string { return protoimpl.X.MessageStringOf(x) } func (*Money) ProtoMessage() {} func (x *Money) ProtoReflect() protoreflect.Message { mi := &file_demo_proto_msgTypes[18] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use Money.ProtoReflect.Descriptor instead. func (*Money) Descriptor() ([]byte, []int) { return file_demo_proto_rawDescGZIP(), []int{18} } func (x *Money) GetCurrencyCode() string { if x != nil { return x.CurrencyCode } return "" } func (x *Money) GetUnits() int64 { if x != nil { return x.Units } return 0 } func (x *Money) GetNanos() int32 { if x != nil { return x.Nanos } return 0 } type GetSupportedCurrenciesResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // The 3-letter currency code defined in ISO 4217. CurrencyCodes []string `protobuf:"bytes,1,rep,name=currency_codes,json=currencyCodes,proto3" json:"currency_codes,omitempty"` } func (x *GetSupportedCurrenciesResponse) Reset() { *x = GetSupportedCurrenciesResponse{} if protoimpl.UnsafeEnabled { mi := &file_demo_proto_msgTypes[19] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *GetSupportedCurrenciesResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*GetSupportedCurrenciesResponse) ProtoMessage() {} func (x *GetSupportedCurrenciesResponse) ProtoReflect() protoreflect.Message { mi := &file_demo_proto_msgTypes[19] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use GetSupportedCurrenciesResponse.ProtoReflect.Descriptor instead. func (*GetSupportedCurrenciesResponse) Descriptor() ([]byte, []int) { return file_demo_proto_rawDescGZIP(), []int{19} } func (x *GetSupportedCurrenciesResponse) GetCurrencyCodes() []string { if x != nil { return x.CurrencyCodes } return nil } type CurrencyConversionRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields From *Money `protobuf:"bytes,1,opt,name=from,proto3" json:"from,omitempty"` // The 3-letter currency code defined in ISO 4217. ToCode string `protobuf:"bytes,2,opt,name=to_code,json=toCode,proto3" json:"to_code,omitempty"` } func (x *CurrencyConversionRequest) Reset() { *x = CurrencyConversionRequest{} if protoimpl.UnsafeEnabled { mi := &file_demo_proto_msgTypes[20] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *CurrencyConversionRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*CurrencyConversionRequest) ProtoMessage() {} func (x *CurrencyConversionRequest) ProtoReflect() protoreflect.Message { mi := &file_demo_proto_msgTypes[20] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use CurrencyConversionRequest.ProtoReflect.Descriptor instead. func (*CurrencyConversionRequest) Descriptor() ([]byte, []int) { return file_demo_proto_rawDescGZIP(), []int{20} } func (x *CurrencyConversionRequest) GetFrom() *Money { if x != nil { return x.From } return nil } func (x *CurrencyConversionRequest) GetToCode() string { if x != nil { return x.ToCode } return "" } type CreditCardInfo struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields CreditCardNumber string `protobuf:"bytes,1,opt,name=credit_card_number,json=creditCardNumber,proto3" json:"credit_card_number,omitempty"` CreditCardCvv int32 `protobuf:"varint,2,opt,name=credit_card_cvv,json=creditCardCvv,proto3" json:"credit_card_cvv,omitempty"` CreditCardExpirationYear int32 `protobuf:"varint,3,opt,name=credit_card_expiration_year,json=creditCardExpirationYear,proto3" json:"credit_card_expiration_year,omitempty"` CreditCardExpirationMonth int32 `protobuf:"varint,4,opt,name=credit_card_expiration_month,json=creditCardExpirationMonth,proto3" json:"credit_card_expiration_month,omitempty"` } func (x *CreditCardInfo) Reset() { *x = CreditCardInfo{} if protoimpl.UnsafeEnabled { mi := &file_demo_proto_msgTypes[21] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *CreditCardInfo) String() string { return protoimpl.X.MessageStringOf(x) } func (*CreditCardInfo) ProtoMessage() {} func (x *CreditCardInfo) ProtoReflect() protoreflect.Message { mi := &file_demo_proto_msgTypes[21] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use CreditCardInfo.ProtoReflect.Descriptor instead. func (*CreditCardInfo) Descriptor() ([]byte, []int) { return file_demo_proto_rawDescGZIP(), []int{21} } func (x *CreditCardInfo) GetCreditCardNumber() string { if x != nil { return x.CreditCardNumber } return "" } func (x *CreditCardInfo) GetCreditCardCvv() int32 { if x != nil { return x.CreditCardCvv } return 0 } func (x *CreditCardInfo) GetCreditCardExpirationYear() int32 { if x != nil { return x.CreditCardExpirationYear } return 0 } func (x *CreditCardInfo) GetCreditCardExpirationMonth() int32 { if x != nil { return x.CreditCardExpirationMonth } return 0 } type ChargeRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Amount *Money `protobuf:"bytes,1,opt,name=amount,proto3" json:"amount,omitempty"` CreditCard *CreditCardInfo `protobuf:"bytes,2,opt,name=credit_card,json=creditCard,proto3" json:"credit_card,omitempty"` } func (x *ChargeRequest) Reset() { *x = ChargeRequest{} if protoimpl.UnsafeEnabled { mi := &file_demo_proto_msgTypes[22] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *ChargeRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*ChargeRequest) ProtoMessage() {} func (x *ChargeRequest) ProtoReflect() protoreflect.Message { mi := &file_demo_proto_msgTypes[22] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use ChargeRequest.ProtoReflect.Descriptor instead. func (*ChargeRequest) Descriptor() ([]byte, []int) { return file_demo_proto_rawDescGZIP(), []int{22} } func (x *ChargeRequest) GetAmount() *Money { if x != nil { return x.Amount } return nil } func (x *ChargeRequest) GetCreditCard() *CreditCardInfo { if x != nil { return x.CreditCard } return nil } type ChargeResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields TransactionId string `protobuf:"bytes,1,opt,name=transaction_id,json=transactionId,proto3" json:"transaction_id,omitempty"` } func (x *ChargeResponse) Reset() { *x = ChargeResponse{} if protoimpl.UnsafeEnabled { mi := &file_demo_proto_msgTypes[23] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *ChargeResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*ChargeResponse) ProtoMessage() {} func (x *ChargeResponse) ProtoReflect() protoreflect.Message { mi := &file_demo_proto_msgTypes[23] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use ChargeResponse.ProtoReflect.Descriptor instead. func (*ChargeResponse) Descriptor() ([]byte, []int) { return file_demo_proto_rawDescGZIP(), []int{23} } func (x *ChargeResponse) GetTransactionId() string { if x != nil { return x.TransactionId } return "" } type OrderItem struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Item *CartItem `protobuf:"bytes,1,opt,name=item,proto3" json:"item,omitempty"` Cost *Money `protobuf:"bytes,2,opt,name=cost,proto3" json:"cost,omitempty"` } func (x *OrderItem) Reset() { *x = OrderItem{} if protoimpl.UnsafeEnabled { mi := &file_demo_proto_msgTypes[24] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *OrderItem) String() string { return protoimpl.X.MessageStringOf(x) } func (*OrderItem) ProtoMessage() {} func (x *OrderItem) ProtoReflect() protoreflect.Message { mi := &file_demo_proto_msgTypes[24] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use OrderItem.ProtoReflect.Descriptor instead. func (*OrderItem) Descriptor() ([]byte, []int) { return file_demo_proto_rawDescGZIP(), []int{24} } func (x *OrderItem) GetItem() *CartItem { if x != nil { return x.Item } return nil } func (x *OrderItem) GetCost() *Money { if x != nil { return x.Cost } return nil } type OrderResult struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields OrderId string `protobuf:"bytes,1,opt,name=order_id,json=orderId,proto3" json:"order_id,omitempty"` ShippingTrackingId string `protobuf:"bytes,2,opt,name=shipping_tracking_id,json=shippingTrackingId,proto3" json:"shipping_tracking_id,omitempty"` ShippingCost *Money `protobuf:"bytes,3,opt,name=shipping_cost,json=shippingCost,proto3" json:"shipping_cost,omitempty"` ShippingAddress *Address `protobuf:"bytes,4,opt,name=shipping_address,json=shippingAddress,proto3" json:"shipping_address,omitempty"` Items []*OrderItem `protobuf:"bytes,5,rep,name=items,proto3" json:"items,omitempty"` } func (x *OrderResult) Reset() { *x = OrderResult{} if protoimpl.UnsafeEnabled { mi := &file_demo_proto_msgTypes[25] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *OrderResult) String() string { return protoimpl.X.MessageStringOf(x) } func (*OrderResult) ProtoMessage() {} func (x *OrderResult) ProtoReflect() protoreflect.Message { mi := &file_demo_proto_msgTypes[25] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use OrderResult.ProtoReflect.Descriptor instead. func (*OrderResult) Descriptor() ([]byte, []int) { return file_demo_proto_rawDescGZIP(), []int{25} } func (x *OrderResult) GetOrderId() string { if x != nil { return x.OrderId } return "" } func (x *OrderResult) GetShippingTrackingId() string { if x != nil { return x.ShippingTrackingId } return "" } func (x *OrderResult) GetShippingCost() *Money { if x != nil { return x.ShippingCost } return nil } func (x *OrderResult) GetShippingAddress() *Address { if x != nil { return x.ShippingAddress } return nil } func (x *OrderResult) GetItems() []*OrderItem { if x != nil { return x.Items } return nil } type SendOrderConfirmationRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Email string `protobuf:"bytes,1,opt,name=email,proto3" json:"email,omitempty"` Order *OrderResult `protobuf:"bytes,2,opt,name=order,proto3" json:"order,omitempty"` } func (x *SendOrderConfirmationRequest) Reset() { *x = SendOrderConfirmationRequest{} if protoimpl.UnsafeEnabled { mi := &file_demo_proto_msgTypes[26] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *SendOrderConfirmationRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*SendOrderConfirmationRequest) ProtoMessage() {} func (x *SendOrderConfirmationRequest) ProtoReflect() protoreflect.Message { mi := &file_demo_proto_msgTypes[26] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use SendOrderConfirmationRequest.ProtoReflect.Descriptor instead. func (*SendOrderConfirmationRequest) Descriptor() ([]byte, []int) { return file_demo_proto_rawDescGZIP(), []int{26} } func (x *SendOrderConfirmationRequest) GetEmail() string { if x != nil { return x.Email } return "" } func (x *SendOrderConfirmationRequest) GetOrder() *OrderResult { if x != nil { return x.Order } return nil } type PlaceOrderRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields UserId string `protobuf:"bytes,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` UserCurrency string `protobuf:"bytes,2,opt,name=user_currency,json=userCurrency,proto3" json:"user_currency,omitempty"` Address *Address `protobuf:"bytes,3,opt,name=address,proto3" json:"address,omitempty"` Email string `protobuf:"bytes,5,opt,name=email,proto3" json:"email,omitempty"` CreditCard *CreditCardInfo `protobuf:"bytes,6,opt,name=credit_card,json=creditCard,proto3" json:"credit_card,omitempty"` } func (x *PlaceOrderRequest) Reset() { *x = PlaceOrderRequest{} if protoimpl.UnsafeEnabled { mi := &file_demo_proto_msgTypes[27] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *PlaceOrderRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*PlaceOrderRequest) ProtoMessage() {} func (x *PlaceOrderRequest) ProtoReflect() protoreflect.Message { mi := &file_demo_proto_msgTypes[27] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use PlaceOrderRequest.ProtoReflect.Descriptor instead. func (*PlaceOrderRequest) Descriptor() ([]byte, []int) { return file_demo_proto_rawDescGZIP(), []int{27} } func (x *PlaceOrderRequest) GetUserId() string { if x != nil { return x.UserId } return "" } func (x *PlaceOrderRequest) GetUserCurrency() string { if x != nil { return x.UserCurrency } return "" } func (x *PlaceOrderRequest) GetAddress() *Address { if x != nil { return x.Address } return nil } func (x *PlaceOrderRequest) GetEmail() string { if x != nil { return x.Email } return "" } func (x *PlaceOrderRequest) GetCreditCard() *CreditCardInfo { if x != nil { return x.CreditCard } return nil } type PlaceOrderResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Order *OrderResult `protobuf:"bytes,1,opt,name=order,proto3" json:"order,omitempty"` } func (x *PlaceOrderResponse) Reset() { *x = PlaceOrderResponse{} if protoimpl.UnsafeEnabled { mi := &file_demo_proto_msgTypes[28] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *PlaceOrderResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*PlaceOrderResponse) ProtoMessage() {} func (x *PlaceOrderResponse) ProtoReflect() protoreflect.Message { mi := &file_demo_proto_msgTypes[28] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use PlaceOrderResponse.ProtoReflect.Descriptor instead. func (*PlaceOrderResponse) Descriptor() ([]byte, []int) { return file_demo_proto_rawDescGZIP(), []int{28} } func (x *PlaceOrderResponse) GetOrder() *OrderResult { if x != nil { return x.Order } return nil } type AdRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // List of important key words from the current page describing the context. ContextKeys []string `protobuf:"bytes,1,rep,name=context_keys,json=contextKeys,proto3" json:"context_keys,omitempty"` } func (x *AdRequest) Reset() { *x = AdRequest{} if protoimpl.UnsafeEnabled { mi := &file_demo_proto_msgTypes[29] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *AdRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*AdRequest) ProtoMessage() {} func (x *AdRequest) ProtoReflect() protoreflect.Message { mi := &file_demo_proto_msgTypes[29] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use AdRequest.ProtoReflect.Descriptor instead. func (*AdRequest) Descriptor() ([]byte, []int) { return file_demo_proto_rawDescGZIP(), []int{29} } func (x *AdRequest) GetContextKeys() []string { if x != nil { return x.ContextKeys } return nil } type AdResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Ads []*Ad `protobuf:"bytes,1,rep,name=ads,proto3" json:"ads,omitempty"` } func (x *AdResponse) Reset() { *x = AdResponse{} if protoimpl.UnsafeEnabled { mi := &file_demo_proto_msgTypes[30] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *AdResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*AdResponse) ProtoMessage() {} func (x *AdResponse) ProtoReflect() protoreflect.Message { mi := &file_demo_proto_msgTypes[30] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use AdResponse.ProtoReflect.Descriptor instead. func (*AdResponse) Descriptor() ([]byte, []int) { return file_demo_proto_rawDescGZIP(), []int{30} } func (x *AdResponse) GetAds() []*Ad { if x != nil { return x.Ads } return nil } type Ad struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // url to redirect to when an ad is clicked. RedirectUrl string `protobuf:"bytes,1,opt,name=redirect_url,json=redirectUrl,proto3" json:"redirect_url,omitempty"` // short advertisement text to display. Text string `protobuf:"bytes,2,opt,name=text,proto3" json:"text,omitempty"` } func (x *Ad) Reset() { *x = Ad{} if protoimpl.UnsafeEnabled { mi := &file_demo_proto_msgTypes[31] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Ad) String() string { return protoimpl.X.MessageStringOf(x) } func (*Ad) ProtoMessage() {} func (x *Ad) ProtoReflect() protoreflect.Message { mi := &file_demo_proto_msgTypes[31] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use Ad.ProtoReflect.Descriptor instead. func (*Ad) Descriptor() ([]byte, []int) { return file_demo_proto_rawDescGZIP(), []int{31} } func (x *Ad) GetRedirectUrl() string { if x != nil { return x.RedirectUrl } return "" } func (x *Ad) GetText() string { if x != nil { return x.Text } return "" } var File_demo_proto protoreflect.FileDescriptor var file_demo_proto_rawDesc = []byte{ 0x0a, 0x0a, 0x64, 0x65, 0x6d, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0b, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x22, 0x45, 0x0a, 0x08, 0x43, 0x61, 0x72, 0x74, 0x49, 0x74, 0x65, 0x6d, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x71, 0x75, 0x61, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x71, 0x75, 0x61, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x22, 0x54, 0x0a, 0x0e, 0x41, 0x64, 0x64, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x29, 0x0a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x43, 0x61, 0x72, 0x74, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x22, 0x2b, 0x0a, 0x10, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x43, 0x61, 0x72, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0x29, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x43, 0x61, 0x72, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0x4c, 0x0a, 0x04, 0x43, 0x61, 0x72, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x2b, 0x0a, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x43, 0x61, 0x72, 0x74, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x22, 0x07, 0x0a, 0x05, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x56, 0x0a, 0x1a, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x49, 0x64, 0x73, 0x22, 0x3e, 0x0a, 0x1b, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x49, 0x64, 0x73, 0x22, 0xba, 0x01, 0x0a, 0x07, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x69, 0x63, 0x74, 0x75, 0x72, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x70, 0x69, 0x63, 0x74, 0x75, 0x72, 0x65, 0x12, 0x2f, 0x0a, 0x09, 0x70, 0x72, 0x69, 0x63, 0x65, 0x5f, 0x75, 0x73, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x4d, 0x6f, 0x6e, 0x65, 0x79, 0x52, 0x08, 0x70, 0x72, 0x69, 0x63, 0x65, 0x55, 0x73, 0x64, 0x12, 0x1e, 0x0a, 0x0a, 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x69, 0x65, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x69, 0x65, 0x73, 0x22, 0x48, 0x0a, 0x14, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x30, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x73, 0x22, 0x23, 0x0a, 0x11, 0x47, 0x65, 0x74, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x22, 0x2d, 0x0a, 0x15, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x22, 0x48, 0x0a, 0x16, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2e, 0x0a, 0x07, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x52, 0x07, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x22, 0x6e, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x51, 0x75, 0x6f, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x2b, 0x0a, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x43, 0x61, 0x72, 0x74, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x22, 0x41, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x51, 0x75, 0x6f, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2d, 0x0a, 0x08, 0x63, 0x6f, 0x73, 0x74, 0x5f, 0x75, 0x73, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x4d, 0x6f, 0x6e, 0x65, 0x79, 0x52, 0x07, 0x63, 0x6f, 0x73, 0x74, 0x55, 0x73, 0x64, 0x22, 0x6f, 0x0a, 0x10, 0x53, 0x68, 0x69, 0x70, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x2b, 0x0a, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x43, 0x61, 0x72, 0x74, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x22, 0x34, 0x0a, 0x11, 0x53, 0x68, 0x69, 0x70, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x74, 0x72, 0x61, 0x63, 0x6b, 0x69, 0x6e, 0x67, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x74, 0x72, 0x61, 0x63, 0x6b, 0x69, 0x6e, 0x67, 0x49, 0x64, 0x22, 0x8f, 0x01, 0x0a, 0x07, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x25, 0x0a, 0x0e, 0x73, 0x74, 0x72, 0x65, 0x65, 0x74, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x73, 0x74, 0x72, 0x65, 0x65, 0x74, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x63, 0x69, 0x74, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x63, 0x69, 0x74, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x19, 0x0a, 0x08, 0x7a, 0x69, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x07, 0x7a, 0x69, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x22, 0x58, 0x0a, 0x05, 0x4d, 0x6f, 0x6e, 0x65, 0x79, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x75, 0x6e, 0x69, 0x74, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x75, 0x6e, 0x69, 0x74, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x6e, 0x61, 0x6e, 0x6f, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x6e, 0x61, 0x6e, 0x6f, 0x73, 0x22, 0x47, 0x0a, 0x1e, 0x47, 0x65, 0x74, 0x53, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x69, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0d, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x43, 0x6f, 0x64, 0x65, 0x73, 0x22, 0x5c, 0x0a, 0x19, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x43, 0x6f, 0x6e, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x26, 0x0a, 0x04, 0x66, 0x72, 0x6f, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x4d, 0x6f, 0x6e, 0x65, 0x79, 0x52, 0x04, 0x66, 0x72, 0x6f, 0x6d, 0x12, 0x17, 0x0a, 0x07, 0x74, 0x6f, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x74, 0x6f, 0x43, 0x6f, 0x64, 0x65, 0x22, 0xe6, 0x01, 0x0a, 0x0e, 0x43, 0x72, 0x65, 0x64, 0x69, 0x74, 0x43, 0x61, 0x72, 0x64, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x2c, 0x0a, 0x12, 0x63, 0x72, 0x65, 0x64, 0x69, 0x74, 0x5f, 0x63, 0x61, 0x72, 0x64, 0x5f, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x63, 0x72, 0x65, 0x64, 0x69, 0x74, 0x43, 0x61, 0x72, 0x64, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x26, 0x0a, 0x0f, 0x63, 0x72, 0x65, 0x64, 0x69, 0x74, 0x5f, 0x63, 0x61, 0x72, 0x64, 0x5f, 0x63, 0x76, 0x76, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0d, 0x63, 0x72, 0x65, 0x64, 0x69, 0x74, 0x43, 0x61, 0x72, 0x64, 0x43, 0x76, 0x76, 0x12, 0x3d, 0x0a, 0x1b, 0x63, 0x72, 0x65, 0x64, 0x69, 0x74, 0x5f, 0x63, 0x61, 0x72, 0x64, 0x5f, 0x65, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x79, 0x65, 0x61, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x18, 0x63, 0x72, 0x65, 0x64, 0x69, 0x74, 0x43, 0x61, 0x72, 0x64, 0x45, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x59, 0x65, 0x61, 0x72, 0x12, 0x3f, 0x0a, 0x1c, 0x63, 0x72, 0x65, 0x64, 0x69, 0x74, 0x5f, 0x63, 0x61, 0x72, 0x64, 0x5f, 0x65, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6d, 0x6f, 0x6e, 0x74, 0x68, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x19, 0x63, 0x72, 0x65, 0x64, 0x69, 0x74, 0x43, 0x61, 0x72, 0x64, 0x45, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x6f, 0x6e, 0x74, 0x68, 0x22, 0x79, 0x0a, 0x0d, 0x43, 0x68, 0x61, 0x72, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2a, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x4d, 0x6f, 0x6e, 0x65, 0x79, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x3c, 0x0a, 0x0b, 0x63, 0x72, 0x65, 0x64, 0x69, 0x74, 0x5f, 0x63, 0x61, 0x72, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x43, 0x72, 0x65, 0x64, 0x69, 0x74, 0x43, 0x61, 0x72, 0x64, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x0a, 0x63, 0x72, 0x65, 0x64, 0x69, 0x74, 0x43, 0x61, 0x72, 0x64, 0x22, 0x37, 0x0a, 0x0e, 0x43, 0x68, 0x61, 0x72, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x22, 0x5e, 0x0a, 0x09, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x49, 0x74, 0x65, 0x6d, 0x12, 0x29, 0x0a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x43, 0x61, 0x72, 0x74, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x12, 0x26, 0x0a, 0x04, 0x63, 0x6f, 0x73, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x4d, 0x6f, 0x6e, 0x65, 0x79, 0x52, 0x04, 0x63, 0x6f, 0x73, 0x74, 0x22, 0x82, 0x02, 0x0a, 0x0b, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x49, 0x64, 0x12, 0x30, 0x0a, 0x14, 0x73, 0x68, 0x69, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x5f, 0x74, 0x72, 0x61, 0x63, 0x6b, 0x69, 0x6e, 0x67, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x12, 0x73, 0x68, 0x69, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x54, 0x72, 0x61, 0x63, 0x6b, 0x69, 0x6e, 0x67, 0x49, 0x64, 0x12, 0x37, 0x0a, 0x0d, 0x73, 0x68, 0x69, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x6f, 0x73, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x4d, 0x6f, 0x6e, 0x65, 0x79, 0x52, 0x0c, 0x73, 0x68, 0x69, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x73, 0x74, 0x12, 0x3f, 0x0a, 0x10, 0x73, 0x68, 0x69, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, 0x0f, 0x73, 0x68, 0x69, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x2c, 0x0a, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x22, 0x64, 0x0a, 0x1c, 0x53, 0x65, 0x6e, 0x64, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x12, 0x2e, 0x0a, 0x05, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x05, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x22, 0xd5, 0x01, 0x0a, 0x11, 0x50, 0x6c, 0x61, 0x63, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x75, 0x73, 0x65, 0x72, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x12, 0x2e, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x12, 0x3c, 0x0a, 0x0b, 0x63, 0x72, 0x65, 0x64, 0x69, 0x74, 0x5f, 0x63, 0x61, 0x72, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x43, 0x72, 0x65, 0x64, 0x69, 0x74, 0x43, 0x61, 0x72, 0x64, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x0a, 0x63, 0x72, 0x65, 0x64, 0x69, 0x74, 0x43, 0x61, 0x72, 0x64, 0x22, 0x44, 0x0a, 0x12, 0x50, 0x6c, 0x61, 0x63, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2e, 0x0a, 0x05, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x05, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x22, 0x2e, 0x0a, 0x09, 0x41, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x5f, 0x6b, 0x65, 0x79, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x4b, 0x65, 0x79, 0x73, 0x22, 0x2f, 0x0a, 0x0a, 0x41, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x21, 0x0a, 0x03, 0x61, 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x41, 0x64, 0x52, 0x03, 0x61, 0x64, 0x73, 0x22, 0x3b, 0x0a, 0x02, 0x41, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x72, 0x65, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x72, 0x65, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x55, 0x72, 0x6c, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x65, 0x78, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x65, 0x78, 0x74, 0x32, 0xca, 0x01, 0x0a, 0x0b, 0x43, 0x61, 0x72, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x3c, 0x0a, 0x07, 0x41, 0x64, 0x64, 0x49, 0x74, 0x65, 0x6d, 0x12, 0x1b, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x41, 0x64, 0x64, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x3b, 0x0a, 0x07, 0x47, 0x65, 0x74, 0x43, 0x61, 0x72, 0x74, 0x12, 0x1b, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x61, 0x72, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x11, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x43, 0x61, 0x72, 0x74, 0x22, 0x00, 0x12, 0x40, 0x0a, 0x09, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x43, 0x61, 0x72, 0x74, 0x12, 0x1d, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x43, 0x61, 0x72, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x32, 0x83, 0x01, 0x0a, 0x15, 0x52, 0x65, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x6a, 0x0a, 0x13, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x27, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x32, 0x83, 0x02, 0x0a, 0x15, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x43, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x47, 0x0a, 0x0c, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x73, 0x12, 0x12, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x21, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x44, 0x0a, 0x0a, 0x47, 0x65, 0x74, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x12, 0x1e, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x47, 0x65, 0x74, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x22, 0x00, 0x12, 0x5b, 0x0a, 0x0e, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x73, 0x12, 0x22, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x32, 0xaa, 0x01, 0x0a, 0x0f, 0x53, 0x68, 0x69, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x49, 0x0a, 0x08, 0x47, 0x65, 0x74, 0x51, 0x75, 0x6f, 0x74, 0x65, 0x12, 0x1c, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x47, 0x65, 0x74, 0x51, 0x75, 0x6f, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x47, 0x65, 0x74, 0x51, 0x75, 0x6f, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x4c, 0x0a, 0x09, 0x53, 0x68, 0x69, 0x70, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x1d, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x53, 0x68, 0x69, 0x70, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x53, 0x68, 0x69, 0x70, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x32, 0xb7, 0x01, 0x0a, 0x0f, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x5b, 0x0a, 0x16, 0x47, 0x65, 0x74, 0x53, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x69, 0x65, 0x73, 0x12, 0x12, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x2b, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x69, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x47, 0x0a, 0x07, 0x43, 0x6f, 0x6e, 0x76, 0x65, 0x72, 0x74, 0x12, 0x26, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x43, 0x6f, 0x6e, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x4d, 0x6f, 0x6e, 0x65, 0x79, 0x22, 0x00, 0x32, 0x55, 0x0a, 0x0e, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x43, 0x0a, 0x06, 0x43, 0x68, 0x61, 0x72, 0x67, 0x65, 0x12, 0x1a, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x43, 0x68, 0x61, 0x72, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x43, 0x68, 0x61, 0x72, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x32, 0x68, 0x0a, 0x0c, 0x45, 0x6d, 0x61, 0x69, 0x6c, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x58, 0x0a, 0x15, 0x53, 0x65, 0x6e, 0x64, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x29, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x32, 0x62, 0x0a, 0x0f, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x6f, 0x75, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x4f, 0x0a, 0x0a, 0x50, 0x6c, 0x61, 0x63, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x1e, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x50, 0x6c, 0x61, 0x63, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x50, 0x6c, 0x61, 0x63, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x32, 0x48, 0x0a, 0x09, 0x41, 0x64, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x3b, 0x0a, 0x06, 0x47, 0x65, 0x74, 0x41, 0x64, 0x73, 0x12, 0x16, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x41, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x41, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x40, 0x5a, 0x3e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x47, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x43, 0x6c, 0x6f, 0x75, 0x64, 0x50, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x2f, 0x6d, 0x69, 0x73, 0x63, 0x72, 0x6f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2d, 0x64, 0x65, 0x6d, 0x6f, 0x2f, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( file_demo_proto_rawDescOnce sync.Once file_demo_proto_rawDescData = file_demo_proto_rawDesc ) func file_demo_proto_rawDescGZIP() []byte { file_demo_proto_rawDescOnce.Do(func() { file_demo_proto_rawDescData = protoimpl.X.CompressGZIP(file_demo_proto_rawDescData) }) return file_demo_proto_rawDescData } var file_demo_proto_msgTypes = make([]protoimpl.MessageInfo, 32) var file_demo_proto_goTypes = []any{ (*CartItem)(nil), // 0: hipstershop.CartItem (*AddItemRequest)(nil), // 1: hipstershop.AddItemRequest (*EmptyCartRequest)(nil), // 2: hipstershop.EmptyCartRequest (*GetCartRequest)(nil), // 3: hipstershop.GetCartRequest (*Cart)(nil), // 4: hipstershop.Cart (*Empty)(nil), // 5: hipstershop.Empty (*ListRecommendationsRequest)(nil), // 6: hipstershop.ListRecommendationsRequest (*ListRecommendationsResponse)(nil), // 7: hipstershop.ListRecommendationsResponse (*Product)(nil), // 8: hipstershop.Product (*ListProductsResponse)(nil), // 9: hipstershop.ListProductsResponse (*GetProductRequest)(nil), // 10: hipstershop.GetProductRequest (*SearchProductsRequest)(nil), // 11: hipstershop.SearchProductsRequest (*SearchProductsResponse)(nil), // 12: hipstershop.SearchProductsResponse (*GetQuoteRequest)(nil), // 13: hipstershop.GetQuoteRequest (*GetQuoteResponse)(nil), // 14: hipstershop.GetQuoteResponse (*ShipOrderRequest)(nil), // 15: hipstershop.ShipOrderRequest (*ShipOrderResponse)(nil), // 16: hipstershop.ShipOrderResponse (*Address)(nil), // 17: hipstershop.Address (*Money)(nil), // 18: hipstershop.Money (*GetSupportedCurrenciesResponse)(nil), // 19: hipstershop.GetSupportedCurrenciesResponse (*CurrencyConversionRequest)(nil), // 20: hipstershop.CurrencyConversionRequest (*CreditCardInfo)(nil), // 21: hipstershop.CreditCardInfo (*ChargeRequest)(nil), // 22: hipstershop.ChargeRequest (*ChargeResponse)(nil), // 23: hipstershop.ChargeResponse (*OrderItem)(nil), // 24: hipstershop.OrderItem (*OrderResult)(nil), // 25: hipstershop.OrderResult (*SendOrderConfirmationRequest)(nil), // 26: hipstershop.SendOrderConfirmationRequest (*PlaceOrderRequest)(nil), // 27: hipstershop.PlaceOrderRequest (*PlaceOrderResponse)(nil), // 28: hipstershop.PlaceOrderResponse (*AdRequest)(nil), // 29: hipstershop.AdRequest (*AdResponse)(nil), // 30: hipstershop.AdResponse (*Ad)(nil), // 31: hipstershop.Ad } var file_demo_proto_depIdxs = []int32{ 0, // 0: hipstershop.AddItemRequest.item:type_name -> hipstershop.CartItem 0, // 1: hipstershop.Cart.items:type_name -> hipstershop.CartItem 18, // 2: hipstershop.Product.price_usd:type_name -> hipstershop.Money 8, // 3: hipstershop.ListProductsResponse.products:type_name -> hipstershop.Product 8, // 4: hipstershop.SearchProductsResponse.results:type_name -> hipstershop.Product 17, // 5: hipstershop.GetQuoteRequest.address:type_name -> hipstershop.Address 0, // 6: hipstershop.GetQuoteRequest.items:type_name -> hipstershop.CartItem 18, // 7: hipstershop.GetQuoteResponse.cost_usd:type_name -> hipstershop.Money 17, // 8: hipstershop.ShipOrderRequest.address:type_name -> hipstershop.Address 0, // 9: hipstershop.ShipOrderRequest.items:type_name -> hipstershop.CartItem 18, // 10: hipstershop.CurrencyConversionRequest.from:type_name -> hipstershop.Money 18, // 11: hipstershop.ChargeRequest.amount:type_name -> hipstershop.Money 21, // 12: hipstershop.ChargeRequest.credit_card:type_name -> hipstershop.CreditCardInfo 0, // 13: hipstershop.OrderItem.item:type_name -> hipstershop.CartItem 18, // 14: hipstershop.OrderItem.cost:type_name -> hipstershop.Money 18, // 15: hipstershop.OrderResult.shipping_cost:type_name -> hipstershop.Money 17, // 16: hipstershop.OrderResult.shipping_address:type_name -> hipstershop.Address 24, // 17: hipstershop.OrderResult.items:type_name -> hipstershop.OrderItem 25, // 18: hipstershop.SendOrderConfirmationRequest.order:type_name -> hipstershop.OrderResult 17, // 19: hipstershop.PlaceOrderRequest.address:type_name -> hipstershop.Address 21, // 20: hipstershop.PlaceOrderRequest.credit_card:type_name -> hipstershop.CreditCardInfo 25, // 21: hipstershop.PlaceOrderResponse.order:type_name -> hipstershop.OrderResult 31, // 22: hipstershop.AdResponse.ads:type_name -> hipstershop.Ad 1, // 23: hipstershop.CartService.AddItem:input_type -> hipstershop.AddItemRequest 3, // 24: hipstershop.CartService.GetCart:input_type -> hipstershop.GetCartRequest 2, // 25: hipstershop.CartService.EmptyCart:input_type -> hipstershop.EmptyCartRequest 6, // 26: hipstershop.RecommendationService.ListRecommendations:input_type -> hipstershop.ListRecommendationsRequest 5, // 27: hipstershop.ProductCatalogService.ListProducts:input_type -> hipstershop.Empty 10, // 28: hipstershop.ProductCatalogService.GetProduct:input_type -> hipstershop.GetProductRequest 11, // 29: hipstershop.ProductCatalogService.SearchProducts:input_type -> hipstershop.SearchProductsRequest 13, // 30: hipstershop.ShippingService.GetQuote:input_type -> hipstershop.GetQuoteRequest 15, // 31: hipstershop.ShippingService.ShipOrder:input_type -> hipstershop.ShipOrderRequest 5, // 32: hipstershop.CurrencyService.GetSupportedCurrencies:input_type -> hipstershop.Empty 20, // 33: hipstershop.CurrencyService.Convert:input_type -> hipstershop.CurrencyConversionRequest 22, // 34: hipstershop.PaymentService.Charge:input_type -> hipstershop.ChargeRequest 26, // 35: hipstershop.EmailService.SendOrderConfirmation:input_type -> hipstershop.SendOrderConfirmationRequest 27, // 36: hipstershop.CheckoutService.PlaceOrder:input_type -> hipstershop.PlaceOrderRequest 29, // 37: hipstershop.AdService.GetAds:input_type -> hipstershop.AdRequest 5, // 38: hipstershop.CartService.AddItem:output_type -> hipstershop.Empty 4, // 39: hipstershop.CartService.GetCart:output_type -> hipstershop.Cart 5, // 40: hipstershop.CartService.EmptyCart:output_type -> hipstershop.Empty 7, // 41: hipstershop.RecommendationService.ListRecommendations:output_type -> hipstershop.ListRecommendationsResponse 9, // 42: hipstershop.ProductCatalogService.ListProducts:output_type -> hipstershop.ListProductsResponse 8, // 43: hipstershop.ProductCatalogService.GetProduct:output_type -> hipstershop.Product 12, // 44: hipstershop.ProductCatalogService.SearchProducts:output_type -> hipstershop.SearchProductsResponse 14, // 45: hipstershop.ShippingService.GetQuote:output_type -> hipstershop.GetQuoteResponse 16, // 46: hipstershop.ShippingService.ShipOrder:output_type -> hipstershop.ShipOrderResponse 19, // 47: hipstershop.CurrencyService.GetSupportedCurrencies:output_type -> hipstershop.GetSupportedCurrenciesResponse 18, // 48: hipstershop.CurrencyService.Convert:output_type -> hipstershop.Money 23, // 49: hipstershop.PaymentService.Charge:output_type -> hipstershop.ChargeResponse 5, // 50: hipstershop.EmailService.SendOrderConfirmation:output_type -> hipstershop.Empty 28, // 51: hipstershop.CheckoutService.PlaceOrder:output_type -> hipstershop.PlaceOrderResponse 30, // 52: hipstershop.AdService.GetAds:output_type -> hipstershop.AdResponse 38, // [38:53] is the sub-list for method output_type 23, // [23:38] is the sub-list for method input_type 23, // [23:23] is the sub-list for extension type_name 23, // [23:23] is the sub-list for extension extendee 0, // [0:23] is the sub-list for field type_name } func init() { file_demo_proto_init() } func file_demo_proto_init() { if File_demo_proto != nil { return } if !protoimpl.UnsafeEnabled { file_demo_proto_msgTypes[0].Exporter = func(v any, i int) any { switch v := v.(*CartItem); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_demo_proto_msgTypes[1].Exporter = func(v any, i int) any { switch v := v.(*AddItemRequest); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_demo_proto_msgTypes[2].Exporter = func(v any, i int) any { switch v := v.(*EmptyCartRequest); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_demo_proto_msgTypes[3].Exporter = func(v any, i int) any { switch v := v.(*GetCartRequest); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_demo_proto_msgTypes[4].Exporter = func(v any, i int) any { switch v := v.(*Cart); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_demo_proto_msgTypes[5].Exporter = func(v any, i int) any { switch v := v.(*Empty); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_demo_proto_msgTypes[6].Exporter = func(v any, i int) any { switch v := v.(*ListRecommendationsRequest); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_demo_proto_msgTypes[7].Exporter = func(v any, i int) any { switch v := v.(*ListRecommendationsResponse); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_demo_proto_msgTypes[8].Exporter = func(v any, i int) any { switch v := v.(*Product); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_demo_proto_msgTypes[9].Exporter = func(v any, i int) any { switch v := v.(*ListProductsResponse); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_demo_proto_msgTypes[10].Exporter = func(v any, i int) any { switch v := v.(*GetProductRequest); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_demo_proto_msgTypes[11].Exporter = func(v any, i int) any { switch v := v.(*SearchProductsRequest); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_demo_proto_msgTypes[12].Exporter = func(v any, i int) any { switch v := v.(*SearchProductsResponse); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_demo_proto_msgTypes[13].Exporter = func(v any, i int) any { switch v := v.(*GetQuoteRequest); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_demo_proto_msgTypes[14].Exporter = func(v any, i int) any { switch v := v.(*GetQuoteResponse); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_demo_proto_msgTypes[15].Exporter = func(v any, i int) any { switch v := v.(*ShipOrderRequest); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_demo_proto_msgTypes[16].Exporter = func(v any, i int) any { switch v := v.(*ShipOrderResponse); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_demo_proto_msgTypes[17].Exporter = func(v any, i int) any { switch v := v.(*Address); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_demo_proto_msgTypes[18].Exporter = func(v any, i int) any { switch v := v.(*Money); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_demo_proto_msgTypes[19].Exporter = func(v any, i int) any { switch v := v.(*GetSupportedCurrenciesResponse); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_demo_proto_msgTypes[20].Exporter = func(v any, i int) any { switch v := v.(*CurrencyConversionRequest); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_demo_proto_msgTypes[21].Exporter = func(v any, i int) any { switch v := v.(*CreditCardInfo); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_demo_proto_msgTypes[22].Exporter = func(v any, i int) any { switch v := v.(*ChargeRequest); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_demo_proto_msgTypes[23].Exporter = func(v any, i int) any { switch v := v.(*ChargeResponse); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_demo_proto_msgTypes[24].Exporter = func(v any, i int) any { switch v := v.(*OrderItem); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_demo_proto_msgTypes[25].Exporter = func(v any, i int) any { switch v := v.(*OrderResult); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_demo_proto_msgTypes[26].Exporter = func(v any, i int) any { switch v := v.(*SendOrderConfirmationRequest); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_demo_proto_msgTypes[27].Exporter = func(v any, i int) any { switch v := v.(*PlaceOrderRequest); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_demo_proto_msgTypes[28].Exporter = func(v any, i int) any { switch v := v.(*PlaceOrderResponse); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_demo_proto_msgTypes[29].Exporter = func(v any, i int) any { switch v := v.(*AdRequest); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_demo_proto_msgTypes[30].Exporter = func(v any, i int) any { switch v := v.(*AdResponse); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_demo_proto_msgTypes[31].Exporter = func(v any, i int) any { switch v := v.(*Ad); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_demo_proto_rawDesc, NumEnums: 0, NumMessages: 32, NumExtensions: 0, NumServices: 9, }, GoTypes: file_demo_proto_goTypes, DependencyIndexes: file_demo_proto_depIdxs, MessageInfos: file_demo_proto_msgTypes, }.Build() File_demo_proto = out.File file_demo_proto_rawDesc = nil file_demo_proto_goTypes = nil file_demo_proto_depIdxs = nil } ================================================ FILE: src/shippingservice/genproto/demo_grpc.pb.go ================================================ // Copyright 2020 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. // Code generated by protoc-gen-go-grpc. DO NOT EDIT. // versions: // - protoc-gen-go-grpc v1.5.1 // - protoc v3.6.1 // source: demo.proto package hipstershop import ( context "context" grpc "google.golang.org/grpc" codes "google.golang.org/grpc/codes" status "google.golang.org/grpc/status" ) // This is a compile-time assertion to ensure that this generated file // is compatible with the grpc package it is being compiled against. // Requires gRPC-Go v1.64.0 or later. const _ = grpc.SupportPackageIsVersion9 const ( CartService_AddItem_FullMethodName = "/hipstershop.CartService/AddItem" CartService_GetCart_FullMethodName = "/hipstershop.CartService/GetCart" CartService_EmptyCart_FullMethodName = "/hipstershop.CartService/EmptyCart" ) // CartServiceClient is the client API for CartService service. // // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. type CartServiceClient interface { AddItem(ctx context.Context, in *AddItemRequest, opts ...grpc.CallOption) (*Empty, error) GetCart(ctx context.Context, in *GetCartRequest, opts ...grpc.CallOption) (*Cart, error) EmptyCart(ctx context.Context, in *EmptyCartRequest, opts ...grpc.CallOption) (*Empty, error) } type cartServiceClient struct { cc grpc.ClientConnInterface } func NewCartServiceClient(cc grpc.ClientConnInterface) CartServiceClient { return &cartServiceClient{cc} } func (c *cartServiceClient) AddItem(ctx context.Context, in *AddItemRequest, opts ...grpc.CallOption) (*Empty, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(Empty) err := c.cc.Invoke(ctx, CartService_AddItem_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } return out, nil } func (c *cartServiceClient) GetCart(ctx context.Context, in *GetCartRequest, opts ...grpc.CallOption) (*Cart, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(Cart) err := c.cc.Invoke(ctx, CartService_GetCart_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } return out, nil } func (c *cartServiceClient) EmptyCart(ctx context.Context, in *EmptyCartRequest, opts ...grpc.CallOption) (*Empty, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(Empty) err := c.cc.Invoke(ctx, CartService_EmptyCart_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } return out, nil } // CartServiceServer is the server API for CartService service. // All implementations must embed UnimplementedCartServiceServer // for forward compatibility. type CartServiceServer interface { AddItem(context.Context, *AddItemRequest) (*Empty, error) GetCart(context.Context, *GetCartRequest) (*Cart, error) EmptyCart(context.Context, *EmptyCartRequest) (*Empty, error) mustEmbedUnimplementedCartServiceServer() } // UnimplementedCartServiceServer must be embedded to have // forward compatible implementations. // // NOTE: this should be embedded by value instead of pointer to avoid a nil // pointer dereference when methods are called. type UnimplementedCartServiceServer struct{} func (UnimplementedCartServiceServer) AddItem(context.Context, *AddItemRequest) (*Empty, error) { return nil, status.Errorf(codes.Unimplemented, "method AddItem not implemented") } func (UnimplementedCartServiceServer) GetCart(context.Context, *GetCartRequest) (*Cart, error) { return nil, status.Errorf(codes.Unimplemented, "method GetCart not implemented") } func (UnimplementedCartServiceServer) EmptyCart(context.Context, *EmptyCartRequest) (*Empty, error) { return nil, status.Errorf(codes.Unimplemented, "method EmptyCart not implemented") } func (UnimplementedCartServiceServer) mustEmbedUnimplementedCartServiceServer() {} func (UnimplementedCartServiceServer) testEmbeddedByValue() {} // UnsafeCartServiceServer may be embedded to opt out of forward compatibility for this service. // Use of this interface is not recommended, as added methods to CartServiceServer will // result in compilation errors. type UnsafeCartServiceServer interface { mustEmbedUnimplementedCartServiceServer() } func RegisterCartServiceServer(s grpc.ServiceRegistrar, srv CartServiceServer) { // If the following call pancis, it indicates UnimplementedCartServiceServer was // embedded by pointer and is nil. This will cause panics if an // unimplemented method is ever invoked, so we test this at initialization // time to prevent it from happening at runtime later due to I/O. if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { t.testEmbeddedByValue() } s.RegisterService(&CartService_ServiceDesc, srv) } func _CartService_AddItem_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(AddItemRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(CartServiceServer).AddItem(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: CartService_AddItem_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(CartServiceServer).AddItem(ctx, req.(*AddItemRequest)) } return interceptor(ctx, in, info, handler) } func _CartService_GetCart_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(GetCartRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(CartServiceServer).GetCart(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: CartService_GetCart_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(CartServiceServer).GetCart(ctx, req.(*GetCartRequest)) } return interceptor(ctx, in, info, handler) } func _CartService_EmptyCart_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(EmptyCartRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(CartServiceServer).EmptyCart(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: CartService_EmptyCart_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(CartServiceServer).EmptyCart(ctx, req.(*EmptyCartRequest)) } return interceptor(ctx, in, info, handler) } // CartService_ServiceDesc is the grpc.ServiceDesc for CartService service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) var CartService_ServiceDesc = grpc.ServiceDesc{ ServiceName: "hipstershop.CartService", HandlerType: (*CartServiceServer)(nil), Methods: []grpc.MethodDesc{ { MethodName: "AddItem", Handler: _CartService_AddItem_Handler, }, { MethodName: "GetCart", Handler: _CartService_GetCart_Handler, }, { MethodName: "EmptyCart", Handler: _CartService_EmptyCart_Handler, }, }, Streams: []grpc.StreamDesc{}, Metadata: "demo.proto", } const ( RecommendationService_ListRecommendations_FullMethodName = "/hipstershop.RecommendationService/ListRecommendations" ) // RecommendationServiceClient is the client API for RecommendationService service. // // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. type RecommendationServiceClient interface { ListRecommendations(ctx context.Context, in *ListRecommendationsRequest, opts ...grpc.CallOption) (*ListRecommendationsResponse, error) } type recommendationServiceClient struct { cc grpc.ClientConnInterface } func NewRecommendationServiceClient(cc grpc.ClientConnInterface) RecommendationServiceClient { return &recommendationServiceClient{cc} } func (c *recommendationServiceClient) ListRecommendations(ctx context.Context, in *ListRecommendationsRequest, opts ...grpc.CallOption) (*ListRecommendationsResponse, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(ListRecommendationsResponse) err := c.cc.Invoke(ctx, RecommendationService_ListRecommendations_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } return out, nil } // RecommendationServiceServer is the server API for RecommendationService service. // All implementations must embed UnimplementedRecommendationServiceServer // for forward compatibility. type RecommendationServiceServer interface { ListRecommendations(context.Context, *ListRecommendationsRequest) (*ListRecommendationsResponse, error) mustEmbedUnimplementedRecommendationServiceServer() } // UnimplementedRecommendationServiceServer must be embedded to have // forward compatible implementations. // // NOTE: this should be embedded by value instead of pointer to avoid a nil // pointer dereference when methods are called. type UnimplementedRecommendationServiceServer struct{} func (UnimplementedRecommendationServiceServer) ListRecommendations(context.Context, *ListRecommendationsRequest) (*ListRecommendationsResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method ListRecommendations not implemented") } func (UnimplementedRecommendationServiceServer) mustEmbedUnimplementedRecommendationServiceServer() {} func (UnimplementedRecommendationServiceServer) testEmbeddedByValue() {} // UnsafeRecommendationServiceServer may be embedded to opt out of forward compatibility for this service. // Use of this interface is not recommended, as added methods to RecommendationServiceServer will // result in compilation errors. type UnsafeRecommendationServiceServer interface { mustEmbedUnimplementedRecommendationServiceServer() } func RegisterRecommendationServiceServer(s grpc.ServiceRegistrar, srv RecommendationServiceServer) { // If the following call pancis, it indicates UnimplementedRecommendationServiceServer was // embedded by pointer and is nil. This will cause panics if an // unimplemented method is ever invoked, so we test this at initialization // time to prevent it from happening at runtime later due to I/O. if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { t.testEmbeddedByValue() } s.RegisterService(&RecommendationService_ServiceDesc, srv) } func _RecommendationService_ListRecommendations_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(ListRecommendationsRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(RecommendationServiceServer).ListRecommendations(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: RecommendationService_ListRecommendations_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(RecommendationServiceServer).ListRecommendations(ctx, req.(*ListRecommendationsRequest)) } return interceptor(ctx, in, info, handler) } // RecommendationService_ServiceDesc is the grpc.ServiceDesc for RecommendationService service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) var RecommendationService_ServiceDesc = grpc.ServiceDesc{ ServiceName: "hipstershop.RecommendationService", HandlerType: (*RecommendationServiceServer)(nil), Methods: []grpc.MethodDesc{ { MethodName: "ListRecommendations", Handler: _RecommendationService_ListRecommendations_Handler, }, }, Streams: []grpc.StreamDesc{}, Metadata: "demo.proto", } const ( ProductCatalogService_ListProducts_FullMethodName = "/hipstershop.ProductCatalogService/ListProducts" ProductCatalogService_GetProduct_FullMethodName = "/hipstershop.ProductCatalogService/GetProduct" ProductCatalogService_SearchProducts_FullMethodName = "/hipstershop.ProductCatalogService/SearchProducts" ) // ProductCatalogServiceClient is the client API for ProductCatalogService service. // // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. type ProductCatalogServiceClient interface { ListProducts(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*ListProductsResponse, error) GetProduct(ctx context.Context, in *GetProductRequest, opts ...grpc.CallOption) (*Product, error) SearchProducts(ctx context.Context, in *SearchProductsRequest, opts ...grpc.CallOption) (*SearchProductsResponse, error) } type productCatalogServiceClient struct { cc grpc.ClientConnInterface } func NewProductCatalogServiceClient(cc grpc.ClientConnInterface) ProductCatalogServiceClient { return &productCatalogServiceClient{cc} } func (c *productCatalogServiceClient) ListProducts(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*ListProductsResponse, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(ListProductsResponse) err := c.cc.Invoke(ctx, ProductCatalogService_ListProducts_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } return out, nil } func (c *productCatalogServiceClient) GetProduct(ctx context.Context, in *GetProductRequest, opts ...grpc.CallOption) (*Product, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(Product) err := c.cc.Invoke(ctx, ProductCatalogService_GetProduct_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } return out, nil } func (c *productCatalogServiceClient) SearchProducts(ctx context.Context, in *SearchProductsRequest, opts ...grpc.CallOption) (*SearchProductsResponse, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(SearchProductsResponse) err := c.cc.Invoke(ctx, ProductCatalogService_SearchProducts_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } return out, nil } // ProductCatalogServiceServer is the server API for ProductCatalogService service. // All implementations must embed UnimplementedProductCatalogServiceServer // for forward compatibility. type ProductCatalogServiceServer interface { ListProducts(context.Context, *Empty) (*ListProductsResponse, error) GetProduct(context.Context, *GetProductRequest) (*Product, error) SearchProducts(context.Context, *SearchProductsRequest) (*SearchProductsResponse, error) mustEmbedUnimplementedProductCatalogServiceServer() } // UnimplementedProductCatalogServiceServer must be embedded to have // forward compatible implementations. // // NOTE: this should be embedded by value instead of pointer to avoid a nil // pointer dereference when methods are called. type UnimplementedProductCatalogServiceServer struct{} func (UnimplementedProductCatalogServiceServer) ListProducts(context.Context, *Empty) (*ListProductsResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method ListProducts not implemented") } func (UnimplementedProductCatalogServiceServer) GetProduct(context.Context, *GetProductRequest) (*Product, error) { return nil, status.Errorf(codes.Unimplemented, "method GetProduct not implemented") } func (UnimplementedProductCatalogServiceServer) SearchProducts(context.Context, *SearchProductsRequest) (*SearchProductsResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method SearchProducts not implemented") } func (UnimplementedProductCatalogServiceServer) mustEmbedUnimplementedProductCatalogServiceServer() {} func (UnimplementedProductCatalogServiceServer) testEmbeddedByValue() {} // UnsafeProductCatalogServiceServer may be embedded to opt out of forward compatibility for this service. // Use of this interface is not recommended, as added methods to ProductCatalogServiceServer will // result in compilation errors. type UnsafeProductCatalogServiceServer interface { mustEmbedUnimplementedProductCatalogServiceServer() } func RegisterProductCatalogServiceServer(s grpc.ServiceRegistrar, srv ProductCatalogServiceServer) { // If the following call pancis, it indicates UnimplementedProductCatalogServiceServer was // embedded by pointer and is nil. This will cause panics if an // unimplemented method is ever invoked, so we test this at initialization // time to prevent it from happening at runtime later due to I/O. if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { t.testEmbeddedByValue() } s.RegisterService(&ProductCatalogService_ServiceDesc, srv) } func _ProductCatalogService_ListProducts_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(Empty) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(ProductCatalogServiceServer).ListProducts(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: ProductCatalogService_ListProducts_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(ProductCatalogServiceServer).ListProducts(ctx, req.(*Empty)) } return interceptor(ctx, in, info, handler) } func _ProductCatalogService_GetProduct_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(GetProductRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(ProductCatalogServiceServer).GetProduct(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: ProductCatalogService_GetProduct_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(ProductCatalogServiceServer).GetProduct(ctx, req.(*GetProductRequest)) } return interceptor(ctx, in, info, handler) } func _ProductCatalogService_SearchProducts_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(SearchProductsRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(ProductCatalogServiceServer).SearchProducts(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: ProductCatalogService_SearchProducts_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(ProductCatalogServiceServer).SearchProducts(ctx, req.(*SearchProductsRequest)) } return interceptor(ctx, in, info, handler) } // ProductCatalogService_ServiceDesc is the grpc.ServiceDesc for ProductCatalogService service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) var ProductCatalogService_ServiceDesc = grpc.ServiceDesc{ ServiceName: "hipstershop.ProductCatalogService", HandlerType: (*ProductCatalogServiceServer)(nil), Methods: []grpc.MethodDesc{ { MethodName: "ListProducts", Handler: _ProductCatalogService_ListProducts_Handler, }, { MethodName: "GetProduct", Handler: _ProductCatalogService_GetProduct_Handler, }, { MethodName: "SearchProducts", Handler: _ProductCatalogService_SearchProducts_Handler, }, }, Streams: []grpc.StreamDesc{}, Metadata: "demo.proto", } const ( ShippingService_GetQuote_FullMethodName = "/hipstershop.ShippingService/GetQuote" ShippingService_ShipOrder_FullMethodName = "/hipstershop.ShippingService/ShipOrder" ) // ShippingServiceClient is the client API for ShippingService service. // // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. type ShippingServiceClient interface { GetQuote(ctx context.Context, in *GetQuoteRequest, opts ...grpc.CallOption) (*GetQuoteResponse, error) ShipOrder(ctx context.Context, in *ShipOrderRequest, opts ...grpc.CallOption) (*ShipOrderResponse, error) } type shippingServiceClient struct { cc grpc.ClientConnInterface } func NewShippingServiceClient(cc grpc.ClientConnInterface) ShippingServiceClient { return &shippingServiceClient{cc} } func (c *shippingServiceClient) GetQuote(ctx context.Context, in *GetQuoteRequest, opts ...grpc.CallOption) (*GetQuoteResponse, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(GetQuoteResponse) err := c.cc.Invoke(ctx, ShippingService_GetQuote_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } return out, nil } func (c *shippingServiceClient) ShipOrder(ctx context.Context, in *ShipOrderRequest, opts ...grpc.CallOption) (*ShipOrderResponse, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(ShipOrderResponse) err := c.cc.Invoke(ctx, ShippingService_ShipOrder_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } return out, nil } // ShippingServiceServer is the server API for ShippingService service. // All implementations must embed UnimplementedShippingServiceServer // for forward compatibility. type ShippingServiceServer interface { GetQuote(context.Context, *GetQuoteRequest) (*GetQuoteResponse, error) ShipOrder(context.Context, *ShipOrderRequest) (*ShipOrderResponse, error) mustEmbedUnimplementedShippingServiceServer() } // UnimplementedShippingServiceServer must be embedded to have // forward compatible implementations. // // NOTE: this should be embedded by value instead of pointer to avoid a nil // pointer dereference when methods are called. type UnimplementedShippingServiceServer struct{} func (UnimplementedShippingServiceServer) GetQuote(context.Context, *GetQuoteRequest) (*GetQuoteResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method GetQuote not implemented") } func (UnimplementedShippingServiceServer) ShipOrder(context.Context, *ShipOrderRequest) (*ShipOrderResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method ShipOrder not implemented") } func (UnimplementedShippingServiceServer) mustEmbedUnimplementedShippingServiceServer() {} func (UnimplementedShippingServiceServer) testEmbeddedByValue() {} // UnsafeShippingServiceServer may be embedded to opt out of forward compatibility for this service. // Use of this interface is not recommended, as added methods to ShippingServiceServer will // result in compilation errors. type UnsafeShippingServiceServer interface { mustEmbedUnimplementedShippingServiceServer() } func RegisterShippingServiceServer(s grpc.ServiceRegistrar, srv ShippingServiceServer) { // If the following call pancis, it indicates UnimplementedShippingServiceServer was // embedded by pointer and is nil. This will cause panics if an // unimplemented method is ever invoked, so we test this at initialization // time to prevent it from happening at runtime later due to I/O. if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { t.testEmbeddedByValue() } s.RegisterService(&ShippingService_ServiceDesc, srv) } func _ShippingService_GetQuote_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(GetQuoteRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(ShippingServiceServer).GetQuote(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: ShippingService_GetQuote_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(ShippingServiceServer).GetQuote(ctx, req.(*GetQuoteRequest)) } return interceptor(ctx, in, info, handler) } func _ShippingService_ShipOrder_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(ShipOrderRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(ShippingServiceServer).ShipOrder(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: ShippingService_ShipOrder_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(ShippingServiceServer).ShipOrder(ctx, req.(*ShipOrderRequest)) } return interceptor(ctx, in, info, handler) } // ShippingService_ServiceDesc is the grpc.ServiceDesc for ShippingService service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) var ShippingService_ServiceDesc = grpc.ServiceDesc{ ServiceName: "hipstershop.ShippingService", HandlerType: (*ShippingServiceServer)(nil), Methods: []grpc.MethodDesc{ { MethodName: "GetQuote", Handler: _ShippingService_GetQuote_Handler, }, { MethodName: "ShipOrder", Handler: _ShippingService_ShipOrder_Handler, }, }, Streams: []grpc.StreamDesc{}, Metadata: "demo.proto", } const ( CurrencyService_GetSupportedCurrencies_FullMethodName = "/hipstershop.CurrencyService/GetSupportedCurrencies" CurrencyService_Convert_FullMethodName = "/hipstershop.CurrencyService/Convert" ) // CurrencyServiceClient is the client API for CurrencyService service. // // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. type CurrencyServiceClient interface { GetSupportedCurrencies(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*GetSupportedCurrenciesResponse, error) Convert(ctx context.Context, in *CurrencyConversionRequest, opts ...grpc.CallOption) (*Money, error) } type currencyServiceClient struct { cc grpc.ClientConnInterface } func NewCurrencyServiceClient(cc grpc.ClientConnInterface) CurrencyServiceClient { return ¤cyServiceClient{cc} } func (c *currencyServiceClient) GetSupportedCurrencies(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*GetSupportedCurrenciesResponse, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(GetSupportedCurrenciesResponse) err := c.cc.Invoke(ctx, CurrencyService_GetSupportedCurrencies_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } return out, nil } func (c *currencyServiceClient) Convert(ctx context.Context, in *CurrencyConversionRequest, opts ...grpc.CallOption) (*Money, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(Money) err := c.cc.Invoke(ctx, CurrencyService_Convert_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } return out, nil } // CurrencyServiceServer is the server API for CurrencyService service. // All implementations must embed UnimplementedCurrencyServiceServer // for forward compatibility. type CurrencyServiceServer interface { GetSupportedCurrencies(context.Context, *Empty) (*GetSupportedCurrenciesResponse, error) Convert(context.Context, *CurrencyConversionRequest) (*Money, error) mustEmbedUnimplementedCurrencyServiceServer() } // UnimplementedCurrencyServiceServer must be embedded to have // forward compatible implementations. // // NOTE: this should be embedded by value instead of pointer to avoid a nil // pointer dereference when methods are called. type UnimplementedCurrencyServiceServer struct{} func (UnimplementedCurrencyServiceServer) GetSupportedCurrencies(context.Context, *Empty) (*GetSupportedCurrenciesResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method GetSupportedCurrencies not implemented") } func (UnimplementedCurrencyServiceServer) Convert(context.Context, *CurrencyConversionRequest) (*Money, error) { return nil, status.Errorf(codes.Unimplemented, "method Convert not implemented") } func (UnimplementedCurrencyServiceServer) mustEmbedUnimplementedCurrencyServiceServer() {} func (UnimplementedCurrencyServiceServer) testEmbeddedByValue() {} // UnsafeCurrencyServiceServer may be embedded to opt out of forward compatibility for this service. // Use of this interface is not recommended, as added methods to CurrencyServiceServer will // result in compilation errors. type UnsafeCurrencyServiceServer interface { mustEmbedUnimplementedCurrencyServiceServer() } func RegisterCurrencyServiceServer(s grpc.ServiceRegistrar, srv CurrencyServiceServer) { // If the following call pancis, it indicates UnimplementedCurrencyServiceServer was // embedded by pointer and is nil. This will cause panics if an // unimplemented method is ever invoked, so we test this at initialization // time to prevent it from happening at runtime later due to I/O. if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { t.testEmbeddedByValue() } s.RegisterService(&CurrencyService_ServiceDesc, srv) } func _CurrencyService_GetSupportedCurrencies_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(Empty) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(CurrencyServiceServer).GetSupportedCurrencies(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: CurrencyService_GetSupportedCurrencies_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(CurrencyServiceServer).GetSupportedCurrencies(ctx, req.(*Empty)) } return interceptor(ctx, in, info, handler) } func _CurrencyService_Convert_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(CurrencyConversionRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(CurrencyServiceServer).Convert(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: CurrencyService_Convert_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(CurrencyServiceServer).Convert(ctx, req.(*CurrencyConversionRequest)) } return interceptor(ctx, in, info, handler) } // CurrencyService_ServiceDesc is the grpc.ServiceDesc for CurrencyService service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) var CurrencyService_ServiceDesc = grpc.ServiceDesc{ ServiceName: "hipstershop.CurrencyService", HandlerType: (*CurrencyServiceServer)(nil), Methods: []grpc.MethodDesc{ { MethodName: "GetSupportedCurrencies", Handler: _CurrencyService_GetSupportedCurrencies_Handler, }, { MethodName: "Convert", Handler: _CurrencyService_Convert_Handler, }, }, Streams: []grpc.StreamDesc{}, Metadata: "demo.proto", } const ( PaymentService_Charge_FullMethodName = "/hipstershop.PaymentService/Charge" ) // PaymentServiceClient is the client API for PaymentService service. // // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. type PaymentServiceClient interface { Charge(ctx context.Context, in *ChargeRequest, opts ...grpc.CallOption) (*ChargeResponse, error) } type paymentServiceClient struct { cc grpc.ClientConnInterface } func NewPaymentServiceClient(cc grpc.ClientConnInterface) PaymentServiceClient { return &paymentServiceClient{cc} } func (c *paymentServiceClient) Charge(ctx context.Context, in *ChargeRequest, opts ...grpc.CallOption) (*ChargeResponse, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(ChargeResponse) err := c.cc.Invoke(ctx, PaymentService_Charge_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } return out, nil } // PaymentServiceServer is the server API for PaymentService service. // All implementations must embed UnimplementedPaymentServiceServer // for forward compatibility. type PaymentServiceServer interface { Charge(context.Context, *ChargeRequest) (*ChargeResponse, error) mustEmbedUnimplementedPaymentServiceServer() } // UnimplementedPaymentServiceServer must be embedded to have // forward compatible implementations. // // NOTE: this should be embedded by value instead of pointer to avoid a nil // pointer dereference when methods are called. type UnimplementedPaymentServiceServer struct{} func (UnimplementedPaymentServiceServer) Charge(context.Context, *ChargeRequest) (*ChargeResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method Charge not implemented") } func (UnimplementedPaymentServiceServer) mustEmbedUnimplementedPaymentServiceServer() {} func (UnimplementedPaymentServiceServer) testEmbeddedByValue() {} // UnsafePaymentServiceServer may be embedded to opt out of forward compatibility for this service. // Use of this interface is not recommended, as added methods to PaymentServiceServer will // result in compilation errors. type UnsafePaymentServiceServer interface { mustEmbedUnimplementedPaymentServiceServer() } func RegisterPaymentServiceServer(s grpc.ServiceRegistrar, srv PaymentServiceServer) { // If the following call pancis, it indicates UnimplementedPaymentServiceServer was // embedded by pointer and is nil. This will cause panics if an // unimplemented method is ever invoked, so we test this at initialization // time to prevent it from happening at runtime later due to I/O. if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { t.testEmbeddedByValue() } s.RegisterService(&PaymentService_ServiceDesc, srv) } func _PaymentService_Charge_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(ChargeRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(PaymentServiceServer).Charge(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: PaymentService_Charge_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(PaymentServiceServer).Charge(ctx, req.(*ChargeRequest)) } return interceptor(ctx, in, info, handler) } // PaymentService_ServiceDesc is the grpc.ServiceDesc for PaymentService service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) var PaymentService_ServiceDesc = grpc.ServiceDesc{ ServiceName: "hipstershop.PaymentService", HandlerType: (*PaymentServiceServer)(nil), Methods: []grpc.MethodDesc{ { MethodName: "Charge", Handler: _PaymentService_Charge_Handler, }, }, Streams: []grpc.StreamDesc{}, Metadata: "demo.proto", } const ( EmailService_SendOrderConfirmation_FullMethodName = "/hipstershop.EmailService/SendOrderConfirmation" ) // EmailServiceClient is the client API for EmailService service. // // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. type EmailServiceClient interface { SendOrderConfirmation(ctx context.Context, in *SendOrderConfirmationRequest, opts ...grpc.CallOption) (*Empty, error) } type emailServiceClient struct { cc grpc.ClientConnInterface } func NewEmailServiceClient(cc grpc.ClientConnInterface) EmailServiceClient { return &emailServiceClient{cc} } func (c *emailServiceClient) SendOrderConfirmation(ctx context.Context, in *SendOrderConfirmationRequest, opts ...grpc.CallOption) (*Empty, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(Empty) err := c.cc.Invoke(ctx, EmailService_SendOrderConfirmation_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } return out, nil } // EmailServiceServer is the server API for EmailService service. // All implementations must embed UnimplementedEmailServiceServer // for forward compatibility. type EmailServiceServer interface { SendOrderConfirmation(context.Context, *SendOrderConfirmationRequest) (*Empty, error) mustEmbedUnimplementedEmailServiceServer() } // UnimplementedEmailServiceServer must be embedded to have // forward compatible implementations. // // NOTE: this should be embedded by value instead of pointer to avoid a nil // pointer dereference when methods are called. type UnimplementedEmailServiceServer struct{} func (UnimplementedEmailServiceServer) SendOrderConfirmation(context.Context, *SendOrderConfirmationRequest) (*Empty, error) { return nil, status.Errorf(codes.Unimplemented, "method SendOrderConfirmation not implemented") } func (UnimplementedEmailServiceServer) mustEmbedUnimplementedEmailServiceServer() {} func (UnimplementedEmailServiceServer) testEmbeddedByValue() {} // UnsafeEmailServiceServer may be embedded to opt out of forward compatibility for this service. // Use of this interface is not recommended, as added methods to EmailServiceServer will // result in compilation errors. type UnsafeEmailServiceServer interface { mustEmbedUnimplementedEmailServiceServer() } func RegisterEmailServiceServer(s grpc.ServiceRegistrar, srv EmailServiceServer) { // If the following call pancis, it indicates UnimplementedEmailServiceServer was // embedded by pointer and is nil. This will cause panics if an // unimplemented method is ever invoked, so we test this at initialization // time to prevent it from happening at runtime later due to I/O. if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { t.testEmbeddedByValue() } s.RegisterService(&EmailService_ServiceDesc, srv) } func _EmailService_SendOrderConfirmation_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(SendOrderConfirmationRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(EmailServiceServer).SendOrderConfirmation(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: EmailService_SendOrderConfirmation_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(EmailServiceServer).SendOrderConfirmation(ctx, req.(*SendOrderConfirmationRequest)) } return interceptor(ctx, in, info, handler) } // EmailService_ServiceDesc is the grpc.ServiceDesc for EmailService service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) var EmailService_ServiceDesc = grpc.ServiceDesc{ ServiceName: "hipstershop.EmailService", HandlerType: (*EmailServiceServer)(nil), Methods: []grpc.MethodDesc{ { MethodName: "SendOrderConfirmation", Handler: _EmailService_SendOrderConfirmation_Handler, }, }, Streams: []grpc.StreamDesc{}, Metadata: "demo.proto", } const ( CheckoutService_PlaceOrder_FullMethodName = "/hipstershop.CheckoutService/PlaceOrder" ) // CheckoutServiceClient is the client API for CheckoutService service. // // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. type CheckoutServiceClient interface { PlaceOrder(ctx context.Context, in *PlaceOrderRequest, opts ...grpc.CallOption) (*PlaceOrderResponse, error) } type checkoutServiceClient struct { cc grpc.ClientConnInterface } func NewCheckoutServiceClient(cc grpc.ClientConnInterface) CheckoutServiceClient { return &checkoutServiceClient{cc} } func (c *checkoutServiceClient) PlaceOrder(ctx context.Context, in *PlaceOrderRequest, opts ...grpc.CallOption) (*PlaceOrderResponse, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(PlaceOrderResponse) err := c.cc.Invoke(ctx, CheckoutService_PlaceOrder_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } return out, nil } // CheckoutServiceServer is the server API for CheckoutService service. // All implementations must embed UnimplementedCheckoutServiceServer // for forward compatibility. type CheckoutServiceServer interface { PlaceOrder(context.Context, *PlaceOrderRequest) (*PlaceOrderResponse, error) mustEmbedUnimplementedCheckoutServiceServer() } // UnimplementedCheckoutServiceServer must be embedded to have // forward compatible implementations. // // NOTE: this should be embedded by value instead of pointer to avoid a nil // pointer dereference when methods are called. type UnimplementedCheckoutServiceServer struct{} func (UnimplementedCheckoutServiceServer) PlaceOrder(context.Context, *PlaceOrderRequest) (*PlaceOrderResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method PlaceOrder not implemented") } func (UnimplementedCheckoutServiceServer) mustEmbedUnimplementedCheckoutServiceServer() {} func (UnimplementedCheckoutServiceServer) testEmbeddedByValue() {} // UnsafeCheckoutServiceServer may be embedded to opt out of forward compatibility for this service. // Use of this interface is not recommended, as added methods to CheckoutServiceServer will // result in compilation errors. type UnsafeCheckoutServiceServer interface { mustEmbedUnimplementedCheckoutServiceServer() } func RegisterCheckoutServiceServer(s grpc.ServiceRegistrar, srv CheckoutServiceServer) { // If the following call pancis, it indicates UnimplementedCheckoutServiceServer was // embedded by pointer and is nil. This will cause panics if an // unimplemented method is ever invoked, so we test this at initialization // time to prevent it from happening at runtime later due to I/O. if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { t.testEmbeddedByValue() } s.RegisterService(&CheckoutService_ServiceDesc, srv) } func _CheckoutService_PlaceOrder_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(PlaceOrderRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(CheckoutServiceServer).PlaceOrder(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: CheckoutService_PlaceOrder_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(CheckoutServiceServer).PlaceOrder(ctx, req.(*PlaceOrderRequest)) } return interceptor(ctx, in, info, handler) } // CheckoutService_ServiceDesc is the grpc.ServiceDesc for CheckoutService service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) var CheckoutService_ServiceDesc = grpc.ServiceDesc{ ServiceName: "hipstershop.CheckoutService", HandlerType: (*CheckoutServiceServer)(nil), Methods: []grpc.MethodDesc{ { MethodName: "PlaceOrder", Handler: _CheckoutService_PlaceOrder_Handler, }, }, Streams: []grpc.StreamDesc{}, Metadata: "demo.proto", } const ( AdService_GetAds_FullMethodName = "/hipstershop.AdService/GetAds" ) // AdServiceClient is the client API for AdService service. // // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. type AdServiceClient interface { GetAds(ctx context.Context, in *AdRequest, opts ...grpc.CallOption) (*AdResponse, error) } type adServiceClient struct { cc grpc.ClientConnInterface } func NewAdServiceClient(cc grpc.ClientConnInterface) AdServiceClient { return &adServiceClient{cc} } func (c *adServiceClient) GetAds(ctx context.Context, in *AdRequest, opts ...grpc.CallOption) (*AdResponse, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(AdResponse) err := c.cc.Invoke(ctx, AdService_GetAds_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } return out, nil } // AdServiceServer is the server API for AdService service. // All implementations must embed UnimplementedAdServiceServer // for forward compatibility. type AdServiceServer interface { GetAds(context.Context, *AdRequest) (*AdResponse, error) mustEmbedUnimplementedAdServiceServer() } // UnimplementedAdServiceServer must be embedded to have // forward compatible implementations. // // NOTE: this should be embedded by value instead of pointer to avoid a nil // pointer dereference when methods are called. type UnimplementedAdServiceServer struct{} func (UnimplementedAdServiceServer) GetAds(context.Context, *AdRequest) (*AdResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method GetAds not implemented") } func (UnimplementedAdServiceServer) mustEmbedUnimplementedAdServiceServer() {} func (UnimplementedAdServiceServer) testEmbeddedByValue() {} // UnsafeAdServiceServer may be embedded to opt out of forward compatibility for this service. // Use of this interface is not recommended, as added methods to AdServiceServer will // result in compilation errors. type UnsafeAdServiceServer interface { mustEmbedUnimplementedAdServiceServer() } func RegisterAdServiceServer(s grpc.ServiceRegistrar, srv AdServiceServer) { // If the following call pancis, it indicates UnimplementedAdServiceServer was // embedded by pointer and is nil. This will cause panics if an // unimplemented method is ever invoked, so we test this at initialization // time to prevent it from happening at runtime later due to I/O. if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { t.testEmbeddedByValue() } s.RegisterService(&AdService_ServiceDesc, srv) } func _AdService_GetAds_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(AdRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(AdServiceServer).GetAds(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: AdService_GetAds_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(AdServiceServer).GetAds(ctx, req.(*AdRequest)) } return interceptor(ctx, in, info, handler) } // AdService_ServiceDesc is the grpc.ServiceDesc for AdService service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) var AdService_ServiceDesc = grpc.ServiceDesc{ ServiceName: "hipstershop.AdService", HandlerType: (*AdServiceServer)(nil), Methods: []grpc.MethodDesc{ { MethodName: "GetAds", Handler: _AdService_GetAds_Handler, }, }, Streams: []grpc.StreamDesc{}, Metadata: "demo.proto", } ================================================ FILE: src/shippingservice/genproto.sh ================================================ #!/bin/bash -eu # # Copyright 2018 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 gke_shippingservice_genproto] PATH=$PATH:$(go env GOPATH)/bin protodir=../../protos outdir=./genproto protoc --proto_path=$protodir --go_out=./$outdir --go_opt=paths=source_relative --go-grpc_out=./$outdir --go-grpc_opt=paths=source_relative $protodir/demo.proto # [END gke_shippingservice_genproto] ================================================ FILE: src/shippingservice/go.mod ================================================ module github.com/GoogleCloudPlatform/microservices-demo/src/shippingservice go 1.25.0 toolchain go1.26.1 require ( cloud.google.com/go/profiler v0.4.3 github.com/sirupsen/logrus v1.9.4 golang.org/x/net v0.51.0 google.golang.org/grpc v1.79.2 google.golang.org/protobuf v1.36.11 ) require ( cloud.google.com/go v0.123.0 // indirect cloud.google.com/go/auth v0.17.0 // indirect cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect cloud.google.com/go/compute/metadata v0.9.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/google/pprof v0.0.0-20251114195745-4902fdda35c8 // indirect github.com/google/s2a-go v0.1.9 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.7 // indirect github.com/googleapis/gax-go/v2 v2.15.0 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0 // indirect go.opentelemetry.io/otel v1.39.0 // indirect go.opentelemetry.io/otel/metric v1.39.0 // indirect go.opentelemetry.io/otel/trace v1.39.0 // indirect golang.org/x/crypto v0.48.0 // indirect golang.org/x/oauth2 v0.34.0 // indirect golang.org/x/sync v0.19.0 // indirect golang.org/x/sys v0.41.0 // indirect golang.org/x/text v0.34.0 // indirect golang.org/x/time v0.14.0 // indirect google.golang.org/api v0.256.0 // indirect google.golang.org/genproto v0.0.0-20251124214823-79d6a2a48846 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect ) ================================================ FILE: src/shippingservice/go.sum ================================================ cel.dev/expr v0.24.0 h1:56OvJKSH3hDGL0ml5uSxZmz3/3Pq4tJ+fb1unVLAFcY= cel.dev/expr v0.24.0/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw= cloud.google.com/go v0.123.0 h1:2NAUJwPR47q+E35uaJeYoNhuNEM9kM8SjgRgdeOJUSE= cloud.google.com/go v0.123.0/go.mod h1:xBoMV08QcqUGuPW65Qfm1o9Y4zKZBpGS+7bImXLTAZU= cloud.google.com/go/auth v0.17.0 h1:74yCm7hCj2rUyyAocqnFzsAYXgJhrG26XCFimrc/Kz4= cloud.google.com/go/auth v0.17.0/go.mod h1:6wv/t5/6rOPAX4fJiRjKkJCvswLwdet7G8+UGXt7nCQ= cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc= cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c= cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs= cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10= cloud.google.com/go/iam v1.5.3 h1:+vMINPiDF2ognBJ97ABAYYwRgsaqxPbQDlMnbHMjolc= cloud.google.com/go/iam v1.5.3/go.mod h1:MR3v9oLkZCTlaqljW6Eb2d3HGDGK5/bDv93jhfISFvU= cloud.google.com/go/monitoring v1.24.3 h1:dde+gMNc0UhPZD1Azu6at2e79bfdztVDS5lvhOdsgaE= cloud.google.com/go/monitoring v1.24.3/go.mod h1:nYP6W0tm3N9H/bOw8am7t62YTzZY+zUeQ+Bi6+2eonI= cloud.google.com/go/profiler v0.4.3 h1:IY3QNKlr8VbXwGWHcZbJQsMA/83ZTH6uAHf8jYyj7OI= cloud.google.com/go/profiler v0.4.3/go.mod h1:3xFodugWfPIQZWFcXdUmfa+yTiiyQ8fWrdT+d2Sg4J0= cloud.google.com/go/storage v1.56.0 h1:iixmq2Fse2tqxMbWhLWC9HfBj1qdxqAmiK8/eqtsLxI= cloud.google.com/go/storage v1.56.0/go.mod h1:Tpuj6t4NweCLzlNbw9Z9iwxEkrSem20AetIeH/shgVU= github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0 h1:sBEjpZlNHzK1voKq9695PJSX2o5NEXl7/OL3coiIY0c= github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0/go.mod h1:P4WPRUkOhJC13W//jWpyfJNDAIpvRbAUIYLX/4jtlE0= github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.53.0 h1:owcC2UnmsZycprQ5RfRgjydWhuoxg71LUfyiQdijZuM= github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.53.0/go.mod h1:ZPpqegjbE99EPKsu3iUWV22A04wzGPcAY/ziSIQEEgs= github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.53.0 h1:Ron4zCA/yk6U7WOBXhTJcDpsUBG9npumK6xw2auFltQ= github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.53.0/go.mod h1:cSgYe11MCNYunTnRXrKiR/tHc0eoKjICUuWpNZoVCOo= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cncf/xds/go v0.0.0-20251022180443-0feb69152e9f h1:Y8xYupdHxryycyPlc9Y+bSQAYZnetRJ70VMVKm5CKI0= github.com/cncf/xds/go v0.0.0-20251022180443-0feb69152e9f/go.mod h1:HlzOvOjVBOfTGSRXRyY0OiCS/3J1akRGQQpRO/7zyF4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/envoyproxy/go-control-plane v0.13.5-0.20251024222203-75eaa193e329 h1:K+fnvUM0VZ7ZFJf0n4L/BRlnsb9pL/GuDG6FqaH+PwM= github.com/envoyproxy/go-control-plane/envoy v1.35.0 h1:ixjkELDE+ru6idPxcHLj8LBVc2bFP7iBytj353BoHUo= github.com/envoyproxy/go-control-plane/envoy v1.35.0/go.mod h1:09qwbGVuSWWAyN5t/b3iyVfz5+z8QWGrzkoqm/8SbEs= github.com/envoyproxy/protoc-gen-validate v1.2.1 h1:DEo3O99U8j4hBFwbJfrz9VtgcDfUKS7KJ7spH3d86P8= github.com/envoyproxy/protoc-gen-validate v1.2.1/go.mod h1:d/C80l/jxXLdfEIhX1W2TmLfsJ31lvEjwamM4DxlWXU= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/go-jose/go-jose/v4 v4.1.3 h1:CVLmWDhDVRa6Mi/IgCgaopNosCaHz7zrMeF9MlZRkrs= github.com/go-jose/go-jose/v4 v4.1.3/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/golang/mock v1.7.0-rc.1 h1:YojYx61/OLFsiv6Rw1Z96LpldJIy31o+UHmwAUMJ6/U= github.com/golang/mock v1.7.0-rc.1/go.mod h1:s42URUywIqd+OcERslBJvOjepvNymP31m3q8d/GkuRs= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/pprof v0.0.0-20251114195745-4902fdda35c8 h1:3DsUAV+VNEQa2CUVLxCY3f87278uWfIDhJnbdvDjvmE= github.com/google/pprof v0.0.0-20251114195745-4902fdda35c8/go.mod h1:I6V7YzU0XDpsHqbsyrghnFZLO1gwK6NPTNvmetQIk9U= github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0= github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/enterprise-certificate-proxy v0.3.7 h1:zrn2Ee/nWmHulBx5sAVrGgAa0f2/R35S4DJwfFaUPFQ= github.com/googleapis/enterprise-certificate-proxy v0.3.7/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA= github.com/googleapis/gax-go/v2 v2.15.0 h1:SyjDc1mGgZU5LncH8gimWo9lW1DtIfPibOG81vgd/bo= github.com/googleapis/gax-go/v2 v2.15.0/go.mod h1:zVVkkxAQHa1RQpg9z2AUCMnKhi0Qld9rcmyfL1OZhoc= github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo= github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w= github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g= github.com/spiffe/go-spiffe/v2 v2.6.0 h1:l+DolpxNWYgruGQVV0xsfeya3CsC7m8iBzDnMpsbLuo= github.com/spiffe/go-spiffe/v2 v2.6.0/go.mod h1:gm2SeUoMZEtpnzPNs2Csc0D/gX33k1xIx7lEzqblHEs= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/contrib/detectors/gcp v1.38.0 h1:ZoYbqX7OaA/TAikspPl3ozPI6iY6LiIY9I8cUfm+pJs= go.opentelemetry.io/contrib/detectors/gcp v1.38.0/go.mod h1:SU+iU7nu5ud4oCb3LQOhIZ3nRLj6FNVrKgtflbaf2ts= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0 h1:YH4g8lQroajqUwWbq/tr2QX1JFmEXaDLgG+ew9bLMWo= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0/go.mod h1:fvPi2qXDqFs8M4B4fmJhE92TyQs9Ydjlg3RvfUp+NbQ= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q= go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E= go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg= go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM= go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q= golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4= golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU= golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0= golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8= golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A= golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts= golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos= golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU= golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY= golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o= golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8= golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo= golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y= golang.org/x/oauth2 v0.33.0 h1:4Q+qn+E5z8gPRJfmRy7C2gGG3T4jIprK6aSYgTXGRpo= golang.org/x/oauth2 v0.33.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw= golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k= golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU= golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY= golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk= golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA= golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= google.golang.org/api v0.256.0 h1:u6Khm8+F9sxbCTYNoBHg6/Hwv0N/i+V94MvkOSor6oI= google.golang.org/api v0.256.0/go.mod h1:KIgPhksXADEKJlnEoRa9qAII4rXcy40vfI8HRqcU964= google.golang.org/genproto v0.0.0-20251124214823-79d6a2a48846 h1:dDbsTLIK7EzwUq36kCSAsk0slouq/S0tWHeeGi97cD8= google.golang.org/genproto v0.0.0-20251124214823-79d6a2a48846/go.mod h1:PP0g88Dz3C7hRAfbQCQggeWAXjuqGsNPLE4s7jh0RGU= google.golang.org/genproto/googleapis/api v0.0.0-20251124214823-79d6a2a48846 h1:ZdyUkS9po3H7G0tuh955QVyyotWvOD4W0aEapeGeUYk= google.golang.org/genproto/googleapis/api v0.0.0-20251124214823-79d6a2a48846/go.mod h1:Fk4kyraUvqD7i5H6S43sj2W98fbZa75lpZz/eUyhfO0= google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 h1:fCvbg86sFXwdrl5LgVcTEvNC+2txB5mgROGmRL5mrls= google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:+rXWjjaukWZun3mLfjmVnQi18E1AsFbDN9QdJ5YXLto= google.golang.org/genproto/googleapis/rpc v0.0.0-20251124214823-79d6a2a48846 h1:Wgl1rcDNThT+Zn47YyCXOXyX/COgMTIdhJ717F0l4xk= google.golang.org/genproto/googleapis/rpc v0.0.0-20251124214823-79d6a2a48846/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 h1:gRkg/vSppuSQoDjxyiGfN4Upv/h/DQmIR10ZU8dh4Ww= google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM= google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig= google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc= google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U= google.golang.org/grpc v1.79.2 h1:fRMD94s2tITpyJGtBBn7MkMseNpOZU8ZxgC3MMBaXRU= google.golang.org/grpc v1.79.2/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= ================================================ FILE: src/shippingservice/main.go ================================================ // Copyright 2018 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. package main import ( "fmt" "net" "os" "time" "cloud.google.com/go/profiler" "github.com/sirupsen/logrus" "golang.org/x/net/context" "google.golang.org/grpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/health" "google.golang.org/grpc/reflection" "google.golang.org/grpc/status" pb "github.com/GoogleCloudPlatform/microservices-demo/src/shippingservice/genproto" healthpb "google.golang.org/grpc/health/grpc_health_v1" ) const ( defaultPort = "50051" ) var log *logrus.Logger func init() { log = logrus.New() log.Level = logrus.DebugLevel log.Formatter = &logrus.JSONFormatter{ FieldMap: logrus.FieldMap{ logrus.FieldKeyTime: "timestamp", logrus.FieldKeyLevel: "severity", logrus.FieldKeyMsg: "message", }, TimestampFormat: time.RFC3339Nano, } log.Out = os.Stdout } func main() { if os.Getenv("DISABLE_TRACING") == "" { log.Info("Tracing enabled, but temporarily unavailable") log.Info("See https://github.com/GoogleCloudPlatform/microservices-demo/issues/422 for more info.") go initTracing() } else { log.Info("Tracing disabled.") } if os.Getenv("DISABLE_PROFILER") == "" { log.Info("Profiling enabled.") go initProfiling("shippingservice", "1.0.0") } else { log.Info("Profiling disabled.") } port := defaultPort if value, ok := os.LookupEnv("PORT"); ok { port = value } port = fmt.Sprintf(":%s", port) lis, err := net.Listen("tcp", port) if err != nil { log.Fatalf("failed to listen: %v", err) } var srv *grpc.Server if os.Getenv("DISABLE_STATS") == "" { log.Info("Stats enabled, but temporarily unavailable") srv = grpc.NewServer() } else { log.Info("Stats disabled.") srv = grpc.NewServer() } svc := &server{} pb.RegisterShippingServiceServer(srv, svc) healthcheck := health.NewServer() healthpb.RegisterHealthServer(srv, healthcheck) log.Infof("Shipping Service listening on port %s", port) // Register reflection service on gRPC server. reflection.Register(srv) if err := srv.Serve(lis); err != nil { log.Fatalf("failed to serve: %v", err) } } // server controls RPC service responses. type server struct { pb.UnimplementedShippingServiceServer } // Check is for health checking. func (s *server) Check(ctx context.Context, req *healthpb.HealthCheckRequest) (*healthpb.HealthCheckResponse, error) { return &healthpb.HealthCheckResponse{Status: healthpb.HealthCheckResponse_SERVING}, nil } func (s *server) Watch(req *healthpb.HealthCheckRequest, ws healthpb.Health_WatchServer) error { return status.Errorf(codes.Unimplemented, "health check via Watch not implemented") } // GetQuote produces a shipping quote (cost) in USD. func (s *server) GetQuote(ctx context.Context, in *pb.GetQuoteRequest) (*pb.GetQuoteResponse, error) { log.Info("[GetQuote] received request") defer log.Info("[GetQuote] completed request") // 1. Generate a quote based on the total number of items to be shipped. count := 0 for _, item := range in.Items { count += int(item.Quantity) } quote := CreateQuoteFromCount(count) // 2. Generate a response. return &pb.GetQuoteResponse{ CostUsd: &pb.Money{ CurrencyCode: "USD", Units: int64(quote.Dollars), Nanos: int32(quote.Cents * 10000000)}, }, nil } // ShipOrder mocks that the requested items will be shipped. // It supplies a tracking ID for notional lookup of shipment delivery status. func (s *server) ShipOrder(ctx context.Context, in *pb.ShipOrderRequest) (*pb.ShipOrderResponse, error) { log.Info("[ShipOrder] received request") defer log.Info("[ShipOrder] completed request") // 1. Create a Tracking ID baseAddress := fmt.Sprintf("%s, %s, %s", in.Address.StreetAddress, in.Address.City, in.Address.State) id := CreateTrackingId(baseAddress) // 2. Generate a response. return &pb.ShipOrderResponse{ TrackingId: id, }, nil } func initStats() { //TODO(arbrown) Implement OpenTelemetry stats } func initTracing() { // TODO(arbrown) Implement OpenTelemetry tracing } func initProfiling(service, version string) { // TODO(ahmetb) this method is duplicated in other microservices using Go // since they are not sharing packages. for i := 1; i <= 3; i++ { if err := profiler.Start(profiler.Config{ Service: service, ServiceVersion: version, // ProjectID must be set if not running on GCP. // ProjectID: "my-project", }); err != nil { log.Warnf("failed to start profiler: %+v", err) } else { log.Info("started Stackdriver profiler") return } d := time.Second * 10 * time.Duration(i) log.Infof("sleeping %v to retry initializing Stackdriver profiler", d) time.Sleep(d) } log.Warn("could not initialize Stackdriver profiler after retrying, giving up") } ================================================ FILE: src/shippingservice/quote.go ================================================ // Copyright 2018 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. package main import ( "fmt" "math" ) // Quote represents a currency value. type Quote struct { Dollars uint32 Cents uint32 } // String representation of the Quote. func (q Quote) String() string { return fmt.Sprintf("$%d.%d", q.Dollars, q.Cents) } // CreateQuoteFromCount takes a number of items and returns a shipping quote. func CreateQuoteFromCount(count int) Quote { if count == 0 { return CreateQuoteFromFloat(0) } return CreateQuoteFromFloat(8.99) } // CreateQuoteFromFloat takes a price represented as a float and creates a Price struct. func CreateQuoteFromFloat(value float64) Quote { units, fraction := math.Modf(value) return Quote{ uint32(units), uint32(math.Trunc(fraction * 100)), } } ================================================ FILE: src/shippingservice/shippingservice_test.go ================================================ // Copyright 2018 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. package main import ( "regexp" "testing" "golang.org/x/net/context" pb "github.com/GoogleCloudPlatform/microservices-demo/src/shippingservice/genproto" ) // TestGetQuote is a basic check on the GetQuote RPC service. func TestGetQuote(t *testing.T) { s := server{} // A basic test case to test logic and protobuf interactions. req := &pb.GetQuoteRequest{ Address: &pb.Address{ StreetAddress: "Muffin Man", City: "London", State: "", Country: "England", }, Items: []*pb.CartItem{ { ProductId: "23", Quantity: 1, }, { ProductId: "46", Quantity: 3, }, }, } res, err := s.GetQuote(context.Background(), req) if err != nil { t.Errorf("TestGetQuote (%v) failed", err) } if res.CostUsd.GetUnits() != 8 || res.CostUsd.GetNanos() != 990000000 { t.Errorf("TestGetQuote: Quote value '%d.%d' does not match expected '8.990000000'", res.CostUsd.GetUnits(), res.CostUsd.GetNanos()) } } // TestGetQuoteEmptyCart verifies that an empty cart returns a zero quote. func TestGetQuoteEmptyCart(t *testing.T) { s := server{} req := &pb.GetQuoteRequest{ Address: &pb.Address{ StreetAddress: "221B Baker Street", City: "London", State: "", Country: "England", }, Items: []*pb.CartItem{}, } res, err := s.GetQuote(context.Background(), req) if err != nil { t.Errorf("TestGetQuoteEmptyCart (%v) failed", err) } if res.CostUsd.GetUnits() != 0 || res.CostUsd.GetNanos() != 0 { t.Errorf("TestGetQuoteEmptyCart: expected zero quote for empty cart, got '%d.%d'", res.CostUsd.GetUnits(), res.CostUsd.GetNanos()) } } // TestShipOrder is a basic check on the ShipOrder RPC service. func TestShipOrder(t *testing.T) { s := server{} // A basic test case to test logic and protobuf interactions. req := &pb.ShipOrderRequest{ Address: &pb.Address{ StreetAddress: "Muffin Man", City: "London", State: "", Country: "England", }, Items: []*pb.CartItem{ { ProductId: "23", Quantity: 1, }, { ProductId: "46", Quantity: 3, }, }, } res, err := s.ShipOrder(context.Background(), req) if err != nil { t.Errorf("TestShipOrder (%v) failed", err) } if len(res.TrackingId) != 18 { t.Errorf("TestShipOrder: Tracking ID is malformed - has %d characters, %d expected", len(res.TrackingId), 18) } } // TestTrackingIdFormat verifies the tracking ID matches the expected pattern. func TestTrackingIdFormat(t *testing.T) { pattern := regexp.MustCompile(`^[A-Z]{2}-\d+-\d+$`) for i := 0; i < 20; i++ { id := CreateTrackingId("test-salt-value") if !pattern.MatchString(id) { t.Errorf("CreateTrackingId: '%s' does not match expected pattern '[A-Z]{2}-\\d+-\\d+'", id) } } } // TestTrackingIdUniqueness checks that generated IDs are not all identical. func TestTrackingIdUniqueness(t *testing.T) { seen := make(map[string]bool) for i := 0; i < 50; i++ { id := CreateTrackingId("same-salt") seen[id] = true } if len(seen) < 2 { t.Errorf("CreateTrackingId: expected unique IDs but got %d distinct values out of 50", len(seen)) } } // TestCreateQuoteFromFloat verifies quote creation from float values. func TestCreateQuoteFromFloat(t *testing.T) { tests := []struct { name string value float64 dollars uint32 cents uint32 }{ {"zero", 0.0, 0, 0}, {"whole dollars", 10.0, 10, 0}, {"with cents", 8.99, 8, 99}, {"small value", 0.50, 0, 50}, {"large value", 100.01, 100, 1}, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { q := CreateQuoteFromFloat(tc.value) if q.Dollars != tc.dollars || q.Cents != tc.cents { t.Errorf("CreateQuoteFromFloat(%v) = $%d.%d, want $%d.%d", tc.value, q.Dollars, q.Cents, tc.dollars, tc.cents) } }) } } // TestCreateQuoteFromCount verifies count-based quote generation. func TestCreateQuoteFromCount(t *testing.T) { zeroQuote := CreateQuoteFromCount(0) if zeroQuote.Dollars != 0 || zeroQuote.Cents != 0 { t.Errorf("CreateQuoteFromCount(0) = %s, want $0.0", zeroQuote) } nonZeroQuote := CreateQuoteFromCount(5) if nonZeroQuote.Dollars == 0 && nonZeroQuote.Cents == 0 { t.Error("CreateQuoteFromCount(5) returned zero, expected a non-zero quote") } } // TestGetRandomLetterCode verifies the output is a valid uppercase letter. func TestGetRandomLetterCode(t *testing.T) { for i := 0; i < 100; i++ { code := getRandomLetterCode() if code < 65 || code > 90 { t.Errorf("getRandomLetterCode: got %d (%c), expected range 65-90 (A-Z)", code, code) } } } // TestGetRandomNumber verifies the output has the correct number of digits. func TestGetRandomNumber(t *testing.T) { for _, digits := range []int{1, 3, 5, 7, 10} { result := getRandomNumber(digits) if len(result) != digits { t.Errorf("getRandomNumber(%d) = '%s' (len %d), expected length %d", digits, result, len(result), digits) } } } // TestQuoteString verifies the string representation of a Quote. func TestQuoteString(t *testing.T) { q := Quote{Dollars: 8, Cents: 99} expected := "$8.99" if q.String() != expected { t.Errorf("Quote.String() = '%s', want '%s'", q.String(), expected) } } ================================================ FILE: src/shippingservice/tracker.go ================================================ // Copyright 2018 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. package main import ( "fmt" "math/rand" ) // CreateTrackingId generates a tracking ID. func CreateTrackingId(salt string) string { return fmt.Sprintf("%c%c-%d%s-%d%s", getRandomLetterCode(), getRandomLetterCode(), len(salt), getRandomNumber(3), len(salt)/2, getRandomNumber(7), ) } // getRandomLetterCode generates a code point value for a capital letter. func getRandomLetterCode() uint32 { return 65 + uint32(rand.Intn(25)) } // getRandomNumber generates a string representation of a number with the requested number of digits. func getRandomNumber(digits int) string { str := "" for i := 0; i < digits; i++ { str = fmt.Sprintf("%s%d", str, rand.Intn(10)) } return str } ================================================ FILE: src/shoppingassistantservice/Dockerfile ================================================ # 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 # # 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. # Define a default value so it's not empty if the builder fails to provide it ARG BUILDPLATFORM=linux/amd64 FROM --platform=$BUILDPLATFORM python:3.14.3-slim@sha256:6a27522252aef8432841f224d9baaa6e9fce07b07584154fa0b9a96603af7456 AS base FROM base AS builder RUN apt-get -qq update \ && apt-get install -y --no-install-recommends g++ \ && rm -rf /var/lib/apt/lists/* # get packages COPY requirements.txt . RUN pip install -r requirements.txt FROM base # Enable unbuffered logging ENV PYTHONUNBUFFERED=1 # get packages WORKDIR /shoppingassistantservice # Grab packages from builder COPY --from=builder /usr/local/lib/python3.14/ /usr/local/lib/python3.14/ # Add the application COPY . . # set listen port ENV PORT "8080" EXPOSE 8080 ENTRYPOINT ["python", "shoppingassistantservice.py"] ================================================ FILE: src/shoppingassistantservice/requirements.in ================================================ flask==3.1.3 langchain-google-genai==4.1.2 langchain==1.2.0 pillow==12.1.1 langchain-google-alloydb-pg==0.13.0 google-cloud-secret-manager==2.26.0 ================================================ FILE: src/shoppingassistantservice/requirements.txt ================================================ # This file was autogenerated by uv via the following command: # uv pip compile requirements.in -o requirements.txt aiofiles==24.1.0 # via google-cloud-alloydb-connector annotated-types==0.7.0 # via pydantic anyio==4.10.0 # via # google-genai # httpx asyncpg==0.30.0 # via # google-cloud-alloydb-connector # langchain-postgres blinker==1.9.0 # via flask cachetools==5.5.2 # via google-auth certifi==2025.8.3 # via # httpcore # httpx # requests cffi==2.0.0 # via cryptography charset-normalizer==3.4.3 # via requests click==8.3.0 # via flask cryptography==46.0.5 # via google-cloud-alloydb-connector distro==1.9.0 # via google-genai filetype==1.2.0 # via langchain-google-genai flask==3.1.3 # via -r requirements.in google-api-core[grpc]==2.25.1 # via # google-cloud-alloydb # google-cloud-alloydb-connector # google-cloud-core # google-cloud-secret-manager # google-cloud-storage google-auth[requests]==2.45.0 # via # google-api-core # google-cloud-alloydb # google-cloud-alloydb-connector # google-cloud-core # google-cloud-secret-manager # google-cloud-storage # google-genai google-cloud-alloydb==0.4.9 # via google-cloud-alloydb-connector google-cloud-alloydb-connector[asyncpg]==1.9.1 # via langchain-google-alloydb-pg google-cloud-core==2.4.3 # via google-cloud-storage google-cloud-secret-manager==2.26.0 # via -r requirements.in google-cloud-storage==3.4.0 # via langchain-google-alloydb-pg google-crc32c==1.7.1 # via # google-cloud-storage # google-resumable-media google-genai==1.56.0 # via langchain-google-genai google-resumable-media==2.7.2 # via google-cloud-storage googleapis-common-protos[grpc]==1.70.0 # via # google-api-core # grpc-google-iam-v1 # grpcio-status greenlet==3.3.0 # via sqlalchemy grpc-google-iam-v1==0.14.2 # via # google-cloud-alloydb # google-cloud-secret-manager grpcio==1.76.0 # via # google-api-core # google-cloud-secret-manager # googleapis-common-protos # grpc-google-iam-v1 # grpcio-status grpcio-status==1.75.0 # via google-api-core h11==0.16.0 # via httpcore httpcore==1.0.9 # via httpx httpx==0.28.1 # via # google-genai # langgraph-sdk # langsmith idna==3.10 # via # anyio # httpx # requests itsdangerous==2.2.0 # via flask jinja2==3.1.6 # via flask jsonpatch==1.33 # via langchain-core jsonpointer==3.0.0 # via jsonpatch langchain==1.2.0 # via -r requirements.in langchain-core==1.2.11 # via # langchain # langchain-google-genai # langchain-postgres # langgraph # langgraph-checkpoint # langgraph-prebuilt langchain-google-alloydb-pg==0.13.0 # via -r requirements.in langchain-google-genai==4.1.2 # via -r requirements.in langchain-postgres==0.0.16 # via langchain-google-alloydb-pg langgraph==1.0.10rc1 # via langchain langgraph-checkpoint==3.0.1 # via # langgraph # langgraph-prebuilt langgraph-prebuilt==1.0.8 # via langgraph langgraph-sdk==0.3.1 # via langgraph langsmith==0.6.3 # via langchain-core markupsafe==3.0.2 # via # flask # jinja2 # werkzeug numpy==2.3.3 # via # langchain-google-alloydb-pg # langchain-postgres # pgvector orjson==3.11.6 # via # langgraph-sdk # langsmith ormsgpack==1.12.1 # via langgraph-checkpoint packaging==25.0 # via # langchain-core # langsmith pgvector==0.3.6 # via langchain-postgres pillow==12.1.1 # via -r requirements.in proto-plus==1.26.1 # via # google-api-core # google-cloud-alloydb # google-cloud-secret-manager protobuf==6.33.5 # via # google-api-core # google-cloud-alloydb # google-cloud-alloydb-connector # google-cloud-secret-manager # googleapis-common-protos # grpc-google-iam-v1 # grpcio-status # proto-plus psycopg[binary]==3.2.10 # via langchain-postgres psycopg-binary==3.2.10 # via psycopg psycopg-pool==3.2.6 # via langchain-postgres pyasn1==0.6.3 # via # pyasn1-modules # rsa pyasn1-modules==0.4.2 # via google-auth pycparser==2.23 # via cffi pydantic==2.12.5 # via # google-genai # langchain # langchain-core # langchain-google-genai # langgraph # langsmith pydantic-core==2.41.5 # via pydantic pyyaml==6.0.2 # via langchain-core requests==2.32.5 # via # google-api-core # google-auth # google-cloud-alloydb-connector # google-cloud-storage # google-genai # langsmith # requests-toolbelt requests-toolbelt==1.0.0 # via langsmith rsa==4.9.1 # via google-auth sniffio==1.3.1 # via # anyio # google-genai sqlalchemy[asyncio]==2.0.43 # via langchain-postgres tenacity==9.1.2 # via # google-genai # langchain-core typing-extensions==4.15.0 # via # google-genai # grpcio # langchain-core # psycopg-pool # pydantic # pydantic-core # sqlalchemy # typing-inspection typing-inspection==0.4.2 # via pydantic urllib3==2.6.3 # via requests uuid-utils==0.12.0 # via # langchain-core # langsmith websockets==15.0.1 # via google-genai werkzeug==3.1.6 # via flask xxhash==3.6.0 # via langgraph zstandard==0.25.0 # via langsmith ================================================ FILE: src/shoppingassistantservice/shoppingassistantservice.py ================================================ #!/usr/bin/python # # 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 # # 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. import os from google.cloud import secretmanager_v1 from urllib.parse import unquote from langchain_core.messages import HumanMessage from langchain_google_genai import ChatGoogleGenerativeAI, GoogleGenerativeAIEmbeddings from flask import Flask, request from langchain_google_alloydb_pg import AlloyDBEngine, AlloyDBVectorStore PROJECT_ID = os.environ["PROJECT_ID"] REGION = os.environ["REGION"] ALLOYDB_DATABASE_NAME = os.environ["ALLOYDB_DATABASE_NAME"] ALLOYDB_TABLE_NAME = os.environ["ALLOYDB_TABLE_NAME"] ALLOYDB_CLUSTER_NAME = os.environ["ALLOYDB_CLUSTER_NAME"] ALLOYDB_INSTANCE_NAME = os.environ["ALLOYDB_INSTANCE_NAME"] ALLOYDB_SECRET_NAME = os.environ["ALLOYDB_SECRET_NAME"] secret_manager_client = secretmanager_v1.SecretManagerServiceClient() secret_name = secret_manager_client.secret_version_path(project=PROJECT_ID, secret=ALLOYDB_SECRET_NAME, secret_version="latest") secret_request = secretmanager_v1.AccessSecretVersionRequest(name=secret_name) secret_response = secret_manager_client.access_secret_version(request=secret_request) PGPASSWORD = secret_response.payload.data.decode("UTF-8").strip() engine = AlloyDBEngine.from_instance( project_id=PROJECT_ID, region=REGION, cluster=ALLOYDB_CLUSTER_NAME, instance=ALLOYDB_INSTANCE_NAME, database=ALLOYDB_DATABASE_NAME, user="postgres", password=PGPASSWORD ) # Create a synchronous connection to our vectorstore vectorstore = AlloyDBVectorStore.create_sync( engine=engine, table_name=ALLOYDB_TABLE_NAME, embedding_service=GoogleGenerativeAIEmbeddings(model="models/embedding-001"), id_column="id", content_column="description", embedding_column="product_embedding", metadata_columns=["id", "name", "categories"] ) def create_app(): app = Flask(__name__) @app.route("/", methods=['POST']) def talkToGemini(): print("Beginning RAG call") prompt = request.json['message'] prompt = unquote(prompt) # Step 1 – Get a room description from Gemini-vision-pro llm_vision = ChatGoogleGenerativeAI(model="gemini-1.5-flash") message = HumanMessage( content=[ { "type": "text", "text": "You are a professional interior designer, give me a detailed decsription of the style of the room in this image", }, {"type": "image_url", "image_url": request.json['image']}, ] ) response = llm_vision.invoke([message]) print("Description step:") print(response) description_response = response.content # Step 2 – Similarity search with the description & user prompt vector_search_prompt = f""" This is the user's request: {prompt} Find the most relevant items for that prompt, while matching style of the room described here: {description_response} """ print(vector_search_prompt) docs = vectorstore.similarity_search(vector_search_prompt) print(f"Vector search: {description_response}") print(f"Retrieved documents: {len(docs)}") #Prepare relevant documents for inclusion in final prompt relevant_docs = "" for doc in docs: doc_details = doc.to_json() print(f"Adding relevant document to prompt context: {doc_details}") relevant_docs += str(doc_details) + ", " # Step 3 – Tie it all together by augmenting our call to Gemini-pro llm = ChatGoogleGenerativeAI(model="gemini-1.5-flash") design_prompt = ( f" You are an interior designer that works for Online Boutique. You are tasked with providing recommendations to a customer on what they should add to a given room from our catalog. This is the description of the room: \n" f"{description_response} Here are a list of products that are relevant to it: {relevant_docs} Specifically, this is what the customer has asked for, see if you can accommodate it: {prompt} Start by repeating a brief description of the room's design to the customer, then provide your recommendations. Do your best to pick the most relevant item out of the list of products provided, but if none of them seem relevant, then say that instead of inventing a new product. At the end of the response, add a list of the IDs of the relevant products in the following format for the top 3 results: [], [], [] ") print("Final design prompt: ") print(design_prompt) design_response = llm.invoke( design_prompt ) data = {'content': design_response.content} return data return app if __name__ == "__main__": # Create an instance of flask server when called directly app = create_app() app.run(host='0.0.0.0', port=8080) ================================================ FILE: terraform/README.md ================================================ # Use Terraform to deploy Online Boutique on a GKE cluster This page walks you through the steps required to deploy the [Online Boutique](https://github.com/GoogleCloudPlatform/microservices-demo) sample application on a [Google Kubernetes Engine (GKE)](https://cloud.google.com/kubernetes-engine) cluster using Terraform. ## Prerequisites 1. [Create a new project or use an existing project](https://cloud.google.com/resource-manager/docs/creating-managing-projects#console) on Google Cloud, and ensure [billing is enabled](https://cloud.google.com/billing/docs/how-to/verify-billing-enabled) on the project. ## Deploy the sample application 1. Clone the Github repository. ```bash git clone https://github.com/GoogleCloudPlatform/microservices-demo.git ``` 1. Move into the `terraform/` directory which contains the Terraform installation scripts. ```bash cd microservices-demo/terraform ``` 1. Open the `terraform.tfvars` file and replace `` with the [GCP Project ID](https://cloud.google.com/resource-manager/docs/creating-managing-projects?hl=en#identifying_projects) for the `gcp_project_id` variable. 1. (Optional) If you want to provision a [Google Cloud Memorystore (Redis)](https://cloud.google.com/memorystore) instance, you can change the value of `memorystore = false` to `memorystore = true` in this `terraform.tfvars` file. 1. Initialize Terraform. ```bash terraform init ``` 1. See what resources will be created. ```bash terraform plan ``` 1. Create the resources and deploy the sample. ```bash terraform apply ``` 1. If there is a confirmation prompt, type `yes` and hit Enter/Return. Note: This step can take about 10 minutes. Do not interrupt the process. Once the Terraform script has finished, you can locate the frontend's external IP address to access the sample application. - Option 1: ```bash kubectl get service frontend-external | awk '{print $4}' ``` - Option 2: On Google Cloud Console, navigate to "Kubernetes Engine" and then "Services & Ingress" to locate the Endpoint associated with "frontend-external". ## Clean up To avoid incurring charges to your Google Cloud account for the resources used in this sample application, either delete the project that contains the resources, or keep the project and delete the individual resources. To remove the individual resources created for by Terraform without deleting the project: 1. Navigate to the `terraform/` directory. 1. Set `deletion_protection` to `false` for the `google_container_cluster` resource (GKE cluster). ```bash # Uncomment the line: "deletion_protection = false" sed -i "s/# deletion_protection/deletion_protection/g" main.tf # Re-apply the Terraform to update the state terraform apply ``` 1. Run the following command: ```bash terraform destroy ``` 1. If there is a confirmation prompt, type `yes` and hit Enter/Return. ================================================ FILE: terraform/main.tf ================================================ # 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. # Definition of local variables locals { base_apis = [ "container.googleapis.com", "monitoring.googleapis.com", "cloudtrace.googleapis.com", "cloudprofiler.googleapis.com" ] memorystore_apis = ["redis.googleapis.com"] cluster_name = google_container_cluster.my_cluster.name } # Enable Google Cloud APIs module "enable_google_apis" { source = "terraform-google-modules/project-factory/google//modules/project_services" version = "~> 18.0" project_id = var.gcp_project_id disable_services_on_destroy = false # activate_apis is the set of base_apis and the APIs required by user-configured deployment options activate_apis = concat(local.base_apis, var.memorystore ? local.memorystore_apis : []) } # Create GKE cluster resource "google_container_cluster" "my_cluster" { name = var.name location = var.region # Enable autopilot for this cluster enable_autopilot = true # Set an empty ip_allocation_policy to allow autopilot cluster to spin up correctly ip_allocation_policy { } # Avoid setting deletion_protection to false # until you're ready (and certain you want) to destroy the cluster. # deletion_protection = false depends_on = [ module.enable_google_apis ] } # Get credentials for cluster module "gcloud" { source = "terraform-google-modules/gcloud/google" version = "~> 4.0" platform = "linux" additional_components = ["kubectl", "beta"] create_cmd_entrypoint = "gcloud" # Module does not support explicit dependency # Enforce implicit dependency through use of local variable create_cmd_body = "container clusters get-credentials ${local.cluster_name} --zone=${var.region} --project=${var.gcp_project_id}" } # Apply YAML kubernetes-manifest configurations resource "null_resource" "apply_deployment" { provisioner "local-exec" { interpreter = ["bash", "-exc"] command = "kubectl apply -k ${var.filepath_manifest} -n ${var.namespace}" } depends_on = [ module.gcloud ] } # Wait condition for all Pods to be ready before finishing resource "null_resource" "wait_conditions" { provisioner "local-exec" { interpreter = ["bash", "-exc"] command = <<-EOT kubectl wait --for=condition=AVAILABLE apiservice/v1beta1.metrics.k8s.io --timeout=180s kubectl wait --for=condition=ready pods --all -n ${var.namespace} --timeout=280s EOT } depends_on = [ resource.null_resource.apply_deployment ] } ================================================ FILE: terraform/memorystore.tf ================================================ # 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. # Create the Memorystore (redis) instance resource "google_redis_instance" "redis-cart" { name = "redis-cart" memory_size_gb = 1 region = var.region # count specifies the number of instances to create; # if var.memorystore is true then the resource is enabled count = var.memorystore ? 1 : 0 redis_version = "REDIS_7_0" project = var.gcp_project_id depends_on = [ module.enable_google_apis ] } # Edit contents of Memorystore kustomization.yaml file to target new Memorystore (redis) instance resource "null_resource" "kustomization-update" { provisioner "local-exec" { interpreter = ["bash", "-exc"] command = "sed -i \"s/REDIS_CONNECTION_STRING/${google_redis_instance.redis-cart[0].host}:${google_redis_instance.redis-cart[0].port}/g\" ../kustomize/components/memorystore/kustomization.yaml" } # count specifies the number of instances to create; # if var.memorystore is true then the resource is enabled count = var.memorystore ? 1 : 0 depends_on = [ resource.google_redis_instance.redis-cart ] } ================================================ FILE: terraform/output.tf ================================================ # 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. output "cluster_location" { description = "Location of the cluster" value = resource.google_container_cluster.my_cluster.location } output "cluster_name" { description = "Name of the cluster" value = resource.google_container_cluster.my_cluster.name } ================================================ FILE: terraform/providers.tf ================================================ # 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. terraform { required_providers { google = { source = "hashicorp/google" version = "7.16.0" } } } provider "google" { project = var.gcp_project_id region = var.region } ================================================ FILE: terraform/variables.tf ================================================ # 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. variable "gcp_project_id" { type = string description = "The GCP project ID to apply this config to" } variable "name" { type = string description = "Name given to the new GKE cluster" default = "online-boutique" } variable "region" { type = string description = "Region of the new GKE cluster" default = "us-central1" } variable "namespace" { type = string description = "Kubernetes Namespace in which the Online Boutique resources are to be deployed" default = "default" } variable "filepath_manifest" { type = string description = "Path to Online Boutique's Kubernetes resources, written using Kustomize" default = "../kustomize/" } variable "memorystore" { type = bool description = "If true, Online Boutique's in-cluster Redis cache will be replaced with a Google Cloud Memorystore Redis cache" }