[
  {
    "path": ".deploystack/deploystack.yaml",
    "content": "# The fields inside this deploystack.yaml file are documented in https://github.com/GoogleCloudPlatform/deploystack.\n\ntitle: Microservices Demo (Online Boutique)\nname: microservices-demo\nduration: 5\ncollect_project: true\ncollect_region: true\nregion_type: compute\nregion_default: us-central1\nhard_settings:\n  filepath_manifest: ../kustomize/\n  memorystore: \"false\"\n  name: online-boutique\n  namespace: default\ndocumentation_link: https://cloud.google.com/shell/docs/cloud-shell-tutorials/deploystack/microservices-demo\n"
  },
  {
    "path": ".deploystack/messages/description.txt",
    "content": "Online Boutique is a cloud-first microservices demo application. Online Boutique\nconsists of an 11-tier microservices application. The application is a web-based\ne-commerce app where users can browse items, add them to the cart, and purchase\nthem."
  },
  {
    "path": ".deploystack/messages/success.txt",
    "content": "Congrats!\nYou have successfully provisioned a GKE (Google Kubernetes Engine) cluster and\ndeployed Online Boutique's 11 microservices, which includes a load generator.\n\n"
  },
  {
    "path": ".deploystack/scripts/preinit.sh",
    "content": "# Copyright 2022 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nROOT=$(pwd)\nsed -i.tmp  \"s/project_id/gcp_project_id/\" $ROOT/terraform/terraform.tfvars\n"
  },
  {
    "path": ".deploystack/test",
    "content": "#! /bin/bash\n# Copyright 2021 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# DEPLOYSTACK - this file is a test script that is used by DeployStack's\n# testing rig to make sure that the Terraform script installs and uninstalls\n# cleanly\n\n# DON'T REMOVE FROM test script.\n\nCYAN='\\033[0;36m'\nBCYAN='\\033[1;36m'\nNC='\\033[0m' # No Color\nDIVIDER=$(printf %\"$(tput cols)\"s | tr \" \" \"*\")\nDIVIDER+=\"\\n\"\n\nfunction get_project_id() {\n    local __resultvar=$1\n    VALUE=$(gcloud config get-value project | xargs)\n    eval $__resultvar=\"'$VALUE'\"\n}\n\nfunction get_project_number() {\n    local __resultvar=$1\n    local PRO=$2\n    VALUE=$(gcloud projects list --filter=\"project_id=$PRO\" --format=\"value(PROJECT_NUMBER)\" | xargs)\n    eval $__resultvar=\"'$VALUE'\"\n}\n\n# DISPLAY HELPERS\n\nfunction section_open() {\n    section_description=$1\n    printf \"$DIVIDER\"\n    printf \"${CYAN}$section_description${NC} \\n\"\n    printf \"$DIVIDER\"\n}\n\nfunction section_close() {\n    printf \"$DIVIDER\"\n    printf \"${CYAN}$section_description ${BCYAN}- done${NC}\\n\"\n    printf \"\\n\\n\"\n}\n\nfunction evalTest() {\n    local command=$1\n    local expected=$2\n\n    local ERR=\"\"\n    got=$(eval $command 2>errFile)\n    ERR=$(<errFile)\n\n    if [ ${#ERR} -gt 0 ]; then\n        if [ \"$expected\" = \"EXPECTERROR\" ]; then\n            printf \"ok \\n\"\n            return\n        fi\n\n        printf \"expecting no error, got error='$ERR'   \\n\"\n        exit 1\n    fi\n\n    if [ \"$got\" != \"$expected\" ]; then\n        printf \"expecting: '$expected' got: '$got'  \\n\"\n        exit 1\n    fi\n\n    printf \"$expected is ok\\n\"\n}\n\nfunction generateProject(){\n    local __resultvar\n    local __STACKSUFFIX\n    local __BA\n    local __RANDOMSUFFIX\n    local __DATELABEL\n    local PROJECTID\n    local current\n\n\n    __resultvar=$1\n    __STACKSUFFIX=$2\n    __BA=$3\n    __RANDOMSUFFIX=$(\n        LC_ALL=C tr -dc 'a-z0-9' </dev/urandom | head -c 8\n        echo\n    )\n    __DATELABEL=$(date +%F)\n    PROJECTID=ds-test-$__STACKSUFFIX-$__RANDOMSUFFIX\n    current=$(gcloud config list account --format \"value(core.account)\")\n    # shellcheck disable=SC2034\n    GOOGLE_APPLICATION_CREDENTIALS=.deploystack/creds.json\n\n    gcloud projects create \"$PROJECTID\" --labels=\"deploystack-disposable-test-project=$__DATELABEL\" --folder=\"155265971980\"\n    \n    gcloud auth activate-service-account --project=\"$PROJECTID\" --key-file=.deploystack/creds.json -q\n    gcloud config set account test-runner@ds-tester-helper.iam.gserviceaccount.com -q\n\n    gcloud beta billing projects link \"$PROJECTID\" --billing-account=\"$__BA\" -q\n    gcloud config set account \"$current\" -q\n    eval \"$__resultvar\"=\"'$PROJECTID'\"\n    \n}\n\n# END DON'T REMOVE FROM test script.\n\nsuffix=ms\n\n# This is only needed if you tests fail alot because of overlapping runs of the\n# same set of tests.\nsection_open \"Generate random test project\"\n    generateProject PROJECT \"$suffix\" \"$BA\"\nsection_close\n\n# We need to force the use of gke-gcloud-auth-plugin (for GKE authentication)\n# if we're using kubectl 1.25 or lower.\nexport USE_GKE_GCLOUD_AUTH_PLUGIN=True\n\nDIR=\"terraform\"\ngcloud services enable cloudresourcemanager.googleapis.com --project=$PROJECT\nREGION=\"us-central1\"\nNAME=\"online-boutique\"\nNAMESPACE=\"default\"\nFILEPATH_MANIFEST=\"../kustomize/\"\nMEMORYSTORE=\"false\"\n\ngcloud config set project ${PROJECT}\n\nterraform -chdir=\"$DIR\" init\nterraform -chdir=\"$DIR\" apply -auto-approve \\\n    -var gcp_project_id=\"${PROJECT}\" \\\n    -var name=\"${NAME}\" \\\n    -var region=\"${REGION}\" \\\n    -var namespace=\"${NAMESPACE}\"  \\\n    -var filepath_manifest=\"${FILEPATH_MANIFEST}\" \\\n    -var memorystore=\"${MEMORYSTORE}\"\n\nsection_open \"Testing Google Kubernetes Engine cluster exists\"\n    evalTest 'gcloud container clusters describe online-boutique --format=\"value(name)\" --region $REGION' \"online-boutique\"\nsection_close\n\nsection_open \"Testing Online Boutique's services are running\"\n    evalTest \"kubectl get deployment adservice --no-headers -o=name 2> /dev/null\"  \"deployment.apps/adservice\"\n    evalTest \"kubectl get deployment cartservice --no-headers -o=name 2> /dev/null\"  \"deployment.apps/cartservice\"\n    evalTest \"kubectl get deployment checkoutservice --no-headers -o=name 2> /dev/null\"  \"deployment.apps/checkoutservice\"\n    evalTest \"kubectl get deployment currencyservice --no-headers -o=name 2> /dev/null\"  \"deployment.apps/currencyservice\"\n    evalTest \"kubectl get deployment emailservice --no-headers -o=name 2> /dev/null\"  \"deployment.apps/emailservice\"\n    evalTest \"kubectl get deployment loadgenerator --no-headers -o=name 2> /dev/null\"  \"deployment.apps/loadgenerator\"\n    evalTest \"kubectl get deployment paymentservice --no-headers -o=name 2> /dev/null\"  \"deployment.apps/paymentservice\"\n    evalTest \"kubectl get deployment productcatalogservice --no-headers -o=name 2> /dev/null\"  \"deployment.apps/productcatalogservice\"\n    evalTest \"kubectl get deployment recommendationservice --no-headers -o=name 2> /dev/null\"  \"deployment.apps/recommendationservice\"\n    evalTest \"kubectl get deployment redis-cart --no-headers -o=name 2> /dev/null\"  \"deployment.apps/redis-cart\"\n    evalTest \"kubectl get deployment shippingservice --no-headers -o=name 2> /dev/null\"  \"deployment.apps/shippingservice\"\nsection_close\n\nsleep 120\n\nENDPOINT=$( kubectl get service frontend-external --no-headers 2> /dev/null | awk '{print $4}')\n\nsection_open \"Testing Online Boutique's front-end is working\"\n    evalTest 'curl -s -o /dev/null -w \"%{http_code}\" $ENDPOINT' \"200\"\nsection_close\n\n# Uncomment the line: \"deletion_protection = false\"\nsed -i \"s/# deletion_protection/deletion_protection/g\" ${DIR}/main.tf\nterraform -chdir=\"$DIR\" apply -auto-approve \\\n    -var gcp_project_id=\"${PROJECT}\" \\\n    -var name=\"${NAME}\" \\\n    -var region=\"${REGION}\" \\\n    -var namespace=\"${NAMESPACE}\"  \\\n    -var filepath_manifest=\"${FILEPATH_MANIFEST}\" \\\n    -var memorystore=\"${MEMORYSTORE}\"\n\nterraform -chdir=\"$DIR\" destroy -auto-approve \\\n    -var gcp_project_id=\"${PROJECT}\" \\\n    -var name=\"${NAME}\" \\\n    -var region=\"${REGION}\" \\\n    -var namespace=\"${NAMESPACE}\"  \\\n    -var filepath_manifest=\"${FILEPATH_MANIFEST}\" \\\n    -var memorystore=\"${MEMORYSTORE}\"\n\nsection_open \"Testing Google Kubernetes Engine cluster does NOT exist\"\n    evalTest 'gcloud container clusters describe online-boutique --format=\"value(name)\" --region $REGION' \"EXPECTERROR\"\nsection_close\n\n# This is only needed if you tests fail alot because of overlapping runs of the\n# same set of tests. Really don't do this if you don't want to severely irritate\n# @tpryan\nsection_open \"Delete Test Project\"\n    gcloud projects delete $PROJECT -q\nsection_close\n\nprintf \"$DIVIDER\"\nprintf \"CONGRATS!!!!!!! \\n\"\nprintf \"You got the end the of your test with everything working. \\n\"\nprintf \"$DIVIDER\"\n"
  },
  {
    "path": ".deploystack/test.yaml",
    "content": "# Copyright 2021 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# DEPLOYSTACK - this file is the cloudbuild for running testing automatically\n# in the testing rig\n\nsteps:\n   - name: 'bash'\n     id: \"creds\"     \n     args: ['-c','echo $$CREDS > .deploystack/creds.json']\n     secretEnv: ['CREDS']\n   - name: 'gcr.io/cloudshell-images/cloudshell:latest'\n     entrypoint: bash\n     args: [ '.deploystack/test' ]\n     secretEnv: ['BA']\ntimeout: 4200s\noptions:\n  machineType: 'E2_HIGHCPU_8'\navailableSecrets:\n  secretManager:\n  - versionName: projects/$PROJECT_ID/secrets/creds/versions/latest\n    env: 'CREDS'\n  - versionName: projects/$PROJECT_ID/secrets/billing_account/versions/latest\n    env: 'BA'  "
  },
  {
    "path": ".editorconfig",
    "content": "# The .editorconfig is used to maintain consistent code style.\n# The .editorconfig file is supported by most text editors.\n# See https://editorconfig.org\n\nroot = true\n\n[*]\ninsert_final_newline = true\ntrim_trailing_whitespace = true\nindent_style = space\nindent_size = 2\n\n[*.cs]\nindent_size = 4\n\n[Dockerfile*]\nindent_size = 4\n\n[*.go]\nindent_style = tab\n\n[*.java]\nindent_size = 4\n\n[*.py]\nindent_size = 4\n"
  },
  {
    "path": ".gitattributes",
    "content": "# This file configures the git settings for this repository.\n\n# Converts \"CR + LF\" to \"LF\", for all \"text\" files — for local files on all OSes and files pushed to the remote repo.\n* text=auto eol=lf\n"
  },
  {
    "path": ".github/CODEOWNERS",
    "content": "# See https://help.github.com/en/articles/about-code-owners\n# for more info about CODEOWNERS file.\n\n# These owners will be the default owners for everything in\n# the repo. Unless a later match takes precedence.\n* @GoogleCloudPlatform/devrel-flagship-app-maintainers @yoshi-approver\n"
  },
  {
    "path": ".github/CODE_OF_CONDUCT.md",
    "content": "# Contributor Code of Conduct\n\nAs contributors and maintainers of this project,\nand in the interest of fostering an open and welcoming community,\nwe pledge to respect all people who contribute through reporting issues,\nposting feature requests, updating documentation,\nsubmitting pull requests or patches, and other activities.\n\nWe are committed to making participation in this project\na harassment-free experience for everyone,\nregardless of level of experience, gender, gender identity and expression,\nsexual orientation, disability, personal appearance,\nbody size, race, ethnicity, age, religion, or nationality.\n\nExamples of unacceptable behavior by participants include:\n\n* The use of sexualized language or imagery\n* Personal attacks\n* Trolling or insulting/derogatory comments\n* Public or private harassment\n* Publishing other's private information,\nsuch as physical or electronic\naddresses, without explicit permission\n* Other unethical or unprofessional conduct.\n\nProject maintainers have the right and responsibility to remove, edit, or reject\ncomments, commits, code, wiki edits, issues, and other contributions\nthat are not aligned to this Code of Conduct.\nBy adopting this Code of Conduct,\nproject maintainers commit themselves to fairly and consistently\napplying these principles to every aspect of managing this project.\nProject maintainers who do not follow or enforce the Code of Conduct\nmay be permanently removed from the project team.\n\nThis code of conduct applies both within project spaces and in public spaces\nwhen an individual is representing the project or its community.\n\nInstances of abusive, harassing, or otherwise unacceptable behavior\nmay be reported by opening an issue\nor contacting one or more of the project maintainers.\n\nThis Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.2.0,\navailable at [http://contributor-covenant.org/version/1/2/0/](http://contributor-covenant.org/version/1/2/0/)\n"
  },
  {
    "path": ".github/CONTRIBUTING.md",
    "content": "# How to Contribute\n\nThank you so much for your interest in contributing to Online Boutique.\nBefore contributing, you must:\n* Sign the [Contributor License Agreement (CLA)](#contributor-license-agreement).\n* Follow the [Google Open Source Community Guidelines](https://opensource.google.com/conduct/).\n* Follow the [Contribution Process](#contribution-process).\n\n## Contributor License Agreement\n\nContributions to Online Boutique must be accompanied by a Contributor License\nAgreement (CLA). You (or your employer) retain the copyright to your contribution.\nThe CLA gives us permission to use and redistribute your contributions as\npart of the project. Head over to <https://cla.developers.google.com/> to see\nyour current agreements on file or to sign a new one.\n\nYou generally only need to submit a CLA once, so if you've already submitted one\n(even if it was for a different project), you probably don't need to do it\nagain.\n\n## Contribution Process\n\nHere's the process for making a change to this repository:\n\n1. Review Online Boutique's [purpose](/docs/purpose.md) and [product requirements](/docs/product-requirements.md).\n1. 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.\n1. For **small changes** (such as a bug fixes or spelling corrections):\n    1. Fork this repository and submit a [pull request](https://help.github.com/articles/about-pull-requests/).\n    1. Wait for a maintainer of this repository to review your change.\n1. For **bigger changes**:\n    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.\n    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.\n\nIf you have any questions, please [create a GitHub issue](https://github.com/GoogleCloudPlatform/microservices-demo/issues/new/choose).\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug-report.md",
    "content": "---\nname: Bug report\nabout: Create a report to help us improve\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n\n### Describe the bug \n<!-- A clear and concise description of what the bug is. -->\n\n### To Reproduce \n<!-- Steps to reproduce the behavior: -->\n<!-- 1. Built image '...' -->\n<!-- 2. Ran command '....' -->\n<!-- 3. See error -->\n\n### Logs  \n<!-- Add logs to help explain your problem -->\n\n### Screenshots \n<!-- If applicable, add screenshots to help explain your problem -->\n\n### Environment \n<!--  - OS: [e.g. MacOS Big Sur v11.6] -->\n<!--  - Kubernetes distribution, version: [e.g. minikube, GKE (Standard or Autopilot), EKS, AWS ... ] -->\n<!--  - Any relevant tool version: [e.g. Docker v20.10.8] -->\n\n### Additional context \n<!-- Add any other context about the problem here -->\n\n### Exposure \n<!-- Is the bug intermittent, persistent? Is it widespread, local? -->\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature-request.md",
    "content": "---\nname: Feature request\nabout: Suggest an idea for this project\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n\n### Describe request or inquiry \n<!-- Add any other context about the problem or helpful links here! -->\n\n### What purpose/environment will this feature serve? \n<!-- Add reasoning -->\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/other.md",
    "content": "---\nname: Other\nabout: Have a question or need clarification?\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n### Write down your inquiry \n<!-- Write your question/inquiry here and any addition context -->\n"
  },
  {
    "path": ".github/SECURITY.md",
    "content": "# Security Policy\n\nTo report a security issue, please use [g.co/vulnz](https://g.co/vulnz).\n\nThe Google Security Team will respond within 5 working days of your report on g.co/vulnz.\n\nWe use g.co/vulnz for our intake, and do coordination and disclosure here using GitHub Security Advisory to privately discuss and fix the issue.\n"
  },
  {
    "path": ".github/auto-approve.yml",
    "content": "# Copyright 2023 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# https://github.com/googleapis/repo-automation-bots/tree/main/packages/auto-approve\nprocesses:\n  - \"PythonDependency\"\n  - \"PythonSampleAppDependency\"\n  - \"JavaDependency\"\n  - \"JavaSampleAppDependency\"\n  - \"GoDependency\"\n  - \"NodeDependency\"\n  - \"DockerDependency\"\n"
  },
  {
    "path": ".github/header-checker-lint.yml",
    "content": "# Copyright 2023 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# This file configures a GitHub Bot called \"License Header Lint GCF\": https://github.com/apps/license-header-lint-gcf\n# The bot runs a GitHub check called \"header-check\" (inside pull-requests) that warns us about invalid/missing license headers.\n# 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.\n\nallowedCopyrightHolders:\n  - 'Google LLC'\n\nallowedLicenses:\n  - 'Apache-2.0'\n\n# If you want to ignore certain files/folders, use ignoreFiles.\n# ignoreFiles:\n#  - '**/requirements.txt'\n\n# If you want to ignore checking the license year, use ignoreLicenseYear.\n# ignoreLicenseYear: true # Useful when migrating in code licensed at previous years.\n\nsourceFileExtensions:\n  - 'cs'\n  - 'css'\n  - 'Dockerfile'\n  - 'dockerignore'\n  - 'gitignore'\n  - 'go'\n  - 'html'\n  - 'java'\n  - 'js'\n  - 'proto'\n  - 'py'\n  - 'sh'\n  - 'tf'\n  - 'yaml'\n  - 'yml'\n"
  },
  {
    "path": ".github/pull_request_template.md",
    "content": "### Background \n<!-- What was happening before this PR, and the problem(s) it solves -->\n\n### Fixes \n<!-- Link the issue(s) this PR fixes-->\n### Change Summary\n<!-- Short summary of the changes submitted -->\n\n### Additional Notes\n<!-- Any remaining concerns -->\n\n### Testing Procedure\n<!-- If applicable, write how to test for reviewers-->\n\n### Related PRs or Issues \n<!-- Dependent PRs, or any relevant linked issues -->\n"
  },
  {
    "path": ".github/release-cluster/README.md",
    "content": "# cymbal-shops.retail.cymbal.dev manifests\n\nThis directory contains extra deploy manifests for configuring Online Boutique solution on GKE for cymbal-shops.retail.cymbal.dev.\n\n_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)._\n\n## Public static IP address\n\nCreate the static public IP address:\n```\nSTATIC_IP_NAME=online-boutique-ip # name hard-coded in: frontend-ingress.yaml\ngcloud compute addresses create $STATIC_IP_NAME --global\n```\n\nWhen ready to do so, you could grab this public IP address and update your DNS:\n```\ngcloud compute addresses describe $STATIC_IP_NAME \\\n    --global \\\n    --format \"value(address)\"\n```\n\n## Cloud Armor\n\nSet up Cloud Armor:\n```\nSECURITY_POLICY_NAME=online-boutique-security-policy # Name hard-coded in: backendconfig.yaml\ngcloud compute security-policies create $SECURITY_POLICY_NAME \\\n    --description \"Block various attacks\"\ngcloud compute security-policies rules create 1000 \\\n    --security-policy $SECURITY_POLICY_NAME \\\n    --expression \"evaluatePreconfiguredExpr('xss-stable')\" \\\n    --action \"deny-403\" \\\n    --description \"XSS attack filtering\"\ngcloud compute security-policies rules create 12345 \\\n    --security-policy $SECURITY_POLICY_NAME \\\n    --expression \"evaluatePreconfiguredExpr('cve-canary')\" \\\n    --action \"deny-403\" \\\n    --description \"CVE-2021-44228 and CVE-2021-45046\"\ngcloud compute security-policies update $SECURITY_POLICY_NAME \\\n    --enable-layer7-ddos-defense\ngcloud compute security-policies update $SECURITY_POLICY_NAME \\\n    --log-level=VERBOSE\n```\n\n## SSL Policy\n\nSet up an SSL policy in order to later set up a redirect from HTTP to HTTPs:\n```\nSSL_POLICY_NAME=online-boutique-ssl-policy # Name hard-coded in: frontendconfig.yaml\ngcloud compute ssl-policies create $SSL_POLICY_NAME \\\n    --profile COMPATIBLE  \\\n    --min-tls-version 1.0\n```\n\n## Deploy Kubernetes manifests\n\nDeploy the Kubernetes manifests in this current folder:\n```\nkubectl apply -f .\n```\n\nWait for the `ManagedCertificate` to be provisioned. This usually takes about 30 minutes.\n```\nkubectl get managedcertificates\n```\n\nRemove the default `LoadBalancer` `Service` not used at this point:\n```\nkubectl delete service frontend-external\n```\n\nRemove the `loadgenerator` `Deployment` not used at this point:\n```\nkubectl delete deployment loadgenerator\n```"
  },
  {
    "path": ".github/release-cluster/backend-config.yaml",
    "content": "# Copyright 2024 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      https://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\napiVersion: cloud.google.com/v1\nkind: BackendConfig\nmetadata:\n  name: frontend-backend-config\nspec:\n  securityPolicy:\n    name: online-boutique-security-policy"
  },
  {
    "path": ".github/release-cluster/frontend-config.yaml",
    "content": "# Copyright 2024 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      https://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\napiVersion: networking.gke.io/v1beta1\nkind: FrontendConfig\nmetadata:\n  name: frontend-frontend-config\nspec:\n  sslPolicy: online-boutique-ssl-policy\n  redirectToHttps:\n    enabled: true\n    responseCodeName: MOVED_PERMANENTLY_DEFAULT"
  },
  {
    "path": ".github/release-cluster/frontend-ingress.yaml",
    "content": "# Copyright 2024 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      https://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\napiVersion: networking.k8s.io/v1\nkind: Ingress\nmetadata:\n  name: frontend-ingress\n  annotations:\n    kubernetes.io/ingress.global-static-ip-name: online-boutique-ip\n    networking.gke.io/managed-certificates: online-boutique-certificate\n    networking.gke.io/v1beta1.FrontendConfig: frontend-frontend-config\nspec:\n  defaultBackend:\n    service:\n      name: frontend\n      port:\n        number: 80\n  rules:\n  - http:\n      paths:\n      - path: /*\n        pathType: ImplementationSpecific\n        backend:\n          service:\n            name: frontend\n            port:\n              number: 80\n"
  },
  {
    "path": ".github/release-cluster/frontend-service.yaml",
    "content": "# Copyright 2024 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      https://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\napiVersion: v1\nkind: Service\nmetadata:\n  name: frontend\n  annotations:\n    cloud.google.com/neg: '{\"ingress\": true}'\n    cloud.google.com/backend-config: '{\"default\": \"frontend-backend-config\"}'\nspec:\n  type: ClusterIP\n  selector:\n    app: frontend\n  ports:\n  - name: http\n    port: 80\n    targetPort: 8080"
  },
  {
    "path": ".github/release-cluster/managed-cert.yaml",
    "content": "# Copyright 2024 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      https://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\napiVersion: networking.gke.io/v1\nkind: ManagedCertificate\nmetadata:\n  name: online-boutique-certificate\nspec:\n  domains:\n    - cymbal-shops.retail.cymbal.dev\n"
  },
  {
    "path": ".github/renovate.json5",
    "content": "{\n  extends: [\n    'github>GoogleCloudPlatform/kubernetes-engine-samples//.github/renovate-configs/dee-platform-ops.json5',\n    'schedule:earlyMondays',\n  ],\n  'pip-compile': {\n    enabled: true,\n    managerFilePatterns: [\n      '/(^|/)requirements\\\\.txt$/',\n    ],\n  },\n  pip_requirements: {\n    enabled: false,\n  },\n  constraints: {\n    python: '~=3.11.0',\n  },\n  kubernetes: {\n    managerFilePatterns: [\n      '/\\\\.yaml$/',\n    ],\n    ignorePaths: [\n      'release/**',\n      'kustomize/base/**',\n    ],\n  },\n}\n"
  },
  {
    "path": ".github/snippet-bot.yml",
    "content": "\n# Copyright 2021 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n"
  },
  {
    "path": ".github/terraform/README.md",
    "content": "This folder contains the Terraform for some of the infrastructure used by the CICD (continuous integration and continuous delivery/continuous deployment) of this repository.\n\n## Update this Terraform\n\nTo make changes to this Terraform, follow these steps:\n\n1. Make sure you have access to the `online-boutique-ci` Google Cloud project.\n1. Move into this folder: `cd .github/terraform`\n1. Set the PROJECT_ID environment variable: `export PROJECT_ID=online-boutique-ci`\n1. Prepare Terraform and download the necessary Terraform dependencies (such as the \"hashicorp/google\" Terraform provider): `terraform init`\n1. Apply the Terraform: `terraform apply -var project_id=${PROJECT_ID}`\n    * Ideally, you would see `Apply complete! Resources: 0 added, 0 changed, 0 destroyed.` in the output.\n1. Make your desired changes to the Terraform code.\n1. Apply the Terraform: `terraform apply -var project_id=${PROJECT_ID}`\n    * This time, Terraform will prompt you confirm your changes before applying them.\n"
  },
  {
    "path": ".github/terraform/main.tf",
    "content": "/**\n * Copyright 2024 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n# Set defaults for the google Terraform provider.\nprovider \"google\" {\n  project = var.project_id\n  region  = \"us-central1\"\n  zone    = \"us-central1-a\"\n}\n\nterraform {\n  # Store the state inside a Google Cloud Storage bucket.\n  backend \"gcs\" {\n    bucket = \"cicd-terraform-state\"\n    prefix = \"terraform-state\"\n  }\n}\n\n# Enable Google Cloud APIs.\nmodule \"enable_google_apis\" {\n  source                      = \"terraform-google-modules/project-factory/google//modules/project_services\"\n  version                     = \"~> 18.0\"\n  disable_services_on_destroy = false\n  activate_apis = [\n    \"cloudresourcemanager.googleapis.com\",\n    \"container.googleapis.com\",\n    \"iam.googleapis.com\",\n    \"storage.googleapis.com\",\n  ]\n  project_id = var.project_id\n}\n\n# Google Cloud Storage for storing Terraform state (.tfstate).\nresource \"google_storage_bucket\" \"terraform_state_storage_bucket\" {\n  name                        = \"cicd-terraform-state\"\n  location                    = \"us\"\n  storage_class               = \"STANDARD\"\n  force_destroy               = false\n  public_access_prevention    = \"enforced\"\n  uniform_bucket_level_access = true\n  versioning {\n    enabled = true\n  }\n}\n\n# Google Cloud IAM service account for GKE clusters.\n# We avoid using the Compute Engine default service account because it's too permissive.\nresource \"google_service_account\" \"gke_clusters_service_account\" {\n  account_id   = \"gke-clusters-service-account\"\n  display_name = \"My Service Account\"\n  depends_on = [\n    module.enable_google_apis\n  ]\n}\n\n# See https://cloud.google.com/kubernetes-engine/docs/how-to/hardening-your-cluster#use_least_privilege_sa\nresource \"google_project_iam_member\" \"gke_clusters_service_account_role_metric_writer\" {\n  project = var.project_id\n  role    = \"roles/monitoring.metricWriter\"\n  member  = \"serviceAccount:${google_service_account.gke_clusters_service_account.email}\"\n}\n\n# See https://cloud.google.com/kubernetes-engine/docs/how-to/hardening-your-cluster#use_least_privilege_sa\nresource \"google_project_iam_member\" \"gke_clusters_service_account_role_logging_writer\" {\n  project = var.project_id\n  role    = \"roles/logging.logWriter\"\n  member  = \"serviceAccount:${google_service_account.gke_clusters_service_account.email}\"\n}\n\n# See https://cloud.google.com/kubernetes-engine/docs/how-to/hardening-your-cluster#use_least_privilege_sa\nresource \"google_project_iam_member\" \"gke_clusters_service_account_role_monitoring_viewer\" {\n  project = var.project_id\n  role    = \"roles/monitoring.viewer\"\n  member  = \"serviceAccount:${google_service_account.gke_clusters_service_account.email}\"\n}\n\n# See https://cloud.google.com/kubernetes-engine/docs/how-to/hardening-your-cluster#use_least_privilege_sa\nresource \"google_project_iam_member\" \"gke_clusters_service_account_role_stackdriver_writer\" {\n  project = var.project_id\n  role    = \"roles/stackdriver.resourceMetadata.writer\"\n  member  = \"serviceAccount:${google_service_account.gke_clusters_service_account.email}\"\n}\n\n# The GKE cluster used for pull-request (PR) staging deployments.\nresource \"google_container_cluster\" \"prs_gke_cluster\" {\n  name                = \"prs-gke-cluster\"\n  location            = \"us-central1\"\n  enable_autopilot    = true\n  project             = var.project_id\n  deletion_protection = true\n  depends_on = [\n    module.enable_google_apis\n  ]\n  cluster_autoscaling {\n    auto_provisioning_defaults {\n      service_account = google_service_account.gke_clusters_service_account.email\n    }\n  }\n  # Need an empty ip_allocation_policy to overcome an error related to autopilot node pool constraints.\n  # Workaround from https://github.com/hashicorp/terraform-provider-google/issues/10782#issuecomment-1024488630\n  ip_allocation_policy {\n  }\n}\n"
  },
  {
    "path": ".github/terraform/variables.tf",
    "content": "/**\n * Copyright 2024 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n# This file lists variables that you can set using the -var flag during \"terraform apply\".\n# Example: terraform apply -var project_id=\"${PROJECT_ID}\"\n\nvariable \"project_id\" {\n  type        = string\n  description = \"The Google Cloud project ID.\"\n}\n"
  },
  {
    "path": ".github/terraform/versions.tf",
    "content": "/**\n * Copyright 2024 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nterraform {\n  required_version = \">= 0.13\"\n  required_providers {\n    google = {\n      source  = \"hashicorp/google\"\n      version = \"~> 7.0\"\n    }\n  }\n}\n"
  },
  {
    "path": ".github/workflows/README.md",
    "content": "# GitHub Actions Workflows\n\nThis page describes the CI/CD workflows for the Online Boutique app, which run in [Github Actions](https://github.com/GoogleCloudPlatform/microservices-demo/actions).\n\n## Infrastructure\n\nThe 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)\n\nWe also host a test GKE cluster, which is where the deploy tests run. Every PR has its own namespace in the cluster.\n\n## Workflows\n\n**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.\n\n### Code Tests - [ci-pr.yaml](ci-pr.yaml)\n\nThese 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.\n\n\n### Deploy Tests- [ci-pr.yaml](ci-pr.yaml)\n\nThese tests run on every commit for every open PR, as well as any commit to main / any release branch. This workflow:\n\n1. Creates a dedicated GKE namespace for that PR, if it doesn't already exist, in the PR GKE cluster.\n2. 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.\n3. Tests to make sure all the pods start up and become ready.\n4. Gets the LoadBalancer IP for the frontend service.\n5. Comments that IP in the pull request, for staging.\n\n### Push and Deploy Latest - [push-deploy](push-deploy.yml)\n\nThis is the Continuous Deployment workflow, and it runs on every commit to the main branch. This workflow:\n\n1. Builds the container images for every service, tagging as `latest`.\n2. Pushes those images to Google Container Registry.\n\nNote 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.\n\n### Cleanup - [cleanup.yaml](cleanup.yaml)\n\nThis 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.\n\n## Appendix - Creating a new Actions runner\n\nShould 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.\n\n1. Create a GCE instance.\n    - VM should be at least n1-standard-4 with 50GB persistent disk\n    - VM should use custom service account with permissions to: access a GKE cluster, create GCS storage buckets, and push to GCR.\n2. SSH into new VM through the Google Cloud Console.\n3. Install project-specific dependencies, including go, docker, skaffold, and kubectl:\n\n```\nwget -O - https://raw.githubusercontent.com/GoogleCloudPlatform/microservices-demo/main/.github/workflows/install-dependencies.sh | bash\n```\n\nThe instance will restart when the script completes in order to finish the Docker install.\n\n4. SSH back into the VM.\n\n5. 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\n6. Start GitHub Actions as a background service:\n```\nsudo ~/actions-runner/svc.sh install ; sudo ~/actions-runner/svc.sh start\n```\n"
  },
  {
    "path": ".github/workflows/ci-main.yaml",
    "content": "# Copyright 2020 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nname: \"Continuous Integration - Main/Release\"\non:\n  push:\n    # run on pushes to main or release/*\n    branches:\n      - main\n      - release/*\n    paths-ignore:\n      - '**/README.md'\n      - 'kustomize/**'\n      - '.github/workflows/kustomize-build-ci.yaml'\n      - 'terraform/**'\n      - '.github/workflows/terraform-validate-ci.yaml'\n      - 'helm-chart/**'\n      - '.github/workflows/helm-chart-ci.yaml'\njobs:\n  code-tests:\n    runs-on: [self-hosted, is-enabled]\n    steps:\n    - uses: actions/checkout@v6\n    - uses: actions/setup-dotnet@v5\n      env:\n        DOTNET_INSTALL_DIR: \"./.dotnet\"\n      with:\n        dotnet-version: '10.0'\n    - uses: actions/setup-go@v6\n      with:\n        go-version: '1.26'\n    - name: Go Unit Tests\n      timeout-minutes: 10\n      run: |\n        for SERVICE in \"shippingservice\" \"productcatalogservice\"; do\n          echo \"testing $SERVICE...\"\n          pushd src/$SERVICE\n          go test\n          popd\n        done\n    - name: C# Unit Tests\n      timeout-minutes: 10\n      run: |\n        dotnet test src/cartservice/\n  deployment-tests:\n    runs-on: [self-hosted, is-enabled]\n    needs: code-tests\n    strategy:\n      matrix:\n        profile: [\"local-code\"]\n      fail-fast: true\n    steps:\n    - uses: actions/checkout@v6\n    - name: Build + Deploy PR images to GKE\n      timeout-minutes: 20\n      run: |\n        PR_NUMBER=$(echo $GITHUB_REF | awk 'BEGIN { FS = \"/\" } ; { print $3 }')\n        NAMESPACE=\"pr${PR_NUMBER}\"\n        echo \"::set-env name=NAMESPACE::$NAMESPACE\"\n        echo \"::set-env name=PR_NUMBER::$PR_NUMBER\"\n\n        yes | gcloud auth configure-docker us-docker.pkg.dev\n        gcloud container clusters get-credentials $PR_CLUSTER --region $REGION --project $PROJECT_ID\n        cat <<EOF | kubectl apply -f -\n        apiVersion: v1\n        kind: Namespace\n        metadata:\n          name: $NAMESPACE\n        EOF\n        echo Deploying application\n        skaffold config set --global local-cluster false\n        skaffold run --default-repo=us-docker.pkg.dev/$PROJECT_ID/$GITHUB_REF --tag=$GITHUB_SHA --namespace=$NAMESPACE -p network-policies\n      env:\n        ACTIONS_ALLOW_UNSECURE_COMMANDS: true\n        PROJECT_ID: \"online-boutique-ci\"\n        PR_CLUSTER: \"prs-gke-cluster\"\n        REGION: \"us-central1\"\n    - name: Wait For Pods\n      timeout-minutes: 20\n      run: |\n        set -x\n        kubectl config set-context --current --namespace=$NAMESPACE\n        kubectl wait --for=condition=available --timeout=1000s deployment/redis-cart\n        kubectl wait --for=condition=available --timeout=1000s deployment/adservice\n        kubectl wait --for=condition=available --timeout=1000s deployment/cartservice\n        kubectl wait --for=condition=available --timeout=1000s deployment/checkoutservice\n        kubectl wait --for=condition=available --timeout=1000s deployment/currencyservice\n        kubectl wait --for=condition=available --timeout=1000s deployment/emailservice\n        kubectl wait --for=condition=available --timeout=1000s deployment/frontend\n        kubectl wait --for=condition=available --timeout=1000s deployment/loadgenerator\n        kubectl wait --for=condition=available --timeout=1000s deployment/paymentservice\n        kubectl wait --for=condition=available --timeout=1000s deployment/productcatalogservice\n        kubectl wait --for=condition=available --timeout=1000s deployment/recommendationservice\n        kubectl wait --for=condition=available --timeout=1000s deployment/shippingservice\n    - name: Smoke Test\n      timeout-minutes: 5\n      run: |\n        set -x\n        # start fresh loadgenerator pod\n        kubectl delete pod -l app=loadgenerator\n        # wait for requests to come in\n        REQUEST_COUNT=\"0\"\n        while [[ \"$REQUEST_COUNT\"  -lt \"50\"  ]]; do\n            sleep 5\n            REQUEST_COUNT=$(kubectl logs -l app=loadgenerator | grep Aggregated | awk '{print $2}')\n        done\n        # ensure there are no errors hitting endpoints\n        ERROR_COUNT=$(kubectl logs -l app=loadgenerator | grep Aggregated | awk '{print $3}' | sed \"s/[(][^)]*[)]//g\")\n        if [[ \"$ERROR_COUNT\" -gt \"0\" ]]; then\n          exit 1\n        fi\n"
  },
  {
    "path": ".github/workflows/ci-pr.yaml",
    "content": "# Copyright 2020 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nname: \"Continuous Integration - Pull Request\"\non:\n  pull_request:\n    branches:\n      - main\n    paths-ignore:\n      - '**/README.md'\n      - 'kustomize/**'\n      - '.github/workflows/kustomize-build-ci.yaml'\n      - 'terraform/**'\n      - '.github/workflows/terraform-validate-ci.yaml'\n      - 'helm-chart/**'\n      - '.github/workflows/helm-chart-ci.yaml'\n\n# Ensure this workflow only runs for the most recent commit of a pull-request\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}\n  cancel-in-progress: true\n\njobs:\n  code-tests:\n    runs-on: [self-hosted, is-enabled]\n    steps:\n    - uses: actions/checkout@v6\n    - uses: actions/setup-dotnet@v5\n      env:\n        DOTNET_INSTALL_DIR: \"./.dotnet\"\n      with:\n        dotnet-version: '10.0'\n    - uses: actions/setup-go@v6\n      with:\n        go-version: '1.26'\n    - name: Go Unit Tests\n      timeout-minutes: 10\n      run: |\n        for GO_PACKAGE in \"shippingservice\" \"productcatalogservice\" \"frontend/validator\"; do\n          echo \"Testing $GO_PACKAGE...\"\n          pushd src/$GO_PACKAGE\n          go test\n          popd\n        done\n    - name: C# Unit Tests\n      timeout-minutes: 10\n      run: |\n        dotnet test src/cartservice/\n\n  deployment-tests:\n    runs-on: [self-hosted, is-enabled]\n    needs: code-tests\n    strategy:\n      matrix:\n        profile: [\"local-code\"]\n      fail-fast: true\n    steps:\n    - uses: actions/checkout@v6\n      with:\n        ref: ${{github.event.pull_request.head.sha}}\n    - name: Build + Deploy PR images to GKE\n      timeout-minutes: 20\n      run: |\n        NAMESPACE=\"pr${PR_NUMBER}\"\n        echo \"::set-env name=NAMESPACE::$NAMESPACE\"\n\n        yes | gcloud auth configure-docker us-docker.pkg.dev\n        gcloud container clusters get-credentials $PR_CLUSTER --region $REGION --project $PROJECT_ID\n        cat <<EOF | kubectl apply -f -\n        apiVersion: v1\n        kind: Namespace\n        metadata:\n          name: $NAMESPACE\n        EOF\n        echo Deploying application\n        skaffold config set --global local-cluster false\n        skaffold run --default-repo=us-docker.pkg.dev/$PROJECT_ID/refs/pull/$PR_NUMBER --tag=$PR_NUMBER --namespace=$NAMESPACE -p network-policies\n      env:\n        ACTIONS_ALLOW_UNSECURE_COMMANDS: true\n        PR_NUMBER: ${{ github.event.pull_request.number }}\n        PROJECT_ID: \"online-boutique-ci\"\n        PR_CLUSTER: \"prs-gke-cluster\"\n        REGION: \"us-central1\"\n    - name: Wait For Pods\n      timeout-minutes: 20\n      run: |\n        set -x\n        kubectl config set-context --current --namespace=$NAMESPACE\n        kubectl wait --for=condition=available --timeout=1000s deployment/redis-cart\n        kubectl wait --for=condition=available --timeout=1000s deployment/adservice\n        kubectl wait --for=condition=available --timeout=1000s deployment/cartservice\n        kubectl wait --for=condition=available --timeout=1000s deployment/checkoutservice\n        kubectl wait --for=condition=available --timeout=1000s deployment/currencyservice\n        kubectl wait --for=condition=available --timeout=1000s deployment/emailservice\n        kubectl wait --for=condition=available --timeout=1000s deployment/frontend\n        kubectl wait --for=condition=available --timeout=1000s deployment/loadgenerator\n        kubectl wait --for=condition=available --timeout=1000s deployment/paymentservice\n        kubectl wait --for=condition=available --timeout=1000s deployment/productcatalogservice\n        kubectl wait --for=condition=available --timeout=1000s deployment/recommendationservice\n        kubectl wait --for=condition=available --timeout=1000s deployment/shippingservice\n    - name: Query EXTERNAL_IP for staging\n      timeout-minutes: 5\n      run: |\n        set -x\n        NAMESPACE=\"pr${PR_NUMBER}\"\n        get_externalIP() {\n          kubectl get service frontend-external --namespace $NAMESPACE -o jsonpath='{.status.loadBalancer.ingress[0].ip}'\n        }\n        until [[ -n \"$(get_externalIP)\" ]]; do\n          echo \"Querying for external IP for frontend-external on namespace: $NAMESPACE{}\"\n        sleep 3\n        done\n        EXTERNAL_IP=$(get_externalIP)\n        echo \"::set-env name=EXTERNAL_IP::$EXTERNAL_IP\"\n      env:\n        ACTIONS_ALLOW_UNSECURE_COMMANDS: true\n        PR_NUMBER: ${{ github.event.pull_request.number }}\n    - name: Smoke Test\n      timeout-minutes: 5\n      run: |\n        set -x\n        # start fresh loadgenerator pod\n        kubectl delete pod -l app=loadgenerator\n        # wait for requests to come in\n        REQUEST_COUNT=\"0\"\n        while [[ \"$REQUEST_COUNT\"  -lt \"50\"  ]]; do\n            sleep 5\n            REQUEST_COUNT=$(kubectl logs -l app=loadgenerator | grep Aggregated | awk '{print $2}')\n        done\n        # ensure there are no errors hitting endpoints\n        ERROR_COUNT=$(kubectl logs -l app=loadgenerator | grep Aggregated | awk '{print $3}' | sed \"s/[(][^)]*[)]//g\")\n        if [[ \"$ERROR_COUNT\" -gt \"0\" ]]; then\n          exit 1\n        fi\n    - name: Comment EXTERNAL_IP\n      timeout-minutes: 5\n      env:\n          COMMENTS_URL: ${{ github.event.pull_request.comments_url }}\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n      run: |\n          curl \\\n            -X POST \\\n            $COMMENTS_URL \\\n            -H \"Content-Type: application/json\" \\\n            -H \"Authorization: token $GITHUB_TOKEN\" \\\n            --data '{ \"body\": \"🚲 PR staged at '\"http://${EXTERNAL_IP}\"'\"}'\n          sleep 60\n"
  },
  {
    "path": ".github/workflows/cleanup.yaml",
    "content": "# Copyright 2020 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nname: \"Clean up deployment\"\non:\n  pull_request:\n    # run on pull requests targeting main\n    branches:\n      - main\n    types: closed\n    paths-ignore:\n      - '**/README.md'\n      - 'kustomize/**'\n      - '.github/workflows/kustomize-build-ci.yaml'\n      - 'terraform/**'\n      - '.github/workflows/terraform-validate-ci.yaml'\njobs:\n  cleanup-namespace:\n    runs-on: [self-hosted, is-enabled]\n    steps:\n      - name: Delete PR namespace in staging cluster\n        if: ${{ always() }}\n        timeout-minutes: 20\n        run: |\n          gcloud container clusters get-credentials $PR_CLUSTER \\\n              --region $REGION --project $PROJECT_ID\n          NAMESPACE=\"pr${PR_NUMBER}\"\n          kubectl delete namespace $NAMESPACE\n        env:\n          PROJECT_ID: \"online-boutique-ci\"\n          PR_CLUSTER: \"prs-gke-cluster\"\n          REGION: \"us-central1\"\n          PR_NUMBER: ${{ github.event.number }}\n"
  },
  {
    "path": ".github/workflows/helm-chart-ci.yaml",
    "content": "# Copyright 2022 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nname: helm-chart-ci\non:\n  push:\n    branches:\n      - main\n    paths:\n      - 'helm-chart/**'\n      - '.github/workflows/helm-chart-ci.yaml'\n  pull_request:\n    paths:\n      - 'helm-chart/**'\n      - '.github/workflows/helm-chart-ci.yaml'\njobs:\n  helm-chart-ci:\n    runs-on: ubuntu-24.04\n    steps:\n      - uses: actions/checkout@v6\n      - name: helm lint\n        run: |\n          cd helm-chart/\n          helm lint --strict\n      - name: helm template default\n        run: |\n          cd helm-chart/\n          helm template . > helm-template.yaml\n          cat helm-template.yaml \n          kustomize create --resources helm-template.yaml\n          kustomize build .\n      - name: helm template grpc health probes\n        run: |\n          # Test related to https://medium.com/google-cloud/b5bd26253a4c\n          cd helm-chart/\n          SPANNER_CONNECTION_STRING=projects/PROJECT_ID/instances/SPANNER_INSTANCE_NAME/databases/SPANNER_DATABASE_NAME\n          helm template . \\\n            --set nativeGrpcHealthCheck=true \\\n            -n onlineboutique \\\n            > helm-template.yaml\n          cat helm-template.yaml\n          kustomize build .\n      - name: helm template spanner\n        run: |\n          # Test related to https://medium.com/google-cloud/f7248e077339\n          cd helm-chart/\n          SPANNER_CONNECTION_STRING=projects/PROJECT_ID/instances/SPANNER_INSTANCE_NAME/databases/SPANNER_DATABASE_NAME\n          SPANNER_DB_USER_GSA_ID=spanner-db-user@my-project.iam.gserviceaccount.com\n          helm template . \\\n            --set cartDatabase.inClusterRedis.create=false \\\n            --set cartDatabase.type=spanner \\\n            --set cartDatabase.connectionString=${SPANNER_CONNECTION_STRING} \\\n            --set serviceAccounts.create=true \\\n            --set serviceAccounts.annotationsOnlyForCartservice=true \\\n            --set \"serviceAccounts.annotations.iam\\.gke\\.io/gcp-service-account=${SPANNER_DB_USER_GSA_ID}\" \\\n            -n onlineboutique \\\n            > helm-template.yaml\n          cat helm-template.yaml\n          kustomize build .\n      - name: helm template asm\n        run: |\n          # Test related to https://medium.com/google-cloud/246119e46d53\n          cd helm-chart/\n          helm template . \\\n            --set networkPolicies.create=true \\\n            --set sidecars.create=true \\\n            --set serviceAccounts.create=true \\\n            --set authorizationPolicies.create=true \\\n            --set frontend.externalService=false \\\n            --set frontend.virtualService.create=true \\\n            --set frontend.virtualService.gateway.name=asm-ingressgateway \\\n            --set frontend.virtualService.gateway.namespace=asm-ingress \\\n            --set frontend.virtualService.gateway.labelKey=asm \\\n            --set frontend.virtualService.gateway.labelValue=ingressgateway \\\n            -n onlineboutique \\\n            > helm-template.yaml\n          cat helm-template.yaml\n          kustomize build .\n      - name: helm template memorystore istio tls origination\n        run: |\n          # Test related to https://medium.com/google-cloud/64b71969318d\n          cd helm-chart/\n          REDIS_IP=0.0.0.0\n          REDIS_PORT=7378\n          REDIS_CERT=dsjfgkldsjflkdsjflksdajfkldsjkfljsdaklfjaskjfakdsjfaklsdjflskadjfklasjfkls\n          helm template . \\\n            --set cartDatabase.inClusterRedis.create=false \\\n            --set cartDatabase.connectionString=${REDIS_IP}:${REDIS_PORT} \\\n            --set cartDatabase.externalRedisTlsOrigination.enable=true \\\n            --set cartDatabase.externalRedisTlsOrigination.certificate=\"${REDIS_CERT}\" \\\n            --set cartDatabase.externalRedisTlsOrigination.endpointAddress=${REDIS_IP} \\\n            --set cartDatabase.externalRedisTlsOrigination.endpointPort=${REDIS_PORT} \\\n            -n onlineboutique \\\n            > helm-template.yaml\n          cat helm-template.yaml\n          kustomize build .\n"
  },
  {
    "path": ".github/workflows/install-dependencies.sh",
    "content": "#!/bin/bash\n# Copyright 2020 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nset -euo pipefail\n\n# install wget\nsudo apt install -y wget\n\n# install dotnet CLI\nsudo apt-get update\nsudo apt-get install wget\nwget -O - https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor > microsoft.asc.gpg\nsudo mv microsoft.asc.gpg /etc/apt/trusted.gpg.d/\nwget https://packages.microsoft.com/config/debian/9/prod.list\nsudo mv prod.list /etc/apt/sources.list.d/microsoft-prod.list\nsudo chown root:root /etc/apt/trusted.gpg.d/microsoft.asc.gpg\nsudo chown root:root /etc/apt/sources.list.d/microsoft-prod.list\n\nsudo apt-get install -y apt-transport-https && \\\nsudo apt-get update && \\\nsudo apt-get install -y dotnet-sdk-10.0\necho \"✅ dotnet installed\"\n\n# install kubectl\nsudo apt-get install -yqq kubectl git\necho \"✅ kubectl installed\"\n\n# install go\nwget https://golang.org/dl/go1.25.linux-amd64.tar.gz\nsudo tar -C /usr/local -xzf go1.25.linux-amd64.tar.gz\necho 'export GOPATH=$HOME/go' >> ~/.profile\necho 'export PATH=$PATH:/usr/local/go/bin:$GOPATH/bin' >> ~/.profile\nsource ~/.profile\necho \"✅ golang installed\"\n\n# install build-essential (gcc, used for go test)\nsudo apt install -y build-essential\n\n# install addlicense\ngo install github.com/google/addlicense@latest\nsudo ln -s $HOME/go/bin/addlicense /bin\n\n# install build-essential (gcc, used for go test)\nsudo apt install -y build-essential\n\n# install skaffold\ncurl -Lo skaffold https://storage.googleapis.com/skaffold/releases/latest/skaffold-linux-amd64 && \\\nchmod +x skaffold && \\\nsudo mv skaffold /usr/local/bin\necho \"✅ skaffold installed\"\n\n# install docker\nsudo apt install -yqq apt-transport-https ca-certificates curl gnupg2 software-properties-common && \\\ncurl -fsSL https://download.docker.com/linux/debian/gpg | sudo apt-key add - && \\\nsudo add-apt-repository \"deb [arch=amd64] https://download.docker.com/linux/debian $(lsb_release -cs) stable\" && \\\nsudo apt-get update && \\\nsudo apt-get install -yqq docker-ce && \\\nsudo usermod -aG docker ${USER}\necho \"✅ docker installed, rebooting...\"\n\n# reboot for docker setup\nsudo reboot\n"
  },
  {
    "path": ".github/workflows/kubevious-manifests-ci.yaml",
    "content": "# Copyright 2023 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nname: kubevious-manifests-ci\non:\n  push:\n    branches:\n      - main\n    paths:\n      - 'helm-chart/**'\n      - 'kustomize/**'\n      - '.github/workflows/kubevious-manifests-ci.yaml'\n  pull_request:\n    paths:\n      - 'helm-chart/**'\n      - 'kustomize/**'\n      - '.github/workflows/kubevious-manifests-ci.yaml'\npermissions:\n  contents: read\njobs:\n  kubevious-manifests-ci:\n    runs-on: ubuntu-24.04\n    timeout-minutes: 1\n    steps:\n      - uses: actions/checkout@v6\n\n      - name: Validate kubernetes-manifests\n        id: kubernetes-manifests-validation\n        uses: kubevious/cli@v1.0.64\n        with:\n          manifests: kubernetes-manifests\n          skip_rules: container-latest-image\n\n      - name: Validate helm-chart\n        id: helm-chart-validation\n        uses: kubevious/cli@v1.0.64\n        with:\n          manifests: helm-chart\n\n      - name: Validate kustomize\n        id: kustomize-validation\n        uses: kubevious/cli@v1.0.64\n        with:\n          manifests: kustomize\n          skip_rules: container-latest-image\n"
  },
  {
    "path": ".github/workflows/kustomize-build-ci.yaml",
    "content": "# Copyright 2020 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nname: kustomize-build-ci\non:\n  push:\n    branches:\n      - main\n    paths:\n      - 'kustomize/**'\n      - '.github/workflows/kustomize-build-ci.yaml'\n  pull_request:\n    paths:\n      - 'kustomize/**'\n      - '.github/workflows/kustomize-build-ci.yaml'\njobs:\n  kustomize-build-ci:\n    runs-on: ubuntu-24.04\n    steps:\n      - uses: actions/checkout@v6\n      - name: kustomize build base\n        run: |\n          cd kustomize/\n          kubectl kustomize .\n      # Build the different combinations of Kustomize components found in kustomize/tests.\n      - name: kustomize build tests\n        run: |\n          cd kustomize/tests\n          KUSTOMIZE_TESTS_SUBFOLDERS=$(ls -d */)\n          for test in $KUSTOMIZE_TESTS_SUBFOLDERS;\n          do\n              echo \"## kustomize build for \" + $test\n              kustomize build $test\n          done\n"
  },
  {
    "path": ".github/workflows/terraform-validate-ci.yaml",
    "content": "# Copyright 2020 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nname: terraform-validate-ci\non:\n  push:\n    branches:\n      - main\n    paths:\n      - 'terraform/**'\n      - '.github/workflows/terraform-validate-ci.yaml'\n  pull_request:\n    paths:\n      - 'terraform/**'\n      - '.github/workflows/terraform-validate-ci.yaml'\njobs:\n  terraform-validate-ci:\n    runs-on: ubuntu-24.04\n    steps:\n      - uses: actions/checkout@v6\n      - uses: hashicorp/setup-terraform@v4\n      - name: terraform init & validate\n        run: |\n          cd terraform/\n          terraform init -backend=false\n          terraform validate\n"
  },
  {
    "path": ".gitignore",
    "content": ".DS_Store\n.eclipse.buildship.core.prefs\n.gradle/\n.idea/\n.kubernetes-manifests-*/\n.project\n.skaffold-*.yaml\n.terraform.lock.hcl\n.terraform/*\n.venv/\n.vs/\n.vscode/\n*.iml\n*.ipr\n*.iws\n*.pyc\n*.swp\n*.tfstate*\n*.tfvars\n*~\nbin/\nbuild/\nDockerfile.pip\nnode_modules/\nobj/\npkg/\nrelease/wi-kubernetes-manifests.yaml\nvendor/\n"
  },
  {
    "path": "LICENSE",
    "content": "\n                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright [yyyy] [name of copyright owner]\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n"
  },
  {
    "path": "README.md",
    "content": "<!-- <p align=\"center\">\n<img src=\"/src/frontend/static/icons/Hipster_HeroLogoMaroon.svg\" width=\"300\" alt=\"Online Boutique\" />\n</p> -->\n![Continuous Integration](https://github.com/GoogleCloudPlatform/microservices-demo/workflows/Continuous%20Integration%20-%20Main/Release/badge.svg)\n\n**Online Boutique** is a cloud-first microservices demo application.  The application is a\nweb-based e-commerce app where users can browse items, add them to the cart, and purchase them.\n\nGoogle 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.\n\nIf you’re using this demo, please **★Star** this repository to show your interest!\n\n**Note to Googlers:** Please fill out the form at [go/microservices-demo](http://go/microservices-demo).\n\n## Architecture\n\n**Online Boutique** is composed of 11 microservices written in different\nlanguages that talk to each other over gRPC.\n\n[![Architecture of\nmicroservices](/docs/img/architecture-diagram.png)](/docs/img/architecture-diagram.png)\n\nFind **Protocol Buffers Descriptions** at the [`./protos` directory](/protos).\n\n| Service                                              | Language      | Description                                                                                                                       |\n| ---------------------------------------------------- | ------------- | --------------------------------------------------------------------------------------------------------------------------------- |\n| [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. |\n| [cartservice](/src/cartservice)                     | C#            | Stores the items in the user's shopping cart in Redis and retrieves it.                                                           |\n| [productcatalogservice](/src/productcatalogservice) | Go            | Provides the list of products from a JSON file and ability to search products and get individual products.                        |\n| [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. |\n| [paymentservice](/src/paymentservice)               | Node.js       | Charges the given credit card info (mock) with the given amount and returns a transaction ID.                                     |\n| [shippingservice](/src/shippingservice)             | Go            | Gives shipping cost estimates based on the shopping cart. Ships items to the given address (mock)                                 |\n| [emailservice](/src/emailservice)                   | Python        | Sends users an order confirmation email (mock).                                                                                   |\n| [checkoutservice](/src/checkoutservice)             | Go            | Retrieves user cart, prepares order and orchestrates the payment, shipping and the email notification.                            |\n| [recommendationservice](/src/recommendationservice) | Python        | Recommends other products based on what's given in the cart.                                                                      |\n| [adservice](/src/adservice)                         | Java          | Provides text ads based on given context words.                                                                                   |\n| [loadgenerator](/src/loadgenerator)                 | Python/Locust | Continuously sends requests imitating realistic user shopping flows to the frontend.                                              |\n\n## Screenshots\n\n| Home Page                                                                                                         | Checkout Screen                                                                                                    |\n| ----------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ |\n| [![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) |\n\n## Quickstart (GKE)\n\n1. Ensure you have the following requirements:\n   - [Google Cloud project](https://cloud.google.com/resource-manager/docs/creating-managing-projects#creating_a_project).\n   - Shell environment with `gcloud`, `git`, and `kubectl`.\n\n2. Clone the latest major version.\n\n   ```sh\n   git clone --depth 1 --branch v0 https://github.com/GoogleCloudPlatform/microservices-demo.git\n   cd microservices-demo/\n   ```\n\n   The `--depth 1` argument skips downloading git history.\n\n3. Set the Google Cloud project and region and ensure the Google Kubernetes Engine API is enabled.\n\n   ```sh\n   export PROJECT_ID=<PROJECT_ID>\n   export REGION=us-central1\n   gcloud services enable container.googleapis.com \\\n     --project=${PROJECT_ID}\n   ```\n\n   Substitute `<PROJECT_ID>` with the ID of your Google Cloud project.\n\n4. Create a GKE cluster and get the credentials for it.\n\n   ```sh\n   gcloud container clusters create-auto online-boutique \\\n     --project=${PROJECT_ID} --region=${REGION}\n   ```\n\n   Creating the cluster may take a few minutes.\n\n5. Deploy Online Boutique to the cluster.\n\n   ```sh\n   kubectl apply -f ./release/kubernetes-manifests.yaml\n   ```\n\n6. Wait for the pods to be ready.\n\n   ```sh\n   kubectl get pods\n   ```\n\n   After a few minutes, you should see the Pods in a `Running` state:\n\n   ```\n   NAME                                     READY   STATUS    RESTARTS   AGE\n   adservice-76bdd69666-ckc5j               1/1     Running   0          2m58s\n   cartservice-66d497c6b7-dp5jr             1/1     Running   0          2m59s\n   checkoutservice-666c784bd6-4jd22         1/1     Running   0          3m1s\n   currencyservice-5d5d496984-4jmd7         1/1     Running   0          2m59s\n   emailservice-667457d9d6-75jcq            1/1     Running   0          3m2s\n   frontend-6b8d69b9fb-wjqdg                1/1     Running   0          3m1s\n   loadgenerator-665b5cd444-gwqdq           1/1     Running   0          3m\n   paymentservice-68596d6dd6-bf6bv          1/1     Running   0          3m\n   productcatalogservice-557d474574-888kr   1/1     Running   0          3m\n   recommendationservice-69c56b74d4-7z8r5   1/1     Running   0          3m1s\n   redis-cart-5f59546cdd-5jnqf              1/1     Running   0          2m58s\n   shippingservice-6ccc89f8fd-v686r         1/1     Running   0          2m58s\n   ```\n\n7. Access the web frontend in a browser using the frontend's external IP.\n\n   ```sh\n   kubectl get service frontend-external | awk '{print $4}'\n   ```\n\n   Visit `http://EXTERNAL_IP` in a web browser to access your instance of Online Boutique.\n\n8. 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).\n\n9. Once you are done with it, delete the GKE cluster.\n\n   ```sh\n   gcloud container clusters delete online-boutique \\\n     --project=${PROJECT_ID} --region=${REGION}\n   ```\n\n   Deleting the cluster may take a few minutes.\n\n## Additional deployment options\n\n- **Terraform**: [See these instructions](/terraform) to learn how to deploy Online Boutique using [Terraform](https://www.terraform.io/intro).\n- **Istio / Cloud Service Mesh**: [See these instructions](/kustomize/components/service-mesh-istio/README.md) to deploy Online Boutique alongside an Istio-backed service mesh.\n- **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.\n- **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.\n- **And more**: The [`/kustomize` directory](/kustomize) contains instructions for customizing the deployment of Online Boutique with other variations.\n\n## Documentation\n\n- [Development](/docs/development-guide.md) to learn how to run and develop this app locally.\n\n## Demos featuring Online Boutique\n\n- [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)\n- [alpine, distroless or scratch?](https://medium.com/google-cloud/alpine-distroless-or-scratch-caac35250e0b)\n- [Platform Engineering in action: Deploy the Online Boutique sample apps with Score and Humanitec](https://medium.com/p/d99101001e69)\n- [The new Kubernetes Gateway API with Istio and Anthos Service Mesh (ASM)](https://medium.com/p/9d64c7009cd)\n- [Use Azure Redis Cache with the Online Boutique sample on AKS](https://medium.com/p/981bd98b53f8)\n- [Sail Sharp, 8 tips to optimize and secure your .NET containers for Kubernetes](https://medium.com/p/c68ba253844a)\n- [Deploy multi-region application with Anthos and Google cloud Spanner](https://medium.com/google-cloud/a2ea3493ed0)\n- [Use Google Cloud Memorystore (Redis) with the Online Boutique sample on GKE](https://medium.com/p/82f7879a900d)\n- [Use Helm to simplify the deployment of Online Boutique, with a Service Mesh, GitOps, and more!](https://medium.com/p/246119e46d53)\n- [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)\n- [gRPC health probes with Kubernetes 1.24+](https://medium.com/p/b5bd26253a4c)\n- [Use Google Cloud Spanner with the Online Boutique sample](https://medium.com/p/f7248e077339)\n- [Seamlessly encrypt traffic from any apps in your Mesh to Memorystore (redis)](https://medium.com/google-cloud/64b71969318d)\n- [Strengthen your app's security with Cloud Service Mesh and Anthos Config Management](https://cloud.google.com/service-mesh/docs/strengthen-app-security)\n- [From edge to mesh: Exposing service mesh applications through GKE Ingress](https://cloud.google.com/architecture/exposing-service-mesh-apps-through-gke-ingress)\n- [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)\n- [Deploying the Online Boutique sample application on Cloud Service Mesh](https://cloud.google.com/service-mesh/docs/onlineboutique-install-kpt)\n- [Anthos Service Mesh Workshop: Lab Guide](https://codelabs.developers.google.com/codelabs/anthos-service-mesh-workshop)\n- [KubeCon EU 2019 - Reinventing Networking: A Deep Dive into Istio's Multicluster Gateways - Steve Dake, Independent](https://youtu.be/-t2BfT59zJA?t=982)\n- Google Cloud Next'18 SF\n  - [Day 1 Keynote](https://youtu.be/vJ9OaAqfxo4?t=2416) showing GKE On-Prem\n  - [Day 3 Keynote](https://youtu.be/JQPOPV_VH5w?t=815) showing Stackdriver\n    APM (Tracing, Code Search, Profiler, Google Cloud Build)\n  - [Introduction to Service Management with Istio](https://www.youtube.com/watch?v=wCJrdKdD6UM&feature=youtu.be&t=586)\n- [Google Cloud Next'18 London – Keynote](https://youtu.be/nIq2pkNcfEI?t=3071)\n  showing Stackdriver Incident Response Management\n- [Microservices demo showcasing Go Micro](https://github.com/go-micro/demo)\n"
  },
  {
    "path": "cloudbuild.yaml",
    "content": "# Copyright 2020 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# [START cloudbuild_microservice_demo_cloudbuild]\n\n# This configuration file is used to build and deploy the app into a\n# GKE cluster using Google Cloud Build.\n#\n# PREREQUISITES:\n# - Cloud Build service account must have role: \"Kubernetes Engine Developer\"\n\n# USAGE:\n# GCP zone and GKE target cluster must be specified as substitutions\n# Example invocation:\n# `gcloud builds submit --config=cloudbuild.yaml --substitutions=_ZONE=us-central1-b,_CLUSTER=demo-app-staging .`\n\nsteps:\n- id: 'Deploy application to cluster'\n  name: 'gcr.io/k8s-skaffold/skaffold:v2.16.1'\n  entrypoint: 'bash'\n  args:\n  - '-c'\n  - >\n    gcloud container clusters get-credentials --zone=$_ZONE $_CLUSTER;\n    skaffold run -f=skaffold.yaml --default-repo=gcr.io/$PROJECT_ID;\n\n# Add more power, and more time, for heavy Skaffold build\ntimeout: '3600s'\noptions:\n  machineType: 'N1_HIGHCPU_8'\n  \n# [END cloudbuild_microservice_demo_cloudbuild]"
  },
  {
    "path": "docs/adding-new-microservice.md",
    "content": "# Adding a new microservice\n\nThis document outlines the steps required to add a new microservice to the Online Boutique application.\n\n## 1. Create a new directory\n\nCreate a new directory for your microservice within the `src/` directory. The directory name should be the name of your microservice.\n\n## 2. Add source code\n\nPlace 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:\n\n- `README.md`: The service's description and documentation.\n- `main.py`: The application's entry point.\n- `requirements.in`: A list of Python dependencies.\n- `Dockerfile`: To containerize the application.\n\nTake a look at existing microservices for inspiration.\n\n## 3. Create a Dockerfile\n\nCreate a `Dockerfile` in your microservice's directory. This file will define the steps to build a container image for your service.\n\nRefer to this example and tweak based on your new service's needs: https://github.com/GoogleCloudPlatform/microservices-demo/blob/main/src/frontend/Dockerfile\n\n## 4. Create Kubernetes manifests\n\nCreate 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:\n\n- A **Deployment** to manage your service's pods.\n- A **Service** to expose your microservice to other services within the cluster.\n\nEnsure 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.\n\nRefer to this example and tweak based on your new service's needs: https://github.com/GoogleCloudPlatform/microservices-demo/tree/main/kustomize/components/shopping-assistant\n\n## 5. Update the root `kustomization.yaml` file\n\nAdd your newly created component to the root kustomization file so it gets picked up by the deployment cycle.\n\nThe file is available here: https://github.com/GoogleCloudPlatform/microservices-demo/blob/main/kustomize/kustomization.yaml\n\n## 6. Update the root `skaffold.yaml`\n\nAdd your newly created service to the root skaffold file so the images build correctly.\n\nThe file is available here: https://github.com/GoogleCloudPlatform/microservices-demo/blob/main/skaffold.yaml\n\n## 7. Update the Helm chart\n\nAdd your newly created service to the Helm chart templates and default values.\n\nThe chart is available here: https://github.com/GoogleCloudPlatform/microservices-demo/tree/main/helm-chart\n\n## 8. Update the documentation\n\nFinally, update the project's documentation to reflect the addition of your new microservice. This may include:\n\n- Adding a section to the main `README.md` if the service introduces significant new functionality.\n- Updating the architecture diagrams in the `docs/img` directory.\n- Adding a new document in the `docs` directory if the service requires detailed explanation.\n"
  },
  {
    "path": "docs/cloudshell-tutorial.md",
    "content": "# Online Boutique quickstart\n\nThis tutorial shows you how to deploy **[Online Boutique](https://github.com/GoogleCloudPlatform/microservices-demo)** to a Kubernetes cluster.\n\nYou'll be able to run Online Boutique on:\n- a local **[minikube](https://minikube.sigs.k8s.io/docs/)** cluster, which comes built in to the Cloud Shell instance\n- 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)\n\nLet's get started!\n\n\n## Kubernetes cluster setup\n\nSet up a Kubernetes cluster using the instructions below for either **minikube** or **GKE**.\n\n### Minikube instructions\n\nMinikube creates a local Kubernetes cluster on Cloud Shell.\n\n1. Click <walkthrough-editor-spotlight spotlightId=\"minikube-status-bar\">minikube</walkthrough-editor-spotlight> on the status bar located at the bottom of the editor window.\n\n2. 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. \n\n3. If prompted, authorize Cloud Shell to make a GCP API call with your credentials.\n\n*It may take a few minutes for minikube to finish starting.*\n\nOnce minikube has started, you're ready to move on to the next step. \n\n### GKE instructions\n\nIn 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.\n\n1. Access the command palette by going to **View > Find Command**.\n\n2. Run the command **\"Cloud Code: Create GKE cluster\"**.\n\n3. Select your GCP project. \n\n4. Apply the following configurations in the GKE wizard:  \n> - Zone: us-central1-b\n> - Cluster name: onlineboutique\n> - Node count: 4\n> - Machine type: e2-standard-2\n\n5. Click **Create Cluster**. Once your cluster has been created successfully, you can move on to the next step.\n\n\n## Run on Kubernetes\n\nNow you can run Online Boutique on your Kubernetes cluster!\n\n1. Launch the <walkthrough-editor-spotlight spotlightId=\"cloud-code-status-bar\">Cloud Code menu</walkthrough-editor-spotlight> from the status bar and select <walkthrough-editor-spotlight spotlightId=\"cloud-code-run-on-k8s\">Run on Kubernetes</walkthrough-editor-spotlight>.\n\n2. If prompted to select a Skaffold Profile, select **[default]**.\n\n3. Select **Yes** to confirm your current context.\n\n4. If you're using a GKE cluster, you'll need to confirm your container image registry.\n\n5. If prompted, authorize Cloud Shell to make a GCP API call with your credentials.\n\nCloud Code uses configurations defined in <walkthrough-editor-open-file filePath=\"skaffold.yaml\">skaffold.yaml</walkthrough-editor-open-file> to build and deploy the app. *It may take a few minutes for the deploy to complete.*\n\n6. Once the app is running, the local URLs will be displayed in the <walkthrough-editor-spotlight spotlightId=\"output\">Output</walkthrough-editor-spotlight> terminal. \n\n7. To access your Online Boutique frontend service, click on the <walkthrough-spotlight-pointer spotlightId=\"devshell-web-preview-button\" target=\"cloudshell\">Web Preview button</walkthrough-spotlight-pointer> in the upper right of the editor window.\n\n8. Select **Change Port** and enter '4503' as the port, then click **Change and Preview**. Your app will open in a new window. \n\n\n## Stop the app\n\nTo stop running the app: \n\n1. Go to the <walkthrough-editor-spotlight spotlightId=\"activity-bar-debug\">Debug view</walkthrough-editor-spotlight> \n\n2. Click the **Stop** icon.\n\n3. Select **Yes** to clean up deployed resources. \n\nYou can start, stop, and debug apps from the Debug view.\n\n### Clean up\n\nIf 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.\n\n1. Navigate to the <walkthrough-editor-spotlight spotlightId=\"activity-bar-cloud-k8s\">Cloud Code - Kubernetes view</walkthrough-editor-spotlight> in the Activity bar.\n\n2. Under the <walkthrough-editor-spotlight spotlightId=\"cloud-code-gke-explorer\">Google Kubernetes Engine Explorer tab</walkthrough-editor-spotlight>, right-click on your cluster and select **Delete Cluster**.\n\n\n## Conclusion\n\n<walkthrough-conclusion-trophy></walkthrough-conclusion-trophy>\n\nCongratulations! You've successfully deployed Online Boutique using Cloud Shell.\n\n<walkthrough-inline-feedback></walkthrough-inline-feedback>\n\n##### What's next?\n\nTry other deployment options for Online Boutique:\n- **Istio/Cloud Service Mesh**: <walkthrough-editor-open-file filePath=\"./kustomize/components/service-mesh-istio/README.md\">See these instructions</walkthrough-editor-open-file>.\n\nLearn more about the [Cloud Shell](https://cloud.google.com/shell) IDE environment and the [Cloud Code](https://cloud.google.com/code) extension.\n"
  },
  {
    "path": "docs/deploystack.md",
    "content": "## Deploy Online Boutique with DeployStack\n\nThe \"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.\n\n<!-- TODO: remove reference to the deploystack-enable branch when it pushes to main -->\n<a href=\"https://ssh.cloud.google.com/cloudshell/editor?cloudshell_git_repo=https%3A%2F%2Fgithub.com%2FGoogleCloudPlatform%2Fmicroservices-demo&shellonly=true&cloudshell_image=gcr.io/ds-artifacts-cloudshell/deploystack_custom_image\" target=\"_new\">\n    <img alt=\"Open in Cloud Shell\" src=\"https://gstatic.com/cloudssh/images/open-btn.svg\">\n</a>\n\nThe 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:\n1. a GKE cluster will be created inside the select project\n2. Online Boutique (and its load generator) will be deployed to that cluster\n"
  },
  {
    "path": "docs/development-guide.md",
    "content": "# Development Guide \n\nThis doc explains how to build and run the Online Boutique source code locally using the `skaffold` command-line tool.  \n\n## Prerequisites\n\n- [Docker for Desktop](https://www.docker.com/products/docker-desktop)\n- [kubectl](https://kubernetes.io/docs/tasks/tools/) (can be installed via `gcloud components install kubectl` for Option 1 - GKE)\n- [skaffold **2.0.2+**](https://skaffold.dev/docs/install/) (latest version recommended), a tool that builds and deploys Docker images in bulk. \n- Clone the repository.\n    ```sh\n    git clone https://github.com/GoogleCloudPlatform/microservices-demo\n    cd microservices-demo/\n    ```\n- A Google Cloud project with Google Container Registry enabled. (for Option 1 - GKE)\n- [Minikube](https://minikube.sigs.k8s.io/docs/start/) (optional for Option 2 - Local Cluster)\n- [Kind](https://kind.sigs.k8s.io/) (optional for Option 2 - Local Cluster)\n\n## Option 1: Google Kubernetes Engine (GKE)\n\n> 💡 Recommended if you're using Google Cloud and want to try it on\n> a realistic cluster. **Note**: If your cluster has Workload Identity enabled, \n> [see these instructions](https://cloud.google.com/kubernetes-engine/docs/how-to/workload-identity#enable)\n\n1.  Create a Google Kubernetes Engine cluster and make sure `kubectl` is pointing\n    to the cluster.\n\n    ```sh\n    gcloud services enable container.googleapis.com\n    ```\n\n    ```sh\n    gcloud container clusters create-auto demo --region=us-central1\n    ```\n\n    ```\n    kubectl get nodes\n    ```\n\n2.  Enable Artifact Registry (AR) on your GCP project and configure the\n    `docker` CLI to authenticate to AR:\n\n    ```sh\n    gcloud services enable artifactregistry.googleapis.com\n    ```\n\n    ```sh\n    gcloud artifacts repositories create microservices-demo \\\n      --repository-format=docker \\\n      --location=us \\\n    ```\n\n    ```sh\n    gcloud auth configure-docker -q \n    ```\n\n3.  In the root of this repository, run:\n\n    ```\n    skaffold run --default-repo=us-docker.pkg.dev/PROJECT_ID/microservices-demo\n    ```\n    \n    Where `PROJECT_ID` is replaced by your Google Cloud project ID.\n\n    This command:\n\n    - Builds the container images.\n    - Pushes them to AR.\n    - Applies the `./kubernetes-manifests` deploying the application to\n      Kubernetes.\n\n    **Troubleshooting:** If you get \"No space left on device\" error on Google\n    Cloud Shell, you can build the images on Google Cloud Build: [Enable the\n    Cloud Build\n    API](https://console.cloud.google.com/flows/enableapi?apiid=cloudbuild.googleapis.com),\n    then run `skaffold run -p gcb --default-repo=us-docker.pkg.dev/[PROJECT_ID]/microservices-demo` instead.\n\n4.  Find the IP address of your application, then visit the application on your\n    browser to confirm installation.\n\n        kubectl get service frontend-external\n\n5.  Navigate to `http://EXTERNAL-IP` to access the web frontend.\n\n## Option 2 - Local Cluster \n\n1. Launch a local Kubernetes cluster with one of the following tools:\n\n    - To launch **Minikube** (tested with Ubuntu Linux). Please, ensure that the\n       local Kubernetes cluster has at least:\n        - 4 CPUs\n        - 4.0 GiB memory\n        - 32 GB disk space\n\n      ```shell\n      minikube start --cpus=4 --memory 4096 --disk-size 32g\n      ```\n\n    - To launch **Docker for Desktop** (tested with Mac/Windows). Go to Preferences:\n        - choose “Enable Kubernetes”,\n        - set CPUs to at least 3, and Memory to at least 6.0 GiB\n        - on the \"Disk\" tab, set at least 32 GB disk space\n\n    - To launch a **Kind** cluster:\n\n      ```shell\n      kind create cluster\n      ```\n\n2. Run `kubectl get nodes` to verify you're connected to the respective control plane.\n\n3. Run `skaffold run` (first time will be slow, it can take ~20 minutes).\n   This will build and deploy the application. If you need to rebuild the images\n   automatically as you refactor the code, run `skaffold dev` command.\n\n4. Run `kubectl get pods` to verify the Pods are ready and running.\n\n5. Run `kubectl port-forward deployment/frontend 8080:8080` to forward a port to the frontend service.\n\n6. Navigate to `localhost:8080` to access the web frontend.\n\n## Adding a new microservice\n\nIn 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.\n\nSee the [Adding a new microservice](adding-new-microservice.md) guide for instructions on how to add a new microservice.\n\n## Cleanup\n\nIf you've deployed the application with `skaffold run` command, you can run\n`skaffold delete` to clean up the deployed resources.\n"
  },
  {
    "path": "docs/product-requirements.md",
    "content": "## Product Requirements\n\nThis document contains a list of requirements that every change made to this repository should meet.\nEvery change must:\n1. Preserve the golden user journey taken by Kubernetes beginners.\n1. Preserve the simplicity of demos.\n1. Preserve the simplicity of the GKE quickstart.\n\nThese requirements are about the default deployment (default configuration) of Online Boutique.\nChanges that will violate any of these rules should not be built into the default configuration of Online Boutique.\nSuch 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).\n\n### 1. Preserve the golden user journey taken by Kubernetes beginners\n\nThe following statement about Online Boutique should always be true:\n\n> A user outside of Google can deploy Online Boutique's default configuration on a [_kind_ Kubernetes cluster](https://kind.sigs.k8s.io/).\n\nThis statement describes the golden user journey that we expect new Kubernetes users to take while onboarding to Online Boutique.\n\nBeing 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.\n\n### 2. Preserve the simplicity of demos\n\nNew changes should not complicate the primary user journey showcased in live demos and tutorials.\n\nToday, the primary user journey is as follows:\n1. Visit Online Boutique on a web browser.\n2. Select an item from the homepage and add the item to the cart.\n3. The checkout form is pre-populated with placeholder data (e.g. the shipping address).\n4. The user checks out and completes the order.\n\n### 3. Preserve the simplicity of the GKE quickstart\n\nNew changes should not add additional complexity in the [main Online Boutique quickstart](https://github.com/GoogleCloudPlatform/microservices-demo#quickstart-gke).\n\nIn particular, new changes should not add extra required steps or additional required tools in that quickstart.\n\nIdeally, 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.\n"
  },
  {
    "path": "docs/purpose.md",
    "content": "## Purpose\n\nToday, the primary purpose of Online Boutique is to demonstrate:\n\n* [Google Kubernetes Engine (GKE)](https://cloud.google.com/kubernetes-engine)\n* [Anthos](https://cloud.google.com/anthos)\n* [Google Cloud Operations](https://cloud.google.com/products/operations)\n* tools and technologies commonly used alongside the above products\n\nwhile being accessible and useful to all new Kubernetes users.\n\n### Why does the purpose matter?\n\nWe filter and prioritize the work to be done in this repository based on the purpose defined above.\nIf 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.\n"
  },
  {
    "path": "docs/releasing/README.md",
    "content": "# Releasing Online Boutique\n\nThis document walks through the process of creating a new release of Online Boutique.\n\n## Prerequisites for tagging a release\n\n1. Choose the logical [next release tag](https://github.com/GoogleCloudPlatform/bank-of-anthos/releases), using [semantic versioning](https://semver.org/): `vX.Y.Z`.\n\n   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`).\n\n2. Ensure that the following commands are in your `PATH`:\n   - `gsed` (found in the `gnu-sed` Brew package for macOS, or by symlinking `sed` for Linux)\n   - `gcloud`\n   - `helm`\n\n3. Make sure that your `gcloud` is authenticated:\n\n   ```sh\n   gcloud auth login\n   gcloud auth configure-docker us-central1-docker.pkg.dev\n   ```\n\n## Create and tag the new release\n\nRun the `make-release.sh` script found inside the `docs/releasing/` directory:\n\n```sh\n# assuming you are inside the root path of the bank-of-anthos repository\nexport TAG=vX.Y.Z # This is the new version (e.g. `v0.3.5`)\nexport REPO_PREFIX=us-central1-docker.pkg.dev/google-samples/microservices-demo # This is the Docker repository for tagged images\nexport PROJECT_ID=google-samples # This is the Google Cloud project for the release CI\n./docs/releasing/make-release.sh\n```\n\nThis script does the following:\n1. Uses `make-docker-images.sh` to build and push a Docker image for each microservice to the previously specified repository.\n2. Uses `make-release-artifacts.sh` to regenerates (and update the image $TAGS) YAML file at `./release/kubernetes-manifests.yaml` and `./kustomize/base/`.\n3. Runs `git tag` and pushes a new branch (e.g., `release/v0.3.5`) with the changes to `./release/kubernetes-manifests.yaml`.\n\nYou 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).\n\n## Create the PR\n\nNow 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).\n\nThis 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.\n\nOnce reviewed and you're ready to merge, make sure to not delete the release branch or the tags during that process.\n\n## Add notes to the release\n\nOnce 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).\n- Click the breadcrumbs on the row of the latest tag that was created in the [tags](https://github.com/GoogleCloudPlatform/microservices-demo/tags) page\n- Select the `Create release` option\n\nThe 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).\n\n> ***Note:*** No assets need to be uploaded. They are picked up automatically from the tagged revision\n\n## Deploy on the production environment\n\nOnce the release notes are published, you should then replace the version of the production environment to the newly published version.\n\n1. 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):\n\n   ```sh\n   gcloud container clusters get-credentials online-boutique-release \\\n     --zone us-central1-c --project online-boutique-ci\n   ```\n\n2. Deploy `release/kubernetes-manifests.yaml` to it:\n\n   ```sh\n   kubectl apply -f ./release/kubernetes-manifests.yaml\n   ```\n\n3. Remove unnecessary objects:\n\n   ```sh\n   kubectl delete service frontend-external\n   kubectl delete deployment loadgenerator\n   ```\n\n3. Make sure [cymbal-shops.retail.cymbal.dev](https://cymbal-shops.retail.cymbal.dev) works.\n\n## Update major tags\n\n1. Update the relevant major tag (for example, `v1`):\n\n  ```sh\n  export MAJOR_TAG=v0 # Edit this as needed (to v1/v2/v3/etc)\n  git checkout release/${TAG}\n  git pull\n  git push --delete origin ${MAJOR_TAG} # Delete the remote tag (if it exists)\n  git tag --delete ${MAJOR_TAG} # Delete the local tag (if it exists)\n  git tag -a ${MAJOR_TAG} -m \"Updating ${MAJOR_TAG} to its most recent release: ${TAG}\"\n  git push origin ${MAJOR_TAG} # Push the new tag to origin\n  ```\n\n## Announce the new release internally\n\nOnce 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).\n"
  },
  {
    "path": "docs/releasing/license_header.txt",
    "content": "# Copyright 2025 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n"
  },
  {
    "path": "docs/releasing/make-docker-images.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright 2019 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# Builds and pushes docker image for each demo microservice.\n\nset -euo pipefail\nSCRIPT_DIR=\"$( cd \"$( dirname \"${BASH_SOURCE[0]}\" )\" && pwd )\"\nREPO_ROOT=$SCRIPT_DIR/../..\n\nlog() { echo \"$1\" >&2; }\n\nTAG=\"${TAG:?TAG env variable must be specified}\"\nREPO_PREFIX=\"${REPO_PREFIX:?REPO_PREFIX env variable must be specified}\"\nPROJECT_ID=\"${PROJECT_ID:?PROJECT_ID env variable must be specified e.g. google-samples}\"\n\nwhile IFS= read -d $'\\0' -r dir; do\n    # build image\n    svcname=\"$(basename \"${dir}\")\"\n    builddir=\"${dir}\"\n    #PR 516 moved cartservice build artifacts one level down to src\n    if [ $svcname == \"cartservice\" ]\n    then\n        builddir=\"${dir}/src\"\n    fi\n    image=\"${REPO_PREFIX}/$svcname:$TAG\"\n    image_with_sample_public_image_tag=\"${REPO_PREFIX}/$svcname:sample-public-image-$TAG\"\n    (\n        cd \"${builddir}\"\n        log \"Building (and pushing) image on Google Cloud Build: ${image}\"\n        gcloud builds submit --project=${PROJECT_ID} --tag=${image}\n        gcloud artifacts docker tags add ${image} ${image_with_sample_public_image_tag}\n    )\ndone < <(find \"${REPO_ROOT}/src\" -mindepth 1 -maxdepth 1 -type d -print0)\n\nlog \"Successfully built and pushed all images.\"\n"
  },
  {
    "path": "docs/releasing/make-helm-chart.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright 2019 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# Packages and pushes Online Boutique's Helm chart in public Artifact Registry.\n\nset -euo pipefail\nSCRIPT_DIR=\"$( cd \"$( dirname \"${BASH_SOURCE[0]}\" )\" && pwd )\"\nREPO_ROOT=$SCRIPT_DIR/../..\n\nlog() { echo \"$1\" >&2; }\n\nTAG=\"${TAG:?TAG env variable must be specified}\"\nHELM_CHART_REPO=\"us-docker.pkg.dev/online-boutique-ci/charts\"\n\ncd ${REPO_ROOT}/helm-chart\ngsed -i \"s/^appVersion:.*/appVersion: \\\"${TAG}\\\"/\" Chart.yaml\ngsed -i \"s/^version:.*/version: ${TAG:1}/\" Chart.yaml\nhelm package .\nhelm push onlineboutique-${TAG:1}.tgz oci://$HELM_CHART_REPO\n\nrm ./onlineboutique-${TAG:1}.tgz\n\nlog \"Successfully built and pushed the Helm chart.\"\n"
  },
  {
    "path": "docs/releasing/make-release-artifacts.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright 2019 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# This script compiles manifest files with the image tags and places them in\n# /release/...\n\nset -euo pipefail\nSCRIPT_DIR=\"$( cd \"$( dirname \"${BASH_SOURCE[0]}\" )\" && pwd )\"\nREPO_ROOT=$SCRIPT_DIR/../..\n[[ -n \"${DEBUG:-}\" ]] && set -x\n\nlog() { echo \"$1\" >&2; }\n\nTAG=\"${TAG:?TAG env variable must be specified}\"\nREPO_PREFIX=\"${REPO_PREFIX:?REPO_PREFIX env variable must be specified}\"\nOUT_DIR=\"${OUT_DIR:-${REPO_ROOT}/release}\"\n\nprint_license_header() {\n    cat \"${SCRIPT_DIR}/license_header.txt\"\n    echo\n}\n\nprint_autogenerated_warning() {\n    cat<<EOF\n# ----------------------------------------------------------\n# WARNING: This file is autogenerated. Do not manually edit.\n# ----------------------------------------------------------\n\nEOF\n}\n\n# define gsed as a function on Linux for compatibility\n[ \"$(uname -s)\" == \"Linux\" ] && gsed() {\n    sed \"$@\"\n}\n\nread_manifests_except_kustomization() {\n    local dir\n    dir=\"$1\"\n\n    while IFS= read -d $'\\0' -r file; do\n        echo \"---\"\n\n        # strip license headers (pattern \"^# \")\n        awk '\n        /^[^# ]/ { found = 1 }\n        found { print }' \"${file}\"\n    done < <(find \"${dir}\" -name '*.yaml' ! -name 'kustomization.yaml' -type f -print0)\n}\n\nmk_kubernetes_manifests() {\n    out_manifest=\"$(read_manifests_except_kustomization \"${REPO_ROOT}/kubernetes-manifests\")\"\n\n    # replace \"image\" repo, tag for each service\n    for dir in ./src/*/\n    do\n        svcname=\"$(basename \"${dir}\")\"\n        image=\"$REPO_PREFIX/$svcname:$TAG\"\n\n        pattern=\"^(\\s*)image:\\s.*$svcname(.*)(\\s*)\"\n        replace=\"\\1image: $image\\3\"\n        out_manifest=\"$(gsed -r \"s|$pattern|$replace|g\" <(echo \"${out_manifest}\") )\"\n    done\n\n    print_license_header\n    print_autogenerated_warning\n    echo '# [START gke_release_kubernetes_manifests_microservices_demo]'\n    echo \"${out_manifest}\"\n    echo \"# [END gke_release_kubernetes_manifests_microservices_demo]\"\n}\n\nmk_istio_manifests() {\n    print_license_header\n    print_autogenerated_warning\n    echo '# [START servicemesh_release_istio_manifests_microservices_demo]'\n\n    # This just copies the yaml from the component (excluding kustomization.yaml)\n    # since there is no easy way to render individual kustomize component resources\n    read_manifests_except_kustomization \"${REPO_ROOT}/kustomize/components/service-mesh-istio/\"\n    echo '# [END servicemesh_release_istio_manifests_microservices_demo]'\n}\n\nmk_kustomize_base() {\n  for file_to_copy in ./kubernetes-manifests/*.yaml\n  do\n    # Don't copy kustomization.yaml.\n    if [[ $file_to_copy == \"./kubernetes-manifests/kustomization.yaml\" ]]; then\n      continue\n    fi\n\n    cp ${file_to_copy} ./kustomize/base/\n\n    service_name=\"$(basename \"${file_to_copy}\" .yaml)\"\n    image=\"$REPO_PREFIX/$service_name:$TAG\"\n\n    # Inside redis.yaml, we use the official `redis:alpine` Docker image.\n    # We don't use an image from `us-central1-docker.pkg.dev/google-samples/microservices-demo`.\n    if [[ $service_name == \"redis\" ]]; then\n      continue\n    fi\n\n    pattern=\"^(\\s*)image:\\s.*${service_name}(.*)(\\s*)\"\n    replace=\"\\1image: ${image}\\3\"\n    gsed --in-place --regexp-extended \"s|${pattern}|${replace}|g\" ./kustomize/base/${service_name}.yaml\n  done\n}\n\nmain() {\n    mkdir -p \"${OUT_DIR}\"\n    local k8s_manifests_file istio_manifests_file\n\n    k8s_manifests_file=\"${OUT_DIR}/kubernetes-manifests.yaml\"\n    mk_kubernetes_manifests > \"${k8s_manifests_file}\"\n    log \"Written ${k8s_manifests_file}\"\n\n    istio_manifests_file=\"${OUT_DIR}/istio-manifests.yaml\"\n    mk_istio_manifests > \"${istio_manifests_file}\"\n    log \"Written ${istio_manifests_file}\"\n\n    mk_kustomize_base\n    log \"Written Kustomize base\"\n}\n\nmain\n"
  },
  {
    "path": "docs/releasing/make-release.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright 2019 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# This script creates a new release by:\n# - 1. building/pushing images\n# - 2. injecting tags into YAML manifests\n# - 3. creating a new git tag\n# - 4. pushing the tag/commit to main.\n\nset -euo pipefail\nSCRIPT_DIR=\"$( cd \"$( dirname \"${BASH_SOURCE[0]}\" )\" && pwd )\"\nREPO_ROOT=$SCRIPT_DIR/../..\n[[ -n \"${DEBUG:-}\" ]] && set -x\n\nlog() { echo \"$1\" >&2; }\nfail() { log \"$1\"; exit 1; }\n\nTAG=\"${TAG:?TAG env variable must be specified}\"\nREPO_PREFIX=\"${REPO_PREFIX:?REPO_PREFIX env variable must be specified e.g. us-central1-docker.pkg.dev\\/google-samples\\/microservices-demo}\"\nPROJECT_ID=\"${PROJECT_ID:?PROJECT_ID env variable must be specified e.g. google-samples}\"\n\nif [[ \"$TAG\" != v* ]]; then\n    fail \"\\$TAG must start with 'v', e.g. v0.1.0 (got: $TAG)\"\nfi\n\n# ensure there are no uncommitted changes\nif [[ $(git status -s | wc -l) -gt 0 ]]; then\n    echo \"error: can't have uncommitted changes\"\n    exit 1\nfi\n\n# make sure local source is up to date\ngit checkout main\ngit pull\n\n# build and push images\n\"${SCRIPT_DIR}\"/make-docker-images.sh\n\n# update yaml\n\"${SCRIPT_DIR}\"/make-release-artifacts.sh\n\n# build and push images\n\"${SCRIPT_DIR}\"/make-helm-chart.sh\n\n# create git release / push to new branch\ngit checkout -b \"release/${TAG}\"\ngit add \"${REPO_ROOT}/release/\"\ngit add \"${REPO_ROOT}/kustomize/base/\"\ngit add \"${REPO_ROOT}/helm-chart/\"\ngit commit --allow-empty -m \"Release $TAG\"\nlog \"Pushing k8s manifests to release/${TAG}...\"\ngit tag \"$TAG\"\ngit push --set-upstream origin \"release/${TAG}\"\ngit push --tags\n\nlog \"Successfully tagged release $TAG.\"\n"
  },
  {
    "path": "helm-chart/Chart.yaml",
    "content": "# Copyright 2023 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\napiVersion: v2\nname: onlineboutique\ndescription: A Helm chart for Kubernetes for Online Boutique\n\n# A chart can be either an 'application' or a 'library' chart.\n#\n# Application charts are a collection of templates that can be packaged into versioned archives\n# to be deployed.\n#\n# Library charts provide useful utilities or functions for the chart developer. They're included as\n# a dependency of application charts to inject those utilities and functions into the rendering\n# pipeline. Library charts do not define any templates and therefore cannot be deployed.\ntype: application\n\n# This is the chart version. This version number should be incremented each time you make changes\n# to the chart and its templates, including the app version.\n# Versions are expected to follow Semantic Versioning (https://semver.org/)\nversion: 0.10.5\n\n# This is the version number of the application being deployed. This version number should be\n# incremented each time you make changes to the application. Versions are not expected to\n# follow Semantic Versioning. They should reflect the version the application is using.\n# It is recommended to use it with quotes.\nappVersion: \"v0.10.5\"\n"
  },
  {
    "path": "helm-chart/README.md",
    "content": "# Helm chart for Online Boutique\n\nIf you'd like to deploy Online Boutique via its Helm chart, you could leverage the following instructions.\n\n**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).\n\nDeploy the default setup of Online Boutique:\n```sh\nhelm upgrade onlineboutique oci://us-docker.pkg.dev/online-boutique-ci/charts/onlineboutique \\\n    --install\n```\n\nDeploy advanced scenario of Online Boutique:\n```sh\nhelm upgrade onlineboutique oci://us-docker.pkg.dev/online-boutique-ci/charts/onlineboutique \\\n    --install \\\n    --create-namespace \\\n    --set images.repository=us-docker.pkg.dev/my-project/microservices-demo \\\n    --set frontend.externalService=false \\\n    --set redis.create=false \\\n    --set cartservice.database.type=spanner \\\n    --set cartservice.database.connectionString=projects/my-project/instances/onlineboutique/databases/carts \\\n    --set serviceAccounts.create=true \\\n    --set authorizationPolicies.create=true \\\n    --set networkPolicies.create=true \\\n    --set sidecars.create=true \\\n    --set frontend.virtualService.create=true \\\n    --set 'serviceAccounts.annotations.iam\\.gke\\.io/gcp-service-account=spanner-db-user@my-project.iam.gserviceaccount.com' \\\n    --set serviceAccounts.annotationsOnlyForCartservice=true \\\n    -n onlineboutique\n```\n\nFor the full list of configurations, see [values.yaml](./values.yaml).\n\nYou could also find advanced scenarios with these blogs below:\n- [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)\n- [gRPC health probes with Kubernetes 1.24+](https://medium.com/google-cloud/b5bd26253a4c)\n- [Use Google Cloud Spanner with the Online Boutique sample](https://medium.com/google-cloud/f7248e077339)"
  },
  {
    "path": "helm-chart/templates/NOTES.txt",
    "content": "{{- if and .Values.frontend.create .Values.frontend.externalService }}\nNote: It may take a few minutes for the LoadBalancer IP to be available.\n\nWatch the status of the frontend IP address with:\n    kubectl get --namespace {{ .Release.Namespace }} svc -w {{ .Values.frontend.name }}-external\n\nGet the external IP address of the frontend:\n    export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ .Values.frontend.name }}-external --template \"{{\"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}\"}}\")\n    echo http://$SERVICE_IP\n{{- end }}\n{{- if .Values.frontend.virtualService.create }}\nGet the external IP address of the ingress gateway:\n    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 }}\"}}\")\n    echo http://$SERVICE_IP\n{{- end }}\n"
  },
  {
    "path": "helm-chart/templates/adservice.yaml",
    "content": "# Copyright 2024 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      https://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n{{- if .Values.adService.create }}\n{{- if .Values.serviceAccounts.create }}\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: {{ .Values.adService.name }}\n  namespace: {{.Release.Namespace}}\n  {{- if not .Values.serviceAccounts.annotationsOnlyForCartservice }}\n  {{- with .Values.serviceAccounts.annotations }}\n  annotations:\n    {{- toYaml . | nindent 4 }}\n  {{- end }}\n  {{- end }}\n---\n{{- end }}\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: {{ .Values.adService.name }}\n  namespace: {{ .Release.Namespace }}\n  labels:\n    app: {{ .Values.adService.name }}\nspec:\n  selector:\n    matchLabels:\n      app: {{ .Values.adService.name }}\n  template:\n    metadata:\n      labels:\n        app: {{ .Values.adService.name }}\n    spec:\n      {{- if .Values.serviceAccounts.create }}\n      serviceAccountName: {{ .Values.adService.name }}\n      {{- else }}\n      serviceAccountName: default\n      {{- end }}\n      terminationGracePeriodSeconds: 5\n      {{- if .Values.securityContext.enable }}\n      securityContext:\n        fsGroup: 1000\n        runAsGroup: 1000\n        runAsNonRoot: true\n        runAsUser: 1000\n        {{- if .Values.seccompProfile.enable }}\n        seccompProfile:\n          type: {{ .Values.seccompProfile.type }}\n        {{- end }}\n      {{- end }}\n      containers:\n      - name: server\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n              - ALL\n          privileged: false\n          readOnlyRootFilesystem: true\n        image: {{ .Values.images.repository }}/{{ .Values.adService.name }}:{{ .Values.images.tag | default .Chart.AppVersion }}\n        ports:\n        - containerPort: 9555\n        env:\n        - name: PORT\n          value: \"9555\"\n        resources:\n          {{- toYaml .Values.adService.resources | nindent 10 }}\n        readinessProbe:\n          initialDelaySeconds: 20\n          periodSeconds: 15\n          grpc:\n            port: 9555\n        livenessProbe:\n          initialDelaySeconds: 20\n          periodSeconds: 15\n          grpc:\n            port: 9555\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: {{ .Values.adService.name }}\n  namespace: {{ .Release.Namespace }}\n  labels:\n    app: {{ .Values.adService.name }}\nspec:\n  type: ClusterIP\n  selector:\n    app: {{ .Values.adService.name }}\n  ports:\n  - name: grpc\n    port: 9555\n    targetPort: 9555\n{{- if .Values.networkPolicies.create }}\n---\napiVersion: networking.k8s.io/v1\nkind: NetworkPolicy\nmetadata:\n  name: {{ .Values.adService.name }}\n  namespace: {{ .Release.Namespace }}\nspec:\n  podSelector:\n    matchLabels:\n      app: {{ .Values.adService.name }}\n  policyTypes:\n  - Ingress\n  - Egress\n  ingress:\n  - from:\n    - podSelector:\n        matchLabels:\n          app: {{ .Values.frontend.name }}\n    ports:\n     - port: 9555\n       protocol: TCP\n  egress:\n  - {}\n{{- end }}\n{{- if .Values.sidecars.create }}\n---\napiVersion: networking.istio.io/v1beta1\nkind: Sidecar\nmetadata:\n  name: {{ .Values.adService.name }}\n  namespace: {{ .Release.Namespace }}\nspec:\n  workloadSelector:\n    labels:\n      app: {{ .Values.adService.name }}\n  egress:\n  - hosts:\n    - istio-system/*\n    {{- if .Values.opentelemetryCollector.create }}\n    - ./{{ .Values.opentelemetryCollector.name }}.{{ .Release.Namespace }}.svc.cluster.local\n    {{- end }}\n{{- end }}\n{{- if .Values.authorizationPolicies.create }}\n---\napiVersion: security.istio.io/v1beta1\nkind: AuthorizationPolicy\nmetadata:\n  name: {{ .Values.adService.name }}\n  namespace: {{ .Release.Namespace }}\nspec:\n  selector:\n    matchLabels:\n      app: {{ .Values.adService.name }}\n  rules:\n  - from:\n    - source:\n        principals:\n        {{- if .Values.serviceAccounts.create }}\n        - cluster.local/ns/{{ .Release.Namespace }}/sa/{{ .Values.frontend.name }}\n        {{- else }}\n        - cluster.local/ns/{{ .Release.Namespace }}/sa/default\n        {{- end }}\n    to:\n    - operation:\n        paths:\n        - /hipstershop.AdService/GetAds\n        methods:\n        - POST\n        ports:\n        - \"9555\"\n{{- end }}\n{{- end }}\n"
  },
  {
    "path": "helm-chart/templates/cartservice.yaml",
    "content": "# Copyright 2024 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      https://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n{{- if .Values.cartService.create }}\n{{- if .Values.serviceAccounts.create }}\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: {{ .Values.cartService.name }}\n  namespace: {{.Release.Namespace}}\n  {{- with .Values.serviceAccounts.annotations }}\n  annotations:\n    {{- toYaml . | nindent 4 }}\n  {{- end }}\n---\n{{- end }}\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: {{ .Values.cartService.name }}\n  namespace: {{ .Release.Namespace }}\n  labels:\n    app: {{ .Values.cartService.name }}\nspec:\n  selector:\n    matchLabels:\n      app: {{ .Values.cartService.name }}\n  template:\n    metadata:\n      {{- if .Values.cartDatabase.externalRedisTlsOrigination.enable }}\n      annotations:\n        sidecar.istio.io/userVolumeMount: '[{\"name\": \"{{ .Values.cartDatabase.externalRedisTlsOrigination.name }}\", \"mountPath\": \"/etc/certs\", \"readonly\": true}]'\n        sidecar.istio.io/userVolume: '[{\"name\": \"{{ .Values.cartDatabase.externalRedisTlsOrigination.name }}\", \"secret\": {\"secretName\": \"{{ .Values.cartDatabase.externalRedisTlsOrigination.name }}\"}}]'\n        proxy.istio.io/config: '{\"holdApplicationUntilProxyStarts\": true}'\n      {{- end }}\n      labels:\n        app: {{ .Values.cartService.name }}\n    spec:\n      {{- if .Values.serviceAccounts.create }}\n      serviceAccountName: {{ .Values.cartService.name }}\n      {{- else }}\n      serviceAccountName: default\n      {{- end }}\n      terminationGracePeriodSeconds: 5\n      {{- if .Values.securityContext.enable }}\n      securityContext:\n        fsGroup: 1000\n        runAsGroup: 1000\n        runAsNonRoot: true\n        runAsUser: 1000\n      {{- end }}\n        {{- if .Values.seccompProfile.enable }}\n        seccompProfile:\n          type: {{ .Values.seccompProfile.type }}\n        {{- end }}\n      containers:\n      - name: server\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n              - ALL\n          privileged: false\n          readOnlyRootFilesystem: true\n        image: {{ .Values.images.repository }}/{{ .Values.cartService.name }}:{{ .Values.images.tag | default .Chart.AppVersion }}\n        ports:\n        - containerPort: 7070\n        env:\n        {{- if eq .Values.cartDatabase.type \"spanner\" }}\n        - name: SPANNER_CONNECTION_STRING\n        {{- else }}\n        - name: REDIS_ADDR\n        {{- end }}\n          value: {{ .Values.cartDatabase.connectionString | quote }}\n        resources:\n          {{- toYaml .Values.cartService.resources | nindent 10 }}\n        readinessProbe:\n          initialDelaySeconds: 15\n          grpc:\n            port: 7070\n        livenessProbe:\n          initialDelaySeconds: 15\n          periodSeconds: 10\n          grpc:\n            port: 7070\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: {{ .Values.cartService.name }}\n  namespace: {{ .Release.Namespace }}\n  labels:\n    app: {{ .Values.cartService.name }}\nspec:\n  type: ClusterIP\n  selector:\n    app: {{ .Values.cartService.name }}\n  ports:\n  - name: grpc\n    port: 7070\n    targetPort: 7070\n{{- if .Values.networkPolicies.create }}\n---\napiVersion: networking.k8s.io/v1\nkind: NetworkPolicy\nmetadata:\n  name: {{ .Values.cartService.name }}\n  namespace: {{ .Release.Namespace }}\nspec:\n  podSelector:\n    matchLabels:\n      app: {{ .Values.cartService.name }}\n  policyTypes:\n  - Ingress\n  - Egress\n  ingress:\n  - from:\n    - podSelector:\n        matchLabels:\n          app: {{ .Values.frontend.name }}\n    - podSelector:\n        matchLabels:\n          app: {{ .Values.checkoutService.name }}\n    ports:\n     - port: 7070\n       protocol: TCP\n  egress:\n  - {}\n{{- end }}\n{{- if .Values.sidecars.create }}\n---\napiVersion: networking.istio.io/v1beta1\nkind: Sidecar\nmetadata:\n  name: {{ .Values.cartService.name }}\n  namespace: {{ .Release.Namespace }}\nspec:\n  workloadSelector:\n    labels:\n      app: {{ .Values.cartService.name }}\n  egress:\n  - hosts:\n    - istio-system/*\n    {{- if eq .Values.cartDatabase.type \"redis\" }}\n    {{- if .Values.cartDatabase.externalRedisTlsOrigination.enable }}\n    - ./{{ .Values.cartDatabase.externalRedisTlsOrigination.name }}.{{ .Release.Namespace }}\n    {{- else }}\n    - ./{{ .Values.cartDatabase.inClusterRedis.name }}.{{ .Release.Namespace }}.svc.cluster.local\n    {{- end }}\n    {{- end }}\n    {{- if .Values.opentelemetryCollector.create }}\n    - ./{{ .Values.opentelemetryCollector.name }}.{{ .Release.Namespace }}.svc.cluster.local\n    {{- end }}\n{{- end }}\n{{- if .Values.authorizationPolicies.create }}\n---\napiVersion: security.istio.io/v1beta1\nkind: AuthorizationPolicy\nmetadata:\n  name: {{ .Values.cartService.name }}\n  namespace: {{ .Release.Namespace }}\nspec:\n  selector:\n    matchLabels:\n      app: {{ .Values.cartService.name }}\n  rules:\n  - from:\n    - source:\n        principals:\n        {{- if .Values.serviceAccounts.create }}\n        - cluster.local/ns/{{ .Release.Namespace }}/sa/{{ .Values.frontend.name }}\n        - cluster.local/ns/{{ .Release.Namespace }}/sa/{{ .Values.checkoutService.name }}\n        {{- else }}\n        - cluster.local/ns/{{ .Release.Namespace }}/sa/default\n        {{- end }}\n    to:\n    - operation:\n        paths:\n        - /hipstershop.CartService/AddItem\n        - /hipstershop.CartService/GetCart\n        - /hipstershop.CartService/EmptyCart\n        methods:\n        - POST\n        ports:\n        - \"7070\"\n{{- end }}\n\n{{- if .Values.cartDatabase.inClusterRedis.create }}\n---\n{{- if .Values.serviceAccounts.create }}\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: {{ .Values.cartDatabase.inClusterRedis.name }}\n  namespace: {{.Release.Namespace}}\n  {{- if not .Values.serviceAccounts.annotationsOnlyForCartservice }}\n  {{- with .Values.serviceAccounts.annotations }}\n  annotations:\n    {{- toYaml . | nindent 4 }}\n  {{- end }}\n  {{- end }}\n---\n{{- end }}\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: {{ .Values.cartDatabase.inClusterRedis.name }}\n  namespace: {{ .Release.Namespace }}\nspec:\n  selector:\n    matchLabels:\n      app: {{ .Values.cartDatabase.inClusterRedis.name }}\n  template:\n    metadata:\n      labels:\n        app: {{ .Values.cartDatabase.inClusterRedis.name }}\n    spec:\n      {{- if .Values.serviceAccounts.create }}\n      serviceAccountName: {{ .Values.cartDatabase.inClusterRedis.name }}\n      {{- else }}\n      serviceAccountName: default\n      {{- end }}\n      {{- if .Values.securityContext.enable }}\n      securityContext:\n        fsGroup: 1000\n        runAsGroup: 1000\n        runAsNonRoot: true\n        runAsUser: 1000\n        {{- if .Values.seccompProfile.enable }}\n        seccompProfile:\n          type: {{ .Values.seccompProfile.type }}\n        {{- end }}\n      {{- end }}\n      containers:\n      - name: redis\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n              - ALL\n          privileged: false\n          readOnlyRootFilesystem: true\n        {{- if .Values.cartDatabase.inClusterRedis.publicRepository }}\n        image: redis:alpine@sha256:2afba59292f25f5d1af200496db41bea2c6c816b059f57ae74703a50a03a27d0\n        {{- else }}\n        image: {{ .Values.images.repository }}/redis:alpine\n        {{- end }}\n        ports:\n        - containerPort: 6379\n        readinessProbe:\n          periodSeconds: 5\n          tcpSocket:\n            port: 6379\n        livenessProbe:\n          periodSeconds: 5\n          tcpSocket:\n            port: 6379\n        volumeMounts:\n        - mountPath: /data\n          name: redis-data\n        resources:\n          limits:\n            memory: 256Mi\n            cpu: 125m\n          requests:\n            cpu: 70m\n            memory: 200Mi\n      volumes:\n      - name: redis-data\n        emptyDir: {}\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: {{ .Values.cartDatabase.inClusterRedis.name }}\n  namespace: {{ .Release.Namespace }}\nspec:\n  type: ClusterIP\n  selector:\n    app: {{ .Values.cartDatabase.inClusterRedis.name }}\n  ports:\n  - name: tcp-redis\n    port: 6379\n    targetPort: 6379\n{{- if .Values.networkPolicies.create }}\n---\napiVersion: networking.k8s.io/v1\nkind: NetworkPolicy\nmetadata:\n  name: {{ .Values.cartDatabase.inClusterRedis.name }}\n  namespace: {{ .Release.Namespace }}\nspec:\n  podSelector:\n    matchLabels:\n      app: {{ .Values.cartDatabase.inClusterRedis.name }}\n  policyTypes:\n  - Ingress\n  - Egress\n  ingress:\n  - from:\n    - podSelector:\n        matchLabels:\n          app: {{ .Values.cartService.name }}\n    ports:\n     - port: 6379\n       protocol: TCP\n  egress:\n  - {}\n{{- end }}\n{{- if .Values.sidecars.create }}\n---\napiVersion: networking.istio.io/v1beta1\nkind: Sidecar\nmetadata:\n  name: {{ .Values.cartDatabase.inClusterRedis.name }}\n  namespace: {{ .Release.Namespace }}\nspec:\n  workloadSelector:\n    labels:\n      app: {{ .Values.cartDatabase.inClusterRedis.name }}\n  egress:\n  - hosts:\n    - istio-system/*\n{{- end }}\n{{- if .Values.authorizationPolicies.create }}\n---\napiVersion: security.istio.io/v1beta1\nkind: AuthorizationPolicy\nmetadata:\n  name: {{ .Values.cartDatabase.inClusterRedis.name }}\n  namespace: {{ .Release.Namespace }}\nspec:\n  selector:\n    matchLabels:\n      app: {{ .Values.cartDatabase.inClusterRedis.name }}\n  rules:\n  - from:\n    - source:\n        principals:\n        {{- if .Values.serviceAccounts.create }}\n        - cluster.local/ns/{{ .Release.Namespace }}/sa/{{ .Values.cartService.name }}\n        {{- else }}\n        - cluster.local/ns/{{ .Release.Namespace }}/sa/default\n        {{- end }}\n    to:\n    - operation:\n        ports:\n        - \"6379\"\n{{- end }}\n{{- end }}\n{{- if .Values.cartDatabase.externalRedisTlsOrigination.enable }}\n---\napiVersion: v1\ndata:\n  {{ .Values.cartDatabase.externalRedisTlsOrigination.name }}.pem: {{ .Values.cartDatabase.externalRedisTlsOrigination.certificate | b64enc | quote }}\nkind: Secret\nmetadata:\n  name: {{ .Values.cartDatabase.externalRedisTlsOrigination.name }}\n  namespace: {{ .Release.Namespace }}\n---\napiVersion: networking.istio.io/v1beta1\nkind: DestinationRule\nmetadata:\n  name: {{ .Values.cartDatabase.externalRedisTlsOrigination.name }}\n  namespace: {{ .Release.Namespace }}\nspec:\n  exportTo:\n  - '.'\n  host: {{ .Values.cartDatabase.externalRedisTlsOrigination.name }}.{{ .Release.Namespace }}\n  trafficPolicy:\n    tls:\n      mode: SIMPLE\n      caCertificates: /etc/certs/{{ .Values.cartDatabase.externalRedisTlsOrigination.name }}.pem\n---\napiVersion: networking.istio.io/v1beta1\nkind: ServiceEntry\nmetadata:\n  name: {{ .Values.cartDatabase.externalRedisTlsOrigination.name }}\n  namespace: {{ .Release.Namespace }}\nspec:\n  hosts:\n  - {{ .Values.cartDatabase.externalRedisTlsOrigination.name }}.{{ .Release.Namespace }}\n  addresses:\n  - {{ .Values.cartDatabase.externalRedisTlsOrigination.endpointAddress }}/32\n  endpoints:\n  - address: {{ .Values.cartDatabase.externalRedisTlsOrigination.endpointAddress }}\n  location: MESH_EXTERNAL\n  resolution: STATIC\n  ports:\n  - number: {{ .Values.cartDatabase.externalRedisTlsOrigination.endpointPort }}\n    name: tcp-redis\n    protocol: TCP\n{{- end }}\n{{- end }}\n"
  },
  {
    "path": "helm-chart/templates/checkoutservice.yaml",
    "content": "# Copyright 2024 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      https://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n{{- if .Values.checkoutService.create }}\n{{- if .Values.serviceAccounts.create }}\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: {{ .Values.checkoutService.name }}\n  namespace: {{.Release.Namespace}}\n  {{- if not .Values.serviceAccounts.annotationsOnlyForCartservice }}\n  {{- with .Values.serviceAccounts.annotations }}\n  annotations:\n    {{- toYaml . | nindent 4 }}\n  {{- end }}\n  {{- end }}\n---\n{{- end }}\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: {{ .Values.checkoutService.name }}\n  namespace: {{ .Release.Namespace }}\n  labels:\n    app: {{ .Values.checkoutService.name }}\nspec:\n  selector:\n    matchLabels:\n      app: {{ .Values.checkoutService.name }}\n  template:\n    metadata:\n      labels:\n        app: {{ .Values.checkoutService.name }}\n    spec:\n      {{- if .Values.serviceAccounts.create }}\n      serviceAccountName: {{ .Values.checkoutService.name }}\n      {{- else }}\n      serviceAccountName: default\n      {{- end }}\n      {{- if .Values.securityContext.enable }}\n      securityContext:\n        fsGroup: 1000\n        runAsGroup: 1000\n        runAsNonRoot: true\n        runAsUser: 1000\n        {{- if .Values.seccompProfile.enable }}\n        seccompProfile:\n          type: {{ .Values.seccompProfile.type }}\n        {{- end }}\n      {{- end }}\n      containers:\n      - name: server\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n              - ALL\n          privileged: false\n          readOnlyRootFilesystem: true\n        image: {{ .Values.images.repository }}/{{ .Values.checkoutService.name }}:{{ .Values.images.tag | default .Chart.AppVersion }}\n        ports:\n        - containerPort: 5050\n        readinessProbe:\n          grpc:\n            port: 5050\n        livenessProbe:\n          grpc:\n            port: 5050\n        env:\n        - name: PORT\n          value: \"5050\"\n        - name: PRODUCT_CATALOG_SERVICE_ADDR\n          value: \"{{ .Values.productCatalogService.name }}:3550\"\n        - name: SHIPPING_SERVICE_ADDR\n          value: \"{{ .Values.shippingService.name }}:50051\"\n        - name: PAYMENT_SERVICE_ADDR\n          value: \"{{ .Values.paymentService.name }}:50051\"\n        - name: EMAIL_SERVICE_ADDR\n          value: \"{{ .Values.emailService.name }}:5000\"\n        - name: CURRENCY_SERVICE_ADDR\n          value: \"{{ .Values.currencyService.name }}:7000\"\n        - name: CART_SERVICE_ADDR\n          value: \"{{ .Values.cartService.name }}:7070\"\n        {{- if .Values.opentelemetryCollector.create }}\n        - name: COLLECTOR_SERVICE_ADDR\n          value: \"{{ .Values.opentelemetryCollector.name }}:4317\"\n        - name: OTEL_SERVICE_NAME\n          value: \"{{ .Values.checkoutService.name }}\"\n        {{- end }}\n        {{- if .Values.googleCloudOperations.tracing }}\n        - name: ENABLE_TRACING\n          value: \"1\"\n        {{- end }}\n        {{- if .Values.googleCloudOperations.profiler }}\n        - name: ENABLE_PROFILER\n          value: \"1\"\n        {{- end }}\n        resources:\n          {{- toYaml .Values.checkoutService.resources | nindent 10 }}\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: {{ .Values.checkoutService.name }}\n  namespace: {{ .Release.Namespace }}\n  labels:\n    app: {{ .Values.checkoutService.name }}\nspec:\n  type: ClusterIP\n  selector:\n    app: {{ .Values.checkoutService.name }}\n  ports:\n  - name: grpc\n    port: 5050\n    targetPort: 5050\n{{- if .Values.networkPolicies.create }}\n---\napiVersion: networking.k8s.io/v1\nkind: NetworkPolicy\nmetadata:\n  name: {{ .Values.checkoutService.name }}\n  namespace: {{ .Release.Namespace }}\nspec:\n  podSelector:\n    matchLabels:\n      app: {{ .Values.checkoutService.name }}\n  policyTypes:\n  - Ingress\n  - Egress\n  ingress:\n  - from:\n    - podSelector:\n        matchLabels:\n          app: {{ .Values.frontend.name }}\n    ports:\n     - port: 5050\n       protocol: TCP\n  egress:\n  - {}\n{{- end }}\n{{- if .Values.sidecars.create }}\n---\napiVersion: networking.istio.io/v1beta1\nkind: Sidecar\nmetadata:\n  name: {{ .Values.checkoutService.name }}\n  namespace: {{ .Release.Namespace }}\nspec:\n  workloadSelector:\n    labels:\n      app: {{ .Values.checkoutService.name }}\n  egress:\n  - hosts:\n    - istio-system/*\n    - ./{{ .Values.cartService.name }}.{{ .Release.Namespace }}.svc.cluster.local\n    - ./{{ .Values.currencyService.name }}.{{ .Release.Namespace }}.svc.cluster.local\n    - ./{{ .Values.emailService.name }}.{{ .Release.Namespace }}.svc.cluster.local\n    - ./{{ .Values.paymentService.name }}.{{ .Release.Namespace }}.svc.cluster.local\n    - ./{{ .Values.productCatalogService.name }}.{{ .Release.Namespace }}.svc.cluster.local\n    - ./{{ .Values.shippingService.name }}.{{ .Release.Namespace }}.svc.cluster.local\n    {{- if .Values.opentelemetryCollector.create }}\n    - ./{{ .Values.opentelemetryCollector.name }}.{{ .Release.Namespace }}.svc.cluster.local\n    {{- end }}\n{{- end }}\n{{- if .Values.authorizationPolicies.create }}\n---\napiVersion: security.istio.io/v1beta1\nkind: AuthorizationPolicy\nmetadata:\n  name: {{ .Values.checkoutService.name }}\n  namespace: {{ .Release.Namespace }}\nspec:\n  selector:\n    matchLabels:\n      app: {{ .Values.checkoutService.name }}\n  rules:\n  - from:\n    - source:\n        principals:\n        {{- if .Values.serviceAccounts.create }}\n        - cluster.local/ns/{{ .Release.Namespace }}/sa/{{ .Values.frontend.name }}\n        {{- else }}\n        - cluster.local/ns/{{ .Release.Namespace }}/sa/default\n        {{- end }}\n    to:\n    - operation:\n        paths:\n        - /hipstershop.CheckoutService/PlaceOrder\n        methods:\n        - POST\n        ports:\n        - \"5050\"\n{{- end }}\n{{- end }}\n"
  },
  {
    "path": "helm-chart/templates/common.yaml",
    "content": "# Copyright 2024 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      https://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n{{- if .Values.networkPolicies.create }}\napiVersion: networking.k8s.io/v1\nkind: NetworkPolicy\nmetadata:\n  name: deny-all\n  namespace: {{ .Release.Namespace }}\nspec:\n  podSelector: {}\n  policyTypes:\n  - Ingress\n  - Egress\n{{- end }}\n{{- if .Values.authorizationPolicies.create }}\n---\napiVersion: security.istio.io/v1beta1\nkind: AuthorizationPolicy\nmetadata:\n  name: deny-all\n  namespace: {{ .Release.Namespace }}\nspec: {}\n{{- end }}\n"
  },
  {
    "path": "helm-chart/templates/currencyservice.yaml",
    "content": "# Copyright 2024 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      https://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n{{- if .Values.currencyService.create }}\n{{- if .Values.serviceAccounts.create }}\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: {{ .Values.currencyService.name }}\n  namespace: {{.Release.Namespace}}\n  {{- if not .Values.serviceAccounts.annotationsOnlyForCartservice }}\n  {{- with .Values.serviceAccounts.annotations }}\n  annotations:\n    {{- toYaml . | nindent 4 }}\n  {{- end }}\n  {{- end }}\n---\n{{- end }}\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: {{ .Values.currencyService.name }}\n  namespace: {{ .Release.Namespace }}\n  labels:\n    app: {{ .Values.currencyService.name }}\nspec:\n  selector:\n    matchLabels:\n      app: {{ .Values.currencyService.name }}\n  template:\n    metadata:\n      labels:\n        app: {{ .Values.currencyService.name }}\n    spec:\n      {{- if .Values.serviceAccounts.create }}\n      serviceAccountName: {{ .Values.currencyService.name }}\n      {{- else }}\n      serviceAccountName: default\n      {{- end }}\n      terminationGracePeriodSeconds: 5\n      {{- if .Values.securityContext.enable }}\n      securityContext:\n        fsGroup: 1000\n        runAsGroup: 1000\n        runAsNonRoot: true\n        runAsUser: 1000\n        {{- if .Values.seccompProfile.enable }}\n        seccompProfile:\n          type: {{ .Values.seccompProfile.type }}\n        {{- end }}\n      {{- end }}\n      containers:\n      - name: server\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n              - ALL\n          privileged: false\n          readOnlyRootFilesystem: true\n        image: {{ .Values.images.repository }}/{{ .Values.currencyService.name }}:{{ .Values.images.tag | default .Chart.AppVersion }}\n        ports:\n        - name: grpc\n          containerPort: 7000\n        env:\n        - name: PORT\n          value: \"7000\"\n        {{- if .Values.opentelemetryCollector.create }}\n        - name: COLLECTOR_SERVICE_ADDR\n          value: \"{{ .Values.opentelemetryCollector.name }}:4317\"\n        - name: OTEL_SERVICE_NAME\n          value: \"{{ .Values.currencyService.name }}\"\n        {{- end }}\n        {{- if .Values.googleCloudOperations.tracing }}\n        - name: ENABLE_TRACING\n          value: \"1\"\n        {{- end }}\n        {{- if not .Values.googleCloudOperations.profiler }}\n        - name: DISABLE_PROFILER\n          value: \"1\"\n        {{- end }}\n        readinessProbe:\n          grpc:\n            port: 7000\n        livenessProbe:\n          grpc:\n            port: 7000\n        resources:\n          {{- toYaml .Values.currencyService.resources | nindent 10 }}\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: {{ .Values.currencyService.name }}\n  namespace: {{ .Release.Namespace }}\n  labels:\n    app: {{ .Values.currencyService.name }}\nspec:\n  type: ClusterIP\n  selector:\n    app: {{ .Values.currencyService.name }}\n  ports:\n  - name: grpc\n    port: 7000\n    targetPort: 7000\n{{- if .Values.networkPolicies.create }}\n---\napiVersion: networking.k8s.io/v1\nkind: NetworkPolicy\nmetadata:\n  name: {{ .Values.currencyService.name }}\n  namespace: {{ .Release.Namespace }}\nspec:\n  podSelector:\n    matchLabels:\n      app: {{ .Values.currencyService.name }}\n  policyTypes:\n  - Ingress\n  - Egress\n  ingress:\n  - from:\n    - podSelector:\n        matchLabels:\n          app: {{ .Values.frontend.name }}\n    - podSelector:\n        matchLabels:\n          app: {{ .Values.checkoutService.name }}\n    ports:\n     - port: 7000\n       protocol: TCP\n  egress:\n  - {}\n{{- end }}\n{{- if .Values.sidecars.create }}\n---\napiVersion: networking.istio.io/v1beta1\nkind: Sidecar\nmetadata:\n  name: {{ .Values.currencyService.name }}\n  namespace: {{ .Release.Namespace }}\nspec:\n  workloadSelector:\n    labels:\n      app: {{ .Values.currencyService.name }}\n  egress:\n  - hosts:\n    - istio-system/*\n    {{- if .Values.opentelemetryCollector.create }}\n    - ./{{ .Values.opentelemetryCollector.name }}.{{ .Release.Namespace }}.svc.cluster.local\n    {{- end }}\n{{- end }}\n{{- if .Values.authorizationPolicies.create }}\n---\napiVersion: security.istio.io/v1beta1\nkind: AuthorizationPolicy\nmetadata:\n  name: {{ .Values.currencyService.name }}\n  namespace: {{ .Release.Namespace }}\nspec:\n  selector:\n    matchLabels:\n      app: {{ .Values.currencyService.name }}\n  rules:\n  - from:\n    - source:\n        principals:\n        {{- if .Values.serviceAccounts.create }}\n        - cluster.local/ns/{{ .Release.Namespace }}/sa/{{ .Values.frontend.name }}\n        - cluster.local/ns/{{ .Release.Namespace }}/sa/{{ .Values.checkoutService.name }}\n        {{- else }}\n        - cluster.local/ns/{{ .Release.Namespace }}/sa/default\n        {{- end }}\n    to:\n    - operation:\n        paths:\n        - /hipstershop.CurrencyService/Convert\n        - /hipstershop.CurrencyService/GetSupportedCurrencies\n        methods:\n        - POST\n        ports:\n        - \"7000\"\n{{- end }}\n{{- end }}\n"
  },
  {
    "path": "helm-chart/templates/emailservice.yaml",
    "content": "# Copyright 2024 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      https://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n{{- if .Values.emailService.create }}\n{{- if .Values.serviceAccounts.create }}\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: {{ .Values.emailService.name }}\n  namespace: {{.Release.Namespace}}\n  {{- if not .Values.serviceAccounts.annotationsOnlyForCartservice }}\n  {{- with .Values.serviceAccounts.annotations }}\n  annotations:\n    {{- toYaml . | nindent 4 }}\n  {{- end }}\n  {{- end }}\n---\n{{- end }}\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: {{ .Values.emailService.name }}\n  namespace: {{ .Release.Namespace }}\n  labels:\n    app: {{ .Values.emailService.name }}\nspec:\n  selector:\n    matchLabels:\n      app: {{ .Values.emailService.name }}\n  template:\n    metadata:\n      labels:\n        app: {{ .Values.emailService.name }}\n    spec:\n      {{- if .Values.serviceAccounts.create }}\n      serviceAccountName: {{ .Values.emailService.name }}\n      {{- else }}\n      serviceAccountName: default\n      {{- end }}\n      terminationGracePeriodSeconds: 5\n      {{- if .Values.securityContext.enable }}\n      securityContext:\n        fsGroup: 1000\n        runAsGroup: 1000\n        runAsNonRoot: true\n        runAsUser: 1000\n        {{- if .Values.seccompProfile.enable }}\n        seccompProfile:\n          type: {{ .Values.seccompProfile.type }}\n        {{- end }}\n      {{- end }}\n      containers:\n      - name: server\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n              - ALL\n          privileged: false\n          readOnlyRootFilesystem: true\n        image: {{ .Values.images.repository }}/{{ .Values.emailService.name }}:{{ .Values.images.tag | default .Chart.AppVersion }}\n        ports:\n        - containerPort: 8080\n        env:\n        - name: PORT\n          value: \"8080\"\n        {{- if .Values.opentelemetryCollector.create }}\n        - name: COLLECTOR_SERVICE_ADDR\n          value: \"{{ .Values.opentelemetryCollector.name }}:4317\"\n        - name: OTEL_SERVICE_NAME\n          value: \"{{ .Values.emailService.name }}\"\n        {{- end }}\n        {{- if .Values.googleCloudOperations.tracing }}\n        - name: ENABLE_TRACING\n          value: \"1\"\n        {{- end }}\n        {{- if not .Values.googleCloudOperations.profiler }}\n        - name: DISABLE_PROFILER\n          value: \"1\"\n        {{- end }}\n        readinessProbe:\n          periodSeconds: 5\n          grpc:\n            port: 8080\n        livenessProbe:\n          periodSeconds: 5\n          grpc:\n            port: 8080\n        resources:\n          {{- toYaml .Values.emailService.resources | nindent 10 }}\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: {{ .Values.emailService.name }}\n  namespace: {{ .Release.Namespace }}\n  labels:\n    app: {{ .Values.emailService.name }}\nspec:\n  type: ClusterIP\n  selector:\n    app: {{ .Values.emailService.name }}\n  ports:\n  - name: grpc\n    port: 5000\n    targetPort: 8080\n{{- if .Values.networkPolicies.create }}\n---\napiVersion: networking.k8s.io/v1\nkind: NetworkPolicy\nmetadata:\n  name: {{ .Values.emailService.name }}\n  namespace: {{ .Release.Namespace }}\nspec:\n  podSelector:\n    matchLabels:\n      app: {{ .Values.emailService.name }}\n  policyTypes:\n  - Ingress\n  - Egress\n  ingress:\n  - from:\n    - podSelector:\n        matchLabels:\n          app: {{ .Values.checkoutService.name }}\n    ports:\n     - port: 8080\n       protocol: TCP\n  egress:\n  - {}\n{{- end }}\n{{- if .Values.sidecars.create }}\n---\napiVersion: networking.istio.io/v1beta1\nkind: Sidecar\nmetadata:\n  name: {{ .Values.emailService.name }}\n  namespace: {{ .Release.Namespace }}\nspec:\n  workloadSelector:\n    labels:\n      app: {{ .Values.emailService.name }}\n  egress:\n  - hosts:\n    - istio-system/*\n    {{- if .Values.opentelemetryCollector.create }}\n    - ./{{ .Values.opentelemetryCollector.name }}.{{ .Release.Namespace }}.svc.cluster.local\n    {{- end }}\n{{- end }}\n{{- if .Values.authorizationPolicies.create }}\n---\napiVersion: security.istio.io/v1beta1\nkind: AuthorizationPolicy\nmetadata:\n  name: {{ .Values.emailService.name }}\n  namespace: {{ .Release.Namespace }}\nspec:\n  selector:\n    matchLabels:\n      app: {{ .Values.emailService.name }}\n  rules:\n  - from:\n    - source:\n        principals:\n        {{- if .Values.serviceAccounts.create }}\n        - cluster.local/ns/{{ .Release.Namespace }}/sa/{{ .Values.checkoutService.name }}\n        {{- else }}\n        - cluster.local/ns/{{ .Release.Namespace }}/sa/default\n        {{- end }}\n    to:\n    - operation:\n        paths:\n        - /hipstershop.EmailService/SendOrderConfirmation\n        methods:\n        - POST\n        ports:\n        - \"8080\"\n{{- end }}\n{{- end }}\n"
  },
  {
    "path": "helm-chart/templates/frontend.yaml",
    "content": "# Copyright 2024 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      https://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n{{- if .Values.frontend.create }}\n{{- if .Values.serviceAccounts.create }}\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: {{ .Values.frontend.name }}\n  namespace: {{.Release.Namespace}}\n  {{- if not .Values.serviceAccounts.annotationsOnlyForCartservice }}\n  {{- with .Values.serviceAccounts.annotations }}\n  annotations:\n    {{- toYaml . | nindent 4 }}\n  {{- end }}\n  {{- end }}\n---\n{{- end }}\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: {{ .Values.frontend.name }}\n  namespace: {{ .Release.Namespace }}\n  labels:\n    app: {{ .Values.frontend.name }}\nspec:\n  selector:\n    matchLabels:\n      app: {{ .Values.frontend.name }}\n  template:\n    metadata:\n      labels:\n        app: {{ .Values.frontend.name }}\n      annotations:\n        sidecar.istio.io/rewriteAppHTTPProbers: \"true\"\n    spec:\n      {{- if .Values.serviceAccounts.create }}\n      serviceAccountName: {{ .Values.frontend.name }}\n      {{- else }}\n      serviceAccountName: default\n      {{- end }}\n      {{- if .Values.securityContext.enable }}\n      securityContext:\n        fsGroup: 1000\n        runAsGroup: 1000\n        runAsNonRoot: true\n        runAsUser: 1000\n        {{- if .Values.seccompProfile.enable }}\n        seccompProfile:\n          type: {{ .Values.seccompProfile.type }}\n        {{- end }}\n      {{- end }}\n      containers:\n        - name: server\n          securityContext:\n            allowPrivilegeEscalation: false\n            capabilities:\n              drop:\n                - ALL\n            privileged: false\n            readOnlyRootFilesystem: true\n          image: {{ .Values.images.repository }}/{{ .Values.frontend.name }}:{{ .Values.images.tag | default .Chart.AppVersion }}\n          ports:\n          - containerPort: 8080\n          readinessProbe:\n            initialDelaySeconds: 10\n            httpGet:\n              path: \"/_healthz\"\n              port: 8080\n              httpHeaders:\n              - name: \"Cookie\"\n                value: \"shop_session-id=x-readiness-probe\"\n          livenessProbe:\n            initialDelaySeconds: 10\n            httpGet:\n              path: \"/_healthz\"\n              port: 8080\n              httpHeaders:\n              - name: \"Cookie\"\n                value: \"shop_session-id=x-liveness-probe\"\n          env:\n          - name: PORT\n            value: \"8080\"\n          - name: PRODUCT_CATALOG_SERVICE_ADDR\n            value: \"{{ .Values.productCatalogService.name }}:3550\"\n          - name: CURRENCY_SERVICE_ADDR\n            value: \"{{ .Values.currencyService.name }}:7000\"\n          - name: CART_SERVICE_ADDR\n            value: \"{{ .Values.cartService.name }}:7070\"\n          - name: RECOMMENDATION_SERVICE_ADDR\n            value: \"{{ .Values.recommendationService.name }}:8080\"\n          - name: SHIPPING_SERVICE_ADDR\n            value: \"{{ .Values.shippingService.name }}:50051\"\n          - name: CHECKOUT_SERVICE_ADDR\n            value: \"{{ .Values.checkoutService.name }}:5050\"\n          - name: AD_SERVICE_ADDR\n            value: \"{{ .Values.adService.name }}:9555\"\n          - name: SHOPPING_ASSISTANT_SERVICE_ADDR\n            value: \"{{ .Values.shoppingAssistantService.name }}:80\"\n          - name: ENV_PLATFORM\n            value: {{ .Values.frontend.platform | quote }}\n          {{- if .Values.opentelemetryCollector.create }}\n          - name: COLLECTOR_SERVICE_ADDR\n            value: \"{{ .Values.opentelemetryCollector.name }}:4317\"\n          - name: OTEL_SERVICE_NAME\n            value: \"{{ .Values.frontend.name }}\"\n          {{- end }}\n          {{- if .Values.googleCloudOperations.tracing }}\n          - name: ENABLE_TRACING\n            value: \"1\"\n          {{- end }}\n          {{- if .Values.googleCloudOperations.profiler }}\n          - name: ENABLE_PROFILER\n            value: \"1\"\n          {{- end }}\n          - name: CYMBAL_BRANDING\n            value: {{ .Values.frontend.cymbalBranding | quote }}\n          - name: ENABLE_ASSISTANT\n            value: {{ .Values.shoppingAssistantService.create | quote }}\n          - name: ENABLE_SINGLE_SHARED_SESSION\n            value: {{ .Values.frontend.singleSharedSession | quote }}\n          resources:\n            {{- toYaml .Values.frontend.resources | nindent 12 }}\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: {{ .Values.frontend.name }}\n  namespace: {{ .Release.Namespace }}\n  labels:\n    app: {{ .Values.frontend.name }}\nspec:\n  type: ClusterIP\n  selector:\n    app: {{ .Values.frontend.name }}\n  ports:\n  - name: http\n    port: 80\n    targetPort: 8080\n{{- if .Values.frontend.externalService }}\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: {{ .Values.frontend.name }}-external\n  namespace: {{ .Release.Namespace }}\nspec:\n  type: LoadBalancer\n  selector:\n    app: {{ .Values.frontend.name }}\n  ports:\n  - name: http\n    port: 80\n    targetPort: 8080\n{{- end }}\n{{- if .Values.networkPolicies.create }}\n---\napiVersion: networking.k8s.io/v1\nkind: NetworkPolicy\nmetadata:\n  name: {{ .Values.frontend.name }}\n  namespace: {{ .Release.Namespace }}\nspec:\n  podSelector:\n    matchLabels:\n      app: {{ .Values.frontend.name }}\n  policyTypes:\n  - Ingress\n  - Egress\n  ingress:\n  {{- if .Values.frontend.externalService }}\n  - {}\n  {{- else }}\n  - from:\n    - podSelector:\n        matchLabels:\n          app: {{ .Values.loadGenerator.name }}\n    {{- if .Values.frontend.virtualService.create }}\n    - namespaceSelector:\n        matchLabels:\n          kubernetes.io/metadata.name: {{ .Values.frontend.virtualService.gateway.namespace }}\n      podSelector:\n        matchLabels:\n          {{ .Values.frontend.virtualService.gateway.labelKey }}: {{ .Values.frontend.virtualService.gateway.labelValue }}\n    {{- end }}\n    ports:\n     - port: 8080\n       protocol: TCP\n  {{- end }}\n  egress:\n  - {}\n{{- end }}\n{{- if .Values.sidecars.create }}\n---\napiVersion: networking.istio.io/v1beta1\nkind: Sidecar\nmetadata:\n  name: {{ .Values.frontend.name }}\n  namespace: {{ .Release.Namespace }}\nspec:\n  workloadSelector:\n    labels:\n      app: {{ .Values.frontend.name }}\n  egress:\n  - hosts:\n    - istio-system/*\n    - ./{{ .Values.adService.name }}.{{ .Release.Namespace }}.svc.cluster.local\n    - ./{{ .Values.cartService.name }}.{{ .Release.Namespace }}.svc.cluster.local\n    - ./{{ .Values.checkoutService.name }}.{{ .Release.Namespace }}.svc.cluster.local\n    - ./{{ .Values.currencyService.name }}.{{ .Release.Namespace }}.svc.cluster.local\n    - ./{{ .Values.productCatalogService.name }}.{{ .Release.Namespace }}.svc.cluster.local\n    - ./{{ .Values.recommendationService.name }}.{{ .Release.Namespace }}.svc.cluster.local\n    - ./{{ .Values.shippingService.name }}.{{ .Release.Namespace }}.svc.cluster.local\n    {{- if .Values.opentelemetryCollector.create }}\n    - ./{{ .Values.opentelemetryCollector.name }}.{{ .Release.Namespace }}.svc.cluster.local\n    {{- end }}\n{{- end }}\n{{- if .Values.authorizationPolicies.create }}\n---\napiVersion: security.istio.io/v1beta1\nkind: AuthorizationPolicy\nmetadata:\n  name: {{ .Values.frontend.name }}\n  namespace: {{ .Release.Namespace }}\nspec:\n  selector:\n    matchLabels:\n      app: {{ .Values.frontend.name }}\n  rules:\n  {{- if .Values.frontend.externalService }}\n  - to:\n  {{- else }}\n  - from:\n    - source:\n        principals:\n        {{- if .Values.serviceAccounts.create }}\n        - cluster.local/ns/{{ .Release.Namespace }}/sa/{{ .Values.loadGenerator.name }}\n        {{- else }}\n        - cluster.local/ns/{{ .Release.Namespace }}/sa/default\n        {{- end }}\n        {{- if .Values.frontend.virtualService.create }}\n        - cluster.local/ns/{{ .Values.frontend.virtualService.gateway.namespace }}/sa/{{ .Values.frontend.virtualService.gateway.name }}\n        {{- end }}\n    to:\n  {{- end }}\n    - operation:\n        methods:\n        - GET\n        - POST\n        ports:\n        - \"8080\"\n{{- end }}\n{{- if .Values.frontend.virtualService.create }}\n---\napiVersion: networking.istio.io/v1alpha3\nkind: VirtualService\nmetadata:\n  name: {{ .Values.frontend.name }}\n  namespace: {{ .Release.Namespace }}\nspec:\n  {{- with .Values.frontend.virtualService.hosts }}\n  hosts:\n  {{- toYaml . | nindent 2 }}\n  {{- end }}\n  gateways:\n  - {{ .Values.frontend.virtualService.gateway.namespace }}/{{ .Values.frontend.virtualService.gateway.name }}\n  http:\n  - route:\n    - destination:\n        host: {{ .Values.frontend.name }}\n        port:\n          number: 80\n{{- end }}\n{{- end }}\n"
  },
  {
    "path": "helm-chart/templates/loadgenerator.yaml",
    "content": "# Copyright 2022 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n{{- if .Values.loadGenerator.create }}\n{{- if .Values.serviceAccounts.create }}\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: {{ .Values.loadGenerator.name }}\n  namespace: {{.Release.Namespace}}\n  {{- if not .Values.serviceAccounts.annotationsOnlyForCartservice }}\n  {{- with .Values.serviceAccounts.annotations }}\n  annotations:\n    {{- toYaml . | nindent 4 }}\n  {{- end }}\n  {{- end }}\n---\n{{- end }}\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: {{ .Values.loadGenerator.name }}\n  namespace: {{ .Release.Namespace }}\n  labels:\n    app: {{ .Values.loadGenerator.name }}\nspec:\n  selector:\n    matchLabels:\n      app: {{ .Values.loadGenerator.name }}\n  replicas: 1\n  template:\n    metadata:\n      labels:\n        app: {{ .Values.loadGenerator.name }}\n      annotations:\n        sidecar.istio.io/rewriteAppHTTPProbers: \"true\"\n    spec:\n      {{- if .Values.serviceAccounts.create }}\n      serviceAccountName: {{ .Values.loadGenerator.name }}\n      {{- else }}\n      serviceAccountName: default\n      {{- end }}\n      terminationGracePeriodSeconds: 5\n      restartPolicy: Always\n      {{- if .Values.securityContext.enable }}\n      securityContext:\n        fsGroup: 1000\n        runAsGroup: 1000\n        runAsNonRoot: true\n        runAsUser: 1000\n        {{- if .Values.seccompProfile.enable }}\n        seccompProfile:\n          type: {{ .Values.seccompProfile.type }}\n        {{- end }}\n      {{- end }}\n      {{- if .Values.loadGenerator.checkFrontendInitContainer }}\n      initContainers:\n      - command:\n        - /bin/sh\n        - -exc\n        - |\n          MAX_RETRIES=12\n          RETRY_INTERVAL=10\n          for i in $(seq 1 $MAX_RETRIES); do\n            echo \"Attempt $i: Pinging frontend: ${FRONTEND_ADDR}...\"\n            STATUSCODE=$(wget --server-response http://${FRONTEND_ADDR} 2>&1 | awk '/^  HTTP/{print $2}')\n            if [ $STATUSCODE -eq 200 ]; then\n                echo \"Frontend is reachable.\"\n                exit 0\n            fi\n            echo \"Error: Could not reach frontend - Status code: ${STATUSCODE}\"\n            sleep $RETRY_INTERVAL\n          done\n          echo \"Failed to reach frontend after $MAX_RETRIES attempts.\"\n          exit 1\n        name: frontend-check\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n              - ALL\n          privileged: false\n          readOnlyRootFilesystem: true\n        image: busybox:latest@sha256:b3255e7dfbcd10cb367af0d409747d511aeb66dfac98cf30e97e87e4207dd76f\n        env:\n        - name: FRONTEND_ADDR\n          value: \"{{ .Values.frontend.name }}:80\"\n      {{- end }}\n      containers:\n      - name: main\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n              - ALL\n          privileged: false\n          readOnlyRootFilesystem: true\n        image: {{ .Values.images.repository }}/{{ .Values.loadGenerator.name }}:{{ .Values.images.tag | default .Chart.AppVersion }}\n        env:\n        - name: FRONTEND_ADDR\n          value: \"{{ .Values.frontend.name }}:80\"\n        - name: USERS\n          value: \"10\"\n        - name: RATE\n          value: \"1\"\n        resources:\n          {{- toYaml .Values.loadGenerator.resources | nindent 10 }}\n{{- if .Values.networkPolicies.create }}\n---\napiVersion: networking.k8s.io/v1\nkind: NetworkPolicy\nmetadata:\n  name: {{ .Values.loadGenerator.name }}\n  namespace: {{ .Release.Namespace }}\n  labels:\n    app: {{ .Values.loadGenerator.name }}\nspec:\n  podSelector:\n    matchLabels:\n      app: {{ .Values.loadGenerator.name }}\n  policyTypes:\n  - Egress\n  egress:\n  - {}\n{{- end }}\n{{- if .Values.sidecars.create }}\n---\napiVersion: networking.istio.io/v1beta1\nkind: Sidecar\nmetadata:\n  name: {{ .Values.loadGenerator.name }}\n  namespace: {{ .Release.Namespace }}\nspec:\n  workloadSelector:\n    labels:\n      app: {{ .Values.loadGenerator.name }}\n  egress:\n  - hosts:\n    - istio-system/*\n    - ./{{ .Values.frontend.name }}.{{ .Release.Namespace }}.svc.cluster.local\n    {{- if .Values.opentelemetryCollector.create }}\n    - ./{{ .Values.opentelemetryCollector.name }}.{{ .Release.Namespace }}.svc.cluster.local\n    {{- end }}\n{{- end }}\n{{- end }}\n"
  },
  {
    "path": "helm-chart/templates/opentelemetry-collector.yaml",
    "content": "# Copyright 2022 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n{{- if .Values.opentelemetryCollector.create }}\n{{- if .Values.serviceAccounts.create }}\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: {{ .Values.opentelemetryCollector.name }}\n  namespace: {{.Release.Namespace}}\n  {{- if not .Values.serviceAccounts.annotationsOnlyForCartservice }}\n  {{- with .Values.serviceAccounts.annotations }}\n  annotations:\n    {{- toYaml . | nindent 4 }}\n  {{- end }}\n  {{- end }}\n---\n{{- end }}\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: {{ .Values.opentelemetryCollector.name }}\n  namespace: {{ .Release.Namespace }}\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: {{ .Values.opentelemetryCollector.name }}\n  template:\n    metadata:\n      labels:\n        app: {{ .Values.opentelemetryCollector.name }}\n    spec:\n      {{- if .Values.serviceAccounts.create }}\n      serviceAccountName: {{ .Values.opentelemetryCollector.name }}\n      {{- else }}\n      serviceAccountName: default\n      {{- end }}\n      {{- if .Values.securityContext.enable }}\n      securityContext:\n        fsGroup: 1000\n        runAsGroup: 1000\n        runAsNonRoot: true\n        runAsUser: 1000\n        {{- if .Values.seccompProfile.enable }}\n        seccompProfile:\n          type: {{ .Values.seccompProfile.type }}\n        {{- end }}\n      {{- end }}\n      {{- if eq .Values.opentelemetryCollector.projectId \"PROJECT_ID\" }}\n      initContainers:\n      # Init container retrieves the current cloud project id from the metadata server\n      # and inserts it into the collector config template\n      # https://cloud.google.com/compute/docs/storing-retrieving-metadata\n      - name: otel-gateway-init\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n              - ALL\n          privileged: false\n          readOnlyRootFilesystem: true\n        image: busybox:latest@sha256:b3255e7dfbcd10cb367af0d409747d511aeb66dfac98cf30e97e87e4207dd76f\n        command:\n        - '/bin/sh'\n        - '-c'\n        - |\n           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\n        volumeMounts:\n        - name: collector-gateway-config-template\n          mountPath: /template\n        - name: collector-gateway-config\n          mountPath: /conf\n      {{- end }}\n      containers:\n      # This gateway container will receive traces and metrics from each microservice\n      # and forward it to GCP\n      - name: otel-gateway\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n              - ALL\n          privileged: false\n          readOnlyRootFilesystem: true\n        args:\n        - --config=/conf/collector-gateway-config.yaml\n        image: otel/opentelemetry-collector-contrib:0.147.0@sha256:e7c92c715f28ff142f3bcaccd4fc5603cf4c71276ef09954a38eb4038500a5a5\n        volumeMounts:\n        - name: collector-gateway-config\n          mountPath: /conf\n      volumes:\n      # Simple ConfigMap volume with template file\n      - name: collector-gateway-config-template\n        configMap:\n          items:\n          - key: collector-gateway-config-template.yaml\n            path: collector-gateway-config-template.yaml\n          name: collector-gateway-config-template\n      # Create a volume to store the expanded template (with correct cloud project ID)\n      - name: collector-gateway-config\n        emptyDir: {}\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: {{ .Values.opentelemetryCollector.name }}\n  namespace: {{ .Release.Namespace }}\nspec:\n  ports:\n  - name: grpc-otlp\n    port: 4317\n    protocol: TCP\n    targetPort: 4317\n  selector:\n    app: {{ .Values.opentelemetryCollector.name }}\n  type: ClusterIP\n---\napiVersion: v1\nkind: ConfigMap\nmetadata:\n  name: collector-gateway-config-template\n  namespace: {{ .Release.Namespace }}\n# Open Telemetry Collector config\n# https://opentelemetry.io/docs/collector/configuration/\ndata:\n  collector-gateway-config-template.yaml: |\n    receivers:\n      otlp:\n        protocols: \n          grpc:\n    processors:\n    exporters:\n      googlecloud:\n        project: {{ .Values.opentelemetryCollector.projectId | quote }}\n    service:\n      pipelines:\n        traces:\n          receivers: [otlp] # Receive otlp-formatted data from other collector instances\n          processors: []\n          exporters: [googlecloud] # Export traces directly to Google Cloud\n        metrics:\n          receivers: [otlp]\n          processors: []\n          exporters: [googlecloud] # Export metrics to Google Cloud\n{{- if .Values.networkPolicies.create }}\n---\napiVersion: networking.k8s.io/v1\nkind: NetworkPolicy\nmetadata:\n  name: {{ .Values.opentelemetryCollector.name }}\n  namespace: {{ .Release.Namespace }}\nspec:\n  podSelector:\n    matchLabels:\n      app: {{ .Values.opentelemetryCollector.name }}\n  policyTypes:\n  - Ingress\n  - Egress\n  ingress:\n  - from:\n    - podSelector:\n        matchLabels:\n          app: {{ .Values.adService.name }}\n    - podSelector:\n        matchLabels:\n          app: {{ .Values.cartService.name }}\n    - podSelector:\n        matchLabels:\n          app: {{ .Values.checkoutService.name }}\n    - podSelector:\n        matchLabels:\n          app: {{ .Values.currencyService.name }}\n    - podSelector:\n        matchLabels:\n          app: {{ .Values.emailService.name }}\n    - podSelector:\n        matchLabels:\n          app: {{ .Values.frontend.name }}\n    - podSelector:\n        matchLabels:\n          app: {{ .Values.loadGenerator.name }}\n    - podSelector:\n        matchLabels:\n          app: {{ .Values.paymentService.name }}\n    - podSelector:\n        matchLabels:\n          app: {{ .Values.productCatalogService.name }}\n    - podSelector:\n        matchLabels:\n          app: {{ .Values.recommendationService.name }}\n    - podSelector:\n        matchLabels:\n          app: {{ .Values.shippingService.name }}\n    ports:\n     - port: 4317\n       protocol: TCP\n  egress:\n  - {}\n{{- end }}\n{{- if .Values.sidecars.create }}\n---\napiVersion: networking.istio.io/v1beta1\nkind: Sidecar\nmetadata:\n  name: {{ .Values.opentelemetryCollector.name }}\n  namespace: {{ .Release.Namespace }}\nspec:\n  workloadSelector:\n    labels:\n      app: {{ .Values.opentelemetryCollector.name }}\n  egress:\n  - hosts:\n    - istio-system/*\n{{- end }}\n{{- if .Values.authorizationPolicies.create }}\n---\napiVersion: security.istio.io/v1beta1\nkind: AuthorizationPolicy\nmetadata:\n  name: {{ .Values.opentelemetryCollector.name }}\n  namespace: {{ .Release.Namespace }}\nspec:\n  selector:\n    matchLabels:\n      app: {{ .Values.opentelemetryCollector.name }}\n  rules:\n  - from:\n    - source:\n        principals:\n        {{- if .Values.serviceAccounts.create }}\n        - cluster.local/ns/{{ .Release.Namespace }}/sa/{{ .Values.adService.name }}\n        - cluster.local/ns/{{ .Release.Namespace }}/sa/{{ .Values.cartService.name }}\n        - cluster.local/ns/{{ .Release.Namespace }}/sa/{{ .Values.checkoutService.name }}\n        - cluster.local/ns/{{ .Release.Namespace }}/sa/{{ .Values.currencyService.name }}\n        - cluster.local/ns/{{ .Release.Namespace }}/sa/{{ .Values.emailService.name }}\n        - cluster.local/ns/{{ .Release.Namespace }}/sa/{{ .Values.frontend.name }}\n        - cluster.local/ns/{{ .Release.Namespace }}/sa/{{ .Values.loadGenerator.name }}\n        - cluster.local/ns/{{ .Release.Namespace }}/sa/{{ .Values.paymentService.name }}\n        - cluster.local/ns/{{ .Release.Namespace }}/sa/{{ .Values.productCatalogService.name }}\n        - cluster.local/ns/{{ .Release.Namespace }}/sa/{{ .Values.recommendationService.name }}\n        - cluster.local/ns/{{ .Release.Namespace }}/sa/{{ .Values.shippingService.name }}\n        {{- else }}\n        - cluster.local/ns/{{ .Release.Namespace }}/sa/default\n        {{- end }}\n    to:\n    - operation:\n        ports:\n        - \"4317\"\n{{- end }}\n{{- end }}\n"
  },
  {
    "path": "helm-chart/templates/paymentservice.yaml",
    "content": "# Copyright 2024 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      https://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n{{- if .Values.paymentService.create }}\n{{- if .Values.serviceAccounts.create }}\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: {{ .Values.paymentService.name }}\n  namespace: {{.Release.Namespace}}\n  {{- if not .Values.serviceAccounts.annotationsOnlyForCartservice }}\n  {{- with .Values.serviceAccounts.annotations }}\n  annotations:\n    {{- toYaml . | nindent 4 }}\n  {{- end }}\n  {{- end }}\n---\n{{- end }}\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: {{ .Values.paymentService.name }}\n  namespace: {{ .Release.Namespace }}\n  labels:\n    app: {{ .Values.paymentService.name }}\nspec:\n  selector:\n    matchLabels:\n      app: {{ .Values.paymentService.name }}\n  template:\n    metadata:\n      labels:\n        app: {{ .Values.paymentService.name }}\n    spec:\n      {{- if .Values.serviceAccounts.create }}\n      serviceAccountName: {{ .Values.paymentService.name }}\n      {{- else }}\n      serviceAccountName: default\n      {{- end }}\n      terminationGracePeriodSeconds: 5\n      {{- if .Values.securityContext.enable }}\n      securityContext:\n        fsGroup: 1000\n        runAsGroup: 1000\n        runAsNonRoot: true\n        runAsUser: 1000\n        {{- if .Values.seccompProfile.enable }}\n        seccompProfile:\n          type: {{ .Values.seccompProfile.type }}\n        {{- end }}\n      {{- end }}\n      containers:\n      - name: server\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n              - ALL\n          privileged: false\n          readOnlyRootFilesystem: true\n        image: {{ .Values.images.repository }}/{{ .Values.paymentService.name }}:{{ .Values.images.tag | default .Chart.AppVersion }}\n        ports:\n        - containerPort: 50051\n        env:\n        - name: PORT\n          value: \"50051\"\n        {{- if .Values.opentelemetryCollector.create }}\n        - name: COLLECTOR_SERVICE_ADDR\n          value: \"{{ .Values.opentelemetryCollector.name }}:4317\"\n        - name: OTEL_SERVICE_NAME\n          value: \"{{ .Values.paymentService.name }}\"\n        {{- end }}\n        {{- if .Values.googleCloudOperations.tracing }}\n        - name: ENABLE_TRACING\n          value: \"1\"\n        {{- end }}\n        {{- if not .Values.googleCloudOperations.profiler }}\n        - name: DISABLE_PROFILER\n          value: \"1\"\n        {{- end }}\n        readinessProbe:\n          grpc:\n            port: 50051\n        livenessProbe:\n          grpc:\n            port: 50051\n        resources:\n          {{- toYaml .Values.paymentService.resources | nindent 10 }}\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: {{ .Values.paymentService.name }}\n  namespace: {{ .Release.Namespace }}\n  labels:\n    app: {{ .Values.paymentService.name }}\nspec:\n  type: ClusterIP\n  selector:\n    app: {{ .Values.paymentService.name }}\n  ports:\n  - name: grpc\n    port: 50051\n    targetPort: 50051\n{{- if .Values.networkPolicies.create }}\n---\napiVersion: networking.k8s.io/v1\nkind: NetworkPolicy\nmetadata:\n  name: {{ .Values.paymentService.name }}\n  namespace: {{ .Release.Namespace }}\nspec:\n  podSelector:\n    matchLabels:\n      app: {{ .Values.paymentService.name }}\n  policyTypes:\n  - Ingress\n  - Egress\n  ingress:\n  - from:\n    - podSelector:\n        matchLabels:\n          app: {{ .Values.checkoutService.name }}\n    ports:\n     - port: 50051\n       protocol: TCP\n  egress:\n  - {}\n{{- end }}\n{{- if .Values.sidecars.create }}\n---\napiVersion: networking.istio.io/v1beta1\nkind: Sidecar\nmetadata:\n  name: {{ .Values.paymentService.name }}\n  namespace: {{ .Release.Namespace }}\nspec:\n  workloadSelector:\n    labels:\n      app: {{ .Values.paymentService.name }}\n  egress:\n  - hosts:\n    - istio-system/*\n    {{- if .Values.opentelemetryCollector.create }}\n    - ./{{ .Values.opentelemetryCollector.name }}.{{ .Release.Namespace }}.svc.cluster.local\n    {{- end }}\n{{- end }}\n{{- if .Values.authorizationPolicies.create }}\n---\napiVersion: security.istio.io/v1beta1\nkind: AuthorizationPolicy\nmetadata:\n  name: {{ .Values.paymentService.name }}\n  namespace: {{ .Release.Namespace }}\nspec:\n  selector:\n    matchLabels:\n      app: {{ .Values.paymentService.name }}\n  rules:\n  - from:\n    - source:\n        principals:\n        {{- if .Values.serviceAccounts.create }}\n        - cluster.local/ns/{{ .Release.Namespace }}/sa/{{ .Values.checkoutService.name }}\n        {{- else }}\n        - cluster.local/ns/{{ .Release.Namespace }}/sa/default\n        {{- end }}\n    to:\n    - operation:\n        paths:\n        - /hipstershop.PaymentService/Charge\n        methods:\n        - POST\n        ports:\n        - \"50051\"\n{{- end }}\n{{- end }}\n"
  },
  {
    "path": "helm-chart/templates/productcatalogservice.yaml",
    "content": "# Copyright 2024 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      https://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n{{- if .Values.productCatalogService.create }}\n{{- if .Values.serviceAccounts.create }}\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: {{ .Values.productCatalogService.name }}\n  namespace: {{.Release.Namespace}}\n  {{- if not .Values.serviceAccounts.annotationsOnlyForCartservice }}\n  {{- with .Values.serviceAccounts.annotations }}\n  annotations:\n    {{- toYaml . | nindent 4 }}\n  {{- end }}\n  {{- end }}\n---\n{{- end }}\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: {{ .Values.productCatalogService.name }}\n  namespace: {{ .Release.Namespace }}\n  labels:\n    app: {{ .Values.productCatalogService.name }}\nspec:\n  selector:\n    matchLabels:\n      app: {{ .Values.productCatalogService.name }}\n  template:\n    metadata:\n      labels:\n        app: {{ .Values.productCatalogService.name }}\n    spec:\n      {{- if .Values.serviceAccounts.create }}\n      serviceAccountName: {{ .Values.productCatalogService.name }}\n      {{- else }}\n      serviceAccountName: default\n      {{- end }}\n      terminationGracePeriodSeconds: 5\n      {{- if .Values.securityContext.enable }}\n      securityContext:\n        fsGroup: 1000\n        runAsGroup: 1000\n        runAsNonRoot: true\n        runAsUser: 1000\n        {{- if .Values.seccompProfile.enable }}\n        seccompProfile:\n          type: {{ .Values.seccompProfile.type }}\n        {{- end }}\n      {{- end }}\n      containers:\n      - name: server\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n              - ALL\n          privileged: false\n          readOnlyRootFilesystem: true\n        image: {{ .Values.images.repository }}/{{ .Values.productCatalogService.name }}:{{ .Values.images.tag | default .Chart.AppVersion }}\n        ports:\n        - containerPort: 3550\n        env:\n        - name: PORT\n          value: \"3550\"\n        {{- if .Values.opentelemetryCollector.create }}\n        - name: COLLECTOR_SERVICE_ADDR\n          value: \"{{ .Values.opentelemetryCollector.name }}:4317\"\n        - name: OTEL_SERVICE_NAME\n          value: \"{{ .Values.productCatalogService.name }}\"\n        {{- end }}\n        {{- if .Values.googleCloudOperations.tracing }}\n        - name: ENABLE_TRACING\n          value: \"1\"\n        {{- end }}\n        {{- if not .Values.googleCloudOperations.profiler }}\n        - name: DISABLE_PROFILER\n          value: \"1\"\n        {{- end }}\n        - name: EXTRA_LATENCY\n          value: {{ .Values.productCatalogService.extraLatency }}\n        readinessProbe:\n          grpc:\n            port: 3550\n        livenessProbe:\n          grpc:\n            port: 3550\n        resources:\n          {{- toYaml .Values.productCatalogService.resources | nindent 10 }}\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: {{ .Values.productCatalogService.name }}\n  namespace: {{ .Release.Namespace }}\n  labels:\n    app: {{ .Values.productCatalogService.name }}\nspec:\n  type: ClusterIP\n  selector:\n    app: {{ .Values.productCatalogService.name }}\n  ports:\n  - name: grpc\n    port: 3550\n    targetPort: 3550\n{{- if .Values.networkPolicies.create }}\n---\napiVersion: networking.k8s.io/v1\nkind: NetworkPolicy\nmetadata:\n  name: {{ .Values.productCatalogService.name }}\n  namespace: {{ .Release.Namespace }}\nspec:\n  podSelector:\n    matchLabels:\n      app: {{ .Values.productCatalogService.name }}\n  policyTypes:\n  - Ingress\n  - Egress\n  ingress:\n  - from:\n    - podSelector:\n        matchLabels:\n          app: {{ .Values.frontend.name }}\n    - podSelector:\n        matchLabels:\n          app: {{ .Values.checkoutService.name }}\n    - podSelector:\n        matchLabels:\n          app: {{ .Values.recommendationService.name }}\n    ports:\n     - port: 3550\n       protocol: TCP\n  egress:\n  - {}\n{{- end }}\n{{- if .Values.sidecars.create }}\n---\napiVersion: networking.istio.io/v1beta1\nkind: Sidecar\nmetadata:\n  name: {{ .Values.productCatalogService.name }}\n  namespace: {{ .Release.Namespace }}\nspec:\n  workloadSelector:\n    labels:\n      app: {{ .Values.productCatalogService.name }}\n  egress:\n  - hosts:\n    - istio-system/*\n    {{- if .Values.opentelemetryCollector.create }}\n    - ./{{ .Values.opentelemetryCollector.name }}.{{ .Release.Namespace }}.svc.cluster.local\n    {{- end }}\n{{- end }}\n{{- if .Values.authorizationPolicies.create }}\n---\napiVersion: security.istio.io/v1beta1\nkind: AuthorizationPolicy\nmetadata:\n  name: {{ .Values.productCatalogService.name }}\n  namespace: {{ .Release.Namespace }}\nspec:\n  selector:\n    matchLabels:\n      app: {{ .Values.productCatalogService.name }}\n  rules:\n  - from:\n    - source:\n        principals:\n        {{- if .Values.serviceAccounts.create }}\n        - cluster.local/ns/{{ .Release.Namespace }}/sa/{{ .Values.frontend.name }}\n        - cluster.local/ns/{{ .Release.Namespace }}/sa/{{ .Values.checkoutService.name }}\n        - cluster.local/ns/{{ .Release.Namespace }}/sa/{{ .Values.recommendationService.name }}\n        {{- else }}\n        - cluster.local/ns/{{ .Release.Namespace }}/sa/default\n        {{- end }}\n    to:\n    - operation:\n        paths:\n        - /hipstershop.ProductCatalogService/GetProduct\n        - /hipstershop.ProductCatalogService/ListProducts\n        methods:\n        - POST\n        ports:\n        - \"3550\"\n{{- end }}\n{{- end }}\n"
  },
  {
    "path": "helm-chart/templates/recommendationservice.yaml",
    "content": "# Copyright 2024 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      https://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n{{- if .Values.recommendationService.create }}\n{{- if .Values.serviceAccounts.create }}\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: {{ .Values.recommendationService.name }}\n  namespace: {{.Release.Namespace}}\n  {{- if not .Values.serviceAccounts.annotationsOnlyForCartservice }}\n  {{- with .Values.serviceAccounts.annotations }}\n  annotations:\n    {{- toYaml . | nindent 4 }}\n  {{- end }}\n  {{- end }}\n---\n{{- end }}\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: {{ .Values.recommendationService.name }}\n  namespace: {{ .Release.Namespace }}\n  labels:\n    app: {{ .Values.recommendationService.name }}\nspec:\n  selector:\n    matchLabels:\n      app: {{ .Values.recommendationService.name }}\n  template:\n    metadata:\n      labels:\n        app: {{ .Values.recommendationService.name }}\n    spec:\n      {{- if .Values.serviceAccounts.create }}\n      serviceAccountName: {{ .Values.recommendationService.name }}\n      {{- else }}\n      serviceAccountName: default\n      {{- end }}\n      terminationGracePeriodSeconds: 5\n      {{- if .Values.securityContext.enable }}\n      securityContext:\n        fsGroup: 1000\n        runAsGroup: 1000\n        runAsNonRoot: true\n        runAsUser: 1000\n        {{- if .Values.seccompProfile.enable }}\n        seccompProfile:\n          type: {{ .Values.seccompProfile.type }}\n        {{- end }}\n      {{- end }}\n      containers:\n      - name: server\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n              - ALL\n          privileged: false\n          readOnlyRootFilesystem: true\n        image: {{ .Values.images.repository }}/{{ .Values.recommendationService.name }}:{{ .Values.images.tag | default .Chart.AppVersion }}\n        ports:\n        - containerPort: 8080\n        readinessProbe:\n          periodSeconds: 5\n          grpc:\n            port: 8080\n        livenessProbe:\n          periodSeconds: 5\n          grpc:\n            port: 8080\n        env:\n        - name: PORT\n          value: \"8080\"\n        - name: PRODUCT_CATALOG_SERVICE_ADDR\n          value: \"{{ .Values.productCatalogService.name }}:3550\"\n        {{- if .Values.opentelemetryCollector.create }}\n        - name: COLLECTOR_SERVICE_ADDR\n          value: \"{{ .Values.opentelemetryCollector.name }}:4317\"\n        - name: OTEL_SERVICE_NAME\n          value: \"{{ .Values.recommendationService.name }}\"\n        {{- end }}\n        {{- if .Values.googleCloudOperations.tracing }}\n        - name: ENABLE_TRACING\n          value: \"1\"\n        {{- end }}\n        {{- if not .Values.googleCloudOperations.profiler }}\n        - name: DISABLE_PROFILER\n          value: \"1\"\n        {{- end }}\n        resources:\n          {{- toYaml .Values.recommendationService.resources | nindent 10 }}\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: {{ .Values.recommendationService.name }}\n  namespace: {{ .Release.Namespace }}\n  labels:\n    app: {{ .Values.recommendationService.name }}\nspec:\n  type: ClusterIP\n  selector:\n    app: {{ .Values.recommendationService.name }}\n  ports:\n  - name: grpc\n    port: 8080\n    targetPort: 8080\n{{- if .Values.networkPolicies.create }}\n---\napiVersion: networking.k8s.io/v1\nkind: NetworkPolicy\nmetadata:\n  name: {{ .Values.recommendationService.name }}\n  namespace: {{ .Release.Namespace }}\nspec:\n  podSelector:\n    matchLabels:\n      app: {{ .Values.recommendationService.name }}\n  policyTypes:\n  - Ingress\n  - Egress\n  ingress:\n  - from:\n    - podSelector:\n        matchLabels:\n          app: {{ .Values.frontend.name }}\n    ports:\n     - port: 8080\n       protocol: TCP\n  egress:\n  - {}\n{{- end }}\n{{- if .Values.sidecars.create }}\n---\napiVersion: networking.istio.io/v1beta1\nkind: Sidecar\nmetadata:\n  name: {{ .Values.recommendationService.name }}\n  namespace: {{ .Release.Namespace }}\nspec:\n  workloadSelector:\n    labels:\n      app: {{ .Values.recommendationService.name }}\n  egress:\n  - hosts:\n    - istio-system/*\n    - ./{{ .Values.productCatalogService.name }}.{{ .Release.Namespace }}.svc.cluster.local\n    {{- if .Values.opentelemetryCollector.create }}\n    - ./{{ .Values.opentelemetryCollector.name }}.{{ .Release.Namespace }}.svc.cluster.local\n    {{- end }}\n{{- end }}\n{{- if .Values.authorizationPolicies.create }}\n---\napiVersion: security.istio.io/v1beta1\nkind: AuthorizationPolicy\nmetadata:\n  name: {{ .Values.recommendationService.name }}\n  namespace: {{ .Release.Namespace }}\nspec:\n  selector:\n    matchLabels:\n      app: {{ .Values.recommendationService.name }}\n  rules:\n  - from:\n    - source:\n        principals:\n        {{- if .Values.serviceAccounts.create }}\n        - cluster.local/ns/{{ .Release.Namespace }}/sa/{{ .Values.frontend.name }}\n        {{- else }}\n        - cluster.local/ns/{{ .Release.Namespace }}/sa/default\n        {{- end }}\n    to:\n    - operation:\n        paths:\n        - /hipstershop.RecommendationService/ListRecommendations\n        methods:\n        - POST\n        ports:\n        - \"8080\"\n{{- end }}\n{{- end }}\n"
  },
  {
    "path": "helm-chart/templates/shippingservice.yaml",
    "content": "# Copyright 2024 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      https://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n{{- if .Values.shippingService.create }}\n{{- if .Values.serviceAccounts.create }}\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: {{ .Values.shippingService.name }}\n  namespace: {{.Release.Namespace}}\n  {{- if not .Values.serviceAccounts.annotationsOnlyForCartservice }}\n  {{- with .Values.serviceAccounts.annotations }}\n  annotations:\n    {{- toYaml . | nindent 4 }}\n  {{- end }}\n  {{- end }}\n---\n{{- end }}\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: {{ .Values.shippingService.name }}\n  namespace: {{ .Release.Namespace }}\n  labels:\n    app: {{ .Values.shippingService.name }}\nspec:\n  selector:\n    matchLabels:\n      app: {{ .Values.shippingService.name }}\n  template:\n    metadata:\n      labels:\n        app: {{ .Values.shippingService.name }}\n    spec:\n      {{- if .Values.serviceAccounts.create }}\n      serviceAccountName: {{ .Values.shippingService.name }}\n      {{- else }}\n      serviceAccountName: default\n      {{- end }}\n      {{- if .Values.securityContext.enable }}\n      securityContext:\n        fsGroup: 1000\n        runAsGroup: 1000\n        runAsNonRoot: true\n        runAsUser: 1000\n        {{- if .Values.seccompProfile.enable }}\n        seccompProfile:\n          type: {{ .Values.seccompProfile.type }}\n        {{- end }}\n      {{- end }}\n      containers:\n      - name: server\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n              - ALL\n          privileged: false\n          readOnlyRootFilesystem: true\n        image: {{ .Values.images.repository }}/{{ .Values.shippingService.name }}:{{ .Values.images.tag | default .Chart.AppVersion }}\n        ports:\n        - containerPort: 50051\n        env:\n        - name: PORT\n          value: \"50051\"\n        {{- if not .Values.googleCloudOperations.profiler }}\n        - name: DISABLE_PROFILER\n          value: \"1\"\n        {{- end }}\n        readinessProbe:\n          periodSeconds: 5\n          grpc:\n            port: 50051\n        livenessProbe:\n          grpc:\n            port: 50051\n        resources:\n          {{- toYaml .Values.shippingService.resources | nindent 10 }}\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: {{ .Values.shippingService.name }}\n  namespace: {{ .Release.Namespace }}\n  labels:\n    app: {{ .Values.shippingService.name }}\nspec:\n  type: ClusterIP\n  selector:\n    app: {{ .Values.shippingService.name }}\n  ports:\n  - name: grpc\n    port: 50051\n    targetPort: 50051\n{{- if .Values.networkPolicies.create }}\n---\napiVersion: networking.k8s.io/v1\nkind: NetworkPolicy\nmetadata:\n  name: {{ .Values.shippingService.name }}\n  namespace: {{ .Release.Namespace }}\nspec:\n  podSelector:\n    matchLabels:\n      app: {{ .Values.shippingService.name }}\n  policyTypes:\n  - Ingress\n  - Egress\n  ingress:\n  - from:\n    - podSelector:\n        matchLabels:\n          app: {{ .Values.frontend.name }}\n    - podSelector:\n        matchLabels:\n          app: {{ .Values.checkoutService.name }}\n    ports:\n     - port: 50051\n       protocol: TCP\n  egress:\n  - {}\n{{- end }}\n{{- if .Values.sidecars.create }}\n---\napiVersion: networking.istio.io/v1beta1\nkind: Sidecar\nmetadata:\n  name: {{ .Values.shippingService.name }}\n  namespace: {{ .Release.Namespace }}\nspec:\n  workloadSelector:\n    labels:\n      app: {{ .Values.shippingService.name }}\n  egress:\n  - hosts:\n    - istio-system/*\n    {{- if .Values.opentelemetryCollector.create }}\n    - ./{{ .Values.opentelemetryCollector.name }}.{{ .Release.Namespace }}.svc.cluster.local\n    {{- end }}\n{{- end }}\n{{- if .Values.authorizationPolicies.create }}\n---\napiVersion: security.istio.io/v1beta1\nkind: AuthorizationPolicy\nmetadata:\n  name: {{ .Values.shippingService.name }}\n  namespace: {{ .Release.Namespace }}\nspec:\n  selector:\n    matchLabels:\n      app: {{ .Values.shippingService.name }}\n  rules:\n  - from:\n    - source:\n        principals:\n        {{- if .Values.serviceAccounts.create }}\n        - cluster.local/ns/{{ .Release.Namespace }}/sa/{{ .Values.frontend.name }}\n        - cluster.local/ns/{{ .Release.Namespace }}/sa/{{ .Values.checkoutService.name }}\n        {{- else }}\n        - cluster.local/ns/{{ .Release.Namespace }}/sa/default\n        {{- end }}\n    to:\n    - operation:\n        paths:\n        - /hipstershop.ShippingService/GetQuote\n        - /hipstershop.ShippingService/ShipOrder\n        methods:\n        - POST\n        ports:\n        - \"50051\"\n{{- end }}\n{{- end }}\n"
  },
  {
    "path": "helm-chart/values.yaml",
    "content": "# Copyright 2024 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      https://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# Default values for onlineboutique.\n# This is a YAML-formatted file.\n# Declare variables to be passed into your templates.\n\nimages:\n  repository: us-central1-docker.pkg.dev/google-samples/microservices-demo\n  # Overrides the image tag whose default is the chart appVersion.\n  tag: \"\"\n\nserviceAccounts:\n  # Specifies whether service accounts should be created.\n  create: true\n  # Annotations to add to the service accounts.\n  annotations: {}\n  # 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.\n  annotationsOnlyForCartservice: false\n\nnetworkPolicies:\n  # Specifies if the NetworkPolicies are created or not. If true, one fine granular NetworkPolicy per app is created.\n  create: false\n\nsidecars:\n  # Specifies if the Sidecars are created or not. If true, one fine granular Sidecar per app is created.\n  create: false\n\nauthorizationPolicies:\n  # Specifies if the AuthorizationPolicies are created or not. If true, one fine granular AuthorizationPolicy per app is created.\n  create: false\n\nopentelemetryCollector:\n  create: false\n  name: opentelemetrycollector\n  # 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.\n  projectId: \"PROJECT_ID\"\n\ngoogleCloudOperations:\n  profiler: false\n  tracing: false\n  metrics: false\n\nseccompProfile:\n  enable: false\n  type: RuntimeDefault\n\nsecurityContext:\n  enable: true\n\nadService:\n  create: true\n  name: adservice\n  resources:\n    requests:\n      cpu: 200m\n      memory: 180Mi\n    limits:\n      cpu: 300m\n      memory: 300Mi\n\ncartService:\n  create: true\n  name: cartservice\n  resources:\n    requests:\n      cpu: 200m\n      memory: 128Mi\n    limits:\n      cpu: 300m\n      memory: 256Mi\n\ncheckoutService:\n  create: true\n  name: checkoutservice\n  resources:\n    requests:\n      cpu: 100m\n      memory: 64Mi\n    limits:\n      cpu: 200m\n      memory: 128Mi\n\ncurrencyService:\n  create: true\n  name: currencyservice\n  resources:\n    requests:\n      cpu: 100m\n      memory: 128Mi\n    limits:\n      cpu: 200m\n      memory: 256Mi\n\nemailService:\n  create: true\n  name: emailservice\n  resources:\n    requests:\n      cpu: 100m\n      memory: 64Mi\n    limits:\n      cpu: 200m\n      memory: 128Mi\n\nfrontend:\n  create: true\n  name: frontend\n  externalService: true\n  cymbalBranding: false\n  # One of: local, gcp, aws, azure, onprem, alibaba. When not set, defaults to \"local\" unless running in GKE, otherwise auto-sets to gcp.\n  platform: local\n  singleSharedSession: false\n  virtualService:\n    create: false\n    hosts:\n    - \"*\"\n    gateway:\n      name: asm-ingressgateway\n      namespace: asm-ingress\n      labelKey: asm\n      labelValue: ingressgateway\n  resources:\n    requests:\n      cpu: 100m\n      memory: 64Mi\n    limits:\n      cpu: 200m\n      memory: 128Mi\n\nloadGenerator:\n  create: true\n  name: loadgenerator\n  checkFrontendInitContainer: true\n  resources:\n    requests:\n      cpu: 300m\n      memory: 256Mi\n    limits:\n      cpu: 500m\n      memory: 512Mi\n\npaymentService:\n  create: true\n  name: paymentservice\n  resources:\n    requests:\n      cpu: 100m\n      memory: 128Mi\n    limits:\n      cpu: 200m\n      memory: 256Mi\n\nproductCatalogService:\n  create: true\n  name: productcatalogservice\n  # Specifies an extra latency to any request on productcatalogservice, by default no extra latency.\n  extraLatency: \"\"\n  resources:\n    requests:\n      cpu: 100m\n      memory: 64Mi\n    limits:\n      cpu: 200m\n      memory: 128Mi\n\nrecommendationService:\n  create: true\n  name: recommendationservice\n  resources:\n    requests:\n      cpu: 100m\n      memory: 220Mi\n    limits:\n      cpu: 200m\n      memory: 450Mi\n\nshippingService:\n  create: true\n  name: shippingservice\n  resources:\n    requests:\n      cpu: 100m\n      memory: 64Mi\n    limits:\n      cpu: 200m\n      memory: 128Mi\n\ncartDatabase:\n  # Specifies the type of the cartservice's database, could be either redis or spanner.\n  type: redis\n  connectionString: \"redis-cart:6379\"\n  inClusterRedis:\n    create: true\n    name: redis-cart\n    # Uses the public redis image from Docker Hub, otherwise will use the images.repository.\n    publicRepository: true\n  externalRedisTlsOrigination:\n    enable: false\n    name: exernal-redis-tls-origination\n    endpointAddress: \"\"\n    endpointPort: \"\"\n    certificate: \"\"\n\n# @TODO: This service is not currently available in Helm.\n# https://github.com/GoogleCloudPlatform/microservices-demo/tree/main/kustomize/components/shopping-assistant\nshoppingAssistantService:\n  create: false\n  name: shoppingassistantservice\n"
  },
  {
    "path": "istio-manifests/allow-egress-googleapis.yaml",
    "content": "# Copyright 2018 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\napiVersion: networking.istio.io/v1alpha3\nkind: ServiceEntry\nmetadata:\n  name: allow-egress-googleapis\nspec:\n  hosts:\n  - \"accounts.google.com\" # Used to get token\n  - \"*.googleapis.com\"\n  ports:\n  - number: 80\n    protocol: HTTP\n    name: http\n  - number: 443\n    protocol: HTTPS\n    name: https\n---\napiVersion: networking.istio.io/v1alpha3\nkind: ServiceEntry\nmetadata:\n  name: allow-egress-google-metadata\nspec:\n  hosts:\n  - metadata.google.internal\n  addresses:\n  - 169.254.169.254 # GCE metadata server\n  ports:\n  - number: 80\n    name: http\n    protocol: HTTP\n  - number: 443\n    name: https\n    protocol: HTTPS\n"
  },
  {
    "path": "istio-manifests/frontend-gateway.yaml",
    "content": "# Copyright 2018 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\napiVersion: networking.istio.io/v1alpha3\nkind: Gateway\nmetadata:\n  name: frontend-gateway\nspec:\n  selector:\n    istio: ingressgateway # use Istio default gateway implementation\n  servers:\n  - port:\n      number: 80\n      name: http\n      protocol: HTTP\n    hosts:\n    - \"*\"\n---\napiVersion: networking.istio.io/v1alpha3\nkind: VirtualService\nmetadata:\n  name: frontend-ingress\nspec:\n  hosts:\n  - \"*\"\n  gateways:\n  - frontend-gateway\n  http:\n  - route:\n    - destination:\n        host: frontend\n        port:\n          number: 80\n"
  },
  {
    "path": "istio-manifests/frontend.yaml",
    "content": "# Copyright 2018 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\napiVersion: networking.istio.io/v1alpha3\nkind: VirtualService\nmetadata:\n  name: frontend\nspec:\n  hosts:\n  - \"frontend.default.svc.cluster.local\"\n  http:\n  - route:\n    - destination:\n        host: frontend\n        port:\n          number: 80\n"
  },
  {
    "path": "kubernetes-manifests/README.md",
    "content": "# ./kubernetes-manifests\n\n:warning: Kubernetes manifests provided in this directory are not directly\ndeployable to a cluster. They are meant to be used with `skaffold` command to\ninsert the correct `image:` tags.\n\nUse the manifests in [/release](/release) directory which are configured with\npre-built public images.\n"
  },
  {
    "path": "kubernetes-manifests/adservice.yaml",
    "content": "# Copyright 2018 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: adservice\n  labels:\n    app: adservice\nspec:\n  selector:\n    matchLabels:\n      app: adservice\n  template:\n    metadata:\n      labels:\n        app: adservice\n    spec:\n      serviceAccountName: adservice\n      terminationGracePeriodSeconds: 5\n      securityContext:\n        fsGroup: 1000\n        runAsGroup: 1000\n        runAsNonRoot: true\n        runAsUser: 1000\n      containers:\n      - name: server\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n              - ALL\n          privileged: false\n          readOnlyRootFilesystem: true\n        image: adservice\n        ports:\n        - containerPort: 9555\n        env:\n        - name: PORT\n          value: \"9555\"\n        resources:\n          requests:\n            cpu: 200m\n            memory: 180Mi\n          limits:\n            cpu: 300m\n            memory: 300Mi\n        readinessProbe:\n          initialDelaySeconds: 20\n          periodSeconds: 15\n          grpc:\n            port: 9555\n        livenessProbe:\n          initialDelaySeconds: 20\n          periodSeconds: 15\n          grpc:\n            port: 9555\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: adservice\n  labels:\n    app: adservice\nspec:\n  type: ClusterIP\n  selector:\n    app: adservice\n  ports:\n  - name: grpc\n    port: 9555\n    targetPort: 9555\n---\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: adservice\n"
  },
  {
    "path": "kubernetes-manifests/cartservice.yaml",
    "content": "# Copyright 2018 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: cartservice\n  labels:\n    app: cartservice\nspec:\n  selector:\n    matchLabels:\n      app: cartservice\n  template:\n    metadata:\n      labels:\n        app: cartservice\n    spec:\n      serviceAccountName: cartservice\n      terminationGracePeriodSeconds: 5\n      securityContext:\n        fsGroup: 1000\n        runAsGroup: 1000\n        runAsNonRoot: true\n        runAsUser: 1000\n      containers:\n      - name: server\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n              - ALL\n          privileged: false\n          readOnlyRootFilesystem: true\n        image: cartservice\n        ports:\n        - containerPort: 7070\n        env:\n        - name: REDIS_ADDR\n          value: \"redis-cart:6379\"\n        resources:\n          requests:\n            cpu: 200m\n            memory: 64Mi\n          limits:\n            cpu: 300m\n            memory: 128Mi\n        readinessProbe:\n          initialDelaySeconds: 15\n          grpc:\n            port: 7070\n        livenessProbe:\n          initialDelaySeconds: 15\n          periodSeconds: 10\n          grpc:\n            port: 7070\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: cartservice\n  labels:\n    app: cartservice\nspec:\n  type: ClusterIP\n  selector:\n    app: cartservice\n  ports:\n  - name: grpc\n    port: 7070\n    targetPort: 7070\n---\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: cartservice\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: redis-cart\n  labels:\n    app: redis-cart\nspec:\n  selector:\n    matchLabels:\n      app: redis-cart\n  template:\n    metadata:\n      labels:\n        app: redis-cart\n    spec:\n      securityContext:\n        fsGroup: 1000\n        runAsGroup: 1000\n        runAsNonRoot: true\n        runAsUser: 1000\n      containers:\n      - name: redis\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n              - ALL\n          privileged: false\n          readOnlyRootFilesystem: true\n        image: redis:alpine\n        ports:\n        - containerPort: 6379\n        readinessProbe:\n          periodSeconds: 5\n          tcpSocket:\n            port: 6379\n        livenessProbe:\n          periodSeconds: 5\n          tcpSocket:\n            port: 6379\n        volumeMounts:\n        - mountPath: /data\n          name: redis-data\n        resources:\n          limits:\n            memory: 256Mi\n            cpu: 125m\n          requests:\n            cpu: 70m\n            memory: 200Mi\n      volumes:\n      - name: redis-data\n        emptyDir: {}\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: redis-cart\n  labels:\n    app: redis-cart\nspec:\n  type: ClusterIP\n  selector:\n    app: redis-cart\n  ports:\n  - name: tcp-redis\n    port: 6379\n    targetPort: 6379\n"
  },
  {
    "path": "kubernetes-manifests/checkoutservice.yaml",
    "content": "# Copyright 2018 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: checkoutservice\n  labels:\n    app: checkoutservice\nspec:\n  selector:\n    matchLabels:\n      app: checkoutservice\n  template:\n    metadata:\n      labels:\n        app: checkoutservice\n    spec:\n      serviceAccountName: checkoutservice\n      securityContext:\n        fsGroup: 1000\n        runAsGroup: 1000\n        runAsNonRoot: true\n        runAsUser: 1000\n      containers:\n        - name: server\n          securityContext:\n            allowPrivilegeEscalation: false\n            capabilities:\n              drop:\n                - ALL\n            privileged: false\n            readOnlyRootFilesystem: true\n          image: checkoutservice\n          ports:\n          - containerPort: 5050\n          readinessProbe:\n            grpc:\n              port: 5050\n          livenessProbe:\n            grpc:\n              port: 5050\n          env:\n          - name: PORT\n            value: \"5050\"\n          - name: PRODUCT_CATALOG_SERVICE_ADDR\n            value: \"productcatalogservice:3550\"\n          - name: SHIPPING_SERVICE_ADDR\n            value: \"shippingservice:50051\"\n          - name: PAYMENT_SERVICE_ADDR\n            value: \"paymentservice:50051\"\n          - name: EMAIL_SERVICE_ADDR\n            value: \"emailservice:5000\"\n          - name: CURRENCY_SERVICE_ADDR\n            value: \"currencyservice:7000\"\n          - name: CART_SERVICE_ADDR\n            value: \"cartservice:7070\"\n          resources:\n            requests:\n              cpu: 100m\n              memory: 64Mi\n            limits:\n              cpu: 200m\n              memory: 128Mi\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: checkoutservice\n  labels:\n    app: checkoutservice\nspec:\n  type: ClusterIP\n  selector:\n    app: checkoutservice\n  ports:\n  - name: grpc\n    port: 5050\n    targetPort: 5050\n---\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: checkoutservice\n"
  },
  {
    "path": "kubernetes-manifests/currencyservice.yaml",
    "content": "# Copyright 2018 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: currencyservice\n  labels:\n    app: currencyservice\nspec:\n  selector:\n    matchLabels:\n      app: currencyservice\n  template:\n    metadata:\n      labels:\n        app: currencyservice\n    spec:\n      serviceAccountName: currencyservice\n      terminationGracePeriodSeconds: 5\n      securityContext:\n        fsGroup: 1000\n        runAsGroup: 1000\n        runAsNonRoot: true\n        runAsUser: 1000\n      containers:\n      - name: server\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n              - ALL\n          privileged: false\n          readOnlyRootFilesystem: true\n        image: currencyservice\n        ports:\n        - name: grpc\n          containerPort: 7000\n        env:\n        - name: PORT\n          value: \"7000\"\n        - name: DISABLE_PROFILER\n          value: \"1\"\n        readinessProbe:\n          grpc:\n            port: 7000\n        livenessProbe:\n          grpc:\n            port: 7000\n        resources:\n          requests:\n            cpu: 100m\n            memory: 64Mi\n          limits:\n            cpu: 200m\n            memory: 128Mi\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: currencyservice\n  labels:\n    app: currencyservice\nspec:\n  type: ClusterIP\n  selector:\n    app: currencyservice\n  ports:\n  - name: grpc\n    port: 7000\n    targetPort: 7000\n---\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: currencyservice\n"
  },
  {
    "path": "kubernetes-manifests/emailservice.yaml",
    "content": "# Copyright 2018 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: emailservice\n  labels:\n    app: emailservice\nspec:\n  selector:\n    matchLabels:\n      app: emailservice\n  template:\n    metadata:\n      labels:\n        app: emailservice\n    spec:\n      serviceAccountName: emailservice\n      terminationGracePeriodSeconds: 5\n      securityContext:\n        fsGroup: 1000\n        runAsGroup: 1000\n        runAsNonRoot: true\n        runAsUser: 1000\n      containers:\n      - name: server\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n              - ALL\n          privileged: false\n          readOnlyRootFilesystem: true\n        image: emailservice\n        ports:\n        - containerPort: 8080\n        env:\n        - name: PORT\n          value: \"8080\"\n        - name: DISABLE_PROFILER\n          value: \"1\"\n        readinessProbe:\n          periodSeconds: 5\n          grpc:\n            port: 8080\n        livenessProbe:\n          periodSeconds: 5\n          grpc:\n            port: 8080\n        resources:\n          requests:\n            cpu: 100m\n            memory: 64Mi\n          limits:\n            cpu: 200m\n            memory: 128Mi\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: emailservice\n  labels:\n    app: emailservice\nspec:\n  type: ClusterIP\n  selector:\n    app: emailservice\n  ports:\n  - name: grpc\n    port: 5000\n    targetPort: 8080\n---\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: emailservice\n"
  },
  {
    "path": "kubernetes-manifests/frontend.yaml",
    "content": "# Copyright 2018 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: frontend\n  labels:\n    app: frontend\nspec:\n  selector:\n    matchLabels:\n      app: frontend\n  template:\n    metadata:\n      labels:\n        app: frontend\n      annotations:\n        sidecar.istio.io/rewriteAppHTTPProbers: \"true\"\n    spec:\n      serviceAccountName: frontend\n      securityContext:\n        fsGroup: 1000\n        runAsGroup: 1000\n        runAsNonRoot: true\n        runAsUser: 1000\n      containers:\n        - name: server\n          securityContext:\n            allowPrivilegeEscalation: false\n            capabilities:\n              drop:\n                - ALL\n            privileged: false\n            readOnlyRootFilesystem: true\n          image: frontend\n          ports:\n          - containerPort: 8080\n          readinessProbe:\n            initialDelaySeconds: 10\n            httpGet:\n              path: \"/_healthz\"\n              port: 8080\n              httpHeaders:\n              - name: \"Cookie\"\n                value: \"shop_session-id=x-readiness-probe\"\n          livenessProbe:\n            initialDelaySeconds: 10\n            httpGet:\n              path: \"/_healthz\"\n              port: 8080\n              httpHeaders:\n              - name: \"Cookie\"\n                value: \"shop_session-id=x-liveness-probe\"\n          env:\n          - name: PORT\n            value: \"8080\"\n          - name: PRODUCT_CATALOG_SERVICE_ADDR\n            value: \"productcatalogservice:3550\"\n          - name: CURRENCY_SERVICE_ADDR\n            value: \"currencyservice:7000\"\n          - name: CART_SERVICE_ADDR\n            value: \"cartservice:7070\"\n          - name: RECOMMENDATION_SERVICE_ADDR\n            value: \"recommendationservice:8080\"\n          - name: SHIPPING_SERVICE_ADDR\n            value: \"shippingservice:50051\"\n          - name: CHECKOUT_SERVICE_ADDR\n            value: \"checkoutservice:5050\"\n          - name: AD_SERVICE_ADDR\n            value: \"adservice:9555\"\n          - name: SHOPPING_ASSISTANT_SERVICE_ADDR\n            value: \"shoppingassistantservice:80\"\n          # # ENV_PLATFORM: One of: local, gcp, aws, azure, onprem, alibaba\n          # # When not set, defaults to \"local\" unless running in GKE, otherwies auto-sets to gcp\n          # - name: ENV_PLATFORM\n          #   value: \"aws\"\n          - name: ENABLE_PROFILER\n            value: \"0\"\n          # - name: CYMBAL_BRANDING\n          #   value: \"true\"\n          # - name: ENABLE_ASSISTANT\n          #   value: \"true\"\n          # - name: FRONTEND_MESSAGE\n          #   value: \"Replace this with a message you want to display on all pages.\"\n          # As part of an optional Google Cloud demo, you can run an optional microservice called the \"packaging service\".\n          # - name: PACKAGING_SERVICE_URL\n          #   value: \"\" # This value would look like \"http://123.123.123\"\n          resources:\n            requests:\n              cpu: 100m\n              memory: 64Mi\n            limits:\n              cpu: 200m\n              memory: 128Mi\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: frontend\n  labels:\n    app: frontend\nspec:\n  type: ClusterIP\n  selector:\n    app: frontend\n  ports:\n  - name: http\n    port: 80\n    targetPort: 8080\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: frontend-external\n  labels:\n    app: frontend\nspec:\n  type: LoadBalancer\n  selector:\n    app: frontend\n  ports:\n  - name: http\n    port: 80\n    targetPort: 8080\n---\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: frontend\n"
  },
  {
    "path": "kubernetes-manifests/kustomization.yaml",
    "content": "# Copyright 2022 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\napiVersion: kustomize.config.k8s.io/v1beta1\nkind: Kustomization\nresources:\n - adservice.yaml\n - cartservice.yaml\n - checkoutservice.yaml\n - currencyservice.yaml\n - emailservice.yaml\n - frontend.yaml\n# - loadgenerator.yaml # During development, the loadgenerator module inside skaffold.yaml will be used.\n - paymentservice.yaml\n - productcatalogservice.yaml\n - recommendationservice.yaml\n - shippingservice.yaml\n# components:\n# - ../kustomize/components/cymbal-branding\n# - ../kustomize/components/google-cloud-operations\n# - ../kustomize/components/memorystore\n# - ../kustomize/components/network-policies\n# - ../kustomize/components/alloydb\n# - ../kustomize/components/shopping-assistant\n# - ../kustomize/components/spanner\n# - ../kustomize/components/container-images-tag\n# - ../kustomize/components/container-images-tag-suffix\n# - ../kustomize/components/container-images-registry\n"
  },
  {
    "path": "kubernetes-manifests/loadgenerator.yaml",
    "content": "# Copyright 2018 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: loadgenerator\n  labels:\n    app: loadgenerator\nspec:\n  selector:\n    matchLabels:\n      app: loadgenerator\n  replicas: 1\n  template:\n    metadata:\n      labels:\n        app: loadgenerator\n      annotations:\n        sidecar.istio.io/rewriteAppHTTPProbers: \"true\"\n    spec:\n      serviceAccountName: loadgenerator\n      terminationGracePeriodSeconds: 5\n      restartPolicy: Always\n      securityContext:\n        fsGroup: 1000\n        runAsGroup: 1000\n        runAsNonRoot: true\n        runAsUser: 1000\n      initContainers:\n      - command:\n        - /bin/sh\n        - -exc\n        - |\n          MAX_RETRIES=12\n          RETRY_INTERVAL=10\n          for i in $(seq 1 $MAX_RETRIES); do\n            echo \"Attempt $i: Pinging frontend: ${FRONTEND_ADDR}...\"\n            STATUSCODE=$(wget --server-response http://${FRONTEND_ADDR} 2>&1 | awk '/^  HTTP/{print $2}')\n            if [ $STATUSCODE -eq 200 ]; then\n                echo \"Frontend is reachable.\"\n                exit 0\n            fi\n            echo \"Error: Could not reach frontend - Status code: ${STATUSCODE}\"\n            sleep $RETRY_INTERVAL\n          done\n          echo \"Failed to reach frontend after $MAX_RETRIES attempts.\"\n          exit 1\n        name: frontend-check\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n              - ALL\n          privileged: false\n          readOnlyRootFilesystem: true\n        image: busybox:latest\n        env:\n        - name: FRONTEND_ADDR\n          value: \"frontend:80\"\n      containers:\n      - name: main\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n              - ALL\n          privileged: false\n          readOnlyRootFilesystem: true\n        image: loadgenerator\n        env:\n        - name: FRONTEND_ADDR\n          value: \"frontend:80\"\n        - name: USERS\n          value: \"10\"\n        - name: RATE\n          value: \"1\"\n        resources:\n          requests:\n            cpu: 300m\n            memory: 256Mi\n          limits:\n            cpu: 500m\n            memory: 512Mi\n---\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: loadgenerator\n"
  },
  {
    "path": "kubernetes-manifests/paymentservice.yaml",
    "content": "# Copyright 2018 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: paymentservice\n  labels:\n    app: paymentservice\nspec:\n  selector:\n    matchLabels:\n      app: paymentservice\n  template:\n    metadata:\n      labels:\n        app: paymentservice\n    spec:\n      serviceAccountName: paymentservice\n      terminationGracePeriodSeconds: 5\n      securityContext:\n        fsGroup: 1000\n        runAsGroup: 1000\n        runAsNonRoot: true\n        runAsUser: 1000\n      containers:\n      - name: server\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n              - ALL\n          privileged: false\n          readOnlyRootFilesystem: true\n        image: paymentservice\n        ports:\n        - containerPort: 50051\n        env:\n        - name: PORT\n          value: \"50051\"\n        - name: DISABLE_PROFILER\n          value: \"1\"\n        readinessProbe:\n          grpc:\n            port: 50051\n        livenessProbe:\n          grpc:\n            port: 50051\n        resources:\n          requests:\n            cpu: 100m\n            memory: 64Mi\n          limits:\n            cpu: 200m\n            memory: 128Mi\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: paymentservice\n  labels:\n    app: paymentservice\nspec:\n  type: ClusterIP\n  selector:\n    app: paymentservice\n  ports:\n  - name: grpc\n    port: 50051\n    targetPort: 50051\n---\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: paymentservice\n"
  },
  {
    "path": "kubernetes-manifests/productcatalogservice.yaml",
    "content": "# Copyright 2018 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: productcatalogservice\n  labels:\n    app: productcatalogservice\nspec:\n  selector:\n    matchLabels:\n      app: productcatalogservice\n  template:\n    metadata:\n      labels:\n        app: productcatalogservice\n    spec:\n      serviceAccountName: productcatalogservice\n      terminationGracePeriodSeconds: 5\n      securityContext:\n        fsGroup: 1000\n        runAsGroup: 1000\n        runAsNonRoot: true\n        runAsUser: 1000\n      containers:\n      - name: server\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n              - ALL\n          privileged: false\n          readOnlyRootFilesystem: true\n        image: productcatalogservice\n        ports:\n        - containerPort: 3550\n        env:\n        - name: PORT\n          value: \"3550\"\n        - name: DISABLE_PROFILER\n          value: \"1\"\n        readinessProbe:\n          grpc:\n            port: 3550\n        livenessProbe:\n          grpc:\n            port: 3550\n        resources:\n          requests:\n            cpu: 100m\n            memory: 64Mi\n          limits:\n            cpu: 200m\n            memory: 128Mi\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: productcatalogservice\n  labels:\n    app: productcatalogservice\nspec:\n  type: ClusterIP\n  selector:\n    app: productcatalogservice\n  ports:\n  - name: grpc\n    port: 3550\n    targetPort: 3550\n---\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: productcatalogservice\n"
  },
  {
    "path": "kubernetes-manifests/recommendationservice.yaml",
    "content": "# Copyright 2018 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: recommendationservice\n  labels:\n    app: recommendationservice\nspec:\n  selector:\n    matchLabels:\n      app: recommendationservice\n  template:\n    metadata:\n      labels:\n        app: recommendationservice\n    spec:\n      serviceAccountName: recommendationservice\n      terminationGracePeriodSeconds: 5\n      securityContext:\n        fsGroup: 1000\n        runAsGroup: 1000\n        runAsNonRoot: true\n        runAsUser: 1000\n      containers:\n      - name: server\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n              - ALL\n          privileged: false\n          readOnlyRootFilesystem: true\n        image: recommendationservice\n        ports:\n        - containerPort: 8080\n        readinessProbe:\n          periodSeconds: 5\n          grpc:\n            port: 8080\n        livenessProbe:\n          periodSeconds: 5\n          grpc:\n            port: 8080\n        env:\n        - name: PORT\n          value: \"8080\"\n        - name: PRODUCT_CATALOG_SERVICE_ADDR\n          value: \"productcatalogservice:3550\"\n        - name: DISABLE_PROFILER\n          value: \"1\"\n        resources:\n          requests:\n            cpu: 100m\n            memory: 220Mi\n          limits:\n            cpu: 200m\n            memory: 450Mi\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: recommendationservice\n  labels:\n    app: recommendationservice\nspec:\n  type: ClusterIP\n  selector:\n    app: recommendationservice\n  ports:\n  - name: grpc\n    port: 8080\n    targetPort: 8080\n---\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: recommendationservice\n"
  },
  {
    "path": "kubernetes-manifests/shippingservice.yaml",
    "content": "# Copyright 2018 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: shippingservice\n  labels:\n    app: shippingservice\nspec:\n  selector:\n    matchLabels:\n      app: shippingservice\n  template:\n    metadata:\n      labels:\n        app: shippingservice\n    spec:\n      serviceAccountName: shippingservice\n      securityContext:\n        fsGroup: 1000\n        runAsGroup: 1000\n        runAsNonRoot: true\n        runAsUser: 1000\n      containers:\n      - name: server\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n              - ALL\n          privileged: false\n          readOnlyRootFilesystem: true\n        image: shippingservice\n        ports:\n        - containerPort: 50051\n        env:\n        - name: PORT\n          value: \"50051\"\n        - name: DISABLE_PROFILER\n          value: \"1\"\n        readinessProbe:\n          periodSeconds: 5\n          grpc:\n            port: 50051\n        livenessProbe:\n          grpc:\n            port: 50051\n        resources:\n          requests:\n            cpu: 100m\n            memory: 64Mi\n          limits:\n            cpu: 200m\n            memory: 128Mi\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: shippingservice\n  labels:\n    app: shippingservice\nspec:\n  type: ClusterIP\n  selector:\n    app: shippingservice\n  ports:\n  - name: grpc\n    port: 50051\n    targetPort: 50051\n---\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: shippingservice\n"
  },
  {
    "path": "kustomize/README.md",
    "content": "# Use Online Boutique with Kustomize\n\nThis 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.\n\n## What is Kustomize?\n\nKustomize 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/).\n\n## Prerequisites\n\nOptionally, [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).\n\nYou 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`](/).\n\n## Deploy Online Boutique with Kustomize\n\n1. From the root folder of this repository, navigate to the `kustomize/` directory.\n\n    ```bash\n    cd kustomize/\n    ```\n\n1. See what the default Kustomize configuration defined by `kustomize/kustomization.yaml` will generate (without actually deploying them yet).\n\n    ```bash\n    kubectl kustomize .\n    ```\n\n1. Apply the default Kustomize configuration (`kustomize/kustomization.yaml`).\n\n    ```bash\n    kubectl apply -k .\n    ```\n\n1. Wait for all Pods to show `STATUS` of `Running`.\n\n    ```bash\n    kubectl get pods\n    ```\n\n    The output should be similar to the following:\n\n    ```terminal\n    NAME                                     READY   STATUS    RESTARTS   AGE\n    adservice-76bdd69666-ckc5j               1/1     Running   0          2m58s\n    cartservice-66d497c6b7-dp5jr             1/1     Running   0          2m59s\n    checkoutservice-666c784bd6-4jd22         1/1     Running   0          3m1s\n    currencyservice-5d5d496984-4jmd7         1/1     Running   0          2m59s\n    emailservice-667457d9d6-75jcq            1/1     Running   0          3m2s\n    frontend-6b8d69b9fb-wjqdg                1/1     Running   0          3m1s\n    loadgenerator-665b5cd444-gwqdq           1/1     Running   0          3m\n    paymentservice-68596d6dd6-bf6bv          1/1     Running   0          3m\n    productcatalogservice-557d474574-888kr   1/1     Running   0          3m\n    recommendationservice-69c56b74d4-7z8r5   1/1     Running   0          3m1s\n    shippingservice-6ccc89f8fd-v686r         1/1     Running   0          2m58s\n    ```\n\n    _Note: It may take 2-3 minutes before the changes are reflected on the deployment._\n\n1. Access the web frontend in a browser using the frontend's `EXTERNAL_IP`.\n\n    ```bash\n    kubectl get service frontend-external | awk '{print $4}'\n    ```\n\n    Note: you may see `<pending>` while GCP provisions the load balancer. If this happens, wait a few minutes and re-run the command.\n\n## Deploy Online Boutique variations with Kustomize\n\nHere is the list of the variations available as Kustomize components that you could leverage:\n\n- [**Change to the Cymbal Shops Branding**](components/cymbal-branding)\n  - 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.\n- [**Integrate with Google Cloud Operations**](components/google-cloud-operations)\n  - 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.\n- [**Integrate with Memorystore (Redis)**](components/memorystore)\n  - 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`.\n- [**Integrate with Spanner**](components/spanner)\n  - 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`.\n- [**Integrate with AlloyDB**](components/alloydb)\n  - 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.\nThese changes directly affect `cartservice`.\n- [**Secure with Network Policies**](components/network-policies)\n  - Deploy fine granular `NetworkPolicies` for Online Boutique.\n- [**Update the registry name of the container images**](components/container-images-registry)\n- [**Update the image tag of the container images**](components/container-images-tag)\n- [**Add an image tag suffix to the container images**](components/container-images-tag-suffix)\n- [**Do not expose the `frontend` publicly**](components/non-public-frontend)\n- [**Set the `frontend` to manage only one single shared session**](components/single-shared-session)\n- [**Configure `Istio` service mesh resources**](components/service-mesh-istio)\n\n### Select variations\n\nTo 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.\n\n#### Use `kustomize edit` to select variations\n\nHere is an example with the [**Cymbal Shops Branding**](components/cymbal-branding) variation, from the `kustomize/` folder, run the command below:\n\n```bash\nkustomize edit add component components/cymbal-branding\n```\n\nYou could now combine it with other variations, like for example with the [**Google Cloud Operations**](components/google-cloud-operations) variation:\n\n```bash\nkustomize edit add component components/google-cloud-operations\n```\n\n### Deploy selected variations\n\nLike explained earlier, you can locally render these manifests by running `kubectl kustomize .` as well as deploying them by running `kubectl apply -k .`.\n\nSo for example, the associated `kustomization.yaml` could look like:\n\n```yaml\napiVersion: kustomize.config.k8s.io/v1beta1\nkind: Kustomization\nresources:\n- base\ncomponents:\n- components/cymbal-branding\n- components/google-cloud-operations\n```\n\n### Use remote Kustomize targets\n\nKustomize allows you to reference public remote resources so the `kustomization.yaml` could look like:\n\n```yaml\napiVersion: kustomize.config.k8s.io/v1beta1\nkind: Kustomization\nresources:\n- github.com/GoogleCloudPlatform/microservices-demo/kustomize/base\ncomponents:\n- github.com/GoogleCloudPlatform/microservices-demo/kustomize/components/cymbal-branding\n- github.com/GoogleCloudPlatform/microservices-demo/kustomize/components/google-cloud-operations\n```\n\nLearn more about [Kustomize remote targets](https://github.com/kubernetes-sigs/kustomize/blob/master/examples/remoteBuild.md).\n"
  },
  {
    "path": "kustomize/base/adservice.yaml",
    "content": "# Copyright 2018 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: adservice\n  labels:\n    app: adservice\nspec:\n  selector:\n    matchLabels:\n      app: adservice\n  template:\n    metadata:\n      labels:\n        app: adservice\n    spec:\n      serviceAccountName: adservice\n      terminationGracePeriodSeconds: 5\n      securityContext:\n        fsGroup: 1000\n        runAsGroup: 1000\n        runAsNonRoot: true\n        runAsUser: 1000\n      containers:\n      - name: server\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n              - ALL\n          privileged: false\n          readOnlyRootFilesystem: true\n        image: us-central1-docker.pkg.dev/google-samples/microservices-demo/adservice:v0.10.5\n        ports:\n        - containerPort: 9555\n        env:\n        - name: PORT\n          value: \"9555\"\n        resources:\n          requests:\n            cpu: 200m\n            memory: 180Mi\n          limits:\n            cpu: 300m\n            memory: 300Mi\n        readinessProbe:\n          initialDelaySeconds: 20\n          periodSeconds: 15\n          grpc:\n            port: 9555\n        livenessProbe:\n          initialDelaySeconds: 20\n          periodSeconds: 15\n          grpc:\n            port: 9555\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: adservice\n  labels:\n    app: adservice\nspec:\n  type: ClusterIP\n  selector:\n    app: adservice\n  ports:\n  - name: grpc\n    port: 9555\n    targetPort: 9555\n---\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: adservice\n"
  },
  {
    "path": "kustomize/base/cartservice.yaml",
    "content": "# Copyright 2018 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: cartservice\n  labels:\n    app: cartservice\nspec:\n  selector:\n    matchLabels:\n      app: cartservice\n  template:\n    metadata:\n      labels:\n        app: cartservice\n    spec:\n      serviceAccountName: cartservice\n      terminationGracePeriodSeconds: 5\n      securityContext:\n        fsGroup: 1000\n        runAsGroup: 1000\n        runAsNonRoot: true\n        runAsUser: 1000\n      containers:\n      - name: server\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n              - ALL\n          privileged: false\n          readOnlyRootFilesystem: true\n        image: us-central1-docker.pkg.dev/google-samples/microservices-demo/cartservice:v0.10.5\n        ports:\n        - containerPort: 7070\n        env:\n        - name: REDIS_ADDR\n          value: \"redis-cart:6379\"\n        resources:\n          requests:\n            cpu: 200m\n            memory: 64Mi\n          limits:\n            cpu: 300m\n            memory: 128Mi\n        readinessProbe:\n          initialDelaySeconds: 15\n          grpc:\n            port: 7070\n        livenessProbe:\n          initialDelaySeconds: 15\n          periodSeconds: 10\n          grpc:\n            port: 7070\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: cartservice\n  labels:\n    app: cartservice\nspec:\n  type: ClusterIP\n  selector:\n    app: cartservice\n  ports:\n  - name: grpc\n    port: 7070\n    targetPort: 7070\n---\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: cartservice\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: redis-cart\n  labels:\n    app: redis-cart\nspec:\n  selector:\n    matchLabels:\n      app: redis-cart\n  template:\n    metadata:\n      labels:\n        app: redis-cart\n    spec:\n      securityContext:\n        fsGroup: 1000\n        runAsGroup: 1000\n        runAsNonRoot: true\n        runAsUser: 1000\n      containers:\n      - name: redis\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n              - ALL\n          privileged: false\n          readOnlyRootFilesystem: true\n        image: redis:alpine\n        ports:\n        - containerPort: 6379\n        readinessProbe:\n          periodSeconds: 5\n          tcpSocket:\n            port: 6379\n        livenessProbe:\n          periodSeconds: 5\n          tcpSocket:\n            port: 6379\n        volumeMounts:\n        - mountPath: /data\n          name: redis-data\n        resources:\n          limits:\n            memory: 256Mi\n            cpu: 125m\n          requests:\n            cpu: 70m\n            memory: 200Mi\n      volumes:\n      - name: redis-data\n        emptyDir: {}\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: redis-cart\n  labels:\n    app: redis-cart\nspec:\n  type: ClusterIP\n  selector:\n    app: redis-cart\n  ports:\n  - name: tcp-redis\n    port: 6379\n    targetPort: 6379\n"
  },
  {
    "path": "kustomize/base/checkoutservice.yaml",
    "content": "# Copyright 2018 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: checkoutservice\n  labels:\n    app: checkoutservice\nspec:\n  selector:\n    matchLabels:\n      app: checkoutservice\n  template:\n    metadata:\n      labels:\n        app: checkoutservice\n    spec:\n      serviceAccountName: checkoutservice\n      securityContext:\n        fsGroup: 1000\n        runAsGroup: 1000\n        runAsNonRoot: true\n        runAsUser: 1000\n      containers:\n        - name: server\n          securityContext:\n            allowPrivilegeEscalation: false\n            capabilities:\n              drop:\n                - ALL\n            privileged: false\n            readOnlyRootFilesystem: true\n          image: us-central1-docker.pkg.dev/google-samples/microservices-demo/checkoutservice:v0.10.5\n          ports:\n          - containerPort: 5050\n          readinessProbe:\n            grpc:\n              port: 5050\n          livenessProbe:\n            grpc:\n              port: 5050\n          env:\n          - name: PORT\n            value: \"5050\"\n          - name: PRODUCT_CATALOG_SERVICE_ADDR\n            value: \"productcatalogservice:3550\"\n          - name: SHIPPING_SERVICE_ADDR\n            value: \"shippingservice:50051\"\n          - name: PAYMENT_SERVICE_ADDR\n            value: \"paymentservice:50051\"\n          - name: EMAIL_SERVICE_ADDR\n            value: \"emailservice:5000\"\n          - name: CURRENCY_SERVICE_ADDR\n            value: \"currencyservice:7000\"\n          - name: CART_SERVICE_ADDR\n            value: \"cartservice:7070\"\n          resources:\n            requests:\n              cpu: 100m\n              memory: 64Mi\n            limits:\n              cpu: 200m\n              memory: 128Mi\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: checkoutservice\n  labels:\n    app: checkoutservice\nspec:\n  type: ClusterIP\n  selector:\n    app: checkoutservice\n  ports:\n  - name: grpc\n    port: 5050\n    targetPort: 5050\n---\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: checkoutservice\n"
  },
  {
    "path": "kustomize/base/currencyservice.yaml",
    "content": "# Copyright 2018 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: currencyservice\n  labels:\n    app: currencyservice\nspec:\n  selector:\n    matchLabels:\n      app: currencyservice\n  template:\n    metadata:\n      labels:\n        app: currencyservice\n    spec:\n      serviceAccountName: currencyservice\n      terminationGracePeriodSeconds: 5\n      securityContext:\n        fsGroup: 1000\n        runAsGroup: 1000\n        runAsNonRoot: true\n        runAsUser: 1000\n      containers:\n      - name: server\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n              - ALL\n          privileged: false\n          readOnlyRootFilesystem: true\n        image: us-central1-docker.pkg.dev/google-samples/microservices-demo/currencyservice:v0.10.5\n        ports:\n        - name: grpc\n          containerPort: 7000\n        env:\n        - name: PORT\n          value: \"7000\"\n        - name: DISABLE_PROFILER\n          value: \"1\"\n        readinessProbe:\n          grpc:\n            port: 7000\n        livenessProbe:\n          grpc:\n            port: 7000\n        resources:\n          requests:\n            cpu: 100m\n            memory: 64Mi\n          limits:\n            cpu: 200m\n            memory: 128Mi\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: currencyservice\n  labels:\n    app: currencyservice\nspec:\n  type: ClusterIP\n  selector:\n    app: currencyservice\n  ports:\n  - name: grpc\n    port: 7000\n    targetPort: 7000\n---\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: currencyservice\n"
  },
  {
    "path": "kustomize/base/emailservice.yaml",
    "content": "# Copyright 2018 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: emailservice\n  labels:\n    app: emailservice\nspec:\n  selector:\n    matchLabels:\n      app: emailservice\n  template:\n    metadata:\n      labels:\n        app: emailservice\n    spec:\n      serviceAccountName: emailservice\n      terminationGracePeriodSeconds: 5\n      securityContext:\n        fsGroup: 1000\n        runAsGroup: 1000\n        runAsNonRoot: true\n        runAsUser: 1000\n      containers:\n      - name: server\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n              - ALL\n          privileged: false\n          readOnlyRootFilesystem: true\n        image: us-central1-docker.pkg.dev/google-samples/microservices-demo/emailservice:v0.10.5\n        ports:\n        - containerPort: 8080\n        env:\n        - name: PORT\n          value: \"8080\"\n        - name: DISABLE_PROFILER\n          value: \"1\"\n        readinessProbe:\n          periodSeconds: 5\n          grpc:\n            port: 8080\n        livenessProbe:\n          periodSeconds: 5\n          grpc:\n            port: 8080\n        resources:\n          requests:\n            cpu: 100m\n            memory: 64Mi\n          limits:\n            cpu: 200m\n            memory: 128Mi\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: emailservice\n  labels:\n    app: emailservice\nspec:\n  type: ClusterIP\n  selector:\n    app: emailservice\n  ports:\n  - name: grpc\n    port: 5000\n    targetPort: 8080\n---\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: emailservice\n"
  },
  {
    "path": "kustomize/base/frontend.yaml",
    "content": "# Copyright 2018 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: frontend\n  labels:\n    app: frontend\nspec:\n  selector:\n    matchLabels:\n      app: frontend\n  template:\n    metadata:\n      labels:\n        app: frontend\n      annotations:\n        sidecar.istio.io/rewriteAppHTTPProbers: \"true\"\n    spec:\n      serviceAccountName: frontend\n      securityContext:\n        fsGroup: 1000\n        runAsGroup: 1000\n        runAsNonRoot: true\n        runAsUser: 1000\n      containers:\n        - name: server\n          securityContext:\n            allowPrivilegeEscalation: false\n            capabilities:\n              drop:\n                - ALL\n            privileged: false\n            readOnlyRootFilesystem: true\n          image: us-central1-docker.pkg.dev/google-samples/microservices-demo/frontend:v0.10.5\n          ports:\n          - containerPort: 8080\n          readinessProbe:\n            initialDelaySeconds: 10\n            httpGet:\n              path: \"/_healthz\"\n              port: 8080\n              httpHeaders:\n              - name: \"Cookie\"\n                value: \"shop_session-id=x-readiness-probe\"\n          livenessProbe:\n            initialDelaySeconds: 10\n            httpGet:\n              path: \"/_healthz\"\n              port: 8080\n              httpHeaders:\n              - name: \"Cookie\"\n                value: \"shop_session-id=x-liveness-probe\"\n          env:\n          - name: PORT\n            value: \"8080\"\n          - name: PRODUCT_CATALOG_SERVICE_ADDR\n            value: \"productcatalogservice:3550\"\n          - name: CURRENCY_SERVICE_ADDR\n            value: \"currencyservice:7000\"\n          - name: CART_SERVICE_ADDR\n            value: \"cartservice:7070\"\n          - name: RECOMMENDATION_SERVICE_ADDR\n            value: \"recommendationservice:8080\"\n          - name: SHIPPING_SERVICE_ADDR\n            value: \"shippingservice:50051\"\n          - name: CHECKOUT_SERVICE_ADDR\n            value: \"checkoutservice:5050\"\n          - name: AD_SERVICE_ADDR\n            value: \"adservice:9555\"\n          - name: SHOPPING_ASSISTANT_SERVICE_ADDR\n            value: \"shoppingassistantservice:80\"\n          # # ENV_PLATFORM: One of: local, gcp, aws, azure, onprem, alibaba\n          # # When not set, defaults to \"local\" unless running in GKE, otherwies auto-sets to gcp\n          # - name: ENV_PLATFORM\n          #   value: \"aws\"\n          - name: ENABLE_PROFILER\n            value: \"0\"\n          # - name: CYMBAL_BRANDING\n          #   value: \"true\"\n          # - name: ENABLE_ASSISTANT\n          #   value: \"true\"\n          # - name: FRONTEND_MESSAGE\n          #   value: \"Replace this with a message you want to display on all pages.\"\n          # As part of an optional Google Cloud demo, you can run an optional microservice called the \"packaging service\".\n          # - name: PACKAGING_SERVICE_URL\n          #   value: \"\" # This value would look like \"http://123.123.123\"\n          resources:\n            requests:\n              cpu: 100m\n              memory: 64Mi\n            limits:\n              cpu: 200m\n              memory: 128Mi\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: frontend\n  labels:\n    app: frontend\nspec:\n  type: ClusterIP\n  selector:\n    app: frontend\n  ports:\n  - name: http\n    port: 80\n    targetPort: 8080\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: frontend-external\n  labels:\n    app: frontend\nspec:\n  type: LoadBalancer\n  selector:\n    app: frontend\n  ports:\n  - name: http\n    port: 80\n    targetPort: 8080\n---\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: frontend\n"
  },
  {
    "path": "kustomize/base/kustomization.yaml",
    "content": "# Copyright 2022 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\napiVersion: kustomize.config.k8s.io/v1beta1\nkind: Kustomization\nresources:\n- adservice.yaml\n- cartservice.yaml\n- checkoutservice.yaml\n- currencyservice.yaml\n- emailservice.yaml\n- frontend.yaml\n- loadgenerator.yaml\n- paymentservice.yaml\n- productcatalogservice.yaml\n- recommendationservice.yaml\n- shippingservice.yaml\n"
  },
  {
    "path": "kustomize/base/loadgenerator.yaml",
    "content": "# Copyright 2018 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: loadgenerator\n  labels:\n    app: loadgenerator\nspec:\n  selector:\n    matchLabels:\n      app: loadgenerator\n  replicas: 1\n  template:\n    metadata:\n      labels:\n        app: loadgenerator\n      annotations:\n        sidecar.istio.io/rewriteAppHTTPProbers: \"true\"\n    spec:\n      serviceAccountName: loadgenerator\n      terminationGracePeriodSeconds: 5\n      restartPolicy: Always\n      securityContext:\n        fsGroup: 1000\n        runAsGroup: 1000\n        runAsNonRoot: true\n        runAsUser: 1000\n      initContainers:\n      - command:\n        - /bin/sh\n        - -exc\n        - |\n          MAX_RETRIES=12\n          RETRY_INTERVAL=10\n          for i in $(seq 1 $MAX_RETRIES); do\n            echo \"Attempt $i: Pinging frontend: ${FRONTEND_ADDR}...\"\n            STATUSCODE=$(wget --server-response http://${FRONTEND_ADDR} 2>&1 | awk '/^  HTTP/{print $2}')\n            if [ $STATUSCODE -eq 200 ]; then\n                echo \"Frontend is reachable.\"\n                exit 0\n            fi\n            echo \"Error: Could not reach frontend - Status code: ${STATUSCODE}\"\n            sleep $RETRY_INTERVAL\n          done\n          echo \"Failed to reach frontend after $MAX_RETRIES attempts.\"\n          exit 1\n        name: frontend-check\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n              - ALL\n          privileged: false\n          readOnlyRootFilesystem: true\n        image: busybox:latest\n        env:\n        - name: FRONTEND_ADDR\n          value: \"frontend:80\"\n      containers:\n      - name: main\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n              - ALL\n          privileged: false\n          readOnlyRootFilesystem: true\n        image: us-central1-docker.pkg.dev/google-samples/microservices-demo/loadgenerator:v0.10.5\n        env:\n        - name: FRONTEND_ADDR\n          value: \"frontend:80\"\n        - name: USERS\n          value: \"10\"\n        - name: RATE\n          value: \"1\"\n        resources:\n          requests:\n            cpu: 300m\n            memory: 256Mi\n          limits:\n            cpu: 500m\n            memory: 512Mi\n---\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: loadgenerator\n"
  },
  {
    "path": "kustomize/base/paymentservice.yaml",
    "content": "# Copyright 2018 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: paymentservice\n  labels:\n    app: paymentservice\nspec:\n  selector:\n    matchLabels:\n      app: paymentservice\n  template:\n    metadata:\n      labels:\n        app: paymentservice\n    spec:\n      serviceAccountName: paymentservice\n      terminationGracePeriodSeconds: 5\n      securityContext:\n        fsGroup: 1000\n        runAsGroup: 1000\n        runAsNonRoot: true\n        runAsUser: 1000\n      containers:\n      - name: server\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n              - ALL\n          privileged: false\n          readOnlyRootFilesystem: true\n        image: us-central1-docker.pkg.dev/google-samples/microservices-demo/paymentservice:v0.10.5\n        ports:\n        - containerPort: 50051\n        env:\n        - name: PORT\n          value: \"50051\"\n        - name: DISABLE_PROFILER\n          value: \"1\"\n        readinessProbe:\n          grpc:\n            port: 50051\n        livenessProbe:\n          grpc:\n            port: 50051\n        resources:\n          requests:\n            cpu: 100m\n            memory: 64Mi\n          limits:\n            cpu: 200m\n            memory: 128Mi\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: paymentservice\n  labels:\n    app: paymentservice\nspec:\n  type: ClusterIP\n  selector:\n    app: paymentservice\n  ports:\n  - name: grpc\n    port: 50051\n    targetPort: 50051\n---\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: paymentservice\n"
  },
  {
    "path": "kustomize/base/productcatalogservice.yaml",
    "content": "# Copyright 2018 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: productcatalogservice\n  labels:\n    app: productcatalogservice\nspec:\n  selector:\n    matchLabels:\n      app: productcatalogservice\n  template:\n    metadata:\n      labels:\n        app: productcatalogservice\n    spec:\n      serviceAccountName: productcatalogservice\n      terminationGracePeriodSeconds: 5\n      securityContext:\n        fsGroup: 1000\n        runAsGroup: 1000\n        runAsNonRoot: true\n        runAsUser: 1000\n      containers:\n      - name: server\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n              - ALL\n          privileged: false\n          readOnlyRootFilesystem: true\n        image: us-central1-docker.pkg.dev/google-samples/microservices-demo/productcatalogservice:v0.10.5\n        ports:\n        - containerPort: 3550\n        env:\n        - name: PORT\n          value: \"3550\"\n        - name: DISABLE_PROFILER\n          value: \"1\"\n        readinessProbe:\n          grpc:\n            port: 3550\n        livenessProbe:\n          grpc:\n            port: 3550\n        resources:\n          requests:\n            cpu: 100m\n            memory: 64Mi\n          limits:\n            cpu: 200m\n            memory: 128Mi\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: productcatalogservice\n  labels:\n    app: productcatalogservice\nspec:\n  type: ClusterIP\n  selector:\n    app: productcatalogservice\n  ports:\n  - name: grpc\n    port: 3550\n    targetPort: 3550\n---\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: productcatalogservice\n"
  },
  {
    "path": "kustomize/base/recommendationservice.yaml",
    "content": "# Copyright 2018 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: recommendationservice\n  labels:\n    app: recommendationservice\nspec:\n  selector:\n    matchLabels:\n      app: recommendationservice\n  template:\n    metadata:\n      labels:\n        app: recommendationservice\n    spec:\n      serviceAccountName: recommendationservice\n      terminationGracePeriodSeconds: 5\n      securityContext:\n        fsGroup: 1000\n        runAsGroup: 1000\n        runAsNonRoot: true\n        runAsUser: 1000\n      containers:\n      - name: server\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n              - ALL\n          privileged: false\n          readOnlyRootFilesystem: true\n        image: us-central1-docker.pkg.dev/google-samples/microservices-demo/recommendationservice:v0.10.5\n        ports:\n        - containerPort: 8080\n        readinessProbe:\n          periodSeconds: 5\n          grpc:\n            port: 8080\n        livenessProbe:\n          periodSeconds: 5\n          grpc:\n            port: 8080\n        env:\n        - name: PORT\n          value: \"8080\"\n        - name: PRODUCT_CATALOG_SERVICE_ADDR\n          value: \"productcatalogservice:3550\"\n        - name: DISABLE_PROFILER\n          value: \"1\"\n        resources:\n          requests:\n            cpu: 100m\n            memory: 220Mi\n          limits:\n            cpu: 200m\n            memory: 450Mi\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: recommendationservice\n  labels:\n    app: recommendationservice\nspec:\n  type: ClusterIP\n  selector:\n    app: recommendationservice\n  ports:\n  - name: grpc\n    port: 8080\n    targetPort: 8080\n---\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: recommendationservice\n"
  },
  {
    "path": "kustomize/base/shippingservice.yaml",
    "content": "# Copyright 2018 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: shippingservice\n  labels:\n    app: shippingservice\nspec:\n  selector:\n    matchLabels:\n      app: shippingservice\n  template:\n    metadata:\n      labels:\n        app: shippingservice\n    spec:\n      serviceAccountName: shippingservice\n      securityContext:\n        fsGroup: 1000\n        runAsGroup: 1000\n        runAsNonRoot: true\n        runAsUser: 1000\n      containers:\n      - name: server\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n              - ALL\n          privileged: false\n          readOnlyRootFilesystem: true\n        image: us-central1-docker.pkg.dev/google-samples/microservices-demo/shippingservice:v0.10.5\n        ports:\n        - containerPort: 50051\n        env:\n        - name: PORT\n          value: \"50051\"\n        - name: DISABLE_PROFILER\n          value: \"1\"\n        readinessProbe:\n          periodSeconds: 5\n          grpc:\n            port: 50051\n        livenessProbe:\n          grpc:\n            port: 50051\n        resources:\n          requests:\n            cpu: 100m\n            memory: 64Mi\n          limits:\n            cpu: 200m\n            memory: 128Mi\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: shippingservice\n  labels:\n    app: shippingservice\nspec:\n  type: ClusterIP\n  selector:\n    app: shippingservice\n  ports:\n  - name: grpc\n    port: 50051\n    targetPort: 50051\n---\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: shippingservice\n"
  },
  {
    "path": "kustomize/components/alloydb/README.md",
    "content": "# Integrate Online Boutique with AlloyDB\n\nBy default the `cartservice` stores its data in an in-cluster Redis database. \nUsing a fully managed database service outside your GKE cluster (such as [AlloyDB](https://cloud.google.com/alloydb)) could bring more resiliency and more security.\n\nNote that because of AlloyDB's current connectivity, you'll need to run all this from a VM with\nVPC access to the network you want to use for everything (out of the box this should just use the\ndefault network). The Cloud Shell doesn't work because of transitive VPC peering not working.\n\n## Provision an AlloyDB database and the supporting infrastructure\n\nEnvironmental 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.\n\n```bash\n# PROJECT_ID should be set to the project ID that was created to hold the demo\nPROJECT_ID=<project_id>\n\n#Pick a region near you that also has AlloyDB available. See available regions: https://cloud.google.com/alloydb/docs/locations\nREGION=<region>\nUSE_GKE_GCLOUD_AUTH_PLUGIN=True\nALLOYDB_NETWORK=default\nALLOYDB_SERVICE_NAME=onlineboutique-network-range\nALLOYDB_CLUSTER_NAME=onlineboutique-cluster\nALLOYDB_INSTANCE_NAME=onlineboutique-instance\n\n# **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\nALLOYDB_PRIMARY_IP=<ip set below after instance created>\nALLOYDB_READ_IP=<ip set below after instance created>\n\nALLOYDB_DATABASE_NAME=carts\nALLOYDB_TABLE_NAME=cart_items\nALLOYDB_USER_GSA_NAME=alloydb-user-sa\nALLOYDB_USER_GSA_ID=${ALLOYDB_USER_GSA_NAME}@${PROJECT_ID}.iam.gserviceaccount.com\nCARTSERVICE_KSA_NAME=cartservice\nALLOYDB_SECRET_NAME=alloydb-secret\n\n# PGPASSWORD needs to be set in order to run the psql from the CLI easily. The value for this\n# needs to be set behind the Secret mentioned above\nPGPASSWORD=<password>\n```\n\nTo provision an AlloyDB instance you can follow the following instructions:\n```bash\ngcloud services enable alloydb.googleapis.com\ngcloud services enable servicenetworking.googleapis.com\ngcloud services enable secretmanager.googleapis.com\n\n# Set our DB credentials behind the secret. Replace <password> with whatever you want\n# to use as the credentials for the database. Don't use $ in the password.\necho <password> | gcloud secrets create ${ALLOYDB_SECRET_NAME} --data-file=-\n\n# Setting up needed service connection\ngcloud compute addresses create ${ALLOYDB_SERVICE_NAME} \\\n    --global \\\n    --purpose=VPC_PEERING \\\n    --prefix-length=16 \\\n    --description=\"Online Boutique Private Services\" \\\n    --network=${ALLOYDB_NETWORK}\n\ngcloud services vpc-peerings connect \\\n    --service=servicenetworking.googleapis.com \\\n    --ranges=${ALLOYDB_SERVICE_NAME} \\\n    --network=${ALLOYDB_NETWORK}\n\ngcloud alloydb clusters create ${ALLOYDB_CLUSTER_NAME} \\\n    --region=${REGION} \\\n    --password=${PGPASSWORD} \\\n    --disable-automated-backup \\\n    --network=${ALLOYDB_NETWORK}\n\ngcloud alloydb instances create ${ALLOYDB_INSTANCE_NAME} \\\n    --cluster=${ALLOYDB_CLUSTER_NAME} \\\n    --region=${REGION} \\\n    --cpu-count=4 \\\n    --instance-type=PRIMARY\n    \ngcloud alloydb instances create ${ALLOYDB_INSTANCE_NAME}-replica \\\n    --cluster=${ALLOYDB_CLUSTER_NAME} \\\n    --region=${REGION} \\\n    --cpu-count=4 \\\n    --instance-type=READ_POOL \\\n    --read-pool-node-count=2\n\n# Need to grab and store the IP addresses for our primary and read replicas\n# Don't forget to set these two values in the environment for later use.\nALLOYDB_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\"\nALLOYDB_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\"\n\npsql -h ${ALLOYDB_PRIMARY_IP} -U postgres -c \"CREATE DATABASE ${ALLOYDB_DATABASE_NAME}\"\npsql -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))\"\npsql -h ${ALLOYDB_PRIMARY_IP} -U postgres -d ${ALLOYDB_DATABASE_NAME} -c \"CREATE INDEX cartItemsByUserId ON ${ALLOYDB_TABLE_NAME}(userId)\"\n```\n\n_Note: It can take more than 20 minutes for the AlloyDB instances to be created._\n\n## Grant the `cartservice`'s service account access to the AlloyDB database\n\n**Important note:** Your GKE cluster should have [Workload Identity enabled](https://cloud.google.com/kubernetes-engine/docs/how-to/workload-identity#enable).\n\nAs 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.:\n```bash\ngcloud iam service-accounts create ${ALLOYDB_USER_GSA_NAME} \\\n    --display-name=${ALLOYDB_USER_GSA_NAME}\n\ngcloud projects add-iam-policy-binding ${PROJECT_ID} --member=serviceAccount:${ALLOYDB_USER_GSA_ID} --role=roles/alloydb.client\ngcloud projects add-iam-policy-binding ${PROJECT_ID} --member=serviceAccount:${ALLOYDB_USER_GSA_ID} --role=roles/secretmanager.secretAccessor\n\ngcloud iam service-accounts add-iam-policy-binding ${ALLOYDB_USER_GSA_ID} \\\n    --member \"serviceAccount:${PROJECT_ID}.svc.id.goog[default/${CARTSERVICE_KSA_NAME}]\" \\\n    --role roles/iam.workloadIdentityUser\n```\n\n## Deploy Online Boutique connected to an AlloyDB database\n\nTo automate the deployment of Online Boutique integrated with AlloyDB you can leverage the following variation with [Kustomize](../..).\n\nFrom the `kustomize/` folder at the root level of this repository, execute these commands:\n```bash\nkustomize edit add component components/alloydb\n```\n_**Note:** this Kustomize component will also remove the `redis-cart` `Deployment` and `Service` not used anymore._\n\nThis will update the `kustomize/kustomization.yaml` file which could be similar to:\n```yaml\napiVersion: kustomize.config.k8s.io/v1beta1\nkind: Kustomization\nresources:\n- base\ncomponents:\n- components/alloydb\n```\n\nUpdate current Kustomize manifest to target this AlloyDB database.\n```bash\nsed -i \"s/PROJECT_ID_VAL/${PROJECT_ID}/g\" components/alloydb/kustomization.yaml\nsed -i \"s/ALLOYDB_PRIMARY_IP_VAL/${ALLOYDB_PRIMARY_IP}/g\" components/alloydb/kustomization.yaml\nsed -i \"s/ALLOYDB_USER_GSA_ID/${ALLOYDB_USER_GSA_ID}/g\" components/alloydb/kustomization.yaml\nsed -i \"s/ALLOYDB_DATABASE_NAME_VAL/${ALLOYDB_DATABASE_NAME}/g\" components/alloydb/kustomization.yaml\nsed -i \"s/ALLOYDB_TABLE_NAME_VAL/${ALLOYDB_TABLE_NAME}/g\" components/alloydb/kustomization.yaml\nsed -i \"s/ALLOYDB_SECRET_NAME_VAL/${ALLOYDB_SECRET_NAME}/g\" components/alloydb/kustomization.yaml\n```\n\nYou can locally render these manifests by running `kubectl kustomize .` as well as deploying them by running `kubectl apply -k .`.\n\n## Extra cleanup steps\n```bash\ngcloud compute addresses delete ${ALLOYDB_SERVICE_NAME} --global\n\n# Force takes care of cleaning up the instances inside the cluster automatically\ngcloud alloydb clusters delete ${ALLOYDB_CLUSTER_NAME} --force --region ${REGION}\n\ngcloud iam service-accounts delete ${ALLOYDB_USER_GSA_ID}\n\ngcloud secrets delete ${ALLOYDB_SECRET_NAME}\n```\n"
  },
  {
    "path": "kustomize/components/alloydb/kustomization.yaml",
    "content": "# Copyright 2022 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\napiVersion: kustomize.config.k8s.io/v1alpha1\nkind: Component\npatches:\n# cartservice - replace REDIS_ADDR by ALLOYDB_PRIMARY_IP for the cartservice Deployment\n# Potentially later we'll factor in splitting traffic to primary/read pool, but for now\n# we'll just manage the primary instance\n- patch: |-\n    apiVersion: apps/v1\n    kind: Deployment\n    metadata:\n      name: cartservice\n    spec:\n      template:\n        spec:\n          containers:\n            - name: server\n              env:\n              - name: REDIS_ADDR\n                $patch: delete\n              - name: ALLOYDB_PRIMARY_IP\n                value: ALLOYDB_PRIMARY_IP_VAL\n              - name: ALLOYDB_DATABASE_NAME\n                value: ALLOYDB_CARTS_DATABASE_NAME_VAL\n              - name: ALLOYDB_TABLE_NAME\n                value: ALLOYDB_CARTS_TABLE_NAME_VAL\n              - name: ALLOYDB_SECRET_NAME\n                value: ALLOYDB_SECRET_NAME_VAL\n              - name: PROJECT_ID\n                value: PROJECT_ID_VAL\n# cartservice - add the GSA annotation for the cartservice KSA\n- patch: |-\n    apiVersion: v1\n    kind: ServiceAccount\n    metadata:\n      name: cartservice\n      annotations:\n        iam.gke.io/gcp-service-account: ALLOYDB_USER_GSA_ID\n# productcatalogservice - replace ALLOYDB environments\n- patch: |-\n    apiVersion: apps/v1\n    kind: Deployment\n    metadata:\n      name: productcatalogservice\n    spec:\n      template:\n        spec:\n          containers:\n            - name: server\n              env:\n              - name: ALLOYDB_CLUSTER_NAME\n                value: ALLOYDB_CLUSTER_NAME_VAL\n              - name: ALLOYDB_INSTANCE_NAME\n                value: ALLOYDB_INSTANCE_NAME_VAL\n              - name: ALLOYDB_DATABASE_NAME\n                value: ALLOYDB_PRODUCTS_DATABASE_NAME_VAL\n              - name: ALLOYDB_TABLE_NAME\n                value: ALLOYDB_PRODUCTS_TABLE_NAME_VAL\n              - name: ALLOYDB_SECRET_NAME\n                value: ALLOYDB_SECRET_NAME_VAL\n              - name: PROJECT_ID\n                value: PROJECT_ID_VAL\n              - name: REGION\n                value: REGION_VAL\n# productcatalogservice - add the GSA annotation for the productcatalogservice KSA\n- patch: |-\n    apiVersion: v1\n    kind: ServiceAccount\n    metadata:\n      name: productcatalogservice\n      annotations:\n        iam.gke.io/gcp-service-account: ALLOYDB_USER_GSA_ID\n# redis - remove the redis-cart Deployment\n- patch: |-\n    apiVersion: apps/v1\n    kind: Deployment\n    metadata:\n      name: redis-cart\n    $patch: delete\n# redis - remove the redis-cart Service\n- patch: |-\n    apiVersion: v1\n    kind: Service\n    metadata:\n      name: redis-cart\n    $patch: delete\n"
  },
  {
    "path": "kustomize/components/container-images-registry/README.md",
    "content": "# Update the container registry of the Online Boutique apps\n\nBy 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.\n\n## Change the default container registry via Kustomize\n\nTo automate the deployment of Online Boutique integrated with your own container registry, you can leverage the following variation with [Kustomize](../..).\n\nFrom the `kustomize/` folder at the root level of this repository, execute this command:\n\n```bash\nREGISTRY=my-registry # Example: us-central1-docker.pkg.dev/my-project/my-directory\nsed -i \"s|CONTAINER_IMAGES_REGISTRY|${REGISTRY}|g\" components/container-images-registry/kustomization.yaml\nkustomize edit add component components/container-images-registry\n```\n\n_Note: this Kustomize component will update the container registry in the `image:` field in all `Deployments`._\n\nThis will update the `kustomize/kustomization.yaml` file which could be similar to:\n\n```yaml\napiVersion: kustomize.config.k8s.io/v1beta1\nkind: Kustomization\nresources:\n- base\ncomponents:\n- components/container-images-registry\n```\n\nYou can (optionally) locally render these manifests by running `kubectl kustomize .`.\nYou can deploy them by running `kubectl apply -k .`.\n"
  },
  {
    "path": "kustomize/components/container-images-registry/kustomization.yaml",
    "content": "# Copyright 2022 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\napiVersion: kustomize.config.k8s.io/v1alpha1\nkind: Component\nimages:\n- name: us-central1-docker.pkg.dev/google-samples/microservices-demo/adservice\n  newName: CONTAINER_IMAGES_REGISTRY/adservice\n- name: us-central1-docker.pkg.dev/google-samples/microservices-demo/cartservice\n  newName: CONTAINER_IMAGES_REGISTRY/cartservice\n- name: us-central1-docker.pkg.dev/google-samples/microservices-demo/checkoutservice\n  newName: CONTAINER_IMAGES_REGISTRY/checkoutservice\n- name: us-central1-docker.pkg.dev/google-samples/microservices-demo/currencyservice\n  newName: CONTAINER_IMAGES_REGISTRY/currencyservice\n- name: us-central1-docker.pkg.dev/google-samples/microservices-demo/emailservice\n  newName: CONTAINER_IMAGES_REGISTRY/emailservice\n- name: us-central1-docker.pkg.dev/google-samples/microservices-demo/frontend\n  newName: CONTAINER_IMAGES_REGISTRY/frontend\n- name: us-central1-docker.pkg.dev/google-samples/microservices-demo/loadgenerator\n  newName: CONTAINER_IMAGES_REGISTRY/loadgenerator\n- name: us-central1-docker.pkg.dev/google-samples/microservices-demo/paymentservice\n  newName: CONTAINER_IMAGES_REGISTRY/paymentservice\n- name: us-central1-docker.pkg.dev/google-samples/microservices-demo/productcatalogservice\n  newName: CONTAINER_IMAGES_REGISTRY/productcatalogservice\n- name: us-central1-docker.pkg.dev/google-samples/microservices-demo/recommendationservice\n  newName: CONTAINER_IMAGES_REGISTRY/recommendationservice\n- name: us-central1-docker.pkg.dev/google-samples/microservices-demo/shippingservice\n  newName: CONTAINER_IMAGES_REGISTRY/shippingservice\n- name: redis\n  newName: CONTAINER_IMAGES_REGISTRY/redis\n"
  },
  {
    "path": "kustomize/components/container-images-tag/README.md",
    "content": "# Update the container image tag of the Online Boutique apps\n\nBy 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.\n\n## Change the default container image tag via Kustomize\n\nTo automate the deployment of the Online Boutique apps with a specific container imag tag, you can leverage the following variation with [Kustomize](../..).\n\nFrom the `kustomize/` folder at the root level of this repository, execute this command:\n\n```bash\nTAG=v1.0.0\nsed -i \"s/CONTAINER_IMAGES_TAG/$TAG/g\" components/container-images-tag/kustomization.yaml\nkustomize edit add component components/container-images-tag\n```\n\n_Note: this Kustomize component will update the container image tag of the `image:` field in all `Deployments`._\n\nThis will update the `kustomize/kustomization.yaml` file which could be similar to:\n\n```yaml\napiVersion: kustomize.config.k8s.io/v1beta1\nkind: Kustomization\nresources:\n- base\ncomponents:\n- components/container-images-tag\n```\n\nYou can locally render these manifests by running `kubectl kustomize .` as well as deploying them by running `kubectl apply -k .`.\n\n**Important notes:** if combining with the other variations, here are some considerations:\n\n- should be placed before `components/container-images-registry`\n\nSo for example here is the order respected:\n\n```yaml\napiVersion: kustomize.config.k8s.io/v1beta1\nkind: Kustomization\nresources:\n- base\ncomponents:\n- components/container-images-tag\n- components/container-images-registry\n```\n"
  },
  {
    "path": "kustomize/components/container-images-tag/kustomization.yaml",
    "content": "# Copyright 2022 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\napiVersion: kustomize.config.k8s.io/v1alpha1\nkind: Component\nimages:\n- name: us-central1-docker.pkg.dev/google-samples/microservices-demo/adservice\n  newTag: CONTAINER_IMAGES_TAG\n- name: us-central1-docker.pkg.dev/google-samples/microservices-demo/cartservice\n  newTag: CONTAINER_IMAGES_TAG\n- name: us-central1-docker.pkg.dev/google-samples/microservices-demo/checkoutservice\n  newTag: CONTAINER_IMAGES_TAG\n- name: us-central1-docker.pkg.dev/google-samples/microservices-demo/currencyservice\n  newTag: CONTAINER_IMAGES_TAG\n- name: us-central1-docker.pkg.dev/google-samples/microservices-demo/emailservice\n  newTag: CONTAINER_IMAGES_TAG\n- name: us-central1-docker.pkg.dev/google-samples/microservices-demo/frontend\n  newTag: CONTAINER_IMAGES_TAG\n- name: us-central1-docker.pkg.dev/google-samples/microservices-demo/loadgenerator\n  newTag: CONTAINER_IMAGES_TAG\n- name: us-central1-docker.pkg.dev/google-samples/microservices-demo/paymentservice\n  newTag: CONTAINER_IMAGES_TAG\n- name: us-central1-docker.pkg.dev/google-samples/microservices-demo/productcatalogservice\n  newTag: CONTAINER_IMAGES_TAG\n- name: us-central1-docker.pkg.dev/google-samples/microservices-demo/recommendationservice\n  newTag: CONTAINER_IMAGES_TAG\n- name: us-central1-docker.pkg.dev/google-samples/microservices-demo/shippingservice\n  newTag: CONTAINER_IMAGES_TAG\n"
  },
  {
    "path": "kustomize/components/container-images-tag-suffix/README.md",
    "content": "# Add a suffix to the image tag of the Online Boutique container images\n\nYou may want to add a suffix to the Online Boutique container image tag to target a specific version.\nThe Kustomize Component inside this folder can help.\n\n## Add a suffix to the container image tag via Kustomize\n\nTo 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](../..).\n\nFrom the `kustomize/` folder at the root level of this repository, execute this command:\n\n```bash\nSUFFIX=-my-suffix\nsed -i \"s/CONTAINER_IMAGES_TAG_SUFFIX/$SUFFIX/g\" components/container-images-tag-suffix/kustomization.yaml\nkustomize edit add component components/container-images-tag-suffix\n```\n\n_Note: this Kustomize component will add a suffix to the container image tag of the `image:` field in all `Deployments`._\n\nThis will update the `kustomize/kustomization.yaml` file which could be similar to:\n\n```yaml\napiVersion: kustomize.config.k8s.io/v1beta1\nkind: Kustomization\nresources:\n- base\ncomponents:\n- components/container-images-tag-suffix\n```\n\nYou 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`.\n\n_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._\n\n## Combine with other Kustomize Components\n\nIf you're combining this Kustomize Component with other variations, here are some considerations:\n\n- `components/container-images-tag-suffix` should be placed before `components/container-images-registry`\n- `components/container-images-tag-suffix` should be placed after `components/container-images-tag`\n\nSo for example here is the order respected:\n\n```yaml\napiVersion: kustomize.config.k8s.io/v1beta1\nkind: Kustomization\nresources:\n- base\ncomponents:\n- components/container-images-tag\n- components/container-images-tag-suffix\n- components/container-images-registry\n```\n"
  },
  {
    "path": "kustomize/components/container-images-tag-suffix/kustomization.yaml",
    "content": "# Copyright 2022 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\napiVersion: kustomize.config.k8s.io/v1alpha1\nkind: Component\nimages:\n- name: us-central1-docker.pkg.dev/google-samples/microservices-demo/adservice\n  tagSuffix: CONTAINER_IMAGES_TAG_SUFFIX\n- name: us-central1-docker.pkg.dev/google-samples/microservices-demo/cartservice\n  tagSuffix: CONTAINER_IMAGES_TAG_SUFFIX\n- name: us-central1-docker.pkg.dev/google-samples/microservices-demo/checkoutservice\n  tagSuffix: CONTAINER_IMAGES_TAG_SUFFIX\n- name: us-central1-docker.pkg.dev/google-samples/microservices-demo/currencyservice\n  tagSuffix: CONTAINER_IMAGES_TAG_SUFFIX\n- name: us-central1-docker.pkg.dev/google-samples/microservices-demo/emailservice\n  tagSuffix: CONTAINER_IMAGES_TAG_SUFFIX\n- name: us-central1-docker.pkg.dev/google-samples/microservices-demo/frontend\n  tagSuffix: CONTAINER_IMAGES_TAG_SUFFIX\n- name: us-central1-docker.pkg.dev/google-samples/microservices-demo/loadgenerator\n  tagSuffix: CONTAINER_IMAGES_TAG_SUFFIX\n- name: us-central1-docker.pkg.dev/google-samples/microservices-demo/paymentservice\n  tagSuffix: CONTAINER_IMAGES_TAG_SUFFIX\n- name: us-central1-docker.pkg.dev/google-samples/microservices-demo/productcatalogservice\n  tagSuffix: CONTAINER_IMAGES_TAG_SUFFIX\n- name: us-central1-docker.pkg.dev/google-samples/microservices-demo/recommendationservice\n  tagSuffix: CONTAINER_IMAGES_TAG_SUFFIX\n- name: us-central1-docker.pkg.dev/google-samples/microservices-demo/shippingservice\n  tagSuffix: CONTAINER_IMAGES_TAG_SUFFIX"
  },
  {
    "path": "kustomize/components/custom-base-url/README.md",
    "content": "# Customize the Base URL for Online Boutique\n\nThis 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.\n\n## What it does\n\n1. Sets the `BASE_URL` environment variable to \"/online-boutique\" for the frontend deployment.\n2. Updates the liveness probe path to \"/online-boutique/_healthz\".\n3. Updates the readiness probe path to \"/online-boutique/_healthz\".\n\n## How to use\n\nTo apply this customization, you can use Kustomize to include this component in your deployment.\n\nFrom the `kustomize/` folder at the root level of this repository, execute this command:\n\n```bash\nkustomize edit add component components/custom-base-url\n```\n\nThis will update the `kustomize/kustomization.yaml` file, which could look similar to:\n\n```yaml\napiVersion: kustomize.config.k8s.io/v1beta1\nkind: Kustomization\nresources:\n- base\ncomponents:\n- components/custom-base-url\n```\n\n## Render and Deploy\n\nYou can locally render these manifests by running:\n\n```bash\nkubectl kustomize .\n```\n\nTo deploy the customized application, run:\n\n```bash\nkubectl apply -k .\n```\n\n## Customizing the Base URL\n\nIf 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:\n\n1. The `BASE_URL` environment variable\n2. The liveness probe path\n3. The readiness probe path\n\nFor example, to change the base URL to \"/shop\", you would modify the values as follows:\n\n```yaml\nvalue: /shop\nvalue: /shop/_healthz\nvalue: /shop/_healthz\n```\n\nNote: After changing the base URL, make sure to update any internal links or references within your application to use the new base URL.\n"
  },
  {
    "path": "kustomize/components/custom-base-url/kustomization.yaml",
    "content": "# Copyright 2024 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\napiVersion: kustomize.config.k8s.io/v1alpha1\nkind: Component\npatches:\n- target:\n    kind: Deployment\n    name: frontend\n  patch: |-\n    - op: add\n      path: /spec/template/spec/containers/0/env/-\n      value:\n        name: BASE_URL\n        value: /online-boutique\n    - op: replace\n      path: /spec/template/spec/containers/0/livenessProbe/httpGet/path\n      value: /online-boutique/_healthz\n    - op: replace\n      path: /spec/template/spec/containers/0/readinessProbe/httpGet/path\n      value: /online-boutique/_healthz\n"
  },
  {
    "path": "kustomize/components/cymbal-branding/README.md",
    "content": "# Change the Online Boutique theme to the Cymbal Shops Branding\n\nBy default, when you deploy this sample app, the \"Online Boutique\" branding (logo and wording) will be used.\nBut you may want to use Google Cloud's fictitious company, _Cymbal Shops_, instead.\n\nTo use \"Cymbal Shops\" branding, set the `CYMBAL_BRANDING` environment variable to `\"true\"` in the the Kubernetes manifest (`.yaml`) for the `frontend` Deployment.\n\n```yaml\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: frontend\nspec:\n  ...\n  template:\n    ...\n    spec:\n      ...\n      containers:\n          ...\n          env:\n            ...\n          - name: CYMBAL_BRANDING\n            value: \"true\"\n```\n\n## Deploy Online Boutique with the Cymbal Shops branding via Kustomize\n\nTo automate the deployment of Online Boutique with the Cymbal Shops branding you can leverage the following variation with [Kustomize](../..).\n\nFrom the `kustomize/` folder at the root level of this repository, execute this command:\n\n```bash\nkustomize edit add component components/cymbal-branding\n```\n\nThis will update the `kustomize/kustomization.yaml` file which could be similar to:\n\n```yaml\napiVersion: kustomize.config.k8s.io/v1beta1\nkind: Kustomization\nresources:\n- base\ncomponents:\n- components/cymbal-branding\n```\n\nYou can locally render these manifests by running `kubectl kustomize .` as well as deploying them by running `kubectl apply -k .`.\n"
  },
  {
    "path": "kustomize/components/cymbal-branding/kustomization.yaml",
    "content": "# Copyright 2022 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\napiVersion: kustomize.config.k8s.io/v1alpha1\nkind: Component\npatches:\n- patch: |-\n    apiVersion: apps/v1\n    kind: Deployment\n    metadata:\n      name: frontend\n    spec:\n      template:\n        spec:\n          containers:\n          - name: server\n            env:\n            - name: CYMBAL_BRANDING\n              value: \"true\"\n"
  },
  {
    "path": "kustomize/components/google-cloud-operations/README.md",
    "content": "# Integrate Online Boutique with Google Cloud Operations\n\nBy 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).\n\nIf 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.\n\nFrom the `kustomize/` folder at the root level of this repository, execute this command:\n\n```bash\nkustomize edit add component components/google-cloud-operations\n```\n\nThis will update the `kustomize/kustomization.yaml` file which could be similar to:\n\n```yaml\napiVersion: kustomize.config.k8s.io/v1beta1\nkind: Kustomization\nresources:\n- base\ncomponents:\n- components/google-cloud-operations\n```\n\nYou can locally render these manifests by running `kubectl kustomize .` as well as deploying them by running `kubectl apply -k .`.\n\nYou will also need to make sure that you have the associated Google APIs enabled in your Google Cloud project:\n\n```bash\nPROJECT_ID=<your-gcp-project-id>\ngcloud services enable \\\n    monitoring.googleapis.com \\\n    cloudtrace.googleapis.com \\\n    cloudprofiler.googleapis.com \\\n    --project ${PROJECT_ID}\n```\n\nIn addition to that, you will need to grant the following IAM roles associated to your Google Service Account (GSA):\n\n```bash\nPROJECT_ID=<your-gcp-project-id>\nGSA_NAME=<your-gsa>\n\ngcloud projects add-iam-policy-binding ${PROJECT_ID} \\\n  --member \"serviceAccount:${GSA_NAME}@${PROJECT_ID}.iam.gserviceaccount.com\" \\\n  --role roles/cloudtrace.agent\n\ngcloud projects add-iam-policy-binding ${PROJECT_ID} \\\n  --member \"serviceAccount:${GSA_NAME}@${PROJECT_ID}.iam.gserviceaccount.com\" \\\n  --role roles/monitoring.metricWriter\n  \ngcloud projects add-iam-policy-binding ${PROJECT_ID} \\\n  --member \"serviceAccount:${GSA_NAME}@${PROJECT_ID}.iam.gserviceaccount.com\" \\\n  --role roles/cloudprofiler.agent\n```\n\n**Note**\nCurrently only trace is supported.  Support for metrics, and more is coming soon.\n\n## Changes\n\nWhen enabling this kustomize module, most services will be patched with a configuration similar to the following:\n\n```yaml\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: productcatalogservice\nspec:\n  template:\n    spec:\n      containers:\n        - name: server\n          env:\n          - name: COLLECTOR_SERVICE_ADDR\n            value: \"opentelemetrycollector:4317\"\n          - name: ENABLE_STATS\n            value: \"1\"\n          - name: ENABLE_TRACING\n            value: \"1\"\n```\n\nThis 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.\n\n## OpenTelemetry Collector\n\nCurrently, this component adds a single collector service which collects traces and metrics from individual services and forwards them to the appropriate Google Cloud backend.\n\n![Collector Architecture Diagram](collector-model.png)\n\nIf 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.\n\n## Workload Identity\n\nIf 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).\n\n* 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:\n\n```bash\ngcloud iam service-accounts list\n```\n\n* 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):\n\n```bash\ngcloud iam service-accounts add-iam-policy-binding ${GSA_EMAIL} \\\n  --role roles/iam.workloadIdentityUser \\\n  --member \"serviceAccount:${PROJECT_ID}.svc.id.goog[default/default]\"\n```\n\n* Annotate your Kubernetes service account (`default/default` for the `default` namespace) to use the Google IAM service account:\n\n```bash\nkubectl annotate serviceaccount default \\\n  iam.gke.io/gcp-service-account=${GSA_EMAIL}\n```\n\n* Finally, restart your `opentelemetrycollector` deployment to reflect the new settings:\n\n```bash\nkubectl rollout restart deployment opentelemetrycollector\n```\n\nWhen the new Pod rolls out, you should start to see traces appear in the cloud console.\n"
  },
  {
    "path": "kustomize/components/google-cloud-operations/kustomization.yaml",
    "content": "# Copyright 2022 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\napiVersion: kustomize.config.k8s.io/v1alpha1\nkind: Component\nresources:\n  - otel-collector.yaml\npatches:\n# adservice - not yet implemented\n# checkoutservice - tracing, profiler\n- patch: |-\n    apiVersion: apps/v1\n    kind: Deployment\n    metadata:\n      name: checkoutservice\n    spec:\n      template:\n        spec:\n          containers:\n            - name: server\n              env:\n              - name: COLLECTOR_SERVICE_ADDR\n                value: \"opentelemetrycollector:4317\"\n              - name: OTEL_SERVICE_NAME\n                value: \"checkoutservice\"\n              - name: ENABLE_TRACING\n                value: \"1\"\n              - name: ENABLE_PROFILER\n                value: \"1\"\n# currencyservice - tracing, profiler\n- patch: |-\n    apiVersion: apps/v1\n    kind: Deployment\n    metadata:\n      name: currencyservice\n    spec:\n      template:\n        spec:\n          containers:\n            - name: server\n              env:\n              - name: COLLECTOR_SERVICE_ADDR\n                value: \"opentelemetrycollector:4317\"\n              - name: OTEL_SERVICE_NAME\n                value: \"currencyservice\"\n              - name: ENABLE_TRACING\n                value: \"1\"\n              - name: DISABLE_PROFILER\n                $patch: delete\n# emailservice - tracing, profiler\n- patch: |-\n    apiVersion: apps/v1\n    kind: Deployment\n    metadata:\n      name: emailservice\n    spec:\n      template:\n        spec:\n          containers:\n            - name: server\n              env:\n              - name: COLLECTOR_SERVICE_ADDR\n                value: \"opentelemetrycollector:4317\"\n              - name: OTEL_SERVICE_NAME\n                value: \"emailservice\"\n              - name: ENABLE_TRACING\n                value: \"1\"\n              - name: DISABLE_PROFILER\n                $patch: delete\n# frontend - tracing, profiler\n- patch: |-\n    apiVersion: apps/v1\n    kind: Deployment\n    metadata:\n      name: frontend\n    spec:\n      template:\n        spec:\n          containers:\n            - name: server\n              env:\n              - name: ENABLE_TRACING\n                value: \"1\"\n              - name: COLLECTOR_SERVICE_ADDR\n                value: \"opentelemetrycollector:4317\"\n              - name: OTEL_SERVICE_NAME\n                value: \"frontend\"\n              - name: ENABLE_PROFILER\n                value: \"1\"\n# paymentservice - tracing, profiler\n- patch: |-\n    apiVersion: apps/v1\n    kind: Deployment\n    metadata:\n      name: paymentservice\n    spec:\n      template:\n        spec:\n          containers:\n            - name: server\n              env:\n              - name: COLLECTOR_SERVICE_ADDR\n                value: \"opentelemetrycollector:4317\"\n              - name: OTEL_SERVICE_NAME\n                value: \"paymentservice\"\n              - name: ENABLE_TRACING\n                value: \"1\"\n              - name: DISABLE_PROFILER\n                $patch: delete\n# productcatalogservice - tracing, profiler\n- patch: |-\n    apiVersion: apps/v1\n    kind: Deployment\n    metadata:\n      name: productcatalogservice\n    spec:\n      template:\n        spec:\n          containers:\n            - name: server\n              env:\n              - name: COLLECTOR_SERVICE_ADDR\n                value: \"opentelemetrycollector:4317\"\n              - name: OTEL_SERVICE_NAME\n                value: \"productcatalogservice\"\n              - name: ENABLE_TRACING\n                value: \"1\"\n              - name: DISABLE_PROFILER\n                value: \"1\"\n# recommendationservice - tracing, profiler\n- patch: |-\n    apiVersion: apps/v1\n    kind: Deployment\n    metadata:\n      name: recommendationservice\n    spec:\n      template:\n        spec:\n          containers:\n            - name: server\n              env:\n              - name: COLLECTOR_SERVICE_ADDR\n                value: \"opentelemetrycollector:4317\"\n              - name: OTEL_SERVICE_NAME\n                value: \"recommendationservice\"\n              - name: ENABLE_TRACING\n                value: \"1\"\n              - name: DISABLE_PROFILER\n                $patch: delete\n# shippingservice - stats, tracing, profiler\n- patch: |-\n    apiVersion: apps/v1\n    kind: Deployment\n    metadata:\n      name: shippingservice\n    spec:\n      template:\n        spec:\n          containers:\n            - name: server\n              env:\n              - name: DISABLE_PROFILER\n                $patch: delete\n"
  },
  {
    "path": "kustomize/components/google-cloud-operations/otel-collector.yaml",
    "content": "# Copyright 2022 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: opentelemetrycollector\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: opentelemetrycollector\n  template:\n    metadata:\n      labels:\n        app: opentelemetrycollector\n    spec:\n      securityContext:\n        fsGroup: 1000\n        runAsGroup: 1000\n        runAsNonRoot: true\n        runAsUser: 1000\n      # Init container retrieves the current cloud project id from the metadata server\n      # and inserts it into the collector config template\n      # https://cloud.google.com/compute/docs/storing-retrieving-metadata\n      initContainers:\n      - name: otel-gateway-init\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n              - ALL\n          privileged: false\n          readOnlyRootFilesystem: true\n        image: busybox:latest@sha256:b3255e7dfbcd10cb367af0d409747d511aeb66dfac98cf30e97e87e4207dd76f\n        command:\n        - '/bin/sh'\n        - '-c'\n        - |\n           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\n        volumeMounts:\n        - name: collector-gateway-config-template\n          mountPath: /template\n        - name: collector-gateway-config\n          mountPath: /conf\n      containers:\n      # This gateway container will receive traces and metrics from each microservice\n      # and forward it to GCP\n      - name: otel-gateway\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n              - ALL\n          privileged: false\n          readOnlyRootFilesystem: true\n        args:\n        - --config=/conf/collector-gateway-config.yaml\n        image: otel/opentelemetry-collector-contrib:0.147.0@sha256:e7c92c715f28ff142f3bcaccd4fc5603cf4c71276ef09954a38eb4038500a5a5\n        volumeMounts:\n        - name: collector-gateway-config\n          mountPath: /conf\n      volumes:\n      # Simple ConfigMap volume with template file\n      - name: collector-gateway-config-template\n        configMap:\n          items:\n          - key: collector-gateway-config-template.yaml\n            path: collector-gateway-config-template.yaml\n          name: collector-gateway-config-template\n      # Create a volume to store the expanded template (with correct cloud project ID)\n      - name: collector-gateway-config\n        emptyDir: {}\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: opentelemetrycollector\nspec:\n  ports:\n  - name: grpc-otlp\n    port: 4317\n    protocol: TCP\n    targetPort: 4317\n  selector:\n    app: opentelemetrycollector\n  type: ClusterIP\n---\napiVersion: v1\nkind: ConfigMap\nmetadata:\n  name: collector-gateway-config-template\n# Open Telemetry Collector config\n# https://opentelemetry.io/docs/collector/configuration/\ndata:\n  collector-gateway-config-template.yaml: |\n    receivers:\n      otlp:\n        protocols: \n          grpc:\n    processors:\n    exporters:\n      googlecloud:\n        project: {{PROJECT_ID}}\n    service:\n      pipelines:\n        traces:\n          receivers: [otlp] # Receive otlp-formatted data from other collector instances\n          processors: []\n          exporters: [googlecloud] # Export traces directly to Google Cloud\n        metrics:\n          receivers: [otlp]\n          processors: []\n          exporters: [googlecloud] # Export metrics to Google Cloud"
  },
  {
    "path": "kustomize/components/memorystore/README.md",
    "content": "# Integrate Online Boutique with Memorystore (Redis)\n\nBy 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).\n\n![Architecture diagram with Memorystore](/docs/img/memorystore.png)\n\n## Provision a Memorystore (Redis) instance\n\nImportant notes:\n\n- 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.\n- You cannot connect to a Memorystore (Redis) instance from a GKE cluster without VPC-native/IP aliasing enabled.\n\nTo provision a Memorystore (Redis) instance you can follow the following instructions:\n\n```bash\nZONE=\"<your-GCP-zone>\"\nREGION=\"<your-GCP-region>\"\n\ngcloud services enable redis.googleapis.com\n\ngcloud redis instances create redis-cart \\\n    --size=1 \\\n    --region=${REGION} \\\n    --zone=${ZONE} \\\n    --redis-version=redis_7_0\n```\n\n_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)._\n\n## Deploy Online Boutique connected to a Memorystore (Redis) instance\n\nTo automate the deployment of Online Boutique integrated with Memorystore (Redis) you can leverage the following variation with [Kustomize](../..).\n\nFrom the `kustomize/` folder at the root level of this repository, execute this command:\n\n```bash\nkustomize edit add component components/memorystore\n```\n\n_Note: this Kustomize component will also remove the `redis-cart` `Deployment` and `Service` not used anymore._\n\nThis will update the `kustomize/kustomization.yaml` file which could be similar to:\n\n```yaml\napiVersion: kustomize.config.k8s.io/v1beta1\nkind: Kustomization\nresources:\n- base\ncomponents:\n- components/memorystore\n```\n\nUpdate current Kustomize manifest to target this Memorystore (Redis) instance.\n\n```bash\nREDIS_IP=$(gcloud redis instances describe redis-cart --region=${REGION} --format='get(host)')\nREDIS_PORT=$(gcloud redis instances describe redis-cart --region=${REGION} --format='get(port)')\nsed -i \"s/REDIS_CONNECTION_STRING/${REDIS_IP}:${REDIS_PORT}/g\" components/memorystore/kustomization.yaml\n```\n\nYou can locally render these manifests by running `kubectl kustomize .` as well as deploying them by running `kubectl apply -k .`.\n\n## Resources\n\n- [Connecting to a Redis instance from a Google Kubernetes Engine cluster](https://cloud.google.com/memorystore/docs/redis/connect-redis-instance-gke)\n- [Seamlessly encrypt traffic from any apps in your Mesh to Memorystore (Redis)](https://medium.com/google-cloud/64b71969318d)\n"
  },
  {
    "path": "kustomize/components/memorystore/kustomization.yaml",
    "content": "# Copyright 2022 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\napiVersion: kustomize.config.k8s.io/v1alpha1\nkind: Component\npatches:\n# cartservice - replace REDIS_ADDR to target new Memorystore (redis) instance\n- patch: |-\n    apiVersion: apps/v1\n    kind: Deployment\n    metadata:\n      name: cartservice\n    spec:\n      template:\n        spec:\n          containers:\n            - name: server\n              env:\n              - name: REDIS_ADDR\n                value: \"REDIS_CONNECTION_STRING\"\n# redis - remove the redis-cart Deployment\n- patch: |-\n    apiVersion: apps/v1\n    kind: Deployment\n    metadata:\n      name: redis-cart\n    $patch: delete\n# redis - remove the redis-cart Service\n- patch: |-\n    apiVersion: v1\n    kind: Service\n    metadata:\n      name: redis-cart\n    $patch: delete\n"
  },
  {
    "path": "kustomize/components/network-policies/README.md",
    "content": "# Secure Online Boutique with Network Policies\n\nYou 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.\n\nTo 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).\n\nTo 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.  \n\n## Deploy Online Boutique with `NetworkPolicies` via Kustomize\n\nTo automate the deployment of Online Boutique integrated with fine granular `NetworkPolicies` (one per `Deployment`), you can leverage the following variation with [Kustomize](../..).\n\nFrom the `kustomize/` folder at the root level of this repository, execute this command:\n\n```bash\nkustomize edit add component components/network-policies\n```\n\nThis will update the `kustomize/kustomization.yaml` file which could be similar to:\n\n```yaml\napiVersion: kustomize.config.k8s.io/v1beta1\nkind: Kustomization\nresources:\n- base\ncomponents:\n- components/network-policies\n```\n\nYou can locally render these manifests by running `kubectl kustomize .` as well as deploying them by running `kubectl apply -k .`.\n\nOnce deployed, you can verify that the `NetworkPolicies` are successfully deployed:\n\n```bash\nkubectl get networkpolicy\n```\n\nThe output could be similar to:\n\n```output\nNAME                    POD-SELECTOR                AGE\nadservice               app=adservice               2m58s\ncartservice             app=cartservice             2m58s\ncheckoutservice         app=checkoutservice         2m58s\ncurrencyservice         app=currencyservice         2m58s\ndeny-all                <none>                      2m58s\nemailservice            app=emailservice            2m58s\nfrontend                app=frontend                2m58s\nloadgenerator           app=loadgenerator           2m58s\npaymentservice          app=paymentservice          2m58s\nproductcatalogservice   app=productcatalogservice   2m58s\nrecommendationservice   app=recommendationservice   2m58s\nredis-cart              app=redis-cart              2m58s\nshippingservice         app=shippingservice         2m58s\n```\n\n_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._\n\n## Related Resources\n\n- [GKE Dataplane V2 announcement](https://cloud.google.com/blog/products/containers-kubernetes/bringing-ebpf-and-cilium-to-google-kubernetes-engine)\n- [Kubernetes Network Policies](https://kubernetes.io/docs/concepts/services-networking/network-policies/)\n- [Kubernetes Network Policy Recipes](https://github.com/ahmetb/kubernetes-network-policy-recipes)\n- [Network policy logging](https://cloud.google.com/kubernetes-engine/docs/how-to/network-policy-logging)\n"
  },
  {
    "path": "kustomize/components/network-policies/kustomization.yaml",
    "content": "# Copyright 2022 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\napiVersion: kustomize.config.k8s.io/v1alpha1\nkind: Component\nresources:\n- network-policy-deny-all.yaml\n- network-policy-adservice.yaml\n- network-policy-cartservice.yaml\n- network-policy-checkoutservice.yaml\n- network-policy-currencyservice.yaml\n- network-policy-emailservice.yaml\n- network-policy-frontend.yaml\n- network-policy-loadgenerator.yaml\n- network-policy-paymentservice.yaml\n- network-policy-productcatalogservice.yaml\n- network-policy-recommendationservice.yaml\n- network-policy-redis.yaml\n- network-policy-shippingservice.yaml\n"
  },
  {
    "path": "kustomize/components/network-policies/network-policy-adservice.yaml",
    "content": "# Copyright 2022 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\napiVersion: networking.k8s.io/v1\nkind: NetworkPolicy\nmetadata:\n  name: adservice\nspec:\n  podSelector:\n    matchLabels:\n      app: adservice\n  policyTypes:\n  - Ingress\n  - Egress\n  ingress:\n  - from:\n    - podSelector:\n        matchLabels:\n          app: frontend\n    ports:\n     - port: 9555\n       protocol: TCP\n  egress:\n  - {}"
  },
  {
    "path": "kustomize/components/network-policies/network-policy-cartservice.yaml",
    "content": "# Copyright 2022 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\napiVersion: networking.k8s.io/v1\nkind: NetworkPolicy\nmetadata:\n  name: cartservice\nspec:\n  podSelector:\n    matchLabels:\n      app: cartservice\n  policyTypes:\n  - Ingress\n  - Egress\n  ingress:\n  - from:\n    - podSelector:\n        matchLabels:\n          app: frontend\n    - podSelector:\n        matchLabels:\n          app: checkoutservice\n    ports:\n     - port: 7070\n       protocol: TCP\n  egress:\n  - {}"
  },
  {
    "path": "kustomize/components/network-policies/network-policy-checkoutservice.yaml",
    "content": "# Copyright 2022 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\napiVersion: networking.k8s.io/v1\nkind: NetworkPolicy\nmetadata:\n  name: checkoutservice\nspec:\n  podSelector:\n    matchLabels:\n      app: checkoutservice\n  policyTypes:\n  - Ingress\n  - Egress\n  ingress:\n  - from:\n    - podSelector:\n        matchLabels:\n          app: frontend\n    ports:\n     - port: 5050\n       protocol: TCP\n  egress:\n  - {}"
  },
  {
    "path": "kustomize/components/network-policies/network-policy-currencyservice.yaml",
    "content": "# Copyright 2022 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\napiVersion: networking.k8s.io/v1\nkind: NetworkPolicy\nmetadata:\n  name: currencyservice\nspec:\n  podSelector:\n    matchLabels:\n      app: currencyservice\n  policyTypes:\n  - Ingress\n  - Egress\n  ingress:\n  - from:\n    - podSelector:\n        matchLabels:\n          app: frontend\n    - podSelector:\n        matchLabels:\n          app: checkoutservice\n    ports:\n     - port: 7000\n       protocol: TCP\n  egress:\n  - {}"
  },
  {
    "path": "kustomize/components/network-policies/network-policy-deny-all.yaml",
    "content": "# Copyright 2022 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\napiVersion: networking.k8s.io/v1\nkind: NetworkPolicy\nmetadata:\n  name: deny-all\nspec:\n  podSelector: {}\n  policyTypes:\n  - Ingress\n  - Egress\n"
  },
  {
    "path": "kustomize/components/network-policies/network-policy-emailservice.yaml",
    "content": "# Copyright 2022 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\napiVersion: networking.k8s.io/v1\nkind: NetworkPolicy\nmetadata:\n  name: emailservice\nspec:\n  podSelector:\n    matchLabels:\n      app: emailservice\n  policyTypes:\n  - Ingress\n  - Egress\n  ingress:\n  - from:\n    - podSelector:\n        matchLabels:\n          app: checkoutservice\n    ports:\n     - port: 8080\n       protocol: TCP\n  egress:\n  - {}"
  },
  {
    "path": "kustomize/components/network-policies/network-policy-frontend.yaml",
    "content": "# Copyright 2022 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\napiVersion: networking.k8s.io/v1\nkind: NetworkPolicy\nmetadata:\n  name: frontend\nspec:\n  podSelector:\n    matchLabels:\n      app: frontend\n  policyTypes:\n  - Ingress\n  - Egress\n  ingress:\n  - {}\n  egress:\n  - {}"
  },
  {
    "path": "kustomize/components/network-policies/network-policy-loadgenerator.yaml",
    "content": "# Copyright 2022 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\napiVersion: networking.k8s.io/v1\nkind: NetworkPolicy\nmetadata:\n  name: loadgenerator\nspec:\n  podSelector:\n    matchLabels:\n      app: loadgenerator\n  policyTypes:\n  - Egress\n  egress:\n  - {}"
  },
  {
    "path": "kustomize/components/network-policies/network-policy-paymentservice.yaml",
    "content": "# Copyright 2022 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\napiVersion: networking.k8s.io/v1\nkind: NetworkPolicy\nmetadata:\n  name: paymentservice\nspec:\n  podSelector:\n    matchLabels:\n      app: paymentservice\n  policyTypes:\n  - Ingress\n  - Egress\n  ingress:\n  - from:\n    - podSelector:\n        matchLabels:\n          app: checkoutservice\n    ports:\n     - port: 50051\n       protocol: TCP\n  egress:\n  - {}"
  },
  {
    "path": "kustomize/components/network-policies/network-policy-productcatalogservice.yaml",
    "content": "# Copyright 2022 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\napiVersion: networking.k8s.io/v1\nkind: NetworkPolicy\nmetadata:\n  name: productcatalogservice\nspec:\n  podSelector:\n    matchLabels:\n      app: productcatalogservice\n  policyTypes:\n  - Ingress\n  - Egress\n  ingress:\n  - from:\n    - podSelector:\n        matchLabels:\n          app: frontend\n    - podSelector:\n        matchLabels:\n          app: checkoutservice\n    - podSelector:\n        matchLabels:\n          app: recommendationservice\n    ports:\n     - port: 3550\n       protocol: TCP\n  egress:\n  - {}"
  },
  {
    "path": "kustomize/components/network-policies/network-policy-recommendationservice.yaml",
    "content": "# Copyright 2022 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\napiVersion: networking.k8s.io/v1\nkind: NetworkPolicy\nmetadata:\n  name: recommendationservice\nspec:\n  podSelector:\n    matchLabels:\n      app: recommendationservice\n  policyTypes:\n  - Ingress\n  - Egress\n  ingress:\n  - from:\n    - podSelector:\n        matchLabels:\n          app: frontend\n    ports:\n     - port: 8080\n       protocol: TCP\n  egress:\n  - {}"
  },
  {
    "path": "kustomize/components/network-policies/network-policy-redis.yaml",
    "content": "# Copyright 2022 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\napiVersion: networking.k8s.io/v1\nkind: NetworkPolicy\nmetadata:\n  name: redis-cart\nspec:\n  podSelector:\n    matchLabels:\n      app: redis-cart\n  policyTypes:\n  - Ingress\n  - Egress\n  ingress:\n  - from:\n    - podSelector:\n        matchLabels:\n          app: cartservice\n    ports:\n     - port: 6379\n       protocol: TCP\n  egress:\n  - {}\n"
  },
  {
    "path": "kustomize/components/network-policies/network-policy-shippingservice.yaml",
    "content": "# Copyright 2022 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\napiVersion: networking.k8s.io/v1\nkind: NetworkPolicy\nmetadata:\n  name: shippingservice\nspec:\n  podSelector:\n    matchLabels:\n      app: shippingservice\n  policyTypes:\n  - Ingress\n  - Egress\n  ingress:\n  - from:\n    - podSelector:\n        matchLabels:\n          app: frontend\n    - podSelector:\n        matchLabels:\n          app: checkoutservice\n    ports:\n     - port: 50051\n       protocol: TCP\n  egress:\n  - {}"
  },
  {
    "path": "kustomize/components/non-public-frontend/README.md",
    "content": "# Remove the public exposure of Online Boutique's frontend\n\nBy default, when you deploy Online Boutique, a `Service` (named `frontend-external`) of type `LoadBalancer` is deployed with a publicly accessible IP address.\nBut you may not want to expose this sample app publicly.\n\n## Deploy Online Boutique without the default public endpoint\n\nTo automate the deployment of Online Boutique without the default public endpoint you can leverage the following variation with [Kustomize](../..).\n\nFrom the `kustomize/` folder at the root level of this repository, execute this command:\n\n```bash\nkustomize edit add component components/non-public-frontend\n```\n\nThis will update the `kustomize/kustomization.yaml` file which could be similar to:\n\n```yaml\napiVersion: kustomize.config.k8s.io/v1beta1\nkind: Kustomization\nresources:\n- base\ncomponents:\n- components/non-public-frontend\n```\n\nYou can locally render these manifests by running `kubectl kustomize .` as well as deploying them by running `kubectl apply -k .`.\n"
  },
  {
    "path": "kustomize/components/non-public-frontend/kustomization.yaml",
    "content": "# Copyright 2022 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\napiVersion: kustomize.config.k8s.io/v1alpha1\nkind: Component\npatches:\n# frontend - delete frontend-external service\n- patch: |-\n    apiVersion: v1\n    kind: Service\n    metadata:\n      name: frontend-external\n    $patch: delete\n"
  },
  {
    "path": "kustomize/components/service-mesh-istio/README.md",
    "content": "# Service mesh with Istio\n\nYou 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.\n\n# Setup\n\nThe following CLI tools needs to be installed and in the PATH:\n\n- `gcloud`\n- `kubectl`\n- `kustomize`\n- `istioctl` (optional)\n\n1. Set-up some default environment variables.\n\n   ```sh\n   PROJECT_ID=\"<your-project-id>\"\n   REGION=\"<your-google-cloud-region\"\n   CLUSTER_NAME=\"online-boutique\"\n   gcloud config set project $PROJECT_ID\n   ```\n\n# Provision a GKE Cluster\n\n1. Create an Autopilot GKE cluster.\n\n   ```sh\n   gcloud container clusters create-auto $CLUSTER_NAME \\\n     --location=$REGION\n   ```\n\n   To make the best use of our service mesh, we need to have [GKE Workload Identity](https://cloud.google.com/kubernetes-engine/docs/how-to/workload-identity), and the [Kubernetes Gateway API resource definitions](https://cloud.google.com/kubernetes-engine/docs/how-to/deploying-gateways) enabled. Autopilot takes care of this for us.\n\n1. Change our kubectl context for the newly created cluster.\n\n   ```sh\n   gcloud container clusters get-credentials $CLUSTER_NAME \\\n     --region $REGION\n   ```\n\n# Provision and Configure Istio Service Mesh\n\n## (Option A) Provision managed Istio using Cloud Service Mesh\n\nCloud Service Mesh (CSM) provides a service mesh experience that includes a fully managed control plane and data plane. The recommended way to [install CSM](https://cloud.google.com/service-mesh/docs/onboarding/provision-control-plane) uses [fleet management](https://cloud.google.com/kubernetes-engine/fleet-management/docs/fleet-creation).\n\n1. Enable the Cloud Service Mesh and GKE Enterprise APIs.\n\n   ```sh\n   gcloud services enable mesh.googleapis.com anthos.googleapis.com\n   ```\n\n1. Enable service mesh support fleet-wide.\n\n   ```sh\n   gcloud container fleet mesh enable\n   ```\n\n1. Register the GKE cluster to the fleet.\n\n   ```sh\n   gcloud container clusters update $CLUSTER_NAME \\\n     --location $REGION \\\n     --fleet-project $PROJECT_ID\n\n1. Enable automatic management of the service mesh feature in the cluster.\n\n   ```sh\n   gcloud container fleet mesh update \\\n     --management automatic \\\n     --memberships $CLUSTER_NAME \\\n     --project $PROJECT_ID \\\n     --location $REGION\n   ```\n\n1. Add the Istio injection labels to the default namespace.\n\n   ```sh\n   kubectl label namespace default \\\n     istio.io/rev- istio-injection=enabled --overwrite\n   ```\n\n1. Verify that the service mesh is fully provisioned. It will take several minutes for both the control plane and data plane to be ready.\n\n   ```sh\n   gcloud container fleet mesh describe\n   ```\n\n   The output should be similar to:\n   ```\n   createTime: '2024-09-18T15:52:36.133664725Z'\n   fleetDefaultMemberConfig:\n     mesh:\n       management: MANAGEMENT_AUTOMATIC\n   membershipSpecs:\n     projects/12345/locations/us-central1/memberships/online-boutique:\n       mesh:\n         management: MANAGEMENT_AUTOMATIC\n       origin:\n         type: USER\n   membershipStates:\n     projects/12345/locations/us-central1/memberships/online-boutique:\n       servicemesh:\n         conditions:\n         - code: VPCSC_GA_SUPPORTED\n           details: This control plane supports VPC-SC GA.\n           documentationLink: http://cloud.google.com/service-mesh/docs/managed/vpc-sc\n           severity: INFO\n         controlPlaneManagement:\n           details:\n           - code: REVISION_READY\n             details: 'Ready: asm-managed'\n           implementation: TRAFFIC_DIRECTOR\n           state: ACTIVE\n         dataPlaneManagement:\n           details:\n           - code: OK\n             details: Service is running.\n           state: ACTIVE\n       state:\n         code: OK\n         description: 'Revision ready for use: asm-managed.'\n         updateTime: '2024-09-18T16:30:37.632583401Z'\n   name: projects/my-project/locations/global/features/servicemesh\n   resourceState:\n     state: ACTIVE\n   spec: {}\n   updateTime: '2024-09-18T16:15:05.957266437Z'\n   ```\n\n1. (Optional) If you require Certificate Authority Service, you can configure it by [following these instructions](https://cloud.google.com/service-mesh/docs/security/certificate-authority-service).\n\n## (Option B) Provision Istio using istioctl\n\n1. Alternatively you can install the open source version of Istio by following the [getting started guide](https://istio.io/latest/docs/setup/getting-started/).\n\n   ```sh\n   # Install istio 1.17 or above\n   istioctl install --set profile=minimal -y\n\n   # Enable sidecar injection for Kubernetes namespace(s) where microservices-demo is deployed\n   kubectl label namespace default istio-injection=enabled\n\n   # Make sure the istiod injection webhook port 15017 is accessible via GKE master nodes\n   # Otherwise your replicaset-controller may be blocked when trying to create new pods with: \n   #   Error creating: Internal error occurred: failed calling \n   #     webhook \"namespace.sidecar-injector.istio.io\" ... context deadline exceeded\n   gcloud compute firewall-rules list --filter=\"name~gke-[0-9a-z-]*-master\"\n   NAME                          NETWORK  DIRECTION  PRIORITY  ALLOW              DENY  DISABLED\n   gke-online-boutique-c94d71e8-master  gke-vpc  INGRESS    1000      tcp:10250,tcp:443        False\n\n   # Update firewall rule (or create a new one) to allow webhook port 15017\n   gcloud compute firewall-rules update gke-online-boutique-c94d71e8-master \\\n    --allow tcp:10250,tcp:443,tcp:15017\n   ```\n\n# Deploy Online Boutique with the Istio component\n\nOnce the service mesh and namespace injection are configured, you can then deploy the Istio manifests using Kustomize. You should also include the [service-accounts component](../service-accounts) if you plan on using AuthorizationPolicies.\n\n1. Enable the service-mesh-istio component.\n\n   ```sh\n   cd kustomize/\n   kustomize edit add component components/service-mesh-istio\n   ```\n\n   This will update the `kustomize/kustomization.yaml` file which could be similar to:\n   ```yaml\n   apiVersion: kustomize.config.k8s.io/v1beta1\n   kind: Kustomization\n   resources:\n   - base\n   components:\n   - components/service-mesh-istio\n   ```\n\n   _Note: `service-mesh-istio` component includes the same delete patch as the `non-public-frontend` component. Trying to use both those components in your kustomization.yaml file will result in an error._\n\n1. Deploy the manifests.\n\n   ```sh\n   kubectl apply -k .\n   ```\n\n   The output should be similar to:\n   ```\n   serviceaccount/adservice created\n   serviceaccount/cartservice created\n   serviceaccount/checkoutservice created\n   serviceaccount/currencyservice created\n   serviceaccount/emailservice created\n   serviceaccount/frontend created\n   serviceaccount/loadgenerator created\n   serviceaccount/paymentservice created\n   serviceaccount/productcatalogservice created\n   serviceaccount/recommendationservice created\n   serviceaccount/shippingservice created\n   service/adservice created\n   service/cartservice created\n   service/checkoutservice created\n   service/currencyservice created\n   service/emailservice created\n   service/frontend created\n   service/paymentservice created\n   service/productcatalogservice created\n   service/recommendationservice created\n   service/redis-cart created\n   service/shippingservice created\n   deployment.apps/adservice created\n   deployment.apps/cartservice created\n   deployment.apps/checkoutservice created\n   deployment.apps/currencyservice created\n   deployment.apps/emailservice created\n   deployment.apps/frontend created\n   deployment.apps/loadgenerator created\n   deployment.apps/paymentservice created\n   deployment.apps/productcatalogservice created\n   deployment.apps/recommendationservice created\n   deployment.apps/redis-cart created\n   deployment.apps/shippingservice created\n   gateway.gateway.networking.k8s.io/istio-gateway created\n   httproute.gateway.networking.k8s.io/frontend-route created\n   serviceentry.networking.istio.io/allow-egress-google-metadata created\n   serviceentry.networking.istio.io/allow-egress-googleapis created\n   virtualservice.networking.istio.io/frontend created\n   ```\n\n# Verify that the deployment succeeded\n\n1. Check that the pods and the gateway are in a healthy and ready state.\n\n   ```sh\n   kubectl get pods,gateways,services\n   ```\n\n   The output should be similar to:\n   ```\n   NAME                                         READY   STATUS    RESTARTS   AGE\n   pod/adservice-6cbd9794f9-8c4gv               2/2     Running   0          47s\n   pod/cartservice-667bbd5f6-84j8v              2/2     Running   0          47s\n   pod/checkoutservice-547557f445-bw46n         2/2     Running   0          47s\n   pod/currencyservice-6bd8885d9c-2cszv         2/2     Running   0          47s\n   pod/emailservice-64997dcf97-8fpsd            2/2     Running   0          47s\n   pod/frontend-c54778dcf-wbgmr                 2/2     Running   0          46s\n   pod/istio-gateway-istio-8577b948c6-cxl8j     1/1     Running   0          45s\n   pod/loadgenerator-ccfd4d598-jh6xj            2/2     Running   0          46s\n   pod/paymentservice-79b77cd7c-6hth7           2/2     Running   0          46s\n   pod/productcatalogservice-5f75795545-nk5wv   2/2     Running   0          46s\n   pod/recommendationservice-56dd4c7df5-gnwwr   2/2     Running   0          46s\n   pod/redis-cart-799c85c644-pxsvt              2/2     Running   0          46s\n   pod/shippingservice-64f8df74f5-7wllf         2/2     Running   0          45s\n\n   NAME                                              CLASS   ADDRESS          READY   AGE\n   gateway.gateway.networking.k8s.io/istio-gateway   istio   35.247.123.146   True    45s\n\n   NAME                            TYPE           CLUSTER-IP      EXTERNAL-IP      PORT(S)                        AGE\n   service/adservice               ClusterIP      10.68.231.142   <none>           9555/TCP                       49s\n   service/cartservice             ClusterIP      10.68.184.25    <none>           7070/TCP                       49s\n   service/checkoutservice         ClusterIP      10.68.177.213   <none>           5050/TCP                       49s\n   service/currencyservice         ClusterIP      10.68.249.87    <none>           7000/TCP                       49s\n   service/emailservice            ClusterIP      10.68.205.123   <none>           5000/TCP                       49s\n   service/frontend                ClusterIP      10.68.94.203    <none>           80/TCP                         48s\n   service/istio-gateway-istio     LoadBalancer   10.68.147.158   35.247.123.146   15021:30376/TCP,80:30332/TCP   45s\n   service/kubernetes              ClusterIP      10.68.0.1       <none>           443/TCP                        65m\n   service/paymentservice          ClusterIP      10.68.114.19    <none>           50051/TCP                      48s\n   service/productcatalogservice   ClusterIP      10.68.240.153   <none>           3550/TCP                       48s\n   service/recommendationservice   ClusterIP      10.68.117.97    <none>           8080/TCP                       48s\n   service/redis-cart              ClusterIP      10.68.189.126   <none>           6379/TCP                       48s\n   service/shippingservice         ClusterIP      10.68.221.62    <none>           50051/TCP                      48s\n   ```\n\n1. Find the external IP address of your Istio gateway.\n\n   ```sh\n   INGRESS_HOST=\"$(kubectl get gateway istio-gateway \\\n       -o jsonpath='{.status.addresses[*].value}')\"\n   ```\n\n1. Navigate to the frontend in a web browser.\n\n   ```\n   http://$INGRESS_HOST\n   ```\n\n# Additional service mesh demos using Online Boutique \n\n- [Canary deployment](https://github.com/GoogleCloudPlatform/istio-samples/tree/master/istio-canary-gke)\n- [Security (mTLS, JWT, Authorization)](https://github.com/GoogleCloudPlatform/istio-samples/tree/master/security-intro)\n- [Cloud Operations (Stackdriver)](https://github.com/GoogleCloudPlatform/istio-samples/tree/master/istio-stackdriver)\n- [Stackdriver metrics (Open source Istio)](https://github.com/GoogleCloudPlatform/istio-samples/tree/master/stackdriver-metrics)\n\n# Related resources\n\n- [Deploying classic istio-ingressgateway in ASM](https://cloud.google.com/service-mesh/docs/gateways#deploy_gateways)\n- [Uninstall Istio via istioctl](https://istio.io/latest/docs/setup/install/istioctl/#uninstall-istio)\n- [Uninstall Cloud Service Mesh](https://cloud.google.com/service-mesh/docs/uninstall)\n"
  },
  {
    "path": "kustomize/components/service-mesh-istio/allow-egress-googleapis.yaml",
    "content": "# Copyright 2018 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\napiVersion: networking.istio.io/v1alpha3\nkind: ServiceEntry\nmetadata:\n  name: allow-egress-googleapis\nspec:\n  hosts:\n  - \"accounts.google.com\" # Used to get token\n  - \"*.googleapis.com\"\n  ports:\n  - number: 80\n    protocol: HTTP\n    name: http\n  - number: 443\n    protocol: HTTPS\n    name: https\n---\napiVersion: networking.istio.io/v1alpha3\nkind: ServiceEntry\nmetadata:\n  name: allow-egress-google-metadata\nspec:\n  hosts:\n  - metadata.google.internal\n  addresses:\n  - 169.254.169.254 # GCE metadata server\n  ports:\n  - number: 80\n    name: http\n    protocol: HTTP\n  - number: 443\n    name: https\n    protocol: HTTPS\n"
  },
  {
    "path": "kustomize/components/service-mesh-istio/frontend-gateway.yaml",
    "content": "# Copyright 2018 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\napiVersion: gateway.networking.k8s.io/v1beta1\nkind: Gateway\nmetadata:\n  name: istio-gateway\nspec:\n  gatewayClassName: istio\n  listeners:\n  - name: http\n    port: 80\n    protocol: HTTP\n    allowedRoutes:\n      namespaces:\n        from: Same\n---\napiVersion: gateway.networking.k8s.io/v1beta1\nkind: HTTPRoute\nmetadata:\n  name: frontend-route\nspec:\n  parentRefs:\n  - name: istio-gateway\n  rules:\n  - matches:\n    - path:\n        value: /\n    backendRefs:\n    - name: frontend\n      port: 80\n"
  },
  {
    "path": "kustomize/components/service-mesh-istio/frontend.yaml",
    "content": "# Copyright 2018 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\napiVersion: networking.istio.io/v1alpha3\nkind: VirtualService\nmetadata:\n  name: frontend\nspec:\n  hosts:\n  - \"frontend.default.svc.cluster.local\"\n  http:\n  - route:\n    - destination:\n        host: frontend\n        port:\n          number: 80\n"
  },
  {
    "path": "kustomize/components/service-mesh-istio/kustomization.yaml",
    "content": "# Copyright 2023 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\napiVersion: kustomize.config.k8s.io/v1alpha1\nkind: Component\nresources:\n  - allow-egress-googleapis.yaml\n  - frontend-gateway.yaml\n  - frontend.yaml\npatches:\n# frontend - delete frontend-external service (same as non-public-frontend component)\n- patch: |-\n    apiVersion: v1\n    kind: Service\n    metadata:\n      name: frontend-external\n    $patch: delete\n"
  },
  {
    "path": "kustomize/components/shopping-assistant/README.md",
    "content": "# Shopping Assistant with RAG & AlloyDB\n\nThis 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.\n\n## Setup instructions\n\n**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.\n\n1. Set some environment variables.\n    ```sh\n    export PROJECT_ID=<project_id>\n    export PROJECT_NUMBER=<project_number>\n    export PGPASSWORD=<pgpassword>\n    ```\n\n    **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.\n\n1. Change your default Google Cloud project.\n    ```sh\n    gcloud auth login\n    gcloud config set project $PROJECT_ID\n    ```\n\n1. Enable the Google Kubernetes Engine (GKE) and Artifact Registry (AR) APIs.\n    ```sh\n    gcloud services enable container.googleapis.com\n    gcloud services enable artifactregistry.googleapis.com\n    ```\n\n1. Create a GKE Autopilot cluster. This may take a few minutes.\n    ```sh\n    gcloud container clusters create-auto cymbal-shops \\\n        --region=us-central1\n    ```\n\n1. Change your Kubernetes context to your newly created GKE cluster.\n    ```sh\n    gcloud container clusters get-credentials cymbal-shops \\\n        --region us-central1\n    ```\n\n1. Create an Artifact Registry container image repository.\n    ```sh\n    gcloud artifacts repositories create images \\\n        --repository-format=docker \\\n        --location=us-central1\n    ```\n\n1. Clone the `microservices-demo` repository locally.\n    ```sh\n    git clone https://github.com/GoogleCloudPlatform/microservices-demo \\\n        && cd microservices-demo/\n    ```\n\n1. Run script #1. If it asks about policy bindings, select the option `None`. This may take a few minutes.\n    ```sh\n    ./kustomize/components/shopping-assistant/scripts/1_deploy_alloydb_infra.sh\n    ```\n\n    **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.\n\n1. Create a Linux VM in Compute Engine (GCE).\n    ```sh\n    gcloud compute instances create gce-linux \\\n        --zone=us-central1-a \\\n        --machine-type=e2-micro \\\n        --image-family=debian-12 \\\n        --image-project=debian-cloud \n    ```\n\n1. SSH into the VM. From here until we exit, all steps happen in the VM.\n    ```sh\n    gcloud compute ssh gce-linux \\\n        --zone \"us-central1-a\"\n    ```\n\n1. Install the Postgres client and set your default Google Cloud project.\n    ```sh\n    sudo apt-get install -y postgresql-client\n    gcloud auth login\n    gcloud config set project <PROJECT_ID>\n    ```\n\n1. Copy script #2, the python script, and the products.json to the VM. Make sure the scripts are executable.\n    ```sh\n    nano 2_create_populate_alloydb_tables.sh # paste content\n    nano generate_sql_from_products.py # paste content\n    nano products.json # paste content\n    chmod +x 2_create_populate_alloydb_tables.sh\n    chmod +x generate_sql_from_products.py\n    ```\n\n    **Note:** You can find the files at the following places:\n    - `kustomize/components/shopping-assistant/scripts/2_create_populate_alloydb_tables.sh`\n    - `kustomize/components/shopping-assistant/scripts/generate_sql_from_products.py`\n    - `src/productcatalogservice/products.json`\n\n1. 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.\n    ```sh\n    ./2_create_populate_alloydb_tables.sh\n    ```\n\n1. Exit SSH.\n    ```sh\n    exit\n    ```\n\n1. 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.\n\n1. Replace the Google API key placeholder in the shoppingassistant service.\n    ```sh\n    export GOOGLE_API_KEY=<google_api_key>\n    sed -i \"s/GOOGLE_API_KEY_VAL/${GOOGLE_API_KEY}/g\" kustomize/components/shopping-assistant/shoppingassistantservice.yaml\n    ```\n\n1. Edit the root Kustomize file to enable the `alloydb` and `shopping-assistant` components.\n    ```sh\n    nano kubernetes-manifests/kustomization.yaml # make the modifications below\n    ```\n    \n    ```yaml\n    # ...head of the file\n    components: # remove this comment\n    # - ../kustomize/components/cymbal-branding\n    # - ../kustomize/components/google-cloud-operations\n    # - ../kustomize/components/memorystore\n    # - ../kustomize/components/network-policies\n     - ../kustomize/components/alloydb # remove this comment\n     - ../kustomize/components/shopping-assistant # remove this comment\n    # - ../kustomize/components/spanner\n    # - ../kustomize/components/container-images-tag\n    # - ../kustomize/components/container-images-tag-suffix\n    # - ../kustomize/components/container-images-registry\n    ```\n\n1. Deploy to the GKE cluster.\n    ```sh\n    skaffold run --default-repo=us-central1-docker.pkg.dev/$PROJECT_ID/images\n    ```\n\n1. Wait for all the pods to be up and running. You can then find the external IP and navigate to it.\n    ```sh\n    kubectl get pods\n    kubectl get services\n    ```\n"
  },
  {
    "path": "kustomize/components/shopping-assistant/kustomization.yaml",
    "content": "# Copyright 2024 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      https://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\napiVersion: kustomize.config.k8s.io/v1alpha1\nkind: Component\nresources:\n- shoppingassistantservice.yaml\npatches:\n- patch: |-\n    apiVersion: apps/v1\n    kind: Deployment\n    metadata:\n      name: frontend\n    spec:\n      template:\n        spec:\n          containers:\n          - name: server\n            env:\n            - name: ENABLE_ASSISTANT\n              value: \"true\"\n"
  },
  {
    "path": "kustomize/components/shopping-assistant/scripts/1_deploy_alloydb_infra.sh",
    "content": "#!/bin/sh\n#\n# Copyright 2024 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     https://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nset -e\nset -x\n\n# Replace me\nPROJECT_ID=$PROJECT_ID\nPROJECT_NUMBER=$PROJECT_NUMBER\nPGPASSWORD=$PGPASSWORD\n\n# Set sensible defaults\nREGION=us-central1\nUSE_GKE_GCLOUD_AUTH_PLUGIN=True\nALLOYDB_NETWORK=default\nALLOYDB_SERVICE_NAME=onlineboutique-network-range\nALLOYDB_CLUSTER_NAME=onlineboutique-cluster\nALLOYDB_INSTANCE_NAME=onlineboutique-instance\nALLOYDB_CARTS_DATABASE_NAME=carts\nALLOYDB_CARTS_TABLE_NAME=cart_items\nALLOYDB_PRODUCTS_DATABASE_NAME=products\nALLOYDB_PRODUCTS_TABLE_NAME=catalog_items\nALLOYDB_USER_GSA_NAME=alloydb-user-sa\nALLOYDB_USER_GSA_ID=${ALLOYDB_USER_GSA_NAME}@${PROJECT_ID}.iam.gserviceaccount.com\nALLOYDB_SECRET_NAME=alloydb-secret\n\n# Enable services\ngcloud services enable alloydb.googleapis.com\ngcloud services enable servicenetworking.googleapis.com\ngcloud services enable secretmanager.googleapis.com\ngcloud services enable aiplatform.googleapis.com\ngcloud services enable generativelanguage.googleapis.com\n\n# Set our DB credentials behind the secret\necho $PGPASSWORD | gcloud secrets create ${ALLOYDB_SECRET_NAME} --data-file=-\n\n# Set up needed service connection\ngcloud compute addresses create ${ALLOYDB_SERVICE_NAME} \\\n    --global \\\n    --purpose=VPC_PEERING \\\n    --prefix-length=16 \\\n    --description=\"Online Boutique Private Services\" \\\n    --network=${ALLOYDB_NETWORK}\n\ngcloud services vpc-peerings connect \\\n    --service=servicenetworking.googleapis.com \\\n    --ranges=${ALLOYDB_SERVICE_NAME} \\\n    --network=${ALLOYDB_NETWORK}\n\ngcloud alloydb clusters create ${ALLOYDB_CLUSTER_NAME} \\\n    --region=${REGION} \\\n    --password=${PGPASSWORD} \\\n    --disable-automated-backup \\\n    --network=${ALLOYDB_NETWORK}\n\ngcloud alloydb instances create ${ALLOYDB_INSTANCE_NAME} \\\n    --cluster=${ALLOYDB_CLUSTER_NAME} \\\n    --region=${REGION} \\\n    --cpu-count=4 \\\n    --instance-type=PRIMARY\n\ngcloud alloydb instances create ${ALLOYDB_INSTANCE_NAME}-replica \\\n    --cluster=${ALLOYDB_CLUSTER_NAME} \\\n    --region=${REGION} \\\n    --cpu-count=4 \\\n    --instance-type=READ_POOL \\\n    --read-pool-node-count=2\n\ngcloud beta alloydb instances update ${ALLOYDB_INSTANCE_NAME} \\\n    --cluster=${ALLOYDB_CLUSTER_NAME} \\\n    --region=${REGION} \\\n    --assign-inbound-public-ip=ASSIGN_IPV4 \\\n    --database-flags password.enforce_complexity=on\n\n# Fetch the primary and read IPs\nALLOYDB_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\"`\nALLOYDB_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\"`\n\n# Substitute environment values (alloydb/kustomization.yaml)\nsed -i \"s/PROJECT_ID_VAL/${PROJECT_ID}/g\" kustomize/components/alloydb/kustomization.yaml\nsed -i \"s/REGION_VAL/${REGION}/g\" kustomize/components/alloydb/kustomization.yaml\nsed -i \"s/ALLOYDB_PRIMARY_IP_VAL/${ALLOYDB_PRIMARY_IP}/g\" kustomize/components/alloydb/kustomization.yaml\nsed -i \"s/ALLOYDB_USER_GSA_ID/${ALLOYDB_USER_GSA_ID}/g\" kustomize/components/alloydb/kustomization.yaml\nsed -i \"s/ALLOYDB_CLUSTER_NAME_VAL/${ALLOYDB_CLUSTER_NAME}/g\" kustomize/components/alloydb/kustomization.yaml\nsed -i \"s/ALLOYDB_INSTANCE_NAME_VAL/${ALLOYDB_INSTANCE_NAME}/g\" kustomize/components/alloydb/kustomization.yaml\nsed -i \"s/ALLOYDB_CARTS_DATABASE_NAME_VAL/${ALLOYDB_CARTS_DATABASE_NAME}/g\" kustomize/components/alloydb/kustomization.yaml\nsed -i \"s/ALLOYDB_CARTS_TABLE_NAME_VAL/${ALLOYDB_CARTS_TABLE_NAME}/g\" kustomize/components/alloydb/kustomization.yaml\nsed -i \"s/ALLOYDB_PRODUCTS_DATABASE_NAME_VAL/${ALLOYDB_PRODUCTS_DATABASE_NAME}/g\" kustomize/components/alloydb/kustomization.yaml\nsed -i \"s/ALLOYDB_PRODUCTS_TABLE_NAME_VAL/${ALLOYDB_PRODUCTS_TABLE_NAME}/g\" kustomize/components/alloydb/kustomization.yaml\nsed -i \"s/ALLOYDB_SECRET_NAME_VAL/${ALLOYDB_SECRET_NAME}/g\" kustomize/components/alloydb/kustomization.yaml\n\n# Substitute environment values (kustomize/components/shopping-assistant/shoppingassistantservice.yaml)\nsed -i \"s/PROJECT_ID_VAL/${PROJECT_ID}/g\" kustomize/components/shopping-assistant/shoppingassistantservice.yaml\nsed -i \"s/REGION_VAL/${REGION}/g\" kustomize/components/shopping-assistant/shoppingassistantservice.yaml\nsed -i \"s/ALLOYDB_CLUSTER_NAME_VAL/${ALLOYDB_CLUSTER_NAME}/g\" kustomize/components/shopping-assistant/shoppingassistantservice.yaml\nsed -i \"s/ALLOYDB_INSTANCE_NAME_VAL/${ALLOYDB_INSTANCE_NAME}/g\" kustomize/components/shopping-assistant/shoppingassistantservice.yaml\nsed -i \"s/ALLOYDB_DATABASE_NAME_VAL/${ALLOYDB_PRODUCTS_DATABASE_NAME}/g\" kustomize/components/shopping-assistant/shoppingassistantservice.yaml\nsed -i \"s/ALLOYDB_TABLE_NAME_VAL/${ALLOYDB_PRODUCTS_TABLE_NAME}/g\" kustomize/components/shopping-assistant/shoppingassistantservice.yaml\nsed -i \"s/ALLOYDB_SECRET_NAME_VAL/${ALLOYDB_SECRET_NAME}/g\" kustomize/components/shopping-assistant/shoppingassistantservice.yaml\nsed -i \"s/ALLOYDB_USER_GSA_ID/${ALLOYDB_USER_GSA_ID}/g\" kustomize/components/shopping-assistant/shoppingassistantservice.yaml\n\n# Create service account for the cart and shopping assistant services\ngcloud iam service-accounts create ${ALLOYDB_USER_GSA_NAME} \\\n    --display-name=${ALLOYDB_USER_GSA_NAME}\n\ngcloud projects add-iam-policy-binding ${PROJECT_ID} --member=serviceAccount:${ALLOYDB_USER_GSA_ID} --role=roles/alloydb.client\ngcloud projects add-iam-policy-binding ${PROJECT_ID} --member=serviceAccount:${ALLOYDB_USER_GSA_ID} --role=roles/alloydb.databaseUser\ngcloud projects add-iam-policy-binding ${PROJECT_ID} --member=serviceAccount:${ALLOYDB_USER_GSA_ID} --role=roles/secretmanager.secretAccessor\ngcloud projects add-iam-policy-binding ${PROJECT_ID} --member=serviceAccount:${ALLOYDB_USER_GSA_ID} --role=roles/serviceusage.serviceUsageConsumer\ngcloud projects add-iam-policy-binding ${PROJECT_ID} --member=serviceAccount:service-${PROJECT_NUMBER}@gcp-sa-alloydb.iam.gserviceaccount.com --role=roles/aiplatform.user\n\ngcloud iam service-accounts add-iam-policy-binding ${ALLOYDB_USER_GSA_ID} \\\n    --member \"serviceAccount:${PROJECT_ID}.svc.id.goog[default/cartservice]\" \\\n    --role roles/iam.workloadIdentityUser\n\ngcloud iam service-accounts add-iam-policy-binding ${ALLOYDB_USER_GSA_ID} \\\n    --member \"serviceAccount:${PROJECT_ID}.svc.id.goog[default/shoppingassistantservice]\" \\\n    --role roles/iam.workloadIdentityUser\n\ngcloud iam service-accounts add-iam-policy-binding ${ALLOYDB_USER_GSA_ID} \\\n    --member \"serviceAccount:${PROJECT_ID}.svc.id.goog[default/productcatalogservice]\" \\\n    --role roles/iam.workloadIdentityUser\n"
  },
  {
    "path": "kustomize/components/shopping-assistant/scripts/2_create_populate_alloydb_tables.sh",
    "content": "#!/bin/sh\n#\n# Copyright 2024 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     https://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nset -e\nset -x\n\n# Set sensible defaults\nREGION=us-central1\nALLOYDB_CLUSTER_NAME=onlineboutique-cluster\nALLOYDB_CARTS_DATABASE_NAME=carts\nALLOYDB_CARTS_TABLE_NAME=cart_items\nALLOYDB_PRODUCTS_DATABASE_NAME=products\nALLOYDB_PRODUCTS_TABLE_NAME=catalog_items\n\n# Fetch the primary and read IPs\nALLOYDB_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\"`\n\n# Create carts database and table\npsql -h ${ALLOYDB_PRIMARY_IP} -U postgres -c \"CREATE DATABASE ${ALLOYDB_CARTS_DATABASE_NAME}\"\npsql -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))\"\npsql -h ${ALLOYDB_PRIMARY_IP} -U postgres -d ${ALLOYDB_CARTS_DATABASE_NAME} -c \"CREATE INDEX cartItemsByUserId ON ${ALLOYDB_CARTS_TABLE_NAME}(userId)\"\n\n# Create products database, table, and extensions\npsql -h ${ALLOYDB_PRIMARY_IP} -U postgres -c \"CREATE DATABASE ${ALLOYDB_PRODUCTS_DATABASE_NAME}\"\npsql -h ${ALLOYDB_PRIMARY_IP} -U postgres -d ${ALLOYDB_PRODUCTS_DATABASE_NAME} -c \"CREATE EXTENSION IF NOT EXISTS vector\"\npsql -h ${ALLOYDB_PRIMARY_IP} -U postgres -d ${ALLOYDB_PRODUCTS_DATABASE_NAME} -c \"CREATE EXTENSION IF NOT EXISTS google_ml_integration CASCADE;\"\npsql -h ${ALLOYDB_PRIMARY_IP} -U postgres -d ${ALLOYDB_PRODUCTS_DATABASE_NAME} -c \"GRANT EXECUTE ON FUNCTION embedding TO postgres;\"\npsql -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)\"\n\n# Generate and insert products table entries\npython3 ./generate_sql_from_products.py > products.sql\npsql -h ${ALLOYDB_PRIMARY_IP} -U postgres -d ${ALLOYDB_PRODUCTS_DATABASE_NAME} -f products.sql\nrm products.sql\n\n# Generate vector embeddings\npsql -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';\"\n"
  },
  {
    "path": "kustomize/components/shopping-assistant/scripts/generate_sql_from_products.py",
    "content": "# Copyright 2024 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     https://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport json\n\ntable_name = \"catalog_items\"\nfields = [\n    'id', 'name', 'description', 'picture', \n    'price_usd_currency_code', 'price_usd_units', 'price_usd_nanos', \n    'categories'\n]\n\n# Load the produts JSON\nwith open(\"products.json\", 'r') as f:\n    data = json.load(f)\n\n# Generate SQL INSERT statements\nfor product in data['products']:\n    columns = ', '.join(fields)\n    placeholders = ', '.join(['{}'] * len(fields))\n    sql = f\"INSERT INTO {table_name} ({columns}) VALUES ({placeholders});\"\n\n    # Escape single quotes within product data \n    product['name'] = product['name'].replace(\"'\", \"\")\n    product['description'] = product['description'].replace(\"'\", \"\")\n\n    escaped_values = (\n        f\"'{product['id']}'\",\n        f\"'{product['name']}'\",\n        f\"'{product['description']}'\",\n        f\"'{product['picture']}'\",\n        f\"'{product['priceUsd']['currencyCode']}'\",\n        product['priceUsd']['units'],\n        product['priceUsd']['nanos'],\n        f\"'{','.join(product['categories'])}'\"\n    )\n\n    # Render the formatted SQL query\n    print(sql.format(*escaped_values))\n"
  },
  {
    "path": "kustomize/components/shopping-assistant/shoppingassistantservice.yaml",
    "content": "# Copyright 2024 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      https://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: shoppingassistantservice\n  labels:\n    app: shoppingassistantservice\nspec:\n  selector:\n    matchLabels:\n      app: shoppingassistantservice\n  template:\n    metadata:\n      labels:\n        app: shoppingassistantservice\n    spec:\n      serviceAccountName: shoppingassistantservice\n      terminationGracePeriodSeconds: 5\n      securityContext:\n        fsGroup: 1000\n        runAsGroup: 1000\n        runAsNonRoot: true\n        runAsUser: 1000\n      containers:\n      - name: server\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n              - ALL\n          privileged: false\n          readOnlyRootFilesystem: false\n        image: shoppingassistantservice\n        ports:\n        - name: http\n          containerPort: 8080\n        env:\n        - name: GOOGLE_API_KEY\n          value: GOOGLE_API_KEY_VAL\n        - name: ALLOYDB_CLUSTER_NAME\n          value: ALLOYDB_CLUSTER_NAME_VAL\n        - name: ALLOYDB_INSTANCE_NAME\n          value: ALLOYDB_INSTANCE_NAME_VAL\n        - name: ALLOYDB_DATABASE_NAME\n          value: ALLOYDB_DATABASE_NAME_VAL\n        - name: ALLOYDB_TABLE_NAME\n          value: ALLOYDB_TABLE_NAME_VAL\n        - name: ALLOYDB_SECRET_NAME\n          value: ALLOYDB_SECRET_NAME_VAL\n        - name: PROJECT_ID\n          value: PROJECT_ID_VAL\n        - name: REGION\n          value: REGION_VAL\n        resources:\n          requests:\n            cpu: 100m\n            memory: 64Mi\n          limits:\n            cpu: 200m\n            memory: 128Mi\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: shoppingassistantservice\n  labels:\n    app: shoppingassistantservice\nspec:\n  type: ClusterIP\n  selector:\n    app: shoppingassistantservice\n  ports:\n  - name: http\n    port: 80\n    targetPort: 8080\n---\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: shoppingassistantservice\n  annotations:\n    iam.gke.io/gcp-service-account: ALLOYDB_USER_GSA_ID\n"
  },
  {
    "path": "kustomize/components/single-shared-session/README.md",
    "content": "# Manage a single shared session for the Online Boutique apps\n\nBy default, when you deploy this sample app, the Online Boutique's `frontend` generates a `shop_session-id` cookie per browser session.\nBut you may want to share one unique `shop_session-id` cookie across all browser sessions.\nThis is useful for multi-cluster environments.\n\n## Deploy Online Boutique to generate a single shared session\n\nTo automate the deployment of Online Boutique to manage a single shared session you can leverage the following variation with [Kustomize](../..).\n\nFrom the `kustomize/` folder at the root level of this repository, execute this command:\n\n```bash\nkustomize edit add component components/single-shared-session\n```\n\nThis will update the `kustomize/kustomization.yaml` file which could be similar to:\n\n```yaml\napiVersion: kustomize.config.k8s.io/v1beta1\nkind: Kustomization\nresources:\n- base\ncomponents:\n- components/single-shared-session\n```\n\nYou can locally render these manifests by running `kubectl kustomize .` as well as deploying them by running `kubectl apply -k .`.\n"
  },
  {
    "path": "kustomize/components/single-shared-session/kustomization.yaml",
    "content": "# Copyright 2022 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\napiVersion: kustomize.config.k8s.io/v1alpha1\nkind: Component\npatches:\n# frontend - ENABLE_SINGLE_SHARED_SESSION\n- patch: |-\n    apiVersion: apps/v1\n    kind: Deployment\n    metadata:\n      name: frontend\n    spec:\n      template:\n        spec:\n          containers:\n            - name: server\n              env:\n              - name: ENABLE_SINGLE_SHARED_SESSION\n                value: \"true\"\n"
  },
  {
    "path": "kustomize/components/spanner/README.md",
    "content": "# Integrate Online Boutique with Spanner\n\nBy default the `cartservice` stores its data in an in-cluster Redis database.\nUsing 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.\n\n## Provision a Spanner database\n\nTo provision a Spanner instance you can follow the following instructions:\n\n```bash\ngcloud services enable spanner.googleapis.com\n\nSPANNER_REGION_CONFIG=\"<your-spanner-region-config-name>\" # e.g. \"regional-us-east5\"\nSPANNER_INSTANCE_NAME=onlineboutique\n\ngcloud spanner instances create ${SPANNER_INSTANCE_NAME} \\\n    --description=\"online boutique shopping cart\" \\\n    --config ${SPANNER_REGION_CONFIG} \\\n    --instance-type free-instance\n```\n\n_Note: With latest version of `gcloud` we are creating a free Spanner instance._\n\nTo provision a Spanner database you can follow the following instructions:\n\n```bash\nSPANNER_DATABASE_NAME=carts\n\ngcloud spanner databases create ${SPANNER_DATABASE_NAME} \\\n    --instance ${SPANNER_INSTANCE_NAME} \\\n    --database-dialect GOOGLE_STANDARD_SQL \\\n    --ddl \"CREATE TABLE CartItems (userId STRING(1024), productId STRING(1024), quantity INT64) PRIMARY KEY (userId, productId); CREATE INDEX CartItemsByUserId ON CartItems(userId);\"\n```\n\n## Grant the `cartservice`'s service account access to the Spanner database\n\n**Important note:** Your GKE cluster should have [Workload Identity enabled](https://cloud.google.com/kubernetes-engine/docs/how-to/workload-identity#enable).\n\nAs a good practice, let's create a dedicated least privilege Google Service Account to allow the `cartservice` to communicate with the Spanner database:\n\n```bash\nPROJECT_ID=<your-project-id>\nSPANNER_DB_USER_GSA_NAME=spanner-db-user-sa\nSPANNER_DB_USER_GSA_ID=${SPANNER_DB_USER_GSA_NAME}@${PROJECT_ID}.iam.gserviceaccount.com\nONLINEBOUTIQUE_NAMESPACE=default\nCARTSERVICE_KSA_NAME=cartservice\n\ngcloud iam service-accounts create ${SPANNER_DB_USER_GSA_NAME} \\\n    --display-name=${SPANNER_DB_USER_GSA_NAME}\n\ngcloud spanner databases add-iam-policy-binding ${SPANNER_DATABASE_NAME} \\\n    --member \"serviceAccount:${SPANNER_DB_USER_GSA_ID}\" \\\n    --role roles/spanner.databaseUser\n\ngcloud iam service-accounts add-iam-policy-binding ${SPANNER_DB_USER_GSA_ID} \\\n    --member \"serviceAccount:${PROJECT_ID}.svc.id.goog[${ONLINEBOUTIQUE_NAMESPACE}/${CARTSERVICE_KSA_NAME}]\" \\\n    --role roles/iam.workloadIdentityUser\n```\n\n## Deploy Online Boutique connected to a Spanner database\n\nTo automate the deployment of Online Boutique integrated with Spanner you can leverage the following variation with [Kustomize](../..).\n\nFrom the `kustomize/` folder at the root level of this repository, execute these commands:\n\n```bash\nkustomize edit add component components/spanner\n```\n\n_Note: this Kustomize component will also remove the `redis-cart` `Deployment` and `Service` not used anymore._\n\nThis will update the `kustomize/kustomization.yaml` file which could be similar to:\n\n```yaml\napiVersion: kustomize.config.k8s.io/v1beta1\nkind: Kustomization\nresources:\n- base\ncomponents:\n- components/spanner\n```\n\nUpdate current Kustomize manifest to target this Spanner database.\n\n```bash\nsed -i \"s/SPANNER_PROJECT/${PROJECT_ID}/g\" components/spanner/kustomization.yaml\nsed -i \"s/SPANNER_INSTANCE/${SPANNER_INSTANCE_NAME}/g\" components/spanner/kustomization.yaml\nsed -i \"s/SPANNER_DATABASE/${SPANNER_DATABASE_NAME}/g\" components/spanner/kustomization.yaml\nsed -i \"s/SPANNER_DB_USER_GSA_ID/${SPANNER_DB_USER_GSA_ID}/g\" components/spanner/kustomization.yaml\n```\n\nYou can locally render these manifests by running `kubectl kustomize .` as well as deploying them by running `kubectl apply -k .`.\n\n## Note on Spanner connection environment variables\n\nThe following environment variables will be used by the `cartservice`, if present:\n\n- `SPANNER_INSTANCE`: defaults to `onlineboutique`, unless specified.\n- `SPANNER_DATABASE`: defaults to `carts`, unless specified.\n- `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.\n\n## Resources\n\n- [Use Google Cloud Spanner with the Online Boutique sample apps](https://medium.com/google-cloud/f7248e077339)\n"
  },
  {
    "path": "kustomize/components/spanner/kustomization.yaml",
    "content": "# Copyright 2022 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\napiVersion: kustomize.config.k8s.io/v1alpha1\nkind: Component\npatches:\n# cartservice - replace REDIS_ADDR by SPANNER_CONNECTION_STRING for the cartservice Deployment\n- patch: |-\n    apiVersion: apps/v1\n    kind: Deployment\n    metadata:\n      name: cartservice\n    spec:\n      template:\n        spec:\n          containers:\n            - name: server\n              env:\n              - name: REDIS_ADDR\n                $patch: delete\n              - name: SPANNER_CONNECTION_STRING\n                value: projects/SPANNER_PROJECT/instances/SPANNER_INSTANCE/databases/SPANNER_DATABASE\n# cartservice - add the GSA annotation for the cartservice KSA\n- patch: |-\n    apiVersion: v1\n    kind: ServiceAccount\n    metadata:\n      name: cartservice\n      annotations:\n        iam.gke.io/gcp-service-account: SPANNER_DB_USER_GSA_ID\n# redis - remove the redis-cart Deployment\n- patch: |-\n    apiVersion: apps/v1\n    kind: Deployment\n    metadata:\n      name: redis-cart\n    $patch: delete\n# redis - remove the redis-cart Service\n- patch: |-\n    apiVersion: v1\n    kind: Service\n    metadata:\n      name: redis-cart\n    $patch: delete\n"
  },
  {
    "path": "kustomize/components/without-loadgenerator/README.md",
    "content": "# Exclude the loadgenerator\n\nBy default, when you deploy Online Boutique, its [loadgenerator](/src/loadgenerator/) will also be deployed.\n\nYou can use this Kustomize component to exclude the loadgenerator.\n\nNote: This Kustomize component has not been tested with [other Kustomize Components](/kustomize/components/) that rely on the loadgenerator.\n\n## Use this component\n\nFrom the `kustomize/` folder at the root level of this repository, execute this command:\n\n```bash\nkustomize edit add component components/without-loadgenerator\n```\n\nThis will update the `kustomize/kustomization.yaml` file which could be similar to:\n\n```yaml\napiVersion: kustomize.config.k8s.io/v1beta1\nkind: Kustomization\nresources:\n- base\ncomponents:\n- components/without-loadgenerator\n```\n\nYou 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 .`.\n\nLearn more about Online Boutique's kustomize components at [/kustomize](/kustomize#readme).\n"
  },
  {
    "path": "kustomize/components/without-loadgenerator/delete-loadgenerator.patch.yaml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: loadgenerator\n$patch: delete\n"
  },
  {
    "path": "kustomize/components/without-loadgenerator/kustomization.yaml",
    "content": "# Copyright 2023 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\napiVersion: kustomize.config.k8s.io/v1alpha1\nkind: Component\npatches:\n- path: delete-loadgenerator.patch.yaml\n"
  },
  {
    "path": "kustomize/kustomization.yaml",
    "content": "# Copyright 2022 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\napiVersion: kustomize.config.k8s.io/v1beta1\nkind: Kustomization\nresources:\n- base\ncomponents:\n# - components/cymbal-branding\n# - components/google-cloud-operations\n# - components/memorystore\n# - components/network-policies\n# - components/non-public-frontend\n# - components/service-accounts\n# - components/alloydb\n# - components/single-shared-session\n# - components/spanner\n# - components/service-mesh-istio\n# - components/without-loadgenerator\n# These must be run last and in this order\n# - components/container-images-tag\n# - components/container-images-tag-suffix\n# - components/container-images-registry\n"
  },
  {
    "path": "kustomize/tests/README.md",
    "content": "This directory contains a list of scenarios (different combinations of Kustomize Components) used for testing.\nSee [/.github/workflows/kustomize-build-ci.yaml](../../.github/workflows/kustomize-build-ci.yaml).\n"
  },
  {
    "path": "kustomize/tests/memorystore-with-all-components/kustomization.yaml",
    "content": "# Copyright 2022 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\napiVersion: kustomize.config.k8s.io/v1beta1\nkind: Kustomization\nresources:\n- ../../base\ncomponents:\n- ../../components/cymbal-branding\n- ../../components/google-cloud-operations\n- ../../components/network-policies\n- ../../components/memorystore\n"
  },
  {
    "path": "kustomize/tests/service-mesh-istio-with-all-components/kustomization.yaml",
    "content": "# Copyright 2023 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\napiVersion: kustomize.config.k8s.io/v1beta1\nkind: Kustomization\nresources:\n- ../../base\ncomponents:\n- ../../components/cymbal-branding\n- ../../components/google-cloud-operations\n- ../../components/network-policies\n- ../../components/service-mesh-istio\n"
  },
  {
    "path": "kustomize/tests/spanner-with-all-components/kustomization.yaml",
    "content": "# Copyright 2022 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\napiVersion: kustomize.config.k8s.io/v1beta1\nkind: Kustomization\nresources:\n- ../../base\ncomponents:\n- ../../components/cymbal-branding\n- ../../components/google-cloud-operations\n- ../../components/network-policies\n- ../../components/spanner\n"
  },
  {
    "path": "protos/demo.proto",
    "content": "// Copyright 2020 Google LLC\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\nsyntax = \"proto3\";\n\npackage hipstershop;\n\noption go_package = \"github.com/GoogleCloudPlatform/microservices-demo/hipstershop\";\n\n// -----------------Cart service-----------------\n\nservice CartService {\n    rpc AddItem(AddItemRequest) returns (Empty) {}\n    rpc GetCart(GetCartRequest) returns (Cart) {}\n    rpc EmptyCart(EmptyCartRequest) returns (Empty) {}\n}\n\nmessage CartItem {\n    string product_id = 1;\n    int32  quantity = 2;\n}\n\nmessage AddItemRequest {\n    string user_id = 1;\n    CartItem item = 2;\n}\n\nmessage EmptyCartRequest {\n    string user_id = 1;\n}\n\nmessage GetCartRequest {\n    string user_id = 1;\n}\n\nmessage Cart {\n    string user_id = 1;\n    repeated CartItem items = 2;\n}\n\nmessage Empty {}\n\n// ---------------Recommendation service----------\n\nservice RecommendationService {\n  rpc ListRecommendations(ListRecommendationsRequest) returns (ListRecommendationsResponse){}\n}\n\nmessage ListRecommendationsRequest {\n    string user_id = 1;\n    repeated string product_ids = 2;\n}\n\nmessage ListRecommendationsResponse {\n    repeated string product_ids = 1;\n}\n\n// ---------------Product Catalog----------------\n\nservice ProductCatalogService {\n    rpc ListProducts(Empty) returns (ListProductsResponse) {}\n    rpc GetProduct(GetProductRequest) returns (Product) {}\n    rpc SearchProducts(SearchProductsRequest) returns (SearchProductsResponse) {}\n}\n\nmessage Product {\n    string id = 1;\n    string name = 2;\n    string description = 3;\n    string picture = 4;\n    Money price_usd = 5;\n\n    // Categories such as \"clothing\" or \"kitchen\" that can be used to look up\n    // other related products.\n    repeated string categories = 6;\n}\n\nmessage ListProductsResponse {\n    repeated Product products = 1;\n}\n\nmessage GetProductRequest {\n    string id = 1;\n}\n\nmessage SearchProductsRequest {\n    string query = 1;\n}\n\nmessage SearchProductsResponse {\n    repeated Product results = 1;\n}\n\n// ---------------Shipping Service----------\n\nservice ShippingService {\n    rpc GetQuote(GetQuoteRequest) returns (GetQuoteResponse) {}\n    rpc ShipOrder(ShipOrderRequest) returns (ShipOrderResponse) {}\n}\n\nmessage GetQuoteRequest {\n    Address address = 1;\n    repeated CartItem items = 2;\n}\n\nmessage GetQuoteResponse {\n    Money cost_usd = 1;\n}\n\nmessage ShipOrderRequest {\n    Address address = 1;\n    repeated CartItem items = 2;\n}\n\nmessage ShipOrderResponse {\n    string tracking_id = 1;\n}\n\nmessage Address {\n    string street_address = 1;\n    string city = 2;\n    string state = 3;\n    string country = 4;\n    int32 zip_code = 5;\n}\n\n// -----------------Currency service-----------------\n\nservice CurrencyService {\n    rpc GetSupportedCurrencies(Empty) returns (GetSupportedCurrenciesResponse) {}\n    rpc Convert(CurrencyConversionRequest) returns (Money) {}\n}\n\n// Represents an amount of money with its currency type.\nmessage Money {\n    // The 3-letter currency code defined in ISO 4217.\n    string currency_code = 1;\n\n    // The whole units of the amount.\n    // For example if `currencyCode` is `\"USD\"`, then 1 unit is one US dollar.\n    int64 units = 2;\n\n    // Number of nano (10^-9) units of the amount.\n    // The value must be between -999,999,999 and +999,999,999 inclusive.\n    // If `units` is positive, `nanos` must be positive or zero.\n    // If `units` is zero, `nanos` can be positive, zero, or negative.\n    // If `units` is negative, `nanos` must be negative or zero.\n    // For example $-1.75 is represented as `units`=-1 and `nanos`=-750,000,000.\n    int32 nanos = 3;\n}\n\nmessage GetSupportedCurrenciesResponse {\n    // The 3-letter currency code defined in ISO 4217.\n    repeated string currency_codes = 1;\n}\n\nmessage CurrencyConversionRequest {\n    Money from = 1;\n\n    // The 3-letter currency code defined in ISO 4217.\n    string to_code = 2;\n}\n\n// -------------Payment service-----------------\n\nservice PaymentService {\n    rpc Charge(ChargeRequest) returns (ChargeResponse) {}\n}\n\nmessage CreditCardInfo {\n    string credit_card_number = 1;\n    int32 credit_card_cvv = 2;\n    int32 credit_card_expiration_year = 3;\n    int32 credit_card_expiration_month = 4;\n}\n\nmessage ChargeRequest {\n    Money amount = 1;\n    CreditCardInfo credit_card = 2;\n}\n\nmessage ChargeResponse {\n    string transaction_id = 1;\n}\n\n// -------------Email service-----------------\n\nservice EmailService {\n    rpc SendOrderConfirmation(SendOrderConfirmationRequest) returns (Empty) {}\n}\n\nmessage OrderItem {\n    CartItem item = 1;\n    Money cost = 2;\n}\n\nmessage OrderResult {\n    string   order_id = 1;\n    string   shipping_tracking_id = 2;\n    Money shipping_cost = 3;\n    Address  shipping_address = 4;\n    repeated OrderItem items = 5;\n}\n\nmessage SendOrderConfirmationRequest {\n    string email = 1;\n    OrderResult order = 2;\n}\n\n\n// -------------Checkout service-----------------\n\nservice CheckoutService {\n    rpc PlaceOrder(PlaceOrderRequest) returns (PlaceOrderResponse) {}\n}\n\nmessage PlaceOrderRequest {\n    string user_id = 1;\n    string user_currency = 2;\n\n    Address address = 3;\n    string email = 5;\n    CreditCardInfo credit_card = 6;\n}\n\nmessage PlaceOrderResponse {\n    OrderResult order = 1;\n}\n\n// ------------Ad service------------------\n\nservice AdService {\n    rpc GetAds(AdRequest) returns (AdResponse) {}\n}\n\nmessage AdRequest {\n    // List of important key words from the current page describing the context.\n    repeated string context_keys = 1;\n}\n\nmessage AdResponse {\n    repeated Ad ads = 1;\n}\n\nmessage Ad {\n    // url to redirect to when an ad is clicked.\n    string redirect_url = 1;\n\n    // short advertisement text to display.\n    string text = 2;\n}\n"
  },
  {
    "path": "protos/grpc/health/v1/health.proto",
    "content": "// Copyright 2015 The gRPC Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// The canonical version of this proto can be found at\n// https://github.com/grpc/grpc-proto/blob/master/grpc/health/v1/health.proto\n\nsyntax = \"proto3\";\n\npackage grpc.health.v1;\n\noption csharp_namespace = \"Grpc.Health.V1\";\noption go_package = \"google.golang.org/grpc/health/grpc_health_v1\";\noption java_multiple_files = true;\noption java_outer_classname = \"HealthProto\";\noption java_package = \"io.grpc.health.v1\";\n\nmessage HealthCheckRequest {\n  string service = 1;\n}\n\nmessage HealthCheckResponse {\n  enum ServingStatus {\n    UNKNOWN = 0;\n    SERVING = 1;\n    NOT_SERVING = 2;\n  }\n  ServingStatus status = 1;\n}\n\nservice Health {\n  rpc Check(HealthCheckRequest) returns (HealthCheckResponse);\n}\n"
  },
  {
    "path": "release/istio-manifests.yaml",
    "content": "# Copyright 2025 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# ----------------------------------------------------------\n# WARNING: This file is autogenerated. Do not manually edit.\n# ----------------------------------------------------------\n\n# [START servicemesh_release_istio_manifests_microservices_demo]\n---\napiVersion: gateway.networking.k8s.io/v1beta1\nkind: Gateway\nmetadata:\n  name: istio-gateway\nspec:\n  gatewayClassName: istio\n  listeners:\n  - name: http\n    port: 80\n    protocol: HTTP\n    allowedRoutes:\n      namespaces:\n        from: Same\n---\napiVersion: gateway.networking.k8s.io/v1beta1\nkind: HTTPRoute\nmetadata:\n  name: frontend-route\nspec:\n  parentRefs:\n  - name: istio-gateway\n  rules:\n  - matches:\n    - path:\n        value: /\n    backendRefs:\n    - name: frontend\n      port: 80\n---\napiVersion: networking.istio.io/v1alpha3\nkind: ServiceEntry\nmetadata:\n  name: allow-egress-googleapis\nspec:\n  hosts:\n  - \"accounts.google.com\" # Used to get token\n  - \"*.googleapis.com\"\n  ports:\n  - number: 80\n    protocol: HTTP\n    name: http\n  - number: 443\n    protocol: HTTPS\n    name: https\n---\napiVersion: networking.istio.io/v1alpha3\nkind: ServiceEntry\nmetadata:\n  name: allow-egress-google-metadata\nspec:\n  hosts:\n  - metadata.google.internal\n  addresses:\n  - 169.254.169.254 # GCE metadata server\n  ports:\n  - number: 80\n    name: http\n    protocol: HTTP\n  - number: 443\n    name: https\n    protocol: HTTPS\n---\napiVersion: networking.istio.io/v1alpha3\nkind: VirtualService\nmetadata:\n  name: frontend\nspec:\n  hosts:\n  - \"frontend.default.svc.cluster.local\"\n  http:\n  - route:\n    - destination:\n        host: frontend\n        port:\n          number: 80\n# [END servicemesh_release_istio_manifests_microservices_demo]\n"
  },
  {
    "path": "release/kubernetes-manifests.yaml",
    "content": "# Copyright 2025 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# ----------------------------------------------------------\n# WARNING: This file is autogenerated. Do not manually edit.\n# ----------------------------------------------------------\n\n# [START gke_release_kubernetes_manifests_microservices_demo]\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: currencyservice\n  labels:\n    app: currencyservice\nspec:\n  selector:\n    matchLabels:\n      app: currencyservice\n  template:\n    metadata:\n      labels:\n        app: currencyservice\n    spec:\n      serviceAccountName: currencyservice\n      terminationGracePeriodSeconds: 5\n      securityContext:\n        fsGroup: 1000\n        runAsGroup: 1000\n        runAsNonRoot: true\n        runAsUser: 1000\n      containers:\n      - name: server\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n              - ALL\n          privileged: false\n          readOnlyRootFilesystem: true\n        image: us-central1-docker.pkg.dev/google-samples/microservices-demo/currencyservice:v0.10.5\n        ports:\n        - name: grpc\n          containerPort: 7000\n        env:\n        - name: PORT\n          value: \"7000\"\n        - name: DISABLE_PROFILER\n          value: \"1\"\n        readinessProbe:\n          grpc:\n            port: 7000\n        livenessProbe:\n          grpc:\n            port: 7000\n        resources:\n          requests:\n            cpu: 100m\n            memory: 64Mi\n          limits:\n            cpu: 200m\n            memory: 128Mi\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: currencyservice\n  labels:\n    app: currencyservice\nspec:\n  type: ClusterIP\n  selector:\n    app: currencyservice\n  ports:\n  - name: grpc\n    port: 7000\n    targetPort: 7000\n---\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: currencyservice\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: loadgenerator\n  labels:\n    app: loadgenerator\nspec:\n  selector:\n    matchLabels:\n      app: loadgenerator\n  replicas: 1\n  template:\n    metadata:\n      labels:\n        app: loadgenerator\n      annotations:\n        sidecar.istio.io/rewriteAppHTTPProbers: \"true\"\n    spec:\n      serviceAccountName: loadgenerator\n      terminationGracePeriodSeconds: 5\n      restartPolicy: Always\n      securityContext:\n        fsGroup: 1000\n        runAsGroup: 1000\n        runAsNonRoot: true\n        runAsUser: 1000\n      initContainers:\n      - command:\n        - /bin/sh\n        - -exc\n        - |\n          MAX_RETRIES=12\n          RETRY_INTERVAL=10\n          for i in $(seq 1 $MAX_RETRIES); do\n            echo \"Attempt $i: Pinging frontend: ${FRONTEND_ADDR}...\"\n            STATUSCODE=$(wget --server-response http://${FRONTEND_ADDR} 2>&1 | awk '/^  HTTP/{print $2}')\n            if [ $STATUSCODE -eq 200 ]; then\n                echo \"Frontend is reachable.\"\n                exit 0\n            fi\n            echo \"Error: Could not reach frontend - Status code: ${STATUSCODE}\"\n            sleep $RETRY_INTERVAL\n          done\n          echo \"Failed to reach frontend after $MAX_RETRIES attempts.\"\n          exit 1\n        name: frontend-check\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n              - ALL\n          privileged: false\n          readOnlyRootFilesystem: true\n        image: busybox:latest\n        env:\n        - name: FRONTEND_ADDR\n          value: \"frontend:80\"\n      containers:\n      - name: main\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n              - ALL\n          privileged: false\n          readOnlyRootFilesystem: true\n        image: us-central1-docker.pkg.dev/google-samples/microservices-demo/loadgenerator:v0.10.5\n        env:\n        - name: FRONTEND_ADDR\n          value: \"frontend:80\"\n        - name: USERS\n          value: \"10\"\n        - name: RATE\n          value: \"1\"\n        resources:\n          requests:\n            cpu: 300m\n            memory: 256Mi\n          limits:\n            cpu: 500m\n            memory: 512Mi\n---\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: loadgenerator\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: productcatalogservice\n  labels:\n    app: productcatalogservice\nspec:\n  selector:\n    matchLabels:\n      app: productcatalogservice\n  template:\n    metadata:\n      labels:\n        app: productcatalogservice\n    spec:\n      serviceAccountName: productcatalogservice\n      terminationGracePeriodSeconds: 5\n      securityContext:\n        fsGroup: 1000\n        runAsGroup: 1000\n        runAsNonRoot: true\n        runAsUser: 1000\n      containers:\n      - name: server\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n              - ALL\n          privileged: false\n          readOnlyRootFilesystem: true\n        image: us-central1-docker.pkg.dev/google-samples/microservices-demo/productcatalogservice:v0.10.5\n        ports:\n        - containerPort: 3550\n        env:\n        - name: PORT\n          value: \"3550\"\n        - name: DISABLE_PROFILER\n          value: \"1\"\n        readinessProbe:\n          grpc:\n            port: 3550\n        livenessProbe:\n          grpc:\n            port: 3550\n        resources:\n          requests:\n            cpu: 100m\n            memory: 64Mi\n          limits:\n            cpu: 200m\n            memory: 128Mi\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: productcatalogservice\n  labels:\n    app: productcatalogservice\nspec:\n  type: ClusterIP\n  selector:\n    app: productcatalogservice\n  ports:\n  - name: grpc\n    port: 3550\n    targetPort: 3550\n---\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: productcatalogservice\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: checkoutservice\n  labels:\n    app: checkoutservice\nspec:\n  selector:\n    matchLabels:\n      app: checkoutservice\n  template:\n    metadata:\n      labels:\n        app: checkoutservice\n    spec:\n      serviceAccountName: checkoutservice\n      securityContext:\n        fsGroup: 1000\n        runAsGroup: 1000\n        runAsNonRoot: true\n        runAsUser: 1000\n      containers:\n        - name: server\n          securityContext:\n            allowPrivilegeEscalation: false\n            capabilities:\n              drop:\n                - ALL\n            privileged: false\n            readOnlyRootFilesystem: true\n          image: us-central1-docker.pkg.dev/google-samples/microservices-demo/checkoutservice:v0.10.5\n          ports:\n          - containerPort: 5050\n          readinessProbe:\n            grpc:\n              port: 5050\n          livenessProbe:\n            grpc:\n              port: 5050\n          env:\n          - name: PORT\n            value: \"5050\"\n          - name: PRODUCT_CATALOG_SERVICE_ADDR\n            value: \"productcatalogservice:3550\"\n          - name: SHIPPING_SERVICE_ADDR\n            value: \"shippingservice:50051\"\n          - name: PAYMENT_SERVICE_ADDR\n            value: \"paymentservice:50051\"\n          - name: EMAIL_SERVICE_ADDR\n            value: \"emailservice:5000\"\n          - name: CURRENCY_SERVICE_ADDR\n            value: \"currencyservice:7000\"\n          - name: CART_SERVICE_ADDR\n            value: \"cartservice:7070\"\n          resources:\n            requests:\n              cpu: 100m\n              memory: 64Mi\n            limits:\n              cpu: 200m\n              memory: 128Mi\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: checkoutservice\n  labels:\n    app: checkoutservice\nspec:\n  type: ClusterIP\n  selector:\n    app: checkoutservice\n  ports:\n  - name: grpc\n    port: 5050\n    targetPort: 5050\n---\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: checkoutservice\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: shippingservice\n  labels:\n    app: shippingservice\nspec:\n  selector:\n    matchLabels:\n      app: shippingservice\n  template:\n    metadata:\n      labels:\n        app: shippingservice\n    spec:\n      serviceAccountName: shippingservice\n      securityContext:\n        fsGroup: 1000\n        runAsGroup: 1000\n        runAsNonRoot: true\n        runAsUser: 1000\n      containers:\n      - name: server\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n              - ALL\n          privileged: false\n          readOnlyRootFilesystem: true\n        image: us-central1-docker.pkg.dev/google-samples/microservices-demo/shippingservice:v0.10.5\n        ports:\n        - containerPort: 50051\n        env:\n        - name: PORT\n          value: \"50051\"\n        - name: DISABLE_PROFILER\n          value: \"1\"\n        readinessProbe:\n          periodSeconds: 5\n          grpc:\n            port: 50051\n        livenessProbe:\n          grpc:\n            port: 50051\n        resources:\n          requests:\n            cpu: 100m\n            memory: 64Mi\n          limits:\n            cpu: 200m\n            memory: 128Mi\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: shippingservice\n  labels:\n    app: shippingservice\nspec:\n  type: ClusterIP\n  selector:\n    app: shippingservice\n  ports:\n  - name: grpc\n    port: 50051\n    targetPort: 50051\n---\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: shippingservice\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: cartservice\n  labels:\n    app: cartservice\nspec:\n  selector:\n    matchLabels:\n      app: cartservice\n  template:\n    metadata:\n      labels:\n        app: cartservice\n    spec:\n      serviceAccountName: cartservice\n      terminationGracePeriodSeconds: 5\n      securityContext:\n        fsGroup: 1000\n        runAsGroup: 1000\n        runAsNonRoot: true\n        runAsUser: 1000\n      containers:\n      - name: server\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n              - ALL\n          privileged: false\n          readOnlyRootFilesystem: true\n        image: us-central1-docker.pkg.dev/google-samples/microservices-demo/cartservice:v0.10.5\n        ports:\n        - containerPort: 7070\n        env:\n        - name: REDIS_ADDR\n          value: \"redis-cart:6379\"\n        resources:\n          requests:\n            cpu: 200m\n            memory: 64Mi\n          limits:\n            cpu: 300m\n            memory: 128Mi\n        readinessProbe:\n          initialDelaySeconds: 15\n          grpc:\n            port: 7070\n        livenessProbe:\n          initialDelaySeconds: 15\n          periodSeconds: 10\n          grpc:\n            port: 7070\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: cartservice\n  labels:\n    app: cartservice\nspec:\n  type: ClusterIP\n  selector:\n    app: cartservice\n  ports:\n  - name: grpc\n    port: 7070\n    targetPort: 7070\n---\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: cartservice\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: redis-cart\n  labels:\n    app: redis-cart\nspec:\n  selector:\n    matchLabels:\n      app: redis-cart\n  template:\n    metadata:\n      labels:\n        app: redis-cart\n    spec:\n      securityContext:\n        fsGroup: 1000\n        runAsGroup: 1000\n        runAsNonRoot: true\n        runAsUser: 1000\n      containers:\n      - name: redis\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n              - ALL\n          privileged: false\n          readOnlyRootFilesystem: true\n        image: redis:alpine\n        ports:\n        - containerPort: 6379\n        readinessProbe:\n          periodSeconds: 5\n          tcpSocket:\n            port: 6379\n        livenessProbe:\n          periodSeconds: 5\n          tcpSocket:\n            port: 6379\n        volumeMounts:\n        - mountPath: /data\n          name: redis-data\n        resources:\n          limits:\n            memory: 256Mi\n            cpu: 125m\n          requests:\n            cpu: 70m\n            memory: 200Mi\n      volumes:\n      - name: redis-data\n        emptyDir: {}\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: redis-cart\n  labels:\n    app: redis-cart\nspec:\n  type: ClusterIP\n  selector:\n    app: redis-cart\n  ports:\n  - name: tcp-redis\n    port: 6379\n    targetPort: 6379\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: emailservice\n  labels:\n    app: emailservice\nspec:\n  selector:\n    matchLabels:\n      app: emailservice\n  template:\n    metadata:\n      labels:\n        app: emailservice\n    spec:\n      serviceAccountName: emailservice\n      terminationGracePeriodSeconds: 5\n      securityContext:\n        fsGroup: 1000\n        runAsGroup: 1000\n        runAsNonRoot: true\n        runAsUser: 1000\n      containers:\n      - name: server\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n              - ALL\n          privileged: false\n          readOnlyRootFilesystem: true\n        image: us-central1-docker.pkg.dev/google-samples/microservices-demo/emailservice:v0.10.5\n        ports:\n        - containerPort: 8080\n        env:\n        - name: PORT\n          value: \"8080\"\n        - name: DISABLE_PROFILER\n          value: \"1\"\n        readinessProbe:\n          periodSeconds: 5\n          grpc:\n            port: 8080\n        livenessProbe:\n          periodSeconds: 5\n          grpc:\n            port: 8080\n        resources:\n          requests:\n            cpu: 100m\n            memory: 64Mi\n          limits:\n            cpu: 200m\n            memory: 128Mi\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: emailservice\n  labels:\n    app: emailservice\nspec:\n  type: ClusterIP\n  selector:\n    app: emailservice\n  ports:\n  - name: grpc\n    port: 5000\n    targetPort: 8080\n---\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: emailservice\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: paymentservice\n  labels:\n    app: paymentservice\nspec:\n  selector:\n    matchLabels:\n      app: paymentservice\n  template:\n    metadata:\n      labels:\n        app: paymentservice\n    spec:\n      serviceAccountName: paymentservice\n      terminationGracePeriodSeconds: 5\n      securityContext:\n        fsGroup: 1000\n        runAsGroup: 1000\n        runAsNonRoot: true\n        runAsUser: 1000\n      containers:\n      - name: server\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n              - ALL\n          privileged: false\n          readOnlyRootFilesystem: true\n        image: us-central1-docker.pkg.dev/google-samples/microservices-demo/paymentservice:v0.10.5\n        ports:\n        - containerPort: 50051\n        env:\n        - name: PORT\n          value: \"50051\"\n        - name: DISABLE_PROFILER\n          value: \"1\"\n        readinessProbe:\n          grpc:\n            port: 50051\n        livenessProbe:\n          grpc:\n            port: 50051\n        resources:\n          requests:\n            cpu: 100m\n            memory: 64Mi\n          limits:\n            cpu: 200m\n            memory: 128Mi\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: paymentservice\n  labels:\n    app: paymentservice\nspec:\n  type: ClusterIP\n  selector:\n    app: paymentservice\n  ports:\n  - name: grpc\n    port: 50051\n    targetPort: 50051\n---\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: paymentservice\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: frontend\n  labels:\n    app: frontend\nspec:\n  selector:\n    matchLabels:\n      app: frontend\n  template:\n    metadata:\n      labels:\n        app: frontend\n      annotations:\n        sidecar.istio.io/rewriteAppHTTPProbers: \"true\"\n    spec:\n      serviceAccountName: frontend\n      securityContext:\n        fsGroup: 1000\n        runAsGroup: 1000\n        runAsNonRoot: true\n        runAsUser: 1000\n      containers:\n        - name: server\n          securityContext:\n            allowPrivilegeEscalation: false\n            capabilities:\n              drop:\n                - ALL\n            privileged: false\n            readOnlyRootFilesystem: true\n          image: us-central1-docker.pkg.dev/google-samples/microservices-demo/frontend:v0.10.5\n          ports:\n          - containerPort: 8080\n          readinessProbe:\n            initialDelaySeconds: 10\n            httpGet:\n              path: \"/_healthz\"\n              port: 8080\n              httpHeaders:\n              - name: \"Cookie\"\n                value: \"shop_session-id=x-readiness-probe\"\n          livenessProbe:\n            initialDelaySeconds: 10\n            httpGet:\n              path: \"/_healthz\"\n              port: 8080\n              httpHeaders:\n              - name: \"Cookie\"\n                value: \"shop_session-id=x-liveness-probe\"\n          env:\n          - name: PORT\n            value: \"8080\"\n          - name: PRODUCT_CATALOG_SERVICE_ADDR\n            value: \"productcatalogservice:3550\"\n          - name: CURRENCY_SERVICE_ADDR\n            value: \"currencyservice:7000\"\n          - name: CART_SERVICE_ADDR\n            value: \"cartservice:7070\"\n          - name: RECOMMENDATION_SERVICE_ADDR\n            value: \"recommendationservice:8080\"\n          - name: SHIPPING_SERVICE_ADDR\n            value: \"shippingservice:50051\"\n          - name: CHECKOUT_SERVICE_ADDR\n            value: \"checkoutservice:5050\"\n          - name: AD_SERVICE_ADDR\n            value: \"adservice:9555\"\n          - name: SHOPPING_ASSISTANT_SERVICE_ADDR\n            value: \"shoppingassistantservice:80\"\n          # # ENV_PLATFORM: One of: local, gcp, aws, azure, onprem, alibaba\n          # # When not set, defaults to \"local\" unless running in GKE, otherwies auto-sets to gcp\n          # - name: ENV_PLATFORM\n          #   value: \"aws\"\n          - name: ENABLE_PROFILER\n            value: \"0\"\n          # - name: CYMBAL_BRANDING\n          #   value: \"true\"\n          # - name: ENABLE_ASSISTANT\n          #   value: \"true\"\n          # - name: FRONTEND_MESSAGE\n          #   value: \"Replace this with a message you want to display on all pages.\"\n          # As part of an optional Google Cloud demo, you can run an optional microservice called the \"packaging service\".\n          # - name: PACKAGING_SERVICE_URL\n          #   value: \"\" # This value would look like \"http://123.123.123\"\n          resources:\n            requests:\n              cpu: 100m\n              memory: 64Mi\n            limits:\n              cpu: 200m\n              memory: 128Mi\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: frontend\n  labels:\n    app: frontend\nspec:\n  type: ClusterIP\n  selector:\n    app: frontend\n  ports:\n  - name: http\n    port: 80\n    targetPort: 8080\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: frontend-external\n  labels:\n    app: frontend\nspec:\n  type: LoadBalancer\n  selector:\n    app: frontend\n  ports:\n  - name: http\n    port: 80\n    targetPort: 8080\n---\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: frontend\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: recommendationservice\n  labels:\n    app: recommendationservice\nspec:\n  selector:\n    matchLabels:\n      app: recommendationservice\n  template:\n    metadata:\n      labels:\n        app: recommendationservice\n    spec:\n      serviceAccountName: recommendationservice\n      terminationGracePeriodSeconds: 5\n      securityContext:\n        fsGroup: 1000\n        runAsGroup: 1000\n        runAsNonRoot: true\n        runAsUser: 1000\n      containers:\n      - name: server\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n              - ALL\n          privileged: false\n          readOnlyRootFilesystem: true\n        image: us-central1-docker.pkg.dev/google-samples/microservices-demo/recommendationservice:v0.10.5\n        ports:\n        - containerPort: 8080\n        readinessProbe:\n          periodSeconds: 5\n          grpc:\n            port: 8080\n        livenessProbe:\n          periodSeconds: 5\n          grpc:\n            port: 8080\n        env:\n        - name: PORT\n          value: \"8080\"\n        - name: PRODUCT_CATALOG_SERVICE_ADDR\n          value: \"productcatalogservice:3550\"\n        - name: DISABLE_PROFILER\n          value: \"1\"\n        resources:\n          requests:\n            cpu: 100m\n            memory: 220Mi\n          limits:\n            cpu: 200m\n            memory: 450Mi\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: recommendationservice\n  labels:\n    app: recommendationservice\nspec:\n  type: ClusterIP\n  selector:\n    app: recommendationservice\n  ports:\n  - name: grpc\n    port: 8080\n    targetPort: 8080\n---\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: recommendationservice\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: adservice\n  labels:\n    app: adservice\nspec:\n  selector:\n    matchLabels:\n      app: adservice\n  template:\n    metadata:\n      labels:\n        app: adservice\n    spec:\n      serviceAccountName: adservice\n      terminationGracePeriodSeconds: 5\n      securityContext:\n        fsGroup: 1000\n        runAsGroup: 1000\n        runAsNonRoot: true\n        runAsUser: 1000\n      containers:\n      - name: server\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n              - ALL\n          privileged: false\n          readOnlyRootFilesystem: true\n        image: us-central1-docker.pkg.dev/google-samples/microservices-demo/adservice:v0.10.5\n        ports:\n        - containerPort: 9555\n        env:\n        - name: PORT\n          value: \"9555\"\n        resources:\n          requests:\n            cpu: 200m\n            memory: 180Mi\n          limits:\n            cpu: 300m\n            memory: 300Mi\n        readinessProbe:\n          initialDelaySeconds: 20\n          periodSeconds: 15\n          grpc:\n            port: 9555\n        livenessProbe:\n          initialDelaySeconds: 20\n          periodSeconds: 15\n          grpc:\n            port: 9555\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: adservice\n  labels:\n    app: adservice\nspec:\n  type: ClusterIP\n  selector:\n    app: adservice\n  ports:\n  - name: grpc\n    port: 9555\n    targetPort: 9555\n---\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: adservice\n# [END gke_release_kubernetes_manifests_microservices_demo]\n"
  },
  {
    "path": "skaffold.yaml",
    "content": "# Copyright 2021 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\napiVersion: skaffold/v3\nkind: Config\nmetadata:\n  name: app\nbuild:\n  platforms: [\"linux/amd64\", \"linux/arm64\"]\n  artifacts:\n  # image tags are relative; to specify an image repo (e.g. GCR), you\n  # must provide a \"default repo\" using one of the methods described\n  # here:\n  # https://skaffold.dev/docs/concepts/#image-repository-handling\n  - image: emailservice\n    context: src/emailservice\n  - image: productcatalogservice\n    context: src/productcatalogservice\n  - image: recommendationservice\n    context: src/recommendationservice\n  - image: shoppingassistantservice\n    context: src/shoppingassistantservice\n  - image: shippingservice\n    context: src/shippingservice\n  - image: checkoutservice\n    context: src/checkoutservice\n  - image: paymentservice\n    context: src/paymentservice\n  - image: currencyservice\n    context: src/currencyservice\n  - image: cartservice\n    context: src/cartservice/src\n    docker:\n      dockerfile: Dockerfile\n  - image: frontend\n    context: src/frontend\n  - image: adservice\n    context: src/adservice\n  tagPolicy:\n    gitCommit: {}\n  local:\n    useDockerCLI: true\n    useBuildkit: true\nmanifests:\n  kustomize:\n    paths:\n    - kubernetes-manifests\ndeploy:\n  kubectl: {}\n# \"gcb\" profile allows building and pushing the images\n# on Google Container Builder without requiring docker\n# installed on the developer machine. However, note that\n# since GCB does not cache the builds, each build will\n# start from scratch and therefore take a long time.\n#\n# This is not used by default. To use it, run:\n#     skaffold run -p gcb\nprofiles:\n- name: gcb\n  build:\n    googleCloudBuild:\n      diskSizeGb: 300\n      machineType: N1_HIGHCPU_32\n      timeout: 4000s\n# \"debug\" profile replaces the default Dockerfile in cartservice with Dockerfile.debug,\n# which enables debugging via skaffold.\n#\n# This profile is used by default when running skaffold debug.\n- name: debug\n  activation:\n  - command: debug\n  patches:\n  - op: replace\n    path: /build/artifacts/7/docker/dockerfile\n    value: Dockerfile.debug\n# The \"network-policies\" profile is not used by default.\n# You can use it in isolation or in combination with other profiles:\n#     skaffold run -p network-policies, debug\n- name: network-policies\n  patches:\n  - op: add\n    path: /manifests/kustomize/paths/1\n    value: kustomize/components/network-policies\n---\napiVersion: skaffold/v3\nkind: Config\nmetadata:\n  name: loadgenerator\nrequires:\n- configs:\n  - app\nbuild:\n  platforms: [\"linux/amd64\", \"linux/arm64\"]\n  artifacts:\n  - image: loadgenerator\n    context: src/loadgenerator\n  local:\n    useDockerCLI: true\n    useBuildkit: true\nmanifests:\n  rawYaml:\n  - ./kubernetes-manifests/loadgenerator.yaml\ndeploy:\n  kubectl: {}\nprofiles:\n- name: gcb\n  build:\n    googleCloudBuild:\n      diskSizeGb: 300\n      machineType: N1_HIGHCPU_32\n      timeout: 4000s\n"
  },
  {
    "path": "src/adservice/Dockerfile",
    "content": "# Copyright 2020 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# Define a default value so it's not empty if the builder fails to provide it\nARG BUILDPLATFORM=linux/amd64\n\nFROM --platform=$BUILDPLATFORM eclipse-temurin:24.0.2_12-jdk-noble@sha256:dacac8e9a0df0d2bd24e702b4431132875c249930b70555ebd7ca285b5bee684 AS builder\n\nWORKDIR /app\n\nCOPY [\"build.gradle\", \"gradlew\", \"./\"]\nCOPY gradle gradle\nRUN chmod +x gradlew\nRUN ./gradlew downloadRepos\n\nCOPY . .\nRUN chmod +x gradlew\nRUN ./gradlew installDist\n\nFROM eclipse-temurin:25.0.2_10-jre-alpine@sha256:f10d6259d0798c1e12179b6bf3b63cea0d6843f7b09c9f9c9c422c50e44379ec\n\n# @TODO: https://github.com/GoogleCloudPlatform/microservices-demo/issues/2517\n# Download Stackdriver Profiler Java agent\n# RUN mkdir -p /opt/cprof && \\\n#     wget -q -O- https://storage.googleapis.com/cloud-profiler/java/latest/profiler_java_agent_alpine.tar.gz \\\n#     | tar xzv -C /opt/cprof && \\\n#     rm -rf profiler_java_agent.tar.gz\n\nWORKDIR /app\nCOPY --from=builder /app .\n\nEXPOSE 9555\nENTRYPOINT [\"/app/build/install/hipstershop/bin/AdService\"]\n"
  },
  {
    "path": "src/adservice/README.md",
    "content": "# Ad Service\n\nThe Ad service provides advertisement based on context keys. If no context keys are provided then it returns random ads.\n\n## Building locally\n\nThe Ad service uses gradlew to compile/install/distribute. Gradle wrapper is already part of the source code. To build Ad Service, run:\n\n```\n./gradlew installDist\n```\nIt will create executable script src/adservice/build/install/hipstershop/bin/AdService\n\n### Upgrading gradle version\nIf you need to upgrade the version of gradle then run\n\n```\n./gradlew wrapper --gradle-version <new-version>\n```\n\n## Building docker image\n\nFrom `src/adservice/`, run:\n\n```\ndocker build ./\n```\n\n"
  },
  {
    "path": "src/adservice/build.gradle",
    "content": "plugins {\n    id 'com.google.protobuf' version '0.9.6'\n    id 'com.github.sherter.google-java-format' version '0.9'\n    id 'idea'\n    id 'application'\n}\n\nrepositories {\n    mavenCentral()\n    mavenLocal()\n}\n\ndescription = 'Ad Service'\ngroup = \"adservice\"\nversion = \"0.1.0-SNAPSHOT\"\n\ndef grpcVersion = \"1.79.0\"\ndef jacksonCoreVersion = \"2.21.1\"\ndef jacksonDatabindVersion = \"2.21.1\"\ndef protocVersion = \"4.34.0\"\n\ntasks.withType(JavaCompile) {\n    sourceCompatibility = JavaVersion.VERSION_21\n    targetCompatibility = JavaVersion.VERSION_21\n}\n\next {\n    speed = project.hasProperty('speed') ? project.getProperty('speed') : false\n    offlineCompile = new File(\"$buildDir/output/lib\")\n}\n\ndependencies {\n    if (speed) {\n        implementation fileTree(dir: offlineCompile, include: '*.jar')\n    } else {\n        implementation \"com.google.api.grpc:proto-google-common-protos:2.66.0\",\n                \"javax.annotation:javax.annotation-api:1.3.2\",\n                \"io.grpc:grpc-protobuf:${grpcVersion}\",\n                \"io.grpc:grpc-stub:${grpcVersion}\",\n                \"io.grpc:grpc-netty:${grpcVersion}\",\n                \"io.grpc:grpc-services:${grpcVersion}\",\n                \"io.grpc:grpc-census:${grpcVersion}\",\n                \"org.apache.logging.log4j:log4j-core:2.25.3\",\n                \"com.google.protobuf:protobuf-java:${protocVersion}\"\n\n        runtimeOnly \"com.fasterxml.jackson.core:jackson-core:${jacksonCoreVersion}\",\n                \"com.fasterxml.jackson.core:jackson-databind:${jacksonDatabindVersion}\",\n                \"io.netty:netty-tcnative-boringssl-static:2.0.75.Final\"\n    }\n}\n\nprotobuf {\n    protoc {\n        artifact = \"com.google.protobuf:protoc:${protocVersion}\"\n    }\n    plugins {\n        grpc {\n            artifact = \"io.grpc:protoc-gen-grpc-java:${grpcVersion}\"\n        }\n    }\n    generateProtoTasks {\n        all()*.plugins {\n            grpc {}\n        }\n        ofSourceSet('main')\n    }\n}\n\ngoogleJavaFormat {\n    toolVersion '1.35.0'\n}\n\n// Inform IDEs like IntelliJ IDEA, Eclipse or NetBeans about the generated code.\nsourceSets {\n    main {\n        java {\n            srcDirs 'hipstershop'\n            srcDirs 'build/generated/source/proto/main/java/hipstershop'\n            srcDirs 'build/generated/source/proto/main/grpc/hipstershop'\n        }\n    }\n}\n\nstartScripts.enabled = false\n\n// This to cache dependencies during Docker image building. First build will take time.\n// Subsequent build will be incremental.\ntask downloadRepos(type: Copy) {\n    from configurations.compileClasspath\n    into offlineCompile\n    from configurations.compileClasspath\n    into offlineCompile\n}\n\ntask adService(type: CreateStartScripts) {\n    mainClass.set('hipstershop.AdService')\n    applicationName = 'AdService'\n    outputDir = new File(project.buildDir, 'tmp')\n    classpath = startScripts.classpath\n    // @TODO: https://github.com/GoogleCloudPlatform/microservices-demo/issues/2517\n    // defaultJvmOpts =\n    //          [\"-agentpath:/opt/cprof/profiler_java_agent.so=-cprof_service=adservice,-cprof_service_version=1.0.0\"]\n}\n\ntask adServiceClient(type: CreateStartScripts) {\n    mainClass.set('hipstershop.AdServiceClient')\n    applicationName = 'AdServiceClient'\n    outputDir = new File(project.buildDir, 'tmp')\n    classpath = startScripts.classpath\n    // @TODO: https://github.com/GoogleCloudPlatform/microservices-demo/issues/2517\n    // defaultJvmOpts =\n    //          [\"-agentpath:/opt/cprof/profiler_java_agent.so=-cprof_service=adserviceclient,-cprof_service_version=1.0.0\"]\n}\n\napplicationDistribution.into('bin') {\n    from(adService)\n    from(adServiceClient)\n    fileMode = 0755\n}\n"
  },
  {
    "path": "src/adservice/genproto.sh",
    "content": "#!/bin/bash -eu\n#\n# Copyright 2018 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# [START gke_adservice_genproto]\n# protos are needed in adservice folder for compiling during Docker build.\n\nmkdir -p proto && \\\ncp ../../protos/demo.proto src/main/proto\n\n# [END gke_adservice_genproto]"
  },
  {
    "path": "src/adservice/gradle/wrapper/gradle-wrapper.properties",
    "content": "distributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-8.14.4-bin.zip\nnetworkTimeout=10000\nvalidateDistributionUrl=true\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dists\n"
  },
  {
    "path": "src/adservice/gradlew",
    "content": "#!/bin/sh\n\n#\n# Copyright © 2015-2021 the original authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      https://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n#\n# SPDX-License-Identifier: Apache-2.0\n#\n\n##############################################################################\n#\n#   Gradle start up script for POSIX generated by Gradle.\n#\n#   Important for running:\n#\n#   (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is\n#       noncompliant, but you have some other compliant shell such as ksh or\n#       bash, then to run this script, type that shell name before the whole\n#       command line, like:\n#\n#           ksh Gradle\n#\n#       Busybox and similar reduced shells will NOT work, because this script\n#       requires all of these POSIX shell features:\n#         * functions;\n#         * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,\n#           «${var#prefix}», «${var%suffix}», and «$( cmd )»;\n#         * compound commands having a testable exit status, especially «case»;\n#         * various built-in commands including «command», «set», and «ulimit».\n#\n#   Important for patching:\n#\n#   (2) This script targets any POSIX shell, so it avoids extensions provided\n#       by Bash, Ksh, etc; in particular arrays are avoided.\n#\n#       The \"traditional\" practice of packing multiple parameters into a\n#       space-separated string is a well documented source of bugs and security\n#       problems, so this is (mostly) avoided, by progressively accumulating\n#       options in \"$@\", and eventually passing that to Java.\n#\n#       Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,\n#       and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;\n#       see the in-line comments for details.\n#\n#       There are tweaks for specific operating systems such as AIX, CygWin,\n#       Darwin, MinGW, and NonStop.\n#\n#   (3) This script is generated from the Groovy template\n#       https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt\n#       within the Gradle project.\n#\n#       You can find Gradle at https://github.com/gradle/gradle/.\n#\n##############################################################################\n\n# Attempt to set APP_HOME\n\n# Resolve links: $0 may be a link\napp_path=$0\n\n# Need this for daisy-chained symlinks.\nwhile\n    APP_HOME=${app_path%\"${app_path##*/}\"}  # leaves a trailing /; empty if no leading path\n    [ -h \"$app_path\" ]\ndo\n    ls=$( ls -ld \"$app_path\" )\n    link=${ls#*' -> '}\n    case $link in             #(\n      /*)   app_path=$link ;; #(\n      *)    app_path=$APP_HOME$link ;;\n    esac\ndone\n\n# This is normally unused\n# shellcheck disable=SC2034\nAPP_BASE_NAME=${0##*/}\n# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)\nAPP_HOME=$( cd -P \"${APP_HOME:-./}\" > /dev/null && printf '%s\\n' \"$PWD\" ) || exit\n\n# Use the maximum available, or set MAX_FD != -1 to use that value.\nMAX_FD=maximum\n\nwarn () {\n    echo \"$*\"\n} >&2\n\ndie () {\n    echo\n    echo \"$*\"\n    echo\n    exit 1\n} >&2\n\n# OS specific support (must be 'true' or 'false').\ncygwin=false\nmsys=false\ndarwin=false\nnonstop=false\ncase \"$( uname )\" in                #(\n  CYGWIN* )         cygwin=true  ;; #(\n  Darwin* )         darwin=true  ;; #(\n  MSYS* | MINGW* )  msys=true    ;; #(\n  NONSTOP* )        nonstop=true ;;\nesac\n\nCLASSPATH=\"\\\\\\\"\\\\\\\"\"\n\n\n# Determine the Java command to use to start the JVM.\nif [ -n \"$JAVA_HOME\" ] ; then\n    if [ -x \"$JAVA_HOME/jre/sh/java\" ] ; then\n        # IBM's JDK on AIX uses strange locations for the executables\n        JAVACMD=$JAVA_HOME/jre/sh/java\n    else\n        JAVACMD=$JAVA_HOME/bin/java\n    fi\n    if [ ! -x \"$JAVACMD\" ] ; then\n        die \"ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nelse\n    JAVACMD=java\n    if ! command -v java >/dev/null 2>&1\n    then\n        die \"ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nfi\n\n# Increase the maximum file descriptors if we can.\nif ! \"$cygwin\" && ! \"$darwin\" && ! \"$nonstop\" ; then\n    case $MAX_FD in #(\n      max*)\n        # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.\n        # shellcheck disable=SC2039,SC3045\n        MAX_FD=$( ulimit -H -n ) ||\n            warn \"Could not query maximum file descriptor limit\"\n    esac\n    case $MAX_FD in  #(\n      '' | soft) :;; #(\n      *)\n        # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.\n        # shellcheck disable=SC2039,SC3045\n        ulimit -n \"$MAX_FD\" ||\n            warn \"Could not set maximum file descriptor limit to $MAX_FD\"\n    esac\nfi\n\n# Collect all arguments for the java command, stacking in reverse order:\n#   * args from the command line\n#   * the main class name\n#   * -classpath\n#   * -D...appname settings\n#   * --module-path (only if needed)\n#   * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.\n\n# For Cygwin or MSYS, switch paths to Windows format before running java\nif \"$cygwin\" || \"$msys\" ; then\n    APP_HOME=$( cygpath --path --mixed \"$APP_HOME\" )\n    CLASSPATH=$( cygpath --path --mixed \"$CLASSPATH\" )\n\n    JAVACMD=$( cygpath --unix \"$JAVACMD\" )\n\n    # Now convert the arguments - kludge to limit ourselves to /bin/sh\n    for arg do\n        if\n            case $arg in                                #(\n              -*)   false ;;                            # don't mess with options #(\n              /?*)  t=${arg#/} t=/${t%%/*}              # looks like a POSIX filepath\n                    [ -e \"$t\" ] ;;                      #(\n              *)    false ;;\n            esac\n        then\n            arg=$( cygpath --path --ignore --mixed \"$arg\" )\n        fi\n        # Roll the args list around exactly as many times as the number of\n        # args, so each arg winds up back in the position where it started, but\n        # possibly modified.\n        #\n        # NB: a `for` loop captures its iteration list before it begins, so\n        # changing the positional parameters here affects neither the number of\n        # iterations, nor the values presented in `arg`.\n        shift                   # remove old arg\n        set -- \"$@\" \"$arg\"      # push replacement arg\n    done\nfi\n\n\n# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nDEFAULT_JVM_OPTS='\"-Xmx64m\" \"-Xms64m\"'\n\n# Collect all arguments for the java command:\n#   * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,\n#     and any embedded shellness will be escaped.\n#   * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be\n#     treated as '${Hostname}' itself on the command line.\n\nset -- \\\n        \"-Dorg.gradle.appname=$APP_BASE_NAME\" \\\n        -classpath \"$CLASSPATH\" \\\n        -jar \"$APP_HOME/gradle/wrapper/gradle-wrapper.jar\" \\\n        \"$@\"\n\n# Stop when \"xargs\" is not available.\nif ! command -v xargs >/dev/null 2>&1\nthen\n    die \"xargs is not available\"\nfi\n\n# Use \"xargs\" to parse quoted args.\n#\n# With -n1 it outputs one arg per line, with the quotes and backslashes removed.\n#\n# In Bash we could simply go:\n#\n#   readarray ARGS < <( xargs -n1 <<<\"$var\" ) &&\n#   set -- \"${ARGS[@]}\" \"$@\"\n#\n# but POSIX shell has neither arrays nor command substitution, so instead we\n# post-process each arg (as a line of input to sed) to backslash-escape any\n# character that might be a shell metacharacter, then use eval to reverse\n# that process (while maintaining the separation between arguments), and wrap\n# the whole thing up as a single \"set\" statement.\n#\n# This will of course break if any of these variables contains a newline or\n# an unmatched quote.\n#\n\neval \"set -- $(\n        printf '%s\\n' \"$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS\" |\n        xargs -n1 |\n        sed ' s~[^-[:alnum:]+,./:=@_]~\\\\&~g; ' |\n        tr '\\n' ' '\n    )\" '\"$@\"'\n\nexec \"$JAVACMD\" \"$@\"\n"
  },
  {
    "path": "src/adservice/gradlew.bat",
    "content": "@rem\n@rem Copyright 2015 the original author or authors.\n@rem\n@rem Licensed under the Apache License, Version 2.0 (the \"License\");\n@rem you may not use this file except in compliance with the License.\n@rem You may obtain a copy of the License at\n@rem\n@rem      https://www.apache.org/licenses/LICENSE-2.0\n@rem\n@rem Unless required by applicable law or agreed to in writing, software\n@rem distributed under the License is distributed on an \"AS IS\" BASIS,\n@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n@rem See the License for the specific language governing permissions and\n@rem limitations under the License.\n@rem\n@rem SPDX-License-Identifier: Apache-2.0\n@rem\n\n@if \"%DEBUG%\"==\"\" @echo off\n@rem ##########################################################################\n@rem\n@rem  Gradle startup script for Windows\n@rem\n@rem ##########################################################################\n\n@rem Set local scope for the variables with windows NT shell\nif \"%OS%\"==\"Windows_NT\" setlocal\n\nset DIRNAME=%~dp0\nif \"%DIRNAME%\"==\"\" set DIRNAME=.\n@rem This is normally unused\nset APP_BASE_NAME=%~n0\nset APP_HOME=%DIRNAME%\n\n@rem Resolve any \".\" and \"..\" in APP_HOME to make it shorter.\nfor %%i in (\"%APP_HOME%\") do set APP_HOME=%%~fi\n\n@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nset DEFAULT_JVM_OPTS=\"-Xmx64m\" \"-Xms64m\"\n\n@rem Find java.exe\nif defined JAVA_HOME goto findJavaFromJavaHome\n\nset JAVA_EXE=java.exe\n%JAVA_EXE% -version >NUL 2>&1\nif %ERRORLEVEL% equ 0 goto execute\n\necho. 1>&2\necho ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2\necho. 1>&2\necho Please set the JAVA_HOME variable in your environment to match the 1>&2\necho location of your Java installation. 1>&2\n\ngoto fail\n\n:findJavaFromJavaHome\nset JAVA_HOME=%JAVA_HOME:\"=%\nset JAVA_EXE=%JAVA_HOME%/bin/java.exe\n\nif exist \"%JAVA_EXE%\" goto execute\n\necho. 1>&2\necho ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2\necho. 1>&2\necho Please set the JAVA_HOME variable in your environment to match the 1>&2\necho location of your Java installation. 1>&2\n\ngoto fail\n\n:execute\n@rem Setup the command line\n\nset CLASSPATH=\n\n\n@rem Execute Gradle\n\"%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\" %*\n\n:end\n@rem End local scope for the variables with windows NT shell\nif %ERRORLEVEL% equ 0 goto mainEnd\n\n:fail\nrem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of\nrem the _cmd.exe /c_ return code!\nset EXIT_CODE=%ERRORLEVEL%\nif %EXIT_CODE% equ 0 set EXIT_CODE=1\nif not \"\"==\"%GRADLE_EXIT_CONSOLE%\" exit %EXIT_CODE%\nexit /b %EXIT_CODE%\n\n:mainEnd\nif \"%OS%\"==\"Windows_NT\" endlocal\n\n:omega\n"
  },
  {
    "path": "src/adservice/settings.gradle",
    "content": "rootProject.name = 'hipstershop'\n"
  },
  {
    "path": "src/adservice/src/main/java/hipstershop/AdService.java",
    "content": "/*\n * Copyright 2018, Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage hipstershop;\n\nimport com.google.common.collect.ImmutableListMultimap;\nimport com.google.common.collect.ImmutableMap;\nimport com.google.common.collect.Iterables;\nimport hipstershop.Demo.Ad;\nimport hipstershop.Demo.AdRequest;\nimport hipstershop.Demo.AdResponse;\nimport io.grpc.Server;\nimport io.grpc.ServerBuilder;\nimport io.grpc.StatusRuntimeException;\nimport io.grpc.health.v1.HealthCheckResponse.ServingStatus;\nimport io.grpc.services.*;\nimport io.grpc.stub.StreamObserver;\nimport java.io.IOException;\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.List;\nimport java.util.Random;\nimport java.util.concurrent.TimeUnit;\nimport org.apache.logging.log4j.Level;\nimport org.apache.logging.log4j.LogManager;\nimport org.apache.logging.log4j.Logger;\n\npublic final class AdService {\n\n  private static final Logger logger = LogManager.getLogger(AdService.class);\n\n  @SuppressWarnings(\"FieldCanBeLocal\")\n  private static int MAX_ADS_TO_SERVE = 2;\n\n  private Server server;\n  private HealthStatusManager healthMgr;\n\n  private static final AdService service = new AdService();\n\n  private void start() throws IOException {\n    int port = Integer.parseInt(System.getenv().getOrDefault(\"PORT\", \"9555\"));\n    healthMgr = new HealthStatusManager();\n\n    server =\n        ServerBuilder.forPort(port)\n            .addService(new AdServiceImpl())\n            .addService(healthMgr.getHealthService())\n            .build()\n            .start();\n    logger.info(\"Ad Service started, listening on \" + port);\n    Runtime.getRuntime()\n        .addShutdownHook(\n            new Thread(\n                () -> {\n                  // Use stderr here since the logger may have been reset by its JVM shutdown hook.\n                  System.err.println(\n                      \"*** shutting down gRPC ads server since JVM is shutting down\");\n                  AdService.this.stop();\n                  System.err.println(\"*** server shut down\");\n                }));\n    healthMgr.setStatus(\"\", ServingStatus.SERVING);\n  }\n\n  private void stop() {\n    if (server != null) {\n      healthMgr.clearStatus(\"\");\n      server.shutdown();\n    }\n  }\n\n  private static class AdServiceImpl extends hipstershop.AdServiceGrpc.AdServiceImplBase {\n\n    /**\n     * Retrieves ads based on context provided in the request {@code AdRequest}.\n     *\n     * @param req the request containing context.\n     * @param responseObserver the stream observer which gets notified with the value of {@code\n     *     AdResponse}\n     */\n    @Override\n    public void getAds(AdRequest req, StreamObserver<AdResponse> responseObserver) {\n      AdService service = AdService.getInstance();\n      try {\n        List<Ad> allAds = new ArrayList<>();\n        logger.info(\"received ad request (context_words=\" + req.getContextKeysList() + \")\");\n        if (req.getContextKeysCount() > 0) {\n          for (int i = 0; i < req.getContextKeysCount(); i++) {\n            Collection<Ad> ads = service.getAdsByCategory(req.getContextKeys(i));\n            allAds.addAll(ads);\n          }\n        } else {\n          allAds = service.getRandomAds();\n        }\n        if (allAds.isEmpty()) {\n          // Serve random ads.\n          allAds = service.getRandomAds();\n        }\n        AdResponse reply = AdResponse.newBuilder().addAllAds(allAds).build();\n        responseObserver.onNext(reply);\n        responseObserver.onCompleted();\n      } catch (StatusRuntimeException e) {\n        logger.log(Level.WARN, \"GetAds Failed with status {}\", e.getStatus());\n        responseObserver.onError(e);\n      }\n    }\n  }\n\n  private static final ImmutableListMultimap<String, Ad> adsMap = createAdsMap();\n\n  private Collection<Ad> getAdsByCategory(String category) {\n    return adsMap.get(category);\n  }\n\n  private static final Random random = new Random();\n\n  private List<Ad> getRandomAds() {\n    List<Ad> ads = new ArrayList<>(MAX_ADS_TO_SERVE);\n    Collection<Ad> allAds = adsMap.values();\n    for (int i = 0; i < MAX_ADS_TO_SERVE; i++) {\n      ads.add(Iterables.get(allAds, random.nextInt(allAds.size())));\n    }\n    return ads;\n  }\n\n  private static AdService getInstance() {\n    return service;\n  }\n\n  /** Await termination on the main thread since the grpc library uses daemon threads. */\n  private void blockUntilShutdown() throws InterruptedException {\n    if (server != null) {\n      server.awaitTermination();\n    }\n  }\n\n  private static ImmutableListMultimap<String, Ad> createAdsMap() {\n    Ad hairdryer =\n        Ad.newBuilder()\n            .setRedirectUrl(\"/product/2ZYFJ3GM2N\")\n            .setText(\"Hairdryer for sale. 50% off.\")\n            .build();\n    Ad tankTop =\n        Ad.newBuilder()\n            .setRedirectUrl(\"/product/66VCHSJNUP\")\n            .setText(\"Tank top for sale. 20% off.\")\n            .build();\n    Ad candleHolder =\n        Ad.newBuilder()\n            .setRedirectUrl(\"/product/0PUK6V6EV0\")\n            .setText(\"Candle holder for sale. 30% off.\")\n            .build();\n    Ad bambooGlassJar =\n        Ad.newBuilder()\n            .setRedirectUrl(\"/product/9SIQT8TOJO\")\n            .setText(\"Bamboo glass jar for sale. 10% off.\")\n            .build();\n    Ad watch =\n        Ad.newBuilder()\n            .setRedirectUrl(\"/product/1YMWWN1N4O\")\n            .setText(\"Watch for sale. Buy one, get second kit for free\")\n            .build();\n    Ad mug =\n        Ad.newBuilder()\n            .setRedirectUrl(\"/product/6E92ZMYYFZ\")\n            .setText(\"Mug for sale. Buy two, get third one for free\")\n            .build();\n    Ad loafers =\n        Ad.newBuilder()\n            .setRedirectUrl(\"/product/L9ECAV7KIM\")\n            .setText(\"Loafers for sale. Buy one, get second one for free\")\n            .build();\n    return ImmutableListMultimap.<String, Ad>builder()\n        .putAll(\"clothing\", tankTop)\n        .putAll(\"accessories\", watch)\n        .putAll(\"footwear\", loafers)\n        .putAll(\"hair\", hairdryer)\n        .putAll(\"decor\", candleHolder)\n        .putAll(\"kitchen\", bambooGlassJar, mug)\n        .build();\n  }\n\n  private static void initStats() {\n    if (System.getenv(\"DISABLE_STATS\") != null) {\n      logger.info(\"Stats disabled.\");\n      return;\n    }\n    logger.info(\"Stats enabled, but temporarily unavailable\");\n\n    long sleepTime = 10; /* seconds */\n    int maxAttempts = 5;\n\n    // TODO(arbrown) Implement OpenTelemetry stats\n\n  }\n\n  private static void initTracing() {\n    if (System.getenv(\"DISABLE_TRACING\") != null) {\n      logger.info(\"Tracing disabled.\");\n      return;\n    }\n    logger.info(\"Tracing enabled but temporarily unavailable\");\n    logger.info(\"See https://github.com/GoogleCloudPlatform/microservices-demo/issues/422 for more info.\");\n\n    // TODO(arbrown) Implement OpenTelemetry tracing\n    \n    logger.info(\"Tracing enabled - Stackdriver exporter initialized.\");\n  }\n\n  /** Main launches the server from the command line. */\n  public static void main(String[] args) throws IOException, InterruptedException {\n\n    new Thread(\n            () -> {\n              initStats();\n              initTracing();\n            })\n        .start();\n\n    // Start the RPC server. You shouldn't see any output from gRPC before this.\n    logger.info(\"AdService starting.\");\n    final AdService service = AdService.getInstance();\n    service.start();\n    service.blockUntilShutdown();\n  }\n}\n"
  },
  {
    "path": "src/adservice/src/main/java/hipstershop/AdServiceClient.java",
    "content": "/*\n * Copyright 2018, Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage hipstershop;\n\nimport hipstershop.Demo.Ad;\nimport hipstershop.Demo.AdRequest;\nimport hipstershop.Demo.AdResponse;\nimport io.grpc.ManagedChannel;\nimport io.grpc.ManagedChannelBuilder;\nimport io.grpc.StatusRuntimeException;\nimport java.util.concurrent.TimeUnit;\nimport javax.annotation.Nullable;\nimport org.apache.logging.log4j.Level;\nimport org.apache.logging.log4j.LogManager;\nimport org.apache.logging.log4j.Logger;\n\n/** A simple client that requests ads from the Ads Service. */\npublic class AdServiceClient {\n\n  private static final Logger logger = LogManager.getLogger(AdServiceClient.class);\n\n  private final ManagedChannel channel;\n  private final hipstershop.AdServiceGrpc.AdServiceBlockingStub blockingStub;\n\n  /** Construct client connecting to Ad Service at {@code host:port}. */\n  private AdServiceClient(String host, int port) {\n    this(\n        ManagedChannelBuilder.forAddress(host, port)\n            // Channels are secure by default (via SSL/TLS). For the example we disable TLS to avoid\n            // needing certificates.\n            .usePlaintext()\n            .build());\n  }\n\n  /** Construct client for accessing RouteGuide server using the existing channel. */\n  private AdServiceClient(ManagedChannel channel) {\n    this.channel = channel;\n    blockingStub = hipstershop.AdServiceGrpc.newBlockingStub(channel);\n  }\n\n  private void shutdown() throws InterruptedException {\n    channel.shutdown().awaitTermination(5, TimeUnit.SECONDS);\n  }\n\n  /** Get Ads from Server. */\n  public void getAds(String contextKey) {\n    logger.info(\"Get Ads with context \" + contextKey + \" ...\");\n    AdRequest request = AdRequest.newBuilder().addContextKeys(contextKey).build();\n    AdResponse response;\n\n    try {\n      response = blockingStub.getAds(request);\n    } catch (StatusRuntimeException e) {\n      logger.log(Level.WARN, \"RPC failed: \" + e.getStatus());\n      return;\n    } \n    for (Ad ads : response.getAdsList()) {\n      logger.info(\"Ads: \" + ads.getText());\n    }\n  }\n\n  private static int getPortOrDefaultFromArgs(String[] args) {\n    int portNumber = 9555;\n    if (2 < args.length) {\n      try {\n        portNumber = Integer.parseInt(args[2]);\n      } catch (NumberFormatException e) {\n        logger.warn(String.format(\"Port %s is invalid, use default port %d.\", args[2], 9555));\n      }\n    }\n    return portNumber;\n  }\n\n  private static String getStringOrDefaultFromArgs(\n      String[] args, int index, @Nullable String defaultString) {\n    String s = defaultString;\n    if (index < args.length) {\n      s = args[index];\n    }\n    return s;\n  }\n\n  /**\n   * Ads Service Client main. If provided, the first element of {@code args} is the context key to\n   * get the ads from the Ads Service\n   */\n  public static void main(String[] args) throws InterruptedException {\n    // Add final keyword to pass checkStyle.\n    final String contextKeys = getStringOrDefaultFromArgs(args, 0, \"camera\");\n    final String host = getStringOrDefaultFromArgs(args, 1, \"localhost\");\n    final int serverPort = getPortOrDefaultFromArgs(args);\n\n    AdServiceClient client = new AdServiceClient(host, serverPort);\n    try {\n      client.getAds(contextKeys);\n    } finally {\n      client.shutdown();\n    }\n\n    logger.info(\"Exiting AdServiceClient...\");\n  }\n}\n"
  },
  {
    "path": "src/adservice/src/main/proto/demo.proto",
    "content": "// Copyright 2020 Google LLC\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\nsyntax = \"proto3\";\n\npackage hipstershop;\n\n// -----------------Cart service-----------------\n\nservice CartService {\n    rpc AddItem(AddItemRequest) returns (Empty) {}\n    rpc GetCart(GetCartRequest) returns (Cart) {}\n    rpc EmptyCart(EmptyCartRequest) returns (Empty) {}\n}\n\nmessage CartItem {\n    string product_id = 1;\n    int32  quantity = 2;\n}\n\nmessage AddItemRequest {\n    string user_id = 1;\n    CartItem item = 2;\n}\n\nmessage EmptyCartRequest {\n    string user_id = 1;\n}\n\nmessage GetCartRequest {\n    string user_id = 1;\n}\n\nmessage Cart {\n    string user_id = 1;\n    repeated CartItem items = 2;\n}\n\nmessage Empty {}\n\n// ---------------Recommendation service----------\n\nservice RecommendationService {\n  rpc ListRecommendations(ListRecommendationsRequest) returns (ListRecommendationsResponse){}\n}\n\nmessage ListRecommendationsRequest {\n    string user_id = 1;\n    repeated string product_ids = 2;\n}\n\nmessage ListRecommendationsResponse {\n    repeated string product_ids = 1;\n}\n\n// ---------------Product Catalog----------------\n\nservice ProductCatalogService {\n    rpc ListProducts(Empty) returns (ListProductsResponse) {}\n    rpc GetProduct(GetProductRequest) returns (Product) {}\n    rpc SearchProducts(SearchProductsRequest) returns (SearchProductsResponse) {}\n}\n\nmessage Product {\n    string id = 1;\n    string name = 2;\n    string description = 3;\n    string picture = 4;\n    Money price_usd = 5;\n\n    // Categories such as \"clothing\" or \"kitchen\" that can be used to look up\n    // other related products.\n    repeated string categories = 6;\n}\n\nmessage ListProductsResponse {\n    repeated Product products = 1;\n}\n\nmessage GetProductRequest {\n    string id = 1;\n}\n\nmessage SearchProductsRequest {\n    string query = 1;\n}\n\nmessage SearchProductsResponse {\n    repeated Product results = 1;\n}\n\n// ---------------Shipping Service----------\n\nservice ShippingService {\n    rpc GetQuote(GetQuoteRequest) returns (GetQuoteResponse) {}\n    rpc ShipOrder(ShipOrderRequest) returns (ShipOrderResponse) {}\n}\n\nmessage GetQuoteRequest {\n    Address address = 1;\n    repeated CartItem items = 2;\n}\n\nmessage GetQuoteResponse {\n    Money cost_usd = 1;\n}\n\nmessage ShipOrderRequest {\n    Address address = 1;\n    repeated CartItem items = 2;\n}\n\nmessage ShipOrderResponse {\n    string tracking_id = 1;\n}\n\nmessage Address {\n    string street_address = 1;\n    string city = 2;\n    string state = 3;\n    string country = 4;\n    int32 zip_code = 5;\n}\n\n// -----------------Currency service-----------------\n\nservice CurrencyService {\n    rpc GetSupportedCurrencies(Empty) returns (GetSupportedCurrenciesResponse) {}\n    rpc Convert(CurrencyConversionRequest) returns (Money) {}\n}\n\n// Represents an amount of money with its currency type.\nmessage Money {\n    // The 3-letter currency code defined in ISO 4217.\n    string currency_code = 1;\n\n    // The whole units of the amount.\n    // For example if `currencyCode` is `\"USD\"`, then 1 unit is one US dollar.\n    int64 units = 2;\n\n    // Number of nano (10^-9) units of the amount.\n    // The value must be between -999,999,999 and +999,999,999 inclusive.\n    // If `units` is positive, `nanos` must be positive or zero.\n    // If `units` is zero, `nanos` can be positive, zero, or negative.\n    // If `units` is negative, `nanos` must be negative or zero.\n    // For example $-1.75 is represented as `units`=-1 and `nanos`=-750,000,000.\n    int32 nanos = 3;\n}\n\nmessage GetSupportedCurrenciesResponse {\n    // The 3-letter currency code defined in ISO 4217.\n    repeated string currency_codes = 1;\n}\n\nmessage CurrencyConversionRequest {\n    Money from = 1;\n\n    // The 3-letter currency code defined in ISO 4217.\n    string to_code = 2;\n}\n\n// -------------Payment service-----------------\n\nservice PaymentService {\n    rpc Charge(ChargeRequest) returns (ChargeResponse) {}\n}\n\nmessage CreditCardInfo {\n    string credit_card_number = 1;\n    int32 credit_card_cvv = 2;\n    int32 credit_card_expiration_year = 3;\n    int32 credit_card_expiration_month = 4;\n}\n\nmessage ChargeRequest {\n    Money amount = 1;\n    CreditCardInfo credit_card = 2;\n}\n\nmessage ChargeResponse {\n    string transaction_id = 1;\n}\n\n// -------------Email service-----------------\n\nservice EmailService {\n    rpc SendOrderConfirmation(SendOrderConfirmationRequest) returns (Empty) {}\n}\n\nmessage OrderItem {\n    CartItem item = 1;\n    Money cost = 2;\n}\n\nmessage OrderResult {\n    string   order_id = 1;\n    string   shipping_tracking_id = 2;\n    Money shipping_cost = 3;\n    Address  shipping_address = 4;\n    repeated OrderItem items = 5;\n}\n\nmessage SendOrderConfirmationRequest {\n    string email = 1;\n    OrderResult order = 2;\n}\n\n\n// -------------Checkout service-----------------\n\nservice CheckoutService {\n    rpc PlaceOrder(PlaceOrderRequest) returns (PlaceOrderResponse) {}\n}\n\nmessage PlaceOrderRequest {\n    string user_id = 1;\n    string user_currency = 2;\n\n    Address address = 3;\n    string email = 5;\n    CreditCardInfo credit_card = 6;\n}\n\nmessage PlaceOrderResponse {\n    OrderResult order = 1;\n}\n\n// ------------Ad service------------------\n\nservice AdService {\n    rpc GetAds(AdRequest) returns (AdResponse) {}\n}\n\nmessage AdRequest {\n    // List of important key words from the current page describing the context.\n    repeated string context_keys = 1;\n}\n\nmessage AdResponse {\n    repeated Ad ads = 1;\n}\n\nmessage Ad {\n    // url to redirect to when an ad is clicked.\n    string redirect_url = 1;\n\n    // short advertisement text to display.\n    string text = 2;\n}\n"
  },
  {
    "path": "src/adservice/src/main/resources/log4j2.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--\n Copyright 2020 Google LLC\n\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n      http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n-->\n\n<Configuration status=\"WARN\">\n  <Appenders>\n    <Console name=\"STDOUT\" target=\"SYSTEM_OUT\">\n\n      <!-- This is a JSON format that can be read by the Stackdriver Logging agent. The trace ID,\n           span ID, sampling decision, and timestamp are interpreted by Stackdriver. It uses the\n           special JSON keys that the Stackdriver Logging agent converts to \"trace\", \"spanId\",\n           \"traceSampled\", and \"timestamp\" in the Stackdriver LogEntry\n           (https://cloud.google.com/logging/docs/agent/configuration#special-fields). -->\n\n      <JsonLayout compact=\"true\" eventEol=\"true\">\n        <KeyValuePair key=\"logging.googleapis.com/trace\" value=\"$${ctx:traceId}\"/>\n        <KeyValuePair key=\"logging.googleapis.com/spanId\" value=\"$${ctx:spanId}\"/>\n        <KeyValuePair key=\"logging.googleapis.com/traceSampled\" value=\"$${ctx:traceSampled}\"/>\n        <KeyValuePair key=\"time\" value=\"$${date:yyyy-MM-dd}T$${date:HH:mm:ss.SSS}Z\"/>\n     </JsonLayout>\n\n    </Console>\n  </Appenders>\n  <Loggers>\n    <Logger name=\"io.grpc.netty\" level=\"INFO\"/>\n    <Logger name=\"io.netty\" level=\"INFO\"/>\n    <Logger name=\"sun.net\" level=\"INFO\"/>\n    <Root level=\"TRACE\">\n      <AppenderRef ref=\"STDOUT\"/>\n    </Root>\n  </Loggers>\n</Configuration>\n"
  },
  {
    "path": "src/cartservice/cartservice.sln",
    "content": "﻿\nMicrosoft Visual Studio Solution File, Format Version 12.00\n# Visual Studio 15\nVisualStudioVersion = 15.0.26124.0\nMinimumVisualStudioVersion = 15.0.26124.0\nProject(\"{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}\") = \"cartservice\", \"src\\cartservice.csproj\", \"{2348C29F-E8D3-4955-916D-D609CBC97FCB}\"\nEndProject\nProject(\"{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}\") = \"cartservice.tests\", \"tests\\cartservice.tests.csproj\", \"{59825342-CE64-4AFA-8744-781692C0811B}\"\nEndProject\nGlobal\n\tGlobalSection(SolutionConfigurationPlatforms) = preSolution\n\t\tDebug|Any CPU = Debug|Any CPU\n\t\tDebug|x64 = Debug|x64\n\t\tDebug|x86 = Debug|x86\n\t\tRelease|Any CPU = Release|Any CPU\n\t\tRelease|x64 = Release|x64\n\t\tRelease|x86 = Release|x86\n\tEndGlobalSection\n\tGlobalSection(SolutionProperties) = preSolution\n\t\tHideSolutionNode = FALSE\n\tEndGlobalSection\n\tGlobalSection(ProjectConfigurationPlatforms) = postSolution\n\t\t{2348C29F-E8D3-4955-916D-D609CBC97FCB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{2348C29F-E8D3-4955-916D-D609CBC97FCB}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{2348C29F-E8D3-4955-916D-D609CBC97FCB}.Debug|x64.ActiveCfg = Debug|Any CPU\n\t\t{2348C29F-E8D3-4955-916D-D609CBC97FCB}.Debug|x64.Build.0 = Debug|Any CPU\n\t\t{2348C29F-E8D3-4955-916D-D609CBC97FCB}.Debug|x86.ActiveCfg = Debug|Any CPU\n\t\t{2348C29F-E8D3-4955-916D-D609CBC97FCB}.Debug|x86.Build.0 = Debug|Any CPU\n\t\t{2348C29F-E8D3-4955-916D-D609CBC97FCB}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{2348C29F-E8D3-4955-916D-D609CBC97FCB}.Release|Any CPU.Build.0 = Release|Any CPU\n\t\t{2348C29F-E8D3-4955-916D-D609CBC97FCB}.Release|x64.ActiveCfg = Release|Any CPU\n\t\t{2348C29F-E8D3-4955-916D-D609CBC97FCB}.Release|x64.Build.0 = Release|Any CPU\n\t\t{2348C29F-E8D3-4955-916D-D609CBC97FCB}.Release|x86.ActiveCfg = Release|Any CPU\n\t\t{2348C29F-E8D3-4955-916D-D609CBC97FCB}.Release|x86.Build.0 = Release|Any CPU\n\t\t{59825342-CE64-4AFA-8744-781692C0811B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{59825342-CE64-4AFA-8744-781692C0811B}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{59825342-CE64-4AFA-8744-781692C0811B}.Debug|x64.ActiveCfg = Debug|Any CPU\n\t\t{59825342-CE64-4AFA-8744-781692C0811B}.Debug|x64.Build.0 = Debug|Any CPU\n\t\t{59825342-CE64-4AFA-8744-781692C0811B}.Debug|x86.ActiveCfg = Debug|Any CPU\n\t\t{59825342-CE64-4AFA-8744-781692C0811B}.Debug|x86.Build.0 = Debug|Any CPU\n\t\t{59825342-CE64-4AFA-8744-781692C0811B}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{59825342-CE64-4AFA-8744-781692C0811B}.Release|Any CPU.Build.0 = Release|Any CPU\n\t\t{59825342-CE64-4AFA-8744-781692C0811B}.Release|x64.ActiveCfg = Release|Any CPU\n\t\t{59825342-CE64-4AFA-8744-781692C0811B}.Release|x64.Build.0 = Release|Any CPU\n\t\t{59825342-CE64-4AFA-8744-781692C0811B}.Release|x86.ActiveCfg = Release|Any CPU\n\t\t{59825342-CE64-4AFA-8744-781692C0811B}.Release|x86.Build.0 = Release|Any CPU\n\tEndGlobalSection\nEndGlobal\n"
  },
  {
    "path": "src/cartservice/src/.dockerignore",
    "content": "**/*.sh\n**/*.bat\n**/bin/\n**/obj/\n**/out/\nDockerfile*"
  },
  {
    "path": "src/cartservice/src/Dockerfile",
    "content": "# Copyright 2021 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# Define a default value so it's not empty if the builder fails to provide it\nARG BUILDPLATFORM=linux/amd64\n\n# https://mcr.microsoft.com/product/dotnet/sdk\nFROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:10.0.100-noble@sha256:c7445f141c04f1a6b454181bd098dcfa606c61ba0bd213d0a702489e5bd4cd71 AS builder\nARG TARGETARCH=amd64\nWORKDIR /app\nCOPY cartservice.csproj .\nRUN dotnet restore cartservice.csproj \\\n    -a $TARGETARCH\nCOPY . .\nRUN dotnet publish cartservice.csproj \\\n    -p:PublishSingleFile=true \\\n    -a $TARGETARCH \\\n    --self-contained true \\\n    -p:PublishTrimmed=true \\\n    -p:TrimMode=full \\\n    -c release \\\n    -o /cartservice\n\n# https://mcr.microsoft.com/product/dotnet/runtime-deps\nFROM mcr.microsoft.com/dotnet/runtime-deps:10.0.0-noble-chiseled@sha256:b857c8cb8d929183cfe4c6dd9994abba92a2639dd2dbaf06005379f815991604\n\nWORKDIR /app\nCOPY --from=builder /cartservice .\nEXPOSE 7070\nENV DOTNET_EnableDiagnostics=0 \\\n    ASPNETCORE_HTTP_PORTS=7070\nUSER 1000\nENTRYPOINT [\"/app/cartservice\"]\n"
  },
  {
    "path": "src/cartservice/src/Dockerfile.debug",
    "content": "# Copyright 2021 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nFROM mcr.microsoft.com/dotnet/sdk:10.0@sha256:478b9038d187e5b5c29bfa8173ded5d29e864b5ad06102a12106380ee01e2e49 AS build\nWORKDIR /app\nCOPY . .\nRUN dotnet restore cartservice.csproj\nRUN dotnet build \"./cartservice.csproj\" -c Debug -o /out\n\nFROM build AS publish\nRUN dotnet publish cartservice.csproj -c Debug -o /out\n\n# Building final image used in running container\nFROM mcr.microsoft.com/dotnet/aspnet:10.0@sha256:a04d1c1d2d26119049494057d80ea6cda25bbd8aef7c444a1fc1ef874fd3955b AS final\n# Installing procps on the container to enable debugging of .NET Core\nRUN apt-get update \\\n    && apt-get install -y unzip procps wget\nWORKDIR /app\nCOPY --from=publish /out .\nENV ASPNETCORE_HTTP_PORTS=7070\n\nENTRYPOINT [\"dotnet\", \"cartservice.dll\"]\n"
  },
  {
    "path": "src/cartservice/src/Program.cs",
    "content": "// Copyright 2020 Google LLC\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\nusing Microsoft.AspNetCore.Hosting;\nusing Microsoft.Extensions.Hosting;\nusing cartservice;\n\nCreateHostBuilder(args).Build().Run();\n\nstatic IHostBuilder CreateHostBuilder(string[] args) =>\n    Host.CreateDefaultBuilder(args)\n        .ConfigureWebHostDefaults(webBuilder =>\n        {\n            webBuilder.UseStartup<Startup>();\n        });"
  },
  {
    "path": "src/cartservice/src/Startup.cs",
    "content": "using System;\nusing Microsoft.AspNetCore.Builder;\nusing Microsoft.AspNetCore.Diagnostics.HealthChecks;\nusing Microsoft.AspNetCore.Hosting;\nusing Microsoft.AspNetCore.Http;\nusing Microsoft.Extensions.Configuration;\nusing Microsoft.Extensions.DependencyInjection;\nusing Microsoft.Extensions.Diagnostics.HealthChecks;\nusing Microsoft.Extensions.Hosting;\nusing cartservice.cartstore;\nusing cartservice.services;\nusing Microsoft.Extensions.Caching.StackExchangeRedis;\n\nnamespace cartservice\n{\n    public class Startup\n    {\n        public Startup(IConfiguration configuration)\n        {\n            Configuration = configuration;\n        }\n\n        public IConfiguration Configuration { get; }\n        \n        // This method gets called by the runtime. Use this method to add services to the container.\n        // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940\n        public void ConfigureServices(IServiceCollection services)\n        {\n            string redisAddress = Configuration[\"REDIS_ADDR\"];\n            string spannerProjectId = Configuration[\"SPANNER_PROJECT\"];\n            string spannerConnectionString = Configuration[\"SPANNER_CONNECTION_STRING\"];\n            string alloyDBConnectionString = Configuration[\"ALLOYDB_PRIMARY_IP\"];\n\n            if (!string.IsNullOrEmpty(redisAddress))\n            {\n                services.AddStackExchangeRedisCache(options =>\n                {\n                    options.Configuration = redisAddress;\n                });\n                services.AddSingleton<ICartStore, RedisCartStore>();\n            }\n            else if (!string.IsNullOrEmpty(spannerProjectId) || !string.IsNullOrEmpty(spannerConnectionString))\n            {\n                services.AddSingleton<ICartStore, SpannerCartStore>();\n            }\n            else if (!string.IsNullOrEmpty(alloyDBConnectionString))\n            {\n                Console.WriteLine(\"Creating AlloyDB cart store\");\n                services.AddSingleton<ICartStore, AlloyDBCartStore>();\n            }\n            else\n            {\n                Console.WriteLine(\"Redis cache host(hostname+port) was not specified. Starting a cart service using in memory store\");\n                services.AddDistributedMemoryCache();\n                services.AddSingleton<ICartStore, RedisCartStore>();\n            }\n\n\n            services.AddGrpc();\n        }\n\n        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.\n        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)\n        {\n            if (env.IsDevelopment())\n            {\n                app.UseDeveloperExceptionPage();\n            }\n\n            app.UseRouting();\n\n            app.UseEndpoints(endpoints =>\n            {\n                endpoints.MapGrpcService<CartService>();\n                endpoints.MapGrpcService<cartservice.services.HealthCheckService>();\n\n                endpoints.MapGet(\"/\", async context =>\n                {\n                    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\");\n                });\n            });\n        }\n    }\n}\n"
  },
  {
    "path": "src/cartservice/src/appsettings.json",
    "content": "{\n    \"Logging\": {\n      \"LogLevel\": {\n        \"Default\": \"Information\",\n        \"Microsoft\": \"Warning\",\n        \"Microsoft.Hosting.Lifetime\": \"Information\"\n      }\n    },\n    \"AllowedHosts\": \"*\",\n    \"Kestrel\": {\n      \"EndpointDefaults\": {\n        \"Protocols\": \"Http2\"\n      }\n    }\n  }"
  },
  {
    "path": "src/cartservice/src/cartservice.csproj",
    "content": "<Project Sdk=\"Microsoft.NET.Sdk.Web\">\n\n  <PropertyGroup>\n    <TargetFramework>net10.0</TargetFramework>\n  </PropertyGroup>\n\n  <ItemGroup>\n    <PackageReference Include=\"Grpc.AspNetCore\" Version=\"2.76.0\" />\n    <PackageReference Include=\"Grpc.HealthCheck\" Version=\"2.76.0\" />\n    <PackageReference Include=\"Microsoft.Extensions.Caching.StackExchangeRedis\" Version=\"10.0.3\" />\n    <PackageReference Include=\"Google.Cloud.Spanner.Data\" Version=\"5.12.0\" />\n    <PackageReference Include=\"Npgsql\" Version=\"10.0.1\" />\n    <PackageReference Include=\"Google.Cloud.SecretManager.V1\" Version=\"2.7.0\" />\n  </ItemGroup>\n\n  <ItemGroup>\n    <Protobuf Include=\"protos\\Cart.proto\" GrpcServices=\"Both\" />\n  </ItemGroup>\n</Project>\n"
  },
  {
    "path": "src/cartservice/src/cartstore/AlloyDBCartStore.cs",
    "content": "// Copyright 2021 Google LLC\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\nusing System;\nusing Grpc.Core;\nusing Npgsql;\nusing Microsoft.Extensions.Configuration;\nusing System.Threading.Tasks;\nusing Google.Api.Gax.ResourceNames;\nusing Google.Cloud.SecretManager.V1;\n \nnamespace cartservice.cartstore\n{\n    public class AlloyDBCartStore : ICartStore\n    {\n        private readonly string tableName;\n        private readonly string connectionString;\n\n        public AlloyDBCartStore(IConfiguration configuration)\n        {\n            // Create a Cloud Secrets client.\n            SecretManagerServiceClient client = SecretManagerServiceClient.Create();\n            var projectId = configuration[\"PROJECT_ID\"];\n            var secretId = configuration[\"ALLOYDB_SECRET_NAME\"];\n            SecretVersionName secretVersionName = new SecretVersionName(projectId, secretId, \"latest\");\n\n            AccessSecretVersionResponse result = client.AccessSecretVersion(secretVersionName);\n            // Convert the payload to a string. Payloads are bytes by default.\n            string alloyDBPassword = result.Payload.Data.ToStringUtf8().TrimEnd('\\r', '\\n');\n        \n            // TODO: Create a separate user for connecting within the application\n            // rather than using our superuser\n            string alloyDBUser = \"postgres\";\n            string databaseName = configuration[\"ALLOYDB_DATABASE_NAME\"];\n            // TODO: Consider splitting workloads into read vs. write and take\n            // advantage of the AlloyDB read pools\n            string primaryIPAddress = configuration[\"ALLOYDB_PRIMARY_IP\"];\n            connectionString = \"Host=\"          +\n                               primaryIPAddress +\n                               \";Username=\"     +\n                               alloyDBUser      +\n                               \";Password=\"     +\n                               alloyDBPassword  +\n                               \";Database=\"     +\n                               databaseName;\n\n            tableName = configuration[\"ALLOYDB_TABLE_NAME\"];\n        }\n\n\n    public async Task AddItemAsync(string userId, string productId, int quantity)\n    {\n        Console.WriteLine($\"AddItemAsync for {userId} called\");\n        try\n        {\n            await using var dataSource = NpgsqlDataSource.Create(connectionString);\n\n            var fetchCmd = $\"SELECT quantity FROM {tableName} WHERE userID='{userId}' AND productID='{productId}'\";\n            var currentQuantity = 0;\n            var cmdRead = dataSource.CreateCommand(fetchCmd);\n            await using (var reader = await cmdRead.ExecuteReaderAsync())\n            {\n                while (await reader.ReadAsync())\n                    currentQuantity += reader.GetInt32(0);\n            }\n\n            var totalQuantity = quantity + currentQuantity;\n\n            // Use INSERT ... ON CONFLICT to prevent duplicate key error\n            var insertCmd = $@\"\n                INSERT INTO {tableName} (userId, productId, quantity)\n                VALUES ('{userId}', '{productId}', {totalQuantity})\n                ON CONFLICT (userId, productId)\n                DO UPDATE SET quantity = {totalQuantity};\n            \";\n\n            await using (var cmdInsert = dataSource.CreateCommand(insertCmd))\n            {\n                await Task.Run(() =>\n                {\n                    return cmdInsert.ExecuteNonQueryAsync();\n                });\n            }\n        }\n        catch (Exception ex)\n        {   \n            throw new RpcException(\n                new Status(StatusCode.FailedPrecondition, $\"Unable to access cart storage due to an internal error. {ex}\"));\n        }\n    }\n\n\n        public async Task<Hipstershop.Cart> GetCartAsync(string userId)\n        {\n            Console.WriteLine($\"GetCartAsync called for userId={userId}\");\n            Hipstershop.Cart cart = new();\n            cart.UserId = userId;\n            try\n            {\n                await using var dataSource = NpgsqlDataSource.Create(connectionString);\n\n                var cartFetchCmd = $\"SELECT productId, quantity FROM {tableName} WHERE userId = '{userId}'\";\n                var cmd = dataSource.CreateCommand(cartFetchCmd);\n                await using (var reader = await cmd.ExecuteReaderAsync())\n                {\n                    while (await reader.ReadAsync())\n                    {\n                        Hipstershop.CartItem item = new()\n                        {\n                            ProductId = reader.GetString(0),\n                            Quantity = reader.GetInt32(1)\n                        };\n                        cart.Items.Add(item);\n                    }\n                }\n                await Task.Run(() =>\n                {\n                    return cart;\n                });\n            }\n            catch (Exception ex)\n            {\n                throw new RpcException(\n                    new Status(StatusCode.FailedPrecondition, $\"Unable to access cart storage due to an internal error. {ex}\"));\n            }\n            return cart;\n        }\n\n\n        public async Task EmptyCartAsync(string userId)\n        {\n            Console.WriteLine($\"EmptyCartAsync called for userId={userId}\");\n\n            try\n            {\n                await using var dataSource = NpgsqlDataSource.Create(connectionString);\n                var deleteCmd = $\"DELETE FROM {tableName} WHERE userID = '{userId}'\";\n                await using (var cmd = dataSource.CreateCommand(deleteCmd))\n                {\n                    await Task.Run(() =>\n                    {\n                        return cmd.ExecuteNonQueryAsync();\n                    });\n                }\n            }\n            catch (Exception ex)\n            {\n                throw new RpcException(\n                    new Status(StatusCode.FailedPrecondition, $\"Unable to access cart storage due to an internal error. {ex}\"));\n            }\n        }\n\n        public bool Ping()\n        {\n            try\n            {\n                return true;\n            }\n            catch (Exception)\n            {\n                return false;\n            }\n        }\n    }\n}\n\n"
  },
  {
    "path": "src/cartservice/src/cartstore/ICartStore.cs",
    "content": "// Copyright 2018 Google LLC\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\nusing System.Threading.Tasks;\n\nnamespace cartservice.cartstore\n{\n    public interface ICartStore\n    {\n        Task AddItemAsync(string userId, string productId, int quantity);\n        Task EmptyCartAsync(string userId);\n        Task<Hipstershop.Cart> GetCartAsync(string userId);\n        bool Ping();\n    }\n}"
  },
  {
    "path": "src/cartservice/src/cartstore/RedisCartStore.cs",
    "content": "// Copyright 2018 Google LLC\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\nusing System;\nusing System.Linq;\nusing System.Threading.Tasks;\nusing Grpc.Core;\nusing Microsoft.Extensions.Caching.Distributed;\nusing Google.Protobuf;\n\nnamespace cartservice.cartstore\n{\n    public class RedisCartStore : ICartStore\n    {\n        private readonly IDistributedCache _cache;\n\n        public RedisCartStore(IDistributedCache cache)\n        {\n            _cache = cache;\n        }\n\n        public async Task AddItemAsync(string userId, string productId, int quantity)\n        {\n            Console.WriteLine($\"AddItemAsync called with userId={userId}, productId={productId}, quantity={quantity}\");\n\n            try\n            {\n                Hipstershop.Cart cart;\n                var value = await _cache.GetAsync(userId);\n                if (value == null)\n                {\n                    cart = new Hipstershop.Cart();\n                    cart.UserId = userId;\n                    cart.Items.Add(new Hipstershop.CartItem { ProductId = productId, Quantity = quantity });\n                }\n                else\n                {\n                    cart = Hipstershop.Cart.Parser.ParseFrom(value);\n                    var existingItem = cart.Items.SingleOrDefault(i => i.ProductId == productId);\n                    if (existingItem == null)\n                    {\n                        cart.Items.Add(new Hipstershop.CartItem { ProductId = productId, Quantity = quantity });\n                    }\n                    else\n                    {\n                        existingItem.Quantity += quantity;\n                    }\n                }\n                await _cache.SetAsync(userId, cart.ToByteArray());\n            }\n            catch (Exception ex)\n            {\n                throw new RpcException(new Status(StatusCode.FailedPrecondition, $\"Can't access cart storage. {ex}\"));\n            }\n        }\n\n        public async Task EmptyCartAsync(string userId)\n        {\n            Console.WriteLine($\"EmptyCartAsync called with userId={userId}\");\n\n            try\n            {\n                var cart = new Hipstershop.Cart();\n                await _cache.SetAsync(userId, cart.ToByteArray());\n            }\n            catch (Exception ex)\n            {\n                throw new RpcException(new Status(StatusCode.FailedPrecondition, $\"Can't access cart storage. {ex}\"));\n            }\n        }\n\n        public async Task<Hipstershop.Cart> GetCartAsync(string userId)\n        {\n            Console.WriteLine($\"GetCartAsync called with userId={userId}\");\n\n            try\n            {\n                // Access the cart from the cache\n                var value = await _cache.GetAsync(userId);\n\n                if (value != null)\n                {\n                    return Hipstershop.Cart.Parser.ParseFrom(value);\n                }\n\n                // We decided to return empty cart in cases when user wasn't in the cache before\n                return new Hipstershop.Cart();\n            }\n            catch (Exception ex)\n            {\n                throw new RpcException(new Status(StatusCode.FailedPrecondition, $\"Can't access cart storage. {ex}\"));\n            }\n        }\n\n        public bool Ping()\n        {\n            try\n            {\n                return true;\n            }\n            catch (Exception)\n            {\n                return false;\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/cartservice/src/cartstore/SpannerCartStore.cs",
    "content": "// Copyright 2021 Google LLC\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\nusing System;\nusing Google.Cloud.Spanner.Data;\nusing Grpc.Core;\nusing Microsoft.Extensions.Configuration;\nusing System.Threading.Tasks;\n\nnamespace cartservice.cartstore\n{\n    public class SpannerCartStore : ICartStore\n    {\n        private static readonly string TableName = \"CartItems\";\n        private static readonly string DefaultInstanceName = \"onlineboutique\";\n        private static readonly string DefaultDatabaseName = \"carts\";\n        private readonly string databaseString;\n\n        public SpannerCartStore(IConfiguration configuration)\n        {\n            string spannerProjectId = configuration[\"SPANNER_PROJECT\"];\n            string spannerInstanceId = configuration[\"SPANNER_INSTANCE\"];\n            string spannerDatabaseId = configuration[\"SPANNER_DATABASE\"];\n            string spannerConnectionString = configuration[\"SPANNER_CONNECTION_STRING\"];\n            SpannerConnectionStringBuilder builder = new();\n            if (!string.IsNullOrEmpty(spannerConnectionString)) {\n                builder.DataSource = spannerConnectionString;\n                databaseString = builder.ToString();\n                Console.WriteLine($\"Spanner connection string: ${databaseString}\");\n                return;\n            }\n            if (string.IsNullOrEmpty(spannerInstanceId))\n                spannerInstanceId = DefaultInstanceName;\n            if (string.IsNullOrEmpty(spannerDatabaseId))\n                spannerDatabaseId = DefaultDatabaseName;\n            builder.DataSource =\n                $\"projects/{spannerProjectId}/instances/{spannerInstanceId}/databases/{spannerDatabaseId}\";\n            databaseString = builder.ToString();\n            Console.WriteLine($\"Built Spanner connection string: '{databaseString}'\");\n        }\n\n\n        public async Task AddItemAsync(string userId, string productId, int quantity)\n        {\n            Console.WriteLine($\"AddItemAsync for {userId} called\");\n            try\n            {\n                using SpannerConnection spannerConnection = new(databaseString);\n                await spannerConnection.RunWithRetriableTransactionAsync(async transaction =>\n                {\n                    int currentQuantity = 0;\n                    var quantityLookup = spannerConnection.CreateSelectCommand(\n                        $\"SELECT * FROM {TableName} WHERE userId = @userId AND productId = @productId\",\n                        new SpannerParameterCollection\n                        {\n                            { \"userId\", SpannerDbType.String },\n                            { \"productId\", SpannerDbType.String }\n                        });\n                    quantityLookup.Parameters[\"userId\"].Value = userId;\n                    quantityLookup.Parameters[\"productId\"].Value = productId;\n                    quantityLookup.Transaction = transaction;\n                    using (var reader = await quantityLookup.ExecuteReaderAsync())\n                    {\n                        while (await reader.ReadAsync()) {\n                            currentQuantity += reader.GetFieldValue<int>(\"quantity\");\n                        }\n                    }\n\n                    var cmd = spannerConnection.CreateInsertOrUpdateCommand(TableName,\n                        new SpannerParameterCollection\n                        {\n                            { \"userId\", SpannerDbType.String },\n                            { \"productId\", SpannerDbType.String },\n                            { \"quantity\", SpannerDbType.Int64 }\n                        });\n                    cmd.Parameters[\"userId\"].Value = userId;\n                    cmd.Parameters[\"productId\"].Value = productId;\n                    cmd.Parameters[\"quantity\"].Value = currentQuantity + quantity;\n                    cmd.Transaction = transaction;\n                    await Task.Run(() =>\n                    {\n                        return cmd.ExecuteNonQueryAsync();\n                    });\n                });\n            }\n            catch (Exception ex)\n            {\n                throw new RpcException(\n                    new Status(StatusCode.FailedPrecondition, $\"Can't access cart storage at {databaseString}. {ex}\"));\n            }\n        }\n\n\n        public async Task<Hipstershop.Cart> GetCartAsync(string userId)\n        {\n            Console.WriteLine($\"GetCartAsync called for userId={userId}\");\n            Hipstershop.Cart cart = new();\n            try\n            {\n                using SpannerConnection spannerConnection = new(databaseString);\n                var cmd = spannerConnection.CreateSelectCommand(\n                    $\"SELECT * FROM {TableName} WHERE userId = @userId\",\n                    new SpannerParameterCollection {\n                        { \"userId\", SpannerDbType.String }\n                    }\n                );\n                cmd.Parameters[\"userId\"].Value = userId;\n                using var reader = await cmd.ExecuteReaderAsync();\n                while (await reader.ReadAsync())\n                {\n                    // Only add the userId if something is in the cart.\n                    // This is based on how the cartservice example behaves.\n                    // An empty cart has no userId attached.\n                    cart.UserId = userId;\n\n                    Hipstershop.CartItem item = new()\n                    {\n                        ProductId = reader.GetFieldValue<string>(\"productId\"),\n                        Quantity = reader.GetFieldValue<int>(\"quantity\")\n                    };\n                    cart.Items.Add(item);\n                }\n\n                return cart;\n            }\n            catch (Exception ex)\n            {\n                throw new RpcException(\n                    new Status(StatusCode.FailedPrecondition, $\"Can't access cart storage at {databaseString}. {ex}\"));\n            }\n        }\n\n\n        public async Task EmptyCartAsync(string userId)\n        {\n            Console.WriteLine($\"EmptyCartAsync called for userId={userId}\");\n\n            try\n            {\n                using SpannerConnection spannerConnection = new(databaseString);\n                await Task.Run(() =>\n                {\n                    var cmd = spannerConnection.CreateDmlCommand(\n                        $\"DELETE FROM {TableName} WHERE userId = @userId\",\n                    new SpannerParameterCollection\n                    {\n                        { \"userId\", SpannerDbType.String }\n                    });\n                    cmd.Parameters[\"userId\"].Value = userId;\n                    return cmd.ExecuteNonQueryAsync();\n                });\n            }\n\n            catch (Exception ex)\n            {\n                throw new RpcException(\n                    new Status(StatusCode.FailedPrecondition, $\"Can't access cart storage at {databaseString}. {ex}\"));\n            }\n        }\n\n        public bool Ping()\n        {\n            try\n            {\n                return true;\n            }\n            catch (Exception)\n            {\n                return false;\n            }\n        }\n    }\n}\n\n"
  },
  {
    "path": "src/cartservice/src/protos/Cart.proto",
    "content": "// Copyright 2020 Google LLC\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\nsyntax = \"proto3\";\n\npackage hipstershop;\n\n// -----------------Cart service-----------------\n\nservice CartService {\n    rpc AddItem(AddItemRequest) returns (Empty) {}\n    rpc GetCart(GetCartRequest) returns (Cart) {}\n    rpc EmptyCart(EmptyCartRequest) returns (Empty) {}\n}\n\nmessage CartItem {\n    string product_id = 1;\n    int32  quantity = 2;\n}\n\nmessage AddItemRequest {\n    string user_id = 1;\n    CartItem item = 2;\n}\n\nmessage EmptyCartRequest {\n    string user_id = 1;\n}\n\nmessage GetCartRequest {\n    string user_id = 1;\n}\n\nmessage Cart {\n    string user_id = 1;\n    repeated CartItem items = 2;\n}\n\nmessage Empty {}\n"
  },
  {
    "path": "src/cartservice/src/services/CartService.cs",
    "content": "// Copyright 2020 Google LLC\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\nusing System;\nusing System.Threading.Tasks;\nusing Grpc.Core;\nusing Microsoft.Extensions.Logging;\nusing cartservice.cartstore;\nusing Hipstershop;\n\nnamespace cartservice.services\n{\n    public class CartService : Hipstershop.CartService.CartServiceBase\n    {\n        private readonly static Empty Empty = new Empty();\n        private readonly ICartStore _cartStore;\n\n        public CartService(ICartStore cartStore)\n        {\n            _cartStore = cartStore;\n        }\n\n        public async override Task<Empty> AddItem(AddItemRequest request, ServerCallContext context)\n        {\n            await _cartStore.AddItemAsync(request.UserId, request.Item.ProductId, request.Item.Quantity);\n            return Empty;\n        }\n\n        public override Task<Cart> GetCart(GetCartRequest request, ServerCallContext context)\n        {\n            return _cartStore.GetCartAsync(request.UserId);\n        }\n\n        public async override Task<Empty> EmptyCart(EmptyCartRequest request, ServerCallContext context)\n        {\n            await _cartStore.EmptyCartAsync(request.UserId);\n            return Empty;\n        }\n    }\n}"
  },
  {
    "path": "src/cartservice/src/services/HealthCheckService.cs",
    "content": "// Copyright 2020 Google LLC\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\nusing System;\nusing System.Threading.Tasks;\nusing Grpc.Core;\nusing Grpc.Health.V1;\nusing static Grpc.Health.V1.Health;\nusing cartservice.cartstore;\n\nnamespace cartservice.services\n{\n    internal class HealthCheckService : HealthBase\n    {\n        private ICartStore _cartStore { get; }\n\n        public HealthCheckService (ICartStore cartStore) \n        {\n            _cartStore = cartStore;\n        }\n\n        public override Task<HealthCheckResponse> Check(HealthCheckRequest request, ServerCallContext context)\n        {\n            Console.WriteLine (\"Checking CartService Health\");\n            return Task.FromResult(new HealthCheckResponse {\n                Status = _cartStore.Ping() ? HealthCheckResponse.Types.ServingStatus.Serving : HealthCheckResponse.Types.ServingStatus.NotServing\n            });\n        }\n    }\n}"
  },
  {
    "path": "src/cartservice/tests/CartServiceTests.cs",
    "content": "// Copyright 2018 Google LLC\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\nusing System;\nusing System.Threading.Tasks;\nusing Grpc.Net.Client;\nusing Hipstershop;\nusing Microsoft.AspNetCore.Hosting;\nusing Microsoft.AspNetCore.TestHost;\nusing Microsoft.Extensions.Hosting;\nusing Xunit;\nusing static Hipstershop.CartService;\n\nnamespace cartservice.tests\n{\n    public class CartServiceTests\n    {\n        private readonly IHostBuilder _host;\n\n        public CartServiceTests()\n        {\n            _host = new HostBuilder().ConfigureWebHost(webBuilder =>\n            {\n                webBuilder\n                    .UseStartup<Startup>()\n                    .UseTestServer();\n            });\n        }\n\n        [Fact]\n        public async Task GetItem_NoAddItemBefore_EmptyCartReturned()\n        {\n            // Setup test server and client\n            using var server = await _host.StartAsync();\n            var httpClient = server.GetTestClient();\n\n            string userId = Guid.NewGuid().ToString();\n\n            // Create a GRPC communication channel between the client and the server\n            var channel = GrpcChannel.ForAddress(httpClient.BaseAddress, new GrpcChannelOptions\n            {\n                HttpClient = httpClient\n            });\n\n            var cartClient = new CartServiceClient(channel);\n\n            var request = new GetCartRequest\n            {\n                UserId = userId,\n            };\n\n            var cart = await cartClient.GetCartAsync(request);\n            Assert.NotNull(cart);\n\n            // All grpc objects implement IEquitable, so we can compare equality with by-value semantics\n            Assert.Equal(new Cart(), cart);\n        }\n\n        [Fact]\n        public async Task AddItem_ItemExists_Updated()\n        {\n            // Setup test server and client\n            using var server = await _host.StartAsync();\n            var httpClient = server.GetTestClient();\n\n            string userId = Guid.NewGuid().ToString();\n\n            // Create a GRPC communication channel between the client and the server\n            var channel = GrpcChannel.ForAddress(httpClient.BaseAddress, new GrpcChannelOptions\n            {\n                HttpClient = httpClient\n            });\n\n            var client = new CartServiceClient(channel);\n            var request = new AddItemRequest\n            {\n                UserId = userId,\n                Item = new CartItem\n                {\n                    ProductId = \"1\",\n                    Quantity = 1\n                }\n            };\n\n            // First add - nothing should fail\n            await client.AddItemAsync(request);\n\n            // Second add of existing product - quantity should be updated\n            await client.AddItemAsync(request);\n\n            var getCartRequest = new GetCartRequest\n            {\n                UserId = userId\n            };\n            var cart = await client.GetCartAsync(getCartRequest);\n            Assert.NotNull(cart);\n            Assert.Equal(userId, cart.UserId);\n            Assert.Single(cart.Items);\n            Assert.Equal(2, cart.Items[0].Quantity);\n\n            // Cleanup\n            await client.EmptyCartAsync(new EmptyCartRequest { UserId = userId });\n        }\n\n        [Fact]\n        public async Task AddItem_New_Inserted()\n        {\n            // Setup test server and client\n            using var server = await _host.StartAsync();\n            var httpClient = server.GetTestClient();\n\n            string userId = Guid.NewGuid().ToString();\n\n            // Create a GRPC communication channel between the client and the server\n            var channel = GrpcChannel.ForAddress(httpClient.BaseAddress, new GrpcChannelOptions\n            {\n                HttpClient = httpClient\n            });\n\n            // Create a proxy object to work with the server\n            var client = new CartServiceClient(channel);\n\n            var request = new AddItemRequest\n            {\n                UserId = userId,\n                Item = new CartItem\n                {\n                    ProductId = \"1\",\n                    Quantity = 1\n                }\n            };\n\n            await client.AddItemAsync(request);\n\n            var getCartRequest = new GetCartRequest\n            {\n                UserId = userId\n            };\n            var cart = await client.GetCartAsync(getCartRequest);\n            Assert.NotNull(cart);\n            Assert.Equal(userId, cart.UserId);\n            Assert.Single(cart.Items);\n\n            await client.EmptyCartAsync(new EmptyCartRequest { UserId = userId });\n            cart = await client.GetCartAsync(getCartRequest);\n            Assert.Empty(cart.Items);\n        }\n    }\n}\n"
  },
  {
    "path": "src/cartservice/tests/cartservice.tests.csproj",
    "content": "<Project Sdk=\"Microsoft.NET.Sdk\">\n\n  <PropertyGroup>\n    <TargetFramework>net10.0</TargetFramework>\n\n    <IsPackable>false</IsPackable>\n  </PropertyGroup>\n\n  <ItemGroup>\n    <PackageReference Include=\"Grpc.Net.Client\" Version=\"2.76.0\" />\n    <PackageReference Include=\"Microsoft.AspNetCore.TestHost\" Version=\"10.0.3\" />\n    <PackageReference Include=\"Microsoft.NET.Test.Sdk\" Version=\"18.3.0\" />\n    <PackageReference Include=\"xunit\" Version=\"2.9.3\" />\n    <PackageReference Include=\"xunit.runner.visualstudio\" Version=\"3.1.5\" />\n  </ItemGroup>\n\n  <ItemGroup>\n    <ProjectReference Include=\"..\\src\\cartservice.csproj\" />\n  </ItemGroup>\n</Project>\n"
  },
  {
    "path": "src/checkoutservice/.dockerignore",
    "content": "vendor/\n"
  },
  {
    "path": "src/checkoutservice/Dockerfile",
    "content": "# Copyright 2020 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# Define a default value so it's not empty if the builder fails to provide it\nARG BUILDPLATFORM=linux/amd64\n\nFROM --platform=$BUILDPLATFORM golang:1.26.1-alpine@sha256:2389ebfa5b7f43eeafbd6be0c3700cc46690ef842ad962f6c5bd6be49ed82039 AS builder\nARG TARGETOS=linux\nARG TARGETARCH=amd64\nWORKDIR /src\n\n# restore dependencies\nCOPY go.mod go.sum ./\nRUN go mod download\n\nCOPY . .\n\n# Skaffold passes in debug-oriented compiler flags\nARG SKAFFOLD_GO_GCFLAGS\nRUN GOOS=${TARGETOS} GOARCH=${TARGETARCH} CGO_ENABLED=0 go build -ldflags=\"-s -w\" -gcflags=\"${SKAFFOLD_GO_GCFLAGS}\" -o /checkoutservice .\n\nFROM gcr.io/distroless/static\n\nWORKDIR /src\nCOPY --from=builder /checkoutservice /src/checkoutservice\n\n# Definition of this variable is used by 'skaffold debug' to identify a golang binary.\n# Default behavior - a failure prints a stack trace for the current goroutine.\n# See https://golang.org/pkg/runtime/\nENV GOTRACEBACK=single\n\nEXPOSE 5050\nENTRYPOINT [\"/src/checkoutservice\"]\n"
  },
  {
    "path": "src/checkoutservice/README.md",
    "content": "# checkoutservice\n\nRun the following command to restore dependencies to `vendor/` directory:\n\n    dep ensure --vendor-only\n"
  },
  {
    "path": "src/checkoutservice/genproto/demo.pb.go",
    "content": "// Copyright 2020 Google LLC\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.34.2\n// \tprotoc        v3.6.1\n// source: demo.proto\n\npackage hipstershop\n\nimport (\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\treflect \"reflect\"\n\tsync \"sync\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\ntype CartItem struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tProductId string `protobuf:\"bytes,1,opt,name=product_id,json=productId,proto3\" json:\"product_id,omitempty\"`\n\tQuantity  int32  `protobuf:\"varint,2,opt,name=quantity,proto3\" json:\"quantity,omitempty\"`\n}\n\nfunc (x *CartItem) Reset() {\n\t*x = CartItem{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_demo_proto_msgTypes[0]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *CartItem) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*CartItem) ProtoMessage() {}\n\nfunc (x *CartItem) ProtoReflect() protoreflect.Message {\n\tmi := &file_demo_proto_msgTypes[0]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use CartItem.ProtoReflect.Descriptor instead.\nfunc (*CartItem) Descriptor() ([]byte, []int) {\n\treturn file_demo_proto_rawDescGZIP(), []int{0}\n}\n\nfunc (x *CartItem) GetProductId() string {\n\tif x != nil {\n\t\treturn x.ProductId\n\t}\n\treturn \"\"\n}\n\nfunc (x *CartItem) GetQuantity() int32 {\n\tif x != nil {\n\t\treturn x.Quantity\n\t}\n\treturn 0\n}\n\ntype AddItemRequest struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tUserId string    `protobuf:\"bytes,1,opt,name=user_id,json=userId,proto3\" json:\"user_id,omitempty\"`\n\tItem   *CartItem `protobuf:\"bytes,2,opt,name=item,proto3\" json:\"item,omitempty\"`\n}\n\nfunc (x *AddItemRequest) Reset() {\n\t*x = AddItemRequest{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_demo_proto_msgTypes[1]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *AddItemRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*AddItemRequest) ProtoMessage() {}\n\nfunc (x *AddItemRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_demo_proto_msgTypes[1]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use AddItemRequest.ProtoReflect.Descriptor instead.\nfunc (*AddItemRequest) Descriptor() ([]byte, []int) {\n\treturn file_demo_proto_rawDescGZIP(), []int{1}\n}\n\nfunc (x *AddItemRequest) GetUserId() string {\n\tif x != nil {\n\t\treturn x.UserId\n\t}\n\treturn \"\"\n}\n\nfunc (x *AddItemRequest) GetItem() *CartItem {\n\tif x != nil {\n\t\treturn x.Item\n\t}\n\treturn nil\n}\n\ntype EmptyCartRequest struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tUserId string `protobuf:\"bytes,1,opt,name=user_id,json=userId,proto3\" json:\"user_id,omitempty\"`\n}\n\nfunc (x *EmptyCartRequest) Reset() {\n\t*x = EmptyCartRequest{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_demo_proto_msgTypes[2]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *EmptyCartRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*EmptyCartRequest) ProtoMessage() {}\n\nfunc (x *EmptyCartRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_demo_proto_msgTypes[2]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use EmptyCartRequest.ProtoReflect.Descriptor instead.\nfunc (*EmptyCartRequest) Descriptor() ([]byte, []int) {\n\treturn file_demo_proto_rawDescGZIP(), []int{2}\n}\n\nfunc (x *EmptyCartRequest) GetUserId() string {\n\tif x != nil {\n\t\treturn x.UserId\n\t}\n\treturn \"\"\n}\n\ntype GetCartRequest struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tUserId string `protobuf:\"bytes,1,opt,name=user_id,json=userId,proto3\" json:\"user_id,omitempty\"`\n}\n\nfunc (x *GetCartRequest) Reset() {\n\t*x = GetCartRequest{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_demo_proto_msgTypes[3]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *GetCartRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*GetCartRequest) ProtoMessage() {}\n\nfunc (x *GetCartRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_demo_proto_msgTypes[3]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use GetCartRequest.ProtoReflect.Descriptor instead.\nfunc (*GetCartRequest) Descriptor() ([]byte, []int) {\n\treturn file_demo_proto_rawDescGZIP(), []int{3}\n}\n\nfunc (x *GetCartRequest) GetUserId() string {\n\tif x != nil {\n\t\treturn x.UserId\n\t}\n\treturn \"\"\n}\n\ntype Cart struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tUserId string      `protobuf:\"bytes,1,opt,name=user_id,json=userId,proto3\" json:\"user_id,omitempty\"`\n\tItems  []*CartItem `protobuf:\"bytes,2,rep,name=items,proto3\" json:\"items,omitempty\"`\n}\n\nfunc (x *Cart) Reset() {\n\t*x = Cart{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_demo_proto_msgTypes[4]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *Cart) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Cart) ProtoMessage() {}\n\nfunc (x *Cart) ProtoReflect() protoreflect.Message {\n\tmi := &file_demo_proto_msgTypes[4]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Cart.ProtoReflect.Descriptor instead.\nfunc (*Cart) Descriptor() ([]byte, []int) {\n\treturn file_demo_proto_rawDescGZIP(), []int{4}\n}\n\nfunc (x *Cart) GetUserId() string {\n\tif x != nil {\n\t\treturn x.UserId\n\t}\n\treturn \"\"\n}\n\nfunc (x *Cart) GetItems() []*CartItem {\n\tif x != nil {\n\t\treturn x.Items\n\t}\n\treturn nil\n}\n\ntype Empty struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n}\n\nfunc (x *Empty) Reset() {\n\t*x = Empty{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_demo_proto_msgTypes[5]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *Empty) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Empty) ProtoMessage() {}\n\nfunc (x *Empty) ProtoReflect() protoreflect.Message {\n\tmi := &file_demo_proto_msgTypes[5]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Empty.ProtoReflect.Descriptor instead.\nfunc (*Empty) Descriptor() ([]byte, []int) {\n\treturn file_demo_proto_rawDescGZIP(), []int{5}\n}\n\ntype ListRecommendationsRequest struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tUserId     string   `protobuf:\"bytes,1,opt,name=user_id,json=userId,proto3\" json:\"user_id,omitempty\"`\n\tProductIds []string `protobuf:\"bytes,2,rep,name=product_ids,json=productIds,proto3\" json:\"product_ids,omitempty\"`\n}\n\nfunc (x *ListRecommendationsRequest) Reset() {\n\t*x = ListRecommendationsRequest{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_demo_proto_msgTypes[6]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *ListRecommendationsRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ListRecommendationsRequest) ProtoMessage() {}\n\nfunc (x *ListRecommendationsRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_demo_proto_msgTypes[6]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use ListRecommendationsRequest.ProtoReflect.Descriptor instead.\nfunc (*ListRecommendationsRequest) Descriptor() ([]byte, []int) {\n\treturn file_demo_proto_rawDescGZIP(), []int{6}\n}\n\nfunc (x *ListRecommendationsRequest) GetUserId() string {\n\tif x != nil {\n\t\treturn x.UserId\n\t}\n\treturn \"\"\n}\n\nfunc (x *ListRecommendationsRequest) GetProductIds() []string {\n\tif x != nil {\n\t\treturn x.ProductIds\n\t}\n\treturn nil\n}\n\ntype ListRecommendationsResponse struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tProductIds []string `protobuf:\"bytes,1,rep,name=product_ids,json=productIds,proto3\" json:\"product_ids,omitempty\"`\n}\n\nfunc (x *ListRecommendationsResponse) Reset() {\n\t*x = ListRecommendationsResponse{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_demo_proto_msgTypes[7]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *ListRecommendationsResponse) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ListRecommendationsResponse) ProtoMessage() {}\n\nfunc (x *ListRecommendationsResponse) ProtoReflect() protoreflect.Message {\n\tmi := &file_demo_proto_msgTypes[7]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use ListRecommendationsResponse.ProtoReflect.Descriptor instead.\nfunc (*ListRecommendationsResponse) Descriptor() ([]byte, []int) {\n\treturn file_demo_proto_rawDescGZIP(), []int{7}\n}\n\nfunc (x *ListRecommendationsResponse) GetProductIds() []string {\n\tif x != nil {\n\t\treturn x.ProductIds\n\t}\n\treturn nil\n}\n\ntype Product struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tId          string `protobuf:\"bytes,1,opt,name=id,proto3\" json:\"id,omitempty\"`\n\tName        string `protobuf:\"bytes,2,opt,name=name,proto3\" json:\"name,omitempty\"`\n\tDescription string `protobuf:\"bytes,3,opt,name=description,proto3\" json:\"description,omitempty\"`\n\tPicture     string `protobuf:\"bytes,4,opt,name=picture,proto3\" json:\"picture,omitempty\"`\n\tPriceUsd    *Money `protobuf:\"bytes,5,opt,name=price_usd,json=priceUsd,proto3\" json:\"price_usd,omitempty\"`\n\t// Categories such as \"clothing\" or \"kitchen\" that can be used to look up\n\t// other related products.\n\tCategories []string `protobuf:\"bytes,6,rep,name=categories,proto3\" json:\"categories,omitempty\"`\n}\n\nfunc (x *Product) Reset() {\n\t*x = Product{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_demo_proto_msgTypes[8]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *Product) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Product) ProtoMessage() {}\n\nfunc (x *Product) ProtoReflect() protoreflect.Message {\n\tmi := &file_demo_proto_msgTypes[8]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Product.ProtoReflect.Descriptor instead.\nfunc (*Product) Descriptor() ([]byte, []int) {\n\treturn file_demo_proto_rawDescGZIP(), []int{8}\n}\n\nfunc (x *Product) GetId() string {\n\tif x != nil {\n\t\treturn x.Id\n\t}\n\treturn \"\"\n}\n\nfunc (x *Product) GetName() string {\n\tif x != nil {\n\t\treturn x.Name\n\t}\n\treturn \"\"\n}\n\nfunc (x *Product) GetDescription() string {\n\tif x != nil {\n\t\treturn x.Description\n\t}\n\treturn \"\"\n}\n\nfunc (x *Product) GetPicture() string {\n\tif x != nil {\n\t\treturn x.Picture\n\t}\n\treturn \"\"\n}\n\nfunc (x *Product) GetPriceUsd() *Money {\n\tif x != nil {\n\t\treturn x.PriceUsd\n\t}\n\treturn nil\n}\n\nfunc (x *Product) GetCategories() []string {\n\tif x != nil {\n\t\treturn x.Categories\n\t}\n\treturn nil\n}\n\ntype ListProductsResponse struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tProducts []*Product `protobuf:\"bytes,1,rep,name=products,proto3\" json:\"products,omitempty\"`\n}\n\nfunc (x *ListProductsResponse) Reset() {\n\t*x = ListProductsResponse{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_demo_proto_msgTypes[9]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *ListProductsResponse) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ListProductsResponse) ProtoMessage() {}\n\nfunc (x *ListProductsResponse) ProtoReflect() protoreflect.Message {\n\tmi := &file_demo_proto_msgTypes[9]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use ListProductsResponse.ProtoReflect.Descriptor instead.\nfunc (*ListProductsResponse) Descriptor() ([]byte, []int) {\n\treturn file_demo_proto_rawDescGZIP(), []int{9}\n}\n\nfunc (x *ListProductsResponse) GetProducts() []*Product {\n\tif x != nil {\n\t\treturn x.Products\n\t}\n\treturn nil\n}\n\ntype GetProductRequest struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tId string `protobuf:\"bytes,1,opt,name=id,proto3\" json:\"id,omitempty\"`\n}\n\nfunc (x *GetProductRequest) Reset() {\n\t*x = GetProductRequest{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_demo_proto_msgTypes[10]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *GetProductRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*GetProductRequest) ProtoMessage() {}\n\nfunc (x *GetProductRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_demo_proto_msgTypes[10]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use GetProductRequest.ProtoReflect.Descriptor instead.\nfunc (*GetProductRequest) Descriptor() ([]byte, []int) {\n\treturn file_demo_proto_rawDescGZIP(), []int{10}\n}\n\nfunc (x *GetProductRequest) GetId() string {\n\tif x != nil {\n\t\treturn x.Id\n\t}\n\treturn \"\"\n}\n\ntype SearchProductsRequest struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tQuery string `protobuf:\"bytes,1,opt,name=query,proto3\" json:\"query,omitempty\"`\n}\n\nfunc (x *SearchProductsRequest) Reset() {\n\t*x = SearchProductsRequest{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_demo_proto_msgTypes[11]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *SearchProductsRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*SearchProductsRequest) ProtoMessage() {}\n\nfunc (x *SearchProductsRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_demo_proto_msgTypes[11]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use SearchProductsRequest.ProtoReflect.Descriptor instead.\nfunc (*SearchProductsRequest) Descriptor() ([]byte, []int) {\n\treturn file_demo_proto_rawDescGZIP(), []int{11}\n}\n\nfunc (x *SearchProductsRequest) GetQuery() string {\n\tif x != nil {\n\t\treturn x.Query\n\t}\n\treturn \"\"\n}\n\ntype SearchProductsResponse struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tResults []*Product `protobuf:\"bytes,1,rep,name=results,proto3\" json:\"results,omitempty\"`\n}\n\nfunc (x *SearchProductsResponse) Reset() {\n\t*x = SearchProductsResponse{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_demo_proto_msgTypes[12]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *SearchProductsResponse) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*SearchProductsResponse) ProtoMessage() {}\n\nfunc (x *SearchProductsResponse) ProtoReflect() protoreflect.Message {\n\tmi := &file_demo_proto_msgTypes[12]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use SearchProductsResponse.ProtoReflect.Descriptor instead.\nfunc (*SearchProductsResponse) Descriptor() ([]byte, []int) {\n\treturn file_demo_proto_rawDescGZIP(), []int{12}\n}\n\nfunc (x *SearchProductsResponse) GetResults() []*Product {\n\tif x != nil {\n\t\treturn x.Results\n\t}\n\treturn nil\n}\n\ntype GetQuoteRequest struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tAddress *Address    `protobuf:\"bytes,1,opt,name=address,proto3\" json:\"address,omitempty\"`\n\tItems   []*CartItem `protobuf:\"bytes,2,rep,name=items,proto3\" json:\"items,omitempty\"`\n}\n\nfunc (x *GetQuoteRequest) Reset() {\n\t*x = GetQuoteRequest{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_demo_proto_msgTypes[13]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *GetQuoteRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*GetQuoteRequest) ProtoMessage() {}\n\nfunc (x *GetQuoteRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_demo_proto_msgTypes[13]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use GetQuoteRequest.ProtoReflect.Descriptor instead.\nfunc (*GetQuoteRequest) Descriptor() ([]byte, []int) {\n\treturn file_demo_proto_rawDescGZIP(), []int{13}\n}\n\nfunc (x *GetQuoteRequest) GetAddress() *Address {\n\tif x != nil {\n\t\treturn x.Address\n\t}\n\treturn nil\n}\n\nfunc (x *GetQuoteRequest) GetItems() []*CartItem {\n\tif x != nil {\n\t\treturn x.Items\n\t}\n\treturn nil\n}\n\ntype GetQuoteResponse struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tCostUsd *Money `protobuf:\"bytes,1,opt,name=cost_usd,json=costUsd,proto3\" json:\"cost_usd,omitempty\"`\n}\n\nfunc (x *GetQuoteResponse) Reset() {\n\t*x = GetQuoteResponse{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_demo_proto_msgTypes[14]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *GetQuoteResponse) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*GetQuoteResponse) ProtoMessage() {}\n\nfunc (x *GetQuoteResponse) ProtoReflect() protoreflect.Message {\n\tmi := &file_demo_proto_msgTypes[14]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use GetQuoteResponse.ProtoReflect.Descriptor instead.\nfunc (*GetQuoteResponse) Descriptor() ([]byte, []int) {\n\treturn file_demo_proto_rawDescGZIP(), []int{14}\n}\n\nfunc (x *GetQuoteResponse) GetCostUsd() *Money {\n\tif x != nil {\n\t\treturn x.CostUsd\n\t}\n\treturn nil\n}\n\ntype ShipOrderRequest struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tAddress *Address    `protobuf:\"bytes,1,opt,name=address,proto3\" json:\"address,omitempty\"`\n\tItems   []*CartItem `protobuf:\"bytes,2,rep,name=items,proto3\" json:\"items,omitempty\"`\n}\n\nfunc (x *ShipOrderRequest) Reset() {\n\t*x = ShipOrderRequest{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_demo_proto_msgTypes[15]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *ShipOrderRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ShipOrderRequest) ProtoMessage() {}\n\nfunc (x *ShipOrderRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_demo_proto_msgTypes[15]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use ShipOrderRequest.ProtoReflect.Descriptor instead.\nfunc (*ShipOrderRequest) Descriptor() ([]byte, []int) {\n\treturn file_demo_proto_rawDescGZIP(), []int{15}\n}\n\nfunc (x *ShipOrderRequest) GetAddress() *Address {\n\tif x != nil {\n\t\treturn x.Address\n\t}\n\treturn nil\n}\n\nfunc (x *ShipOrderRequest) GetItems() []*CartItem {\n\tif x != nil {\n\t\treturn x.Items\n\t}\n\treturn nil\n}\n\ntype ShipOrderResponse struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tTrackingId string `protobuf:\"bytes,1,opt,name=tracking_id,json=trackingId,proto3\" json:\"tracking_id,omitempty\"`\n}\n\nfunc (x *ShipOrderResponse) Reset() {\n\t*x = ShipOrderResponse{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_demo_proto_msgTypes[16]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *ShipOrderResponse) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ShipOrderResponse) ProtoMessage() {}\n\nfunc (x *ShipOrderResponse) ProtoReflect() protoreflect.Message {\n\tmi := &file_demo_proto_msgTypes[16]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use ShipOrderResponse.ProtoReflect.Descriptor instead.\nfunc (*ShipOrderResponse) Descriptor() ([]byte, []int) {\n\treturn file_demo_proto_rawDescGZIP(), []int{16}\n}\n\nfunc (x *ShipOrderResponse) GetTrackingId() string {\n\tif x != nil {\n\t\treturn x.TrackingId\n\t}\n\treturn \"\"\n}\n\ntype Address struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tStreetAddress string `protobuf:\"bytes,1,opt,name=street_address,json=streetAddress,proto3\" json:\"street_address,omitempty\"`\n\tCity          string `protobuf:\"bytes,2,opt,name=city,proto3\" json:\"city,omitempty\"`\n\tState         string `protobuf:\"bytes,3,opt,name=state,proto3\" json:\"state,omitempty\"`\n\tCountry       string `protobuf:\"bytes,4,opt,name=country,proto3\" json:\"country,omitempty\"`\n\tZipCode       int32  `protobuf:\"varint,5,opt,name=zip_code,json=zipCode,proto3\" json:\"zip_code,omitempty\"`\n}\n\nfunc (x *Address) Reset() {\n\t*x = Address{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_demo_proto_msgTypes[17]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *Address) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Address) ProtoMessage() {}\n\nfunc (x *Address) ProtoReflect() protoreflect.Message {\n\tmi := &file_demo_proto_msgTypes[17]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Address.ProtoReflect.Descriptor instead.\nfunc (*Address) Descriptor() ([]byte, []int) {\n\treturn file_demo_proto_rawDescGZIP(), []int{17}\n}\n\nfunc (x *Address) GetStreetAddress() string {\n\tif x != nil {\n\t\treturn x.StreetAddress\n\t}\n\treturn \"\"\n}\n\nfunc (x *Address) GetCity() string {\n\tif x != nil {\n\t\treturn x.City\n\t}\n\treturn \"\"\n}\n\nfunc (x *Address) GetState() string {\n\tif x != nil {\n\t\treturn x.State\n\t}\n\treturn \"\"\n}\n\nfunc (x *Address) GetCountry() string {\n\tif x != nil {\n\t\treturn x.Country\n\t}\n\treturn \"\"\n}\n\nfunc (x *Address) GetZipCode() int32 {\n\tif x != nil {\n\t\treturn x.ZipCode\n\t}\n\treturn 0\n}\n\n// Represents an amount of money with its currency type.\ntype Money struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// The 3-letter currency code defined in ISO 4217.\n\tCurrencyCode string `protobuf:\"bytes,1,opt,name=currency_code,json=currencyCode,proto3\" json:\"currency_code,omitempty\"`\n\t// The whole units of the amount.\n\t// For example if `currencyCode` is `\"USD\"`, then 1 unit is one US dollar.\n\tUnits int64 `protobuf:\"varint,2,opt,name=units,proto3\" json:\"units,omitempty\"`\n\t// Number of nano (10^-9) units of the amount.\n\t// The value must be between -999,999,999 and +999,999,999 inclusive.\n\t// If `units` is positive, `nanos` must be positive or zero.\n\t// If `units` is zero, `nanos` can be positive, zero, or negative.\n\t// If `units` is negative, `nanos` must be negative or zero.\n\t// For example $-1.75 is represented as `units`=-1 and `nanos`=-750,000,000.\n\tNanos int32 `protobuf:\"varint,3,opt,name=nanos,proto3\" json:\"nanos,omitempty\"`\n}\n\nfunc (x *Money) Reset() {\n\t*x = Money{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_demo_proto_msgTypes[18]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *Money) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Money) ProtoMessage() {}\n\nfunc (x *Money) ProtoReflect() protoreflect.Message {\n\tmi := &file_demo_proto_msgTypes[18]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Money.ProtoReflect.Descriptor instead.\nfunc (*Money) Descriptor() ([]byte, []int) {\n\treturn file_demo_proto_rawDescGZIP(), []int{18}\n}\n\nfunc (x *Money) GetCurrencyCode() string {\n\tif x != nil {\n\t\treturn x.CurrencyCode\n\t}\n\treturn \"\"\n}\n\nfunc (x *Money) GetUnits() int64 {\n\tif x != nil {\n\t\treturn x.Units\n\t}\n\treturn 0\n}\n\nfunc (x *Money) GetNanos() int32 {\n\tif x != nil {\n\t\treturn x.Nanos\n\t}\n\treturn 0\n}\n\ntype GetSupportedCurrenciesResponse struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// The 3-letter currency code defined in ISO 4217.\n\tCurrencyCodes []string `protobuf:\"bytes,1,rep,name=currency_codes,json=currencyCodes,proto3\" json:\"currency_codes,omitempty\"`\n}\n\nfunc (x *GetSupportedCurrenciesResponse) Reset() {\n\t*x = GetSupportedCurrenciesResponse{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_demo_proto_msgTypes[19]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *GetSupportedCurrenciesResponse) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*GetSupportedCurrenciesResponse) ProtoMessage() {}\n\nfunc (x *GetSupportedCurrenciesResponse) ProtoReflect() protoreflect.Message {\n\tmi := &file_demo_proto_msgTypes[19]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use GetSupportedCurrenciesResponse.ProtoReflect.Descriptor instead.\nfunc (*GetSupportedCurrenciesResponse) Descriptor() ([]byte, []int) {\n\treturn file_demo_proto_rawDescGZIP(), []int{19}\n}\n\nfunc (x *GetSupportedCurrenciesResponse) GetCurrencyCodes() []string {\n\tif x != nil {\n\t\treturn x.CurrencyCodes\n\t}\n\treturn nil\n}\n\ntype CurrencyConversionRequest struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tFrom *Money `protobuf:\"bytes,1,opt,name=from,proto3\" json:\"from,omitempty\"`\n\t// The 3-letter currency code defined in ISO 4217.\n\tToCode string `protobuf:\"bytes,2,opt,name=to_code,json=toCode,proto3\" json:\"to_code,omitempty\"`\n}\n\nfunc (x *CurrencyConversionRequest) Reset() {\n\t*x = CurrencyConversionRequest{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_demo_proto_msgTypes[20]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *CurrencyConversionRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*CurrencyConversionRequest) ProtoMessage() {}\n\nfunc (x *CurrencyConversionRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_demo_proto_msgTypes[20]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use CurrencyConversionRequest.ProtoReflect.Descriptor instead.\nfunc (*CurrencyConversionRequest) Descriptor() ([]byte, []int) {\n\treturn file_demo_proto_rawDescGZIP(), []int{20}\n}\n\nfunc (x *CurrencyConversionRequest) GetFrom() *Money {\n\tif x != nil {\n\t\treturn x.From\n\t}\n\treturn nil\n}\n\nfunc (x *CurrencyConversionRequest) GetToCode() string {\n\tif x != nil {\n\t\treturn x.ToCode\n\t}\n\treturn \"\"\n}\n\ntype CreditCardInfo struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tCreditCardNumber          string `protobuf:\"bytes,1,opt,name=credit_card_number,json=creditCardNumber,proto3\" json:\"credit_card_number,omitempty\"`\n\tCreditCardCvv             int32  `protobuf:\"varint,2,opt,name=credit_card_cvv,json=creditCardCvv,proto3\" json:\"credit_card_cvv,omitempty\"`\n\tCreditCardExpirationYear  int32  `protobuf:\"varint,3,opt,name=credit_card_expiration_year,json=creditCardExpirationYear,proto3\" json:\"credit_card_expiration_year,omitempty\"`\n\tCreditCardExpirationMonth int32  `protobuf:\"varint,4,opt,name=credit_card_expiration_month,json=creditCardExpirationMonth,proto3\" json:\"credit_card_expiration_month,omitempty\"`\n}\n\nfunc (x *CreditCardInfo) Reset() {\n\t*x = CreditCardInfo{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_demo_proto_msgTypes[21]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *CreditCardInfo) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*CreditCardInfo) ProtoMessage() {}\n\nfunc (x *CreditCardInfo) ProtoReflect() protoreflect.Message {\n\tmi := &file_demo_proto_msgTypes[21]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use CreditCardInfo.ProtoReflect.Descriptor instead.\nfunc (*CreditCardInfo) Descriptor() ([]byte, []int) {\n\treturn file_demo_proto_rawDescGZIP(), []int{21}\n}\n\nfunc (x *CreditCardInfo) GetCreditCardNumber() string {\n\tif x != nil {\n\t\treturn x.CreditCardNumber\n\t}\n\treturn \"\"\n}\n\nfunc (x *CreditCardInfo) GetCreditCardCvv() int32 {\n\tif x != nil {\n\t\treturn x.CreditCardCvv\n\t}\n\treturn 0\n}\n\nfunc (x *CreditCardInfo) GetCreditCardExpirationYear() int32 {\n\tif x != nil {\n\t\treturn x.CreditCardExpirationYear\n\t}\n\treturn 0\n}\n\nfunc (x *CreditCardInfo) GetCreditCardExpirationMonth() int32 {\n\tif x != nil {\n\t\treturn x.CreditCardExpirationMonth\n\t}\n\treturn 0\n}\n\ntype ChargeRequest struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tAmount     *Money          `protobuf:\"bytes,1,opt,name=amount,proto3\" json:\"amount,omitempty\"`\n\tCreditCard *CreditCardInfo `protobuf:\"bytes,2,opt,name=credit_card,json=creditCard,proto3\" json:\"credit_card,omitempty\"`\n}\n\nfunc (x *ChargeRequest) Reset() {\n\t*x = ChargeRequest{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_demo_proto_msgTypes[22]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *ChargeRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ChargeRequest) ProtoMessage() {}\n\nfunc (x *ChargeRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_demo_proto_msgTypes[22]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use ChargeRequest.ProtoReflect.Descriptor instead.\nfunc (*ChargeRequest) Descriptor() ([]byte, []int) {\n\treturn file_demo_proto_rawDescGZIP(), []int{22}\n}\n\nfunc (x *ChargeRequest) GetAmount() *Money {\n\tif x != nil {\n\t\treturn x.Amount\n\t}\n\treturn nil\n}\n\nfunc (x *ChargeRequest) GetCreditCard() *CreditCardInfo {\n\tif x != nil {\n\t\treturn x.CreditCard\n\t}\n\treturn nil\n}\n\ntype ChargeResponse struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tTransactionId string `protobuf:\"bytes,1,opt,name=transaction_id,json=transactionId,proto3\" json:\"transaction_id,omitempty\"`\n}\n\nfunc (x *ChargeResponse) Reset() {\n\t*x = ChargeResponse{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_demo_proto_msgTypes[23]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *ChargeResponse) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ChargeResponse) ProtoMessage() {}\n\nfunc (x *ChargeResponse) ProtoReflect() protoreflect.Message {\n\tmi := &file_demo_proto_msgTypes[23]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use ChargeResponse.ProtoReflect.Descriptor instead.\nfunc (*ChargeResponse) Descriptor() ([]byte, []int) {\n\treturn file_demo_proto_rawDescGZIP(), []int{23}\n}\n\nfunc (x *ChargeResponse) GetTransactionId() string {\n\tif x != nil {\n\t\treturn x.TransactionId\n\t}\n\treturn \"\"\n}\n\ntype OrderItem struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tItem *CartItem `protobuf:\"bytes,1,opt,name=item,proto3\" json:\"item,omitempty\"`\n\tCost *Money    `protobuf:\"bytes,2,opt,name=cost,proto3\" json:\"cost,omitempty\"`\n}\n\nfunc (x *OrderItem) Reset() {\n\t*x = OrderItem{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_demo_proto_msgTypes[24]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *OrderItem) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*OrderItem) ProtoMessage() {}\n\nfunc (x *OrderItem) ProtoReflect() protoreflect.Message {\n\tmi := &file_demo_proto_msgTypes[24]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use OrderItem.ProtoReflect.Descriptor instead.\nfunc (*OrderItem) Descriptor() ([]byte, []int) {\n\treturn file_demo_proto_rawDescGZIP(), []int{24}\n}\n\nfunc (x *OrderItem) GetItem() *CartItem {\n\tif x != nil {\n\t\treturn x.Item\n\t}\n\treturn nil\n}\n\nfunc (x *OrderItem) GetCost() *Money {\n\tif x != nil {\n\t\treturn x.Cost\n\t}\n\treturn nil\n}\n\ntype OrderResult struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tOrderId            string       `protobuf:\"bytes,1,opt,name=order_id,json=orderId,proto3\" json:\"order_id,omitempty\"`\n\tShippingTrackingId string       `protobuf:\"bytes,2,opt,name=shipping_tracking_id,json=shippingTrackingId,proto3\" json:\"shipping_tracking_id,omitempty\"`\n\tShippingCost       *Money       `protobuf:\"bytes,3,opt,name=shipping_cost,json=shippingCost,proto3\" json:\"shipping_cost,omitempty\"`\n\tShippingAddress    *Address     `protobuf:\"bytes,4,opt,name=shipping_address,json=shippingAddress,proto3\" json:\"shipping_address,omitempty\"`\n\tItems              []*OrderItem `protobuf:\"bytes,5,rep,name=items,proto3\" json:\"items,omitempty\"`\n}\n\nfunc (x *OrderResult) Reset() {\n\t*x = OrderResult{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_demo_proto_msgTypes[25]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *OrderResult) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*OrderResult) ProtoMessage() {}\n\nfunc (x *OrderResult) ProtoReflect() protoreflect.Message {\n\tmi := &file_demo_proto_msgTypes[25]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use OrderResult.ProtoReflect.Descriptor instead.\nfunc (*OrderResult) Descriptor() ([]byte, []int) {\n\treturn file_demo_proto_rawDescGZIP(), []int{25}\n}\n\nfunc (x *OrderResult) GetOrderId() string {\n\tif x != nil {\n\t\treturn x.OrderId\n\t}\n\treturn \"\"\n}\n\nfunc (x *OrderResult) GetShippingTrackingId() string {\n\tif x != nil {\n\t\treturn x.ShippingTrackingId\n\t}\n\treturn \"\"\n}\n\nfunc (x *OrderResult) GetShippingCost() *Money {\n\tif x != nil {\n\t\treturn x.ShippingCost\n\t}\n\treturn nil\n}\n\nfunc (x *OrderResult) GetShippingAddress() *Address {\n\tif x != nil {\n\t\treturn x.ShippingAddress\n\t}\n\treturn nil\n}\n\nfunc (x *OrderResult) GetItems() []*OrderItem {\n\tif x != nil {\n\t\treturn x.Items\n\t}\n\treturn nil\n}\n\ntype SendOrderConfirmationRequest struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tEmail string       `protobuf:\"bytes,1,opt,name=email,proto3\" json:\"email,omitempty\"`\n\tOrder *OrderResult `protobuf:\"bytes,2,opt,name=order,proto3\" json:\"order,omitempty\"`\n}\n\nfunc (x *SendOrderConfirmationRequest) Reset() {\n\t*x = SendOrderConfirmationRequest{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_demo_proto_msgTypes[26]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *SendOrderConfirmationRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*SendOrderConfirmationRequest) ProtoMessage() {}\n\nfunc (x *SendOrderConfirmationRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_demo_proto_msgTypes[26]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use SendOrderConfirmationRequest.ProtoReflect.Descriptor instead.\nfunc (*SendOrderConfirmationRequest) Descriptor() ([]byte, []int) {\n\treturn file_demo_proto_rawDescGZIP(), []int{26}\n}\n\nfunc (x *SendOrderConfirmationRequest) GetEmail() string {\n\tif x != nil {\n\t\treturn x.Email\n\t}\n\treturn \"\"\n}\n\nfunc (x *SendOrderConfirmationRequest) GetOrder() *OrderResult {\n\tif x != nil {\n\t\treturn x.Order\n\t}\n\treturn nil\n}\n\ntype PlaceOrderRequest struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tUserId       string          `protobuf:\"bytes,1,opt,name=user_id,json=userId,proto3\" json:\"user_id,omitempty\"`\n\tUserCurrency string          `protobuf:\"bytes,2,opt,name=user_currency,json=userCurrency,proto3\" json:\"user_currency,omitempty\"`\n\tAddress      *Address        `protobuf:\"bytes,3,opt,name=address,proto3\" json:\"address,omitempty\"`\n\tEmail        string          `protobuf:\"bytes,5,opt,name=email,proto3\" json:\"email,omitempty\"`\n\tCreditCard   *CreditCardInfo `protobuf:\"bytes,6,opt,name=credit_card,json=creditCard,proto3\" json:\"credit_card,omitempty\"`\n}\n\nfunc (x *PlaceOrderRequest) Reset() {\n\t*x = PlaceOrderRequest{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_demo_proto_msgTypes[27]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *PlaceOrderRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*PlaceOrderRequest) ProtoMessage() {}\n\nfunc (x *PlaceOrderRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_demo_proto_msgTypes[27]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use PlaceOrderRequest.ProtoReflect.Descriptor instead.\nfunc (*PlaceOrderRequest) Descriptor() ([]byte, []int) {\n\treturn file_demo_proto_rawDescGZIP(), []int{27}\n}\n\nfunc (x *PlaceOrderRequest) GetUserId() string {\n\tif x != nil {\n\t\treturn x.UserId\n\t}\n\treturn \"\"\n}\n\nfunc (x *PlaceOrderRequest) GetUserCurrency() string {\n\tif x != nil {\n\t\treturn x.UserCurrency\n\t}\n\treturn \"\"\n}\n\nfunc (x *PlaceOrderRequest) GetAddress() *Address {\n\tif x != nil {\n\t\treturn x.Address\n\t}\n\treturn nil\n}\n\nfunc (x *PlaceOrderRequest) GetEmail() string {\n\tif x != nil {\n\t\treturn x.Email\n\t}\n\treturn \"\"\n}\n\nfunc (x *PlaceOrderRequest) GetCreditCard() *CreditCardInfo {\n\tif x != nil {\n\t\treturn x.CreditCard\n\t}\n\treturn nil\n}\n\ntype PlaceOrderResponse struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tOrder *OrderResult `protobuf:\"bytes,1,opt,name=order,proto3\" json:\"order,omitempty\"`\n}\n\nfunc (x *PlaceOrderResponse) Reset() {\n\t*x = PlaceOrderResponse{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_demo_proto_msgTypes[28]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *PlaceOrderResponse) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*PlaceOrderResponse) ProtoMessage() {}\n\nfunc (x *PlaceOrderResponse) ProtoReflect() protoreflect.Message {\n\tmi := &file_demo_proto_msgTypes[28]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use PlaceOrderResponse.ProtoReflect.Descriptor instead.\nfunc (*PlaceOrderResponse) Descriptor() ([]byte, []int) {\n\treturn file_demo_proto_rawDescGZIP(), []int{28}\n}\n\nfunc (x *PlaceOrderResponse) GetOrder() *OrderResult {\n\tif x != nil {\n\t\treturn x.Order\n\t}\n\treturn nil\n}\n\ntype AdRequest struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// List of important key words from the current page describing the context.\n\tContextKeys []string `protobuf:\"bytes,1,rep,name=context_keys,json=contextKeys,proto3\" json:\"context_keys,omitempty\"`\n}\n\nfunc (x *AdRequest) Reset() {\n\t*x = AdRequest{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_demo_proto_msgTypes[29]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *AdRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*AdRequest) ProtoMessage() {}\n\nfunc (x *AdRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_demo_proto_msgTypes[29]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use AdRequest.ProtoReflect.Descriptor instead.\nfunc (*AdRequest) Descriptor() ([]byte, []int) {\n\treturn file_demo_proto_rawDescGZIP(), []int{29}\n}\n\nfunc (x *AdRequest) GetContextKeys() []string {\n\tif x != nil {\n\t\treturn x.ContextKeys\n\t}\n\treturn nil\n}\n\ntype AdResponse struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tAds []*Ad `protobuf:\"bytes,1,rep,name=ads,proto3\" json:\"ads,omitempty\"`\n}\n\nfunc (x *AdResponse) Reset() {\n\t*x = AdResponse{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_demo_proto_msgTypes[30]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *AdResponse) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*AdResponse) ProtoMessage() {}\n\nfunc (x *AdResponse) ProtoReflect() protoreflect.Message {\n\tmi := &file_demo_proto_msgTypes[30]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use AdResponse.ProtoReflect.Descriptor instead.\nfunc (*AdResponse) Descriptor() ([]byte, []int) {\n\treturn file_demo_proto_rawDescGZIP(), []int{30}\n}\n\nfunc (x *AdResponse) GetAds() []*Ad {\n\tif x != nil {\n\t\treturn x.Ads\n\t}\n\treturn nil\n}\n\ntype Ad struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// url to redirect to when an ad is clicked.\n\tRedirectUrl string `protobuf:\"bytes,1,opt,name=redirect_url,json=redirectUrl,proto3\" json:\"redirect_url,omitempty\"`\n\t// short advertisement text to display.\n\tText string `protobuf:\"bytes,2,opt,name=text,proto3\" json:\"text,omitempty\"`\n}\n\nfunc (x *Ad) Reset() {\n\t*x = Ad{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_demo_proto_msgTypes[31]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *Ad) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Ad) ProtoMessage() {}\n\nfunc (x *Ad) ProtoReflect() protoreflect.Message {\n\tmi := &file_demo_proto_msgTypes[31]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Ad.ProtoReflect.Descriptor instead.\nfunc (*Ad) Descriptor() ([]byte, []int) {\n\treturn file_demo_proto_rawDescGZIP(), []int{31}\n}\n\nfunc (x *Ad) GetRedirectUrl() string {\n\tif x != nil {\n\t\treturn x.RedirectUrl\n\t}\n\treturn \"\"\n}\n\nfunc (x *Ad) GetText() string {\n\tif x != nil {\n\t\treturn x.Text\n\t}\n\treturn \"\"\n}\n\nvar File_demo_proto protoreflect.FileDescriptor\n\nvar file_demo_proto_rawDesc = []byte{\n\t0x0a, 0x0a, 0x64, 0x65, 0x6d, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0b, 0x68, 0x69,\n\t0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x22, 0x45, 0x0a, 0x08, 0x43, 0x61, 0x72,\n\t0x74, 0x49, 0x74, 0x65, 0x6d, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74,\n\t0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x70, 0x72, 0x6f, 0x64, 0x75,\n\t0x63, 0x74, 0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x71, 0x75, 0x61, 0x6e, 0x74, 0x69, 0x74, 0x79,\n\t0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x71, 0x75, 0x61, 0x6e, 0x74, 0x69, 0x74, 0x79,\n\t0x22, 0x54, 0x0a, 0x0e, 0x41, 0x64, 0x64, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65,\n\t0x73, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20,\n\t0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x29, 0x0a, 0x04, 0x69,\n\t0x74, 0x65, 0x6d, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x68, 0x69, 0x70, 0x73,\n\t0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x43, 0x61, 0x72, 0x74, 0x49, 0x74, 0x65, 0x6d,\n\t0x52, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x22, 0x2b, 0x0a, 0x10, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x43,\n\t0x61, 0x72, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73,\n\t0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65,\n\t0x72, 0x49, 0x64, 0x22, 0x29, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x43, 0x61, 0x72, 0x74, 0x52, 0x65,\n\t0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64,\n\t0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0x4c,\n\t0x0a, 0x04, 0x43, 0x61, 0x72, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69,\n\t0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12,\n\t0x2b, 0x0a, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15,\n\t0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x43, 0x61, 0x72,\n\t0x74, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x22, 0x07, 0x0a, 0x05,\n\t0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x56, 0x0a, 0x1a, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x63,\n\t0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75,\n\t0x65, 0x73, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01,\n\t0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b,\n\t0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28,\n\t0x09, 0x52, 0x0a, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x49, 0x64, 0x73, 0x22, 0x3e, 0x0a,\n\t0x1b, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x64, 0x61, 0x74,\n\t0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1f, 0x0a, 0x0b,\n\t0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28,\n\t0x09, 0x52, 0x0a, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x49, 0x64, 0x73, 0x22, 0xba, 0x01,\n\t0x0a, 0x07, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18,\n\t0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d,\n\t0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x20, 0x0a,\n\t0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01,\n\t0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12,\n\t0x18, 0x0a, 0x07, 0x70, 0x69, 0x63, 0x74, 0x75, 0x72, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09,\n\t0x52, 0x07, 0x70, 0x69, 0x63, 0x74, 0x75, 0x72, 0x65, 0x12, 0x2f, 0x0a, 0x09, 0x70, 0x72, 0x69,\n\t0x63, 0x65, 0x5f, 0x75, 0x73, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x68,\n\t0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x4d, 0x6f, 0x6e, 0x65, 0x79,\n\t0x52, 0x08, 0x70, 0x72, 0x69, 0x63, 0x65, 0x55, 0x73, 0x64, 0x12, 0x1e, 0x0a, 0x0a, 0x63, 0x61,\n\t0x74, 0x65, 0x67, 0x6f, 0x72, 0x69, 0x65, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a,\n\t0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x69, 0x65, 0x73, 0x22, 0x48, 0x0a, 0x14, 0x4c, 0x69,\n\t0x73, 0x74, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,\n\t0x73, 0x65, 0x12, 0x30, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x73, 0x18, 0x01,\n\t0x20, 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68,\n\t0x6f, 0x70, 0x2e, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x64,\n\t0x75, 0x63, 0x74, 0x73, 0x22, 0x23, 0x0a, 0x11, 0x47, 0x65, 0x74, 0x50, 0x72, 0x6f, 0x64, 0x75,\n\t0x63, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18,\n\t0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x22, 0x2d, 0x0a, 0x15, 0x53, 0x65, 0x61,\n\t0x72, 0x63, 0x68, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65,\n\t0x73, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28,\n\t0x09, 0x52, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x22, 0x48, 0x0a, 0x16, 0x53, 0x65, 0x61, 0x72,\n\t0x63, 0x68, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,\n\t0x73, 0x65, 0x12, 0x2e, 0x0a, 0x07, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x18, 0x01, 0x20,\n\t0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f,\n\t0x70, 0x2e, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x52, 0x07, 0x72, 0x65, 0x73, 0x75, 0x6c,\n\t0x74, 0x73, 0x22, 0x6e, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x51, 0x75, 0x6f, 0x74, 0x65, 0x52, 0x65,\n\t0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73,\n\t0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72,\n\t0x73, 0x68, 0x6f, 0x70, 0x2e, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, 0x07, 0x61, 0x64,\n\t0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x2b, 0x0a, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x18, 0x02,\n\t0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68,\n\t0x6f, 0x70, 0x2e, 0x43, 0x61, 0x72, 0x74, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x05, 0x69, 0x74, 0x65,\n\t0x6d, 0x73, 0x22, 0x41, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x51, 0x75, 0x6f, 0x74, 0x65, 0x52, 0x65,\n\t0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2d, 0x0a, 0x08, 0x63, 0x6f, 0x73, 0x74, 0x5f, 0x75,\n\t0x73, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74,\n\t0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x4d, 0x6f, 0x6e, 0x65, 0x79, 0x52, 0x07, 0x63, 0x6f,\n\t0x73, 0x74, 0x55, 0x73, 0x64, 0x22, 0x6f, 0x0a, 0x10, 0x53, 0x68, 0x69, 0x70, 0x4f, 0x72, 0x64,\n\t0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x07, 0x61, 0x64, 0x64,\n\t0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x68, 0x69, 0x70,\n\t0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73,\n\t0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x2b, 0x0a, 0x05, 0x69, 0x74, 0x65,\n\t0x6d, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74,\n\t0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x43, 0x61, 0x72, 0x74, 0x49, 0x74, 0x65, 0x6d, 0x52,\n\t0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x22, 0x34, 0x0a, 0x11, 0x53, 0x68, 0x69, 0x70, 0x4f, 0x72,\n\t0x64, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x74,\n\t0x72, 0x61, 0x63, 0x6b, 0x69, 0x6e, 0x67, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,\n\t0x52, 0x0a, 0x74, 0x72, 0x61, 0x63, 0x6b, 0x69, 0x6e, 0x67, 0x49, 0x64, 0x22, 0x8f, 0x01, 0x0a,\n\t0x07, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x25, 0x0a, 0x0e, 0x73, 0x74, 0x72, 0x65,\n\t0x65, 0x74, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,\n\t0x52, 0x0d, 0x73, 0x74, 0x72, 0x65, 0x65, 0x74, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12,\n\t0x12, 0x0a, 0x04, 0x63, 0x69, 0x74, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x63,\n\t0x69, 0x74, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01,\n\t0x28, 0x09, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x75,\n\t0x6e, 0x74, 0x72, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x6f, 0x75, 0x6e,\n\t0x74, 0x72, 0x79, 0x12, 0x19, 0x0a, 0x08, 0x7a, 0x69, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18,\n\t0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x07, 0x7a, 0x69, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x22, 0x58,\n\t0x0a, 0x05, 0x4d, 0x6f, 0x6e, 0x65, 0x79, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x75, 0x72, 0x72, 0x65,\n\t0x6e, 0x63, 0x79, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c,\n\t0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x14, 0x0a, 0x05,\n\t0x75, 0x6e, 0x69, 0x74, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x75, 0x6e, 0x69,\n\t0x74, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x6e, 0x61, 0x6e, 0x6f, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28,\n\t0x05, 0x52, 0x05, 0x6e, 0x61, 0x6e, 0x6f, 0x73, 0x22, 0x47, 0x0a, 0x1e, 0x47, 0x65, 0x74, 0x53,\n\t0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x69,\n\t0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x63, 0x75,\n\t0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03,\n\t0x28, 0x09, 0x52, 0x0d, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x43, 0x6f, 0x64, 0x65,\n\t0x73, 0x22, 0x5c, 0x0a, 0x19, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x43, 0x6f, 0x6e,\n\t0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x26,\n\t0x0a, 0x04, 0x66, 0x72, 0x6f, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x68,\n\t0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x4d, 0x6f, 0x6e, 0x65, 0x79,\n\t0x52, 0x04, 0x66, 0x72, 0x6f, 0x6d, 0x12, 0x17, 0x0a, 0x07, 0x74, 0x6f, 0x5f, 0x63, 0x6f, 0x64,\n\t0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x74, 0x6f, 0x43, 0x6f, 0x64, 0x65, 0x22,\n\t0xe6, 0x01, 0x0a, 0x0e, 0x43, 0x72, 0x65, 0x64, 0x69, 0x74, 0x43, 0x61, 0x72, 0x64, 0x49, 0x6e,\n\t0x66, 0x6f, 0x12, 0x2c, 0x0a, 0x12, 0x63, 0x72, 0x65, 0x64, 0x69, 0x74, 0x5f, 0x63, 0x61, 0x72,\n\t0x64, 0x5f, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10,\n\t0x63, 0x72, 0x65, 0x64, 0x69, 0x74, 0x43, 0x61, 0x72, 0x64, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72,\n\t0x12, 0x26, 0x0a, 0x0f, 0x63, 0x72, 0x65, 0x64, 0x69, 0x74, 0x5f, 0x63, 0x61, 0x72, 0x64, 0x5f,\n\t0x63, 0x76, 0x76, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0d, 0x63, 0x72, 0x65, 0x64, 0x69,\n\t0x74, 0x43, 0x61, 0x72, 0x64, 0x43, 0x76, 0x76, 0x12, 0x3d, 0x0a, 0x1b, 0x63, 0x72, 0x65, 0x64,\n\t0x69, 0x74, 0x5f, 0x63, 0x61, 0x72, 0x64, 0x5f, 0x65, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69,\n\t0x6f, 0x6e, 0x5f, 0x79, 0x65, 0x61, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x18, 0x63,\n\t0x72, 0x65, 0x64, 0x69, 0x74, 0x43, 0x61, 0x72, 0x64, 0x45, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74,\n\t0x69, 0x6f, 0x6e, 0x59, 0x65, 0x61, 0x72, 0x12, 0x3f, 0x0a, 0x1c, 0x63, 0x72, 0x65, 0x64, 0x69,\n\t0x74, 0x5f, 0x63, 0x61, 0x72, 0x64, 0x5f, 0x65, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f,\n\t0x6e, 0x5f, 0x6d, 0x6f, 0x6e, 0x74, 0x68, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x19, 0x63,\n\t0x72, 0x65, 0x64, 0x69, 0x74, 0x43, 0x61, 0x72, 0x64, 0x45, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74,\n\t0x69, 0x6f, 0x6e, 0x4d, 0x6f, 0x6e, 0x74, 0x68, 0x22, 0x79, 0x0a, 0x0d, 0x43, 0x68, 0x61, 0x72,\n\t0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2a, 0x0a, 0x06, 0x61, 0x6d, 0x6f,\n\t0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x68, 0x69, 0x70, 0x73,\n\t0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x4d, 0x6f, 0x6e, 0x65, 0x79, 0x52, 0x06, 0x61,\n\t0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x3c, 0x0a, 0x0b, 0x63, 0x72, 0x65, 0x64, 0x69, 0x74, 0x5f,\n\t0x63, 0x61, 0x72, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x68, 0x69, 0x70,\n\t0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x43, 0x72, 0x65, 0x64, 0x69, 0x74, 0x43,\n\t0x61, 0x72, 0x64, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x0a, 0x63, 0x72, 0x65, 0x64, 0x69, 0x74, 0x43,\n\t0x61, 0x72, 0x64, 0x22, 0x37, 0x0a, 0x0e, 0x43, 0x68, 0x61, 0x72, 0x67, 0x65, 0x52, 0x65, 0x73,\n\t0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63,\n\t0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x74,\n\t0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x22, 0x5e, 0x0a, 0x09,\n\t0x4f, 0x72, 0x64, 0x65, 0x72, 0x49, 0x74, 0x65, 0x6d, 0x12, 0x29, 0x0a, 0x04, 0x69, 0x74, 0x65,\n\t0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65,\n\t0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x43, 0x61, 0x72, 0x74, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x04,\n\t0x69, 0x74, 0x65, 0x6d, 0x12, 0x26, 0x0a, 0x04, 0x63, 0x6f, 0x73, 0x74, 0x18, 0x02, 0x20, 0x01,\n\t0x28, 0x0b, 0x32, 0x12, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70,\n\t0x2e, 0x4d, 0x6f, 0x6e, 0x65, 0x79, 0x52, 0x04, 0x63, 0x6f, 0x73, 0x74, 0x22, 0x82, 0x02, 0x0a,\n\t0x0b, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x19, 0x0a, 0x08,\n\t0x6f, 0x72, 0x64, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07,\n\t0x6f, 0x72, 0x64, 0x65, 0x72, 0x49, 0x64, 0x12, 0x30, 0x0a, 0x14, 0x73, 0x68, 0x69, 0x70, 0x70,\n\t0x69, 0x6e, 0x67, 0x5f, 0x74, 0x72, 0x61, 0x63, 0x6b, 0x69, 0x6e, 0x67, 0x5f, 0x69, 0x64, 0x18,\n\t0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x12, 0x73, 0x68, 0x69, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x54,\n\t0x72, 0x61, 0x63, 0x6b, 0x69, 0x6e, 0x67, 0x49, 0x64, 0x12, 0x37, 0x0a, 0x0d, 0x73, 0x68, 0x69,\n\t0x70, 0x70, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x6f, 0x73, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b,\n\t0x32, 0x12, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x4d,\n\t0x6f, 0x6e, 0x65, 0x79, 0x52, 0x0c, 0x73, 0x68, 0x69, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x43, 0x6f,\n\t0x73, 0x74, 0x12, 0x3f, 0x0a, 0x10, 0x73, 0x68, 0x69, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x5f, 0x61,\n\t0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x68,\n\t0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x41, 0x64, 0x64, 0x72, 0x65,\n\t0x73, 0x73, 0x52, 0x0f, 0x73, 0x68, 0x69, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x41, 0x64, 0x64, 0x72,\n\t0x65, 0x73, 0x73, 0x12, 0x2c, 0x0a, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x18, 0x05, 0x20, 0x03,\n\t0x28, 0x0b, 0x32, 0x16, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70,\n\t0x2e, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d,\n\t0x73, 0x22, 0x64, 0x0a, 0x1c, 0x53, 0x65, 0x6e, 0x64, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x43, 0x6f,\n\t0x6e, 0x66, 0x69, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,\n\t0x74, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,\n\t0x52, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x12, 0x2e, 0x0a, 0x05, 0x6f, 0x72, 0x64, 0x65, 0x72,\n\t0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72,\n\t0x73, 0x68, 0x6f, 0x70, 0x2e, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74,\n\t0x52, 0x05, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x22, 0xd5, 0x01, 0x0a, 0x11, 0x50, 0x6c, 0x61, 0x63,\n\t0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x17, 0x0a,\n\t0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06,\n\t0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x63,\n\t0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x75,\n\t0x73, 0x65, 0x72, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x12, 0x2e, 0x0a, 0x07, 0x61,\n\t0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x68,\n\t0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x41, 0x64, 0x64, 0x72, 0x65,\n\t0x73, 0x73, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x65,\n\t0x6d, 0x61, 0x69, 0x6c, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x6d, 0x61, 0x69,\n\t0x6c, 0x12, 0x3c, 0x0a, 0x0b, 0x63, 0x72, 0x65, 0x64, 0x69, 0x74, 0x5f, 0x63, 0x61, 0x72, 0x64,\n\t0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72,\n\t0x73, 0x68, 0x6f, 0x70, 0x2e, 0x43, 0x72, 0x65, 0x64, 0x69, 0x74, 0x43, 0x61, 0x72, 0x64, 0x49,\n\t0x6e, 0x66, 0x6f, 0x52, 0x0a, 0x63, 0x72, 0x65, 0x64, 0x69, 0x74, 0x43, 0x61, 0x72, 0x64, 0x22,\n\t0x44, 0x0a, 0x12, 0x50, 0x6c, 0x61, 0x63, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x73,\n\t0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2e, 0x0a, 0x05, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x18, 0x01,\n\t0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68,\n\t0x6f, 0x70, 0x2e, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x05,\n\t0x6f, 0x72, 0x64, 0x65, 0x72, 0x22, 0x2e, 0x0a, 0x09, 0x41, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65,\n\t0x73, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x5f, 0x6b, 0x65,\n\t0x79, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78,\n\t0x74, 0x4b, 0x65, 0x79, 0x73, 0x22, 0x2f, 0x0a, 0x0a, 0x41, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f,\n\t0x6e, 0x73, 0x65, 0x12, 0x21, 0x0a, 0x03, 0x61, 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b,\n\t0x32, 0x0f, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x41,\n\t0x64, 0x52, 0x03, 0x61, 0x64, 0x73, 0x22, 0x3b, 0x0a, 0x02, 0x41, 0x64, 0x12, 0x21, 0x0a, 0x0c,\n\t0x72, 0x65, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01,\n\t0x28, 0x09, 0x52, 0x0b, 0x72, 0x65, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x55, 0x72, 0x6c, 0x12,\n\t0x12, 0x0a, 0x04, 0x74, 0x65, 0x78, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74,\n\t0x65, 0x78, 0x74, 0x32, 0xca, 0x01, 0x0a, 0x0b, 0x43, 0x61, 0x72, 0x74, 0x53, 0x65, 0x72, 0x76,\n\t0x69, 0x63, 0x65, 0x12, 0x3c, 0x0a, 0x07, 0x41, 0x64, 0x64, 0x49, 0x74, 0x65, 0x6d, 0x12, 0x1b,\n\t0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x41, 0x64, 0x64,\n\t0x49, 0x74, 0x65, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x68, 0x69,\n\t0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22,\n\t0x00, 0x12, 0x3b, 0x0a, 0x07, 0x47, 0x65, 0x74, 0x43, 0x61, 0x72, 0x74, 0x12, 0x1b, 0x2e, 0x68,\n\t0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x61,\n\t0x72, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x11, 0x2e, 0x68, 0x69, 0x70, 0x73,\n\t0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x43, 0x61, 0x72, 0x74, 0x22, 0x00, 0x12, 0x40,\n\t0x0a, 0x09, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x43, 0x61, 0x72, 0x74, 0x12, 0x1d, 0x2e, 0x68, 0x69,\n\t0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x43,\n\t0x61, 0x72, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x68, 0x69, 0x70,\n\t0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00,\n\t0x32, 0x83, 0x01, 0x0a, 0x15, 0x52, 0x65, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x64, 0x61, 0x74,\n\t0x69, 0x6f, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x6a, 0x0a, 0x13, 0x4c, 0x69,\n\t0x73, 0x74, 0x52, 0x65, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e,\n\t0x73, 0x12, 0x27, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e,\n\t0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x64, 0x61, 0x74, 0x69,\n\t0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, 0x68, 0x69, 0x70,\n\t0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x63,\n\t0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70,\n\t0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x32, 0x83, 0x02, 0x0a, 0x15, 0x50, 0x72, 0x6f, 0x64, 0x75,\n\t0x63, 0x74, 0x43, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65,\n\t0x12, 0x47, 0x0a, 0x0c, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x73,\n\t0x12, 0x12, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x45,\n\t0x6d, 0x70, 0x74, 0x79, 0x1a, 0x21, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68,\n\t0x6f, 0x70, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x73, 0x52,\n\t0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x44, 0x0a, 0x0a, 0x47, 0x65, 0x74,\n\t0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x12, 0x1e, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65,\n\t0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x47, 0x65, 0x74, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74,\n\t0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65,\n\t0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x22, 0x00, 0x12,\n\t0x5b, 0x0a, 0x0e, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74,\n\t0x73, 0x12, 0x22, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e,\n\t0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x73, 0x52, 0x65,\n\t0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73,\n\t0x68, 0x6f, 0x70, 0x2e, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63,\n\t0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x32, 0xaa, 0x01, 0x0a,\n\t0x0f, 0x53, 0x68, 0x69, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65,\n\t0x12, 0x49, 0x0a, 0x08, 0x47, 0x65, 0x74, 0x51, 0x75, 0x6f, 0x74, 0x65, 0x12, 0x1c, 0x2e, 0x68,\n\t0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x47, 0x65, 0x74, 0x51, 0x75,\n\t0x6f, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x68, 0x69, 0x70,\n\t0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x47, 0x65, 0x74, 0x51, 0x75, 0x6f, 0x74,\n\t0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x4c, 0x0a, 0x09, 0x53,\n\t0x68, 0x69, 0x70, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x1d, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74,\n\t0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x53, 0x68, 0x69, 0x70, 0x4f, 0x72, 0x64, 0x65, 0x72,\n\t0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65,\n\t0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x53, 0x68, 0x69, 0x70, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52,\n\t0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x32, 0xb7, 0x01, 0x0a, 0x0f, 0x43, 0x75,\n\t0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x5b, 0x0a,\n\t0x16, 0x47, 0x65, 0x74, 0x53, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x43, 0x75, 0x72,\n\t0x72, 0x65, 0x6e, 0x63, 0x69, 0x65, 0x73, 0x12, 0x12, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65,\n\t0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x2b, 0x2e, 0x68, 0x69,\n\t0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x75, 0x70,\n\t0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x69, 0x65, 0x73,\n\t0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x47, 0x0a, 0x07, 0x43, 0x6f,\n\t0x6e, 0x76, 0x65, 0x72, 0x74, 0x12, 0x26, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73,\n\t0x68, 0x6f, 0x70, 0x2e, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x43, 0x6f, 0x6e, 0x76,\n\t0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e,\n\t0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x4d, 0x6f, 0x6e, 0x65,\n\t0x79, 0x22, 0x00, 0x32, 0x55, 0x0a, 0x0e, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x65,\n\t0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x43, 0x0a, 0x06, 0x43, 0x68, 0x61, 0x72, 0x67, 0x65, 0x12,\n\t0x1a, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x43, 0x68,\n\t0x61, 0x72, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x68, 0x69,\n\t0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x43, 0x68, 0x61, 0x72, 0x67, 0x65,\n\t0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x32, 0x68, 0x0a, 0x0c, 0x45, 0x6d,\n\t0x61, 0x69, 0x6c, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x58, 0x0a, 0x15, 0x53, 0x65,\n\t0x6e, 0x64, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x61, 0x74,\n\t0x69, 0x6f, 0x6e, 0x12, 0x29, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f,\n\t0x70, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69,\n\t0x72, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12,\n\t0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x45, 0x6d, 0x70,\n\t0x74, 0x79, 0x22, 0x00, 0x32, 0x62, 0x0a, 0x0f, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x6f, 0x75, 0x74,\n\t0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x4f, 0x0a, 0x0a, 0x50, 0x6c, 0x61, 0x63, 0x65,\n\t0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x1e, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73,\n\t0x68, 0x6f, 0x70, 0x2e, 0x50, 0x6c, 0x61, 0x63, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65,\n\t0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73,\n\t0x68, 0x6f, 0x70, 0x2e, 0x50, 0x6c, 0x61, 0x63, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65,\n\t0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x32, 0x48, 0x0a, 0x09, 0x41, 0x64, 0x53, 0x65,\n\t0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x3b, 0x0a, 0x06, 0x47, 0x65, 0x74, 0x41, 0x64, 0x73, 0x12,\n\t0x16, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x41, 0x64,\n\t0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65,\n\t0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x41, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,\n\t0x22, 0x00, 0x42, 0x40, 0x5a, 0x3e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d,\n\t0x2f, 0x47, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x43, 0x6c, 0x6f, 0x75, 0x64, 0x50, 0x6c, 0x61, 0x74,\n\t0x66, 0x6f, 0x72, 0x6d, 0x2f, 0x6d, 0x69, 0x73, 0x63, 0x72, 0x6f, 0x73, 0x65, 0x72, 0x76, 0x69,\n\t0x63, 0x65, 0x73, 0x2d, 0x64, 0x65, 0x6d, 0x6f, 0x2f, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72,\n\t0x73, 0x68, 0x6f, 0x70, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,\n}\n\nvar (\n\tfile_demo_proto_rawDescOnce sync.Once\n\tfile_demo_proto_rawDescData = file_demo_proto_rawDesc\n)\n\nfunc file_demo_proto_rawDescGZIP() []byte {\n\tfile_demo_proto_rawDescOnce.Do(func() {\n\t\tfile_demo_proto_rawDescData = protoimpl.X.CompressGZIP(file_demo_proto_rawDescData)\n\t})\n\treturn file_demo_proto_rawDescData\n}\n\nvar file_demo_proto_msgTypes = make([]protoimpl.MessageInfo, 32)\nvar file_demo_proto_goTypes = []any{\n\t(*CartItem)(nil),                       // 0: hipstershop.CartItem\n\t(*AddItemRequest)(nil),                 // 1: hipstershop.AddItemRequest\n\t(*EmptyCartRequest)(nil),               // 2: hipstershop.EmptyCartRequest\n\t(*GetCartRequest)(nil),                 // 3: hipstershop.GetCartRequest\n\t(*Cart)(nil),                           // 4: hipstershop.Cart\n\t(*Empty)(nil),                          // 5: hipstershop.Empty\n\t(*ListRecommendationsRequest)(nil),     // 6: hipstershop.ListRecommendationsRequest\n\t(*ListRecommendationsResponse)(nil),    // 7: hipstershop.ListRecommendationsResponse\n\t(*Product)(nil),                        // 8: hipstershop.Product\n\t(*ListProductsResponse)(nil),           // 9: hipstershop.ListProductsResponse\n\t(*GetProductRequest)(nil),              // 10: hipstershop.GetProductRequest\n\t(*SearchProductsRequest)(nil),          // 11: hipstershop.SearchProductsRequest\n\t(*SearchProductsResponse)(nil),         // 12: hipstershop.SearchProductsResponse\n\t(*GetQuoteRequest)(nil),                // 13: hipstershop.GetQuoteRequest\n\t(*GetQuoteResponse)(nil),               // 14: hipstershop.GetQuoteResponse\n\t(*ShipOrderRequest)(nil),               // 15: hipstershop.ShipOrderRequest\n\t(*ShipOrderResponse)(nil),              // 16: hipstershop.ShipOrderResponse\n\t(*Address)(nil),                        // 17: hipstershop.Address\n\t(*Money)(nil),                          // 18: hipstershop.Money\n\t(*GetSupportedCurrenciesResponse)(nil), // 19: hipstershop.GetSupportedCurrenciesResponse\n\t(*CurrencyConversionRequest)(nil),      // 20: hipstershop.CurrencyConversionRequest\n\t(*CreditCardInfo)(nil),                 // 21: hipstershop.CreditCardInfo\n\t(*ChargeRequest)(nil),                  // 22: hipstershop.ChargeRequest\n\t(*ChargeResponse)(nil),                 // 23: hipstershop.ChargeResponse\n\t(*OrderItem)(nil),                      // 24: hipstershop.OrderItem\n\t(*OrderResult)(nil),                    // 25: hipstershop.OrderResult\n\t(*SendOrderConfirmationRequest)(nil),   // 26: hipstershop.SendOrderConfirmationRequest\n\t(*PlaceOrderRequest)(nil),              // 27: hipstershop.PlaceOrderRequest\n\t(*PlaceOrderResponse)(nil),             // 28: hipstershop.PlaceOrderResponse\n\t(*AdRequest)(nil),                      // 29: hipstershop.AdRequest\n\t(*AdResponse)(nil),                     // 30: hipstershop.AdResponse\n\t(*Ad)(nil),                             // 31: hipstershop.Ad\n}\nvar file_demo_proto_depIdxs = []int32{\n\t0,  // 0: hipstershop.AddItemRequest.item:type_name -> hipstershop.CartItem\n\t0,  // 1: hipstershop.Cart.items:type_name -> hipstershop.CartItem\n\t18, // 2: hipstershop.Product.price_usd:type_name -> hipstershop.Money\n\t8,  // 3: hipstershop.ListProductsResponse.products:type_name -> hipstershop.Product\n\t8,  // 4: hipstershop.SearchProductsResponse.results:type_name -> hipstershop.Product\n\t17, // 5: hipstershop.GetQuoteRequest.address:type_name -> hipstershop.Address\n\t0,  // 6: hipstershop.GetQuoteRequest.items:type_name -> hipstershop.CartItem\n\t18, // 7: hipstershop.GetQuoteResponse.cost_usd:type_name -> hipstershop.Money\n\t17, // 8: hipstershop.ShipOrderRequest.address:type_name -> hipstershop.Address\n\t0,  // 9: hipstershop.ShipOrderRequest.items:type_name -> hipstershop.CartItem\n\t18, // 10: hipstershop.CurrencyConversionRequest.from:type_name -> hipstershop.Money\n\t18, // 11: hipstershop.ChargeRequest.amount:type_name -> hipstershop.Money\n\t21, // 12: hipstershop.ChargeRequest.credit_card:type_name -> hipstershop.CreditCardInfo\n\t0,  // 13: hipstershop.OrderItem.item:type_name -> hipstershop.CartItem\n\t18, // 14: hipstershop.OrderItem.cost:type_name -> hipstershop.Money\n\t18, // 15: hipstershop.OrderResult.shipping_cost:type_name -> hipstershop.Money\n\t17, // 16: hipstershop.OrderResult.shipping_address:type_name -> hipstershop.Address\n\t24, // 17: hipstershop.OrderResult.items:type_name -> hipstershop.OrderItem\n\t25, // 18: hipstershop.SendOrderConfirmationRequest.order:type_name -> hipstershop.OrderResult\n\t17, // 19: hipstershop.PlaceOrderRequest.address:type_name -> hipstershop.Address\n\t21, // 20: hipstershop.PlaceOrderRequest.credit_card:type_name -> hipstershop.CreditCardInfo\n\t25, // 21: hipstershop.PlaceOrderResponse.order:type_name -> hipstershop.OrderResult\n\t31, // 22: hipstershop.AdResponse.ads:type_name -> hipstershop.Ad\n\t1,  // 23: hipstershop.CartService.AddItem:input_type -> hipstershop.AddItemRequest\n\t3,  // 24: hipstershop.CartService.GetCart:input_type -> hipstershop.GetCartRequest\n\t2,  // 25: hipstershop.CartService.EmptyCart:input_type -> hipstershop.EmptyCartRequest\n\t6,  // 26: hipstershop.RecommendationService.ListRecommendations:input_type -> hipstershop.ListRecommendationsRequest\n\t5,  // 27: hipstershop.ProductCatalogService.ListProducts:input_type -> hipstershop.Empty\n\t10, // 28: hipstershop.ProductCatalogService.GetProduct:input_type -> hipstershop.GetProductRequest\n\t11, // 29: hipstershop.ProductCatalogService.SearchProducts:input_type -> hipstershop.SearchProductsRequest\n\t13, // 30: hipstershop.ShippingService.GetQuote:input_type -> hipstershop.GetQuoteRequest\n\t15, // 31: hipstershop.ShippingService.ShipOrder:input_type -> hipstershop.ShipOrderRequest\n\t5,  // 32: hipstershop.CurrencyService.GetSupportedCurrencies:input_type -> hipstershop.Empty\n\t20, // 33: hipstershop.CurrencyService.Convert:input_type -> hipstershop.CurrencyConversionRequest\n\t22, // 34: hipstershop.PaymentService.Charge:input_type -> hipstershop.ChargeRequest\n\t26, // 35: hipstershop.EmailService.SendOrderConfirmation:input_type -> hipstershop.SendOrderConfirmationRequest\n\t27, // 36: hipstershop.CheckoutService.PlaceOrder:input_type -> hipstershop.PlaceOrderRequest\n\t29, // 37: hipstershop.AdService.GetAds:input_type -> hipstershop.AdRequest\n\t5,  // 38: hipstershop.CartService.AddItem:output_type -> hipstershop.Empty\n\t4,  // 39: hipstershop.CartService.GetCart:output_type -> hipstershop.Cart\n\t5,  // 40: hipstershop.CartService.EmptyCart:output_type -> hipstershop.Empty\n\t7,  // 41: hipstershop.RecommendationService.ListRecommendations:output_type -> hipstershop.ListRecommendationsResponse\n\t9,  // 42: hipstershop.ProductCatalogService.ListProducts:output_type -> hipstershop.ListProductsResponse\n\t8,  // 43: hipstershop.ProductCatalogService.GetProduct:output_type -> hipstershop.Product\n\t12, // 44: hipstershop.ProductCatalogService.SearchProducts:output_type -> hipstershop.SearchProductsResponse\n\t14, // 45: hipstershop.ShippingService.GetQuote:output_type -> hipstershop.GetQuoteResponse\n\t16, // 46: hipstershop.ShippingService.ShipOrder:output_type -> hipstershop.ShipOrderResponse\n\t19, // 47: hipstershop.CurrencyService.GetSupportedCurrencies:output_type -> hipstershop.GetSupportedCurrenciesResponse\n\t18, // 48: hipstershop.CurrencyService.Convert:output_type -> hipstershop.Money\n\t23, // 49: hipstershop.PaymentService.Charge:output_type -> hipstershop.ChargeResponse\n\t5,  // 50: hipstershop.EmailService.SendOrderConfirmation:output_type -> hipstershop.Empty\n\t28, // 51: hipstershop.CheckoutService.PlaceOrder:output_type -> hipstershop.PlaceOrderResponse\n\t30, // 52: hipstershop.AdService.GetAds:output_type -> hipstershop.AdResponse\n\t38, // [38:53] is the sub-list for method output_type\n\t23, // [23:38] is the sub-list for method input_type\n\t23, // [23:23] is the sub-list for extension type_name\n\t23, // [23:23] is the sub-list for extension extendee\n\t0,  // [0:23] is the sub-list for field type_name\n}\n\nfunc init() { file_demo_proto_init() }\nfunc file_demo_proto_init() {\n\tif File_demo_proto != nil {\n\t\treturn\n\t}\n\tif !protoimpl.UnsafeEnabled {\n\t\tfile_demo_proto_msgTypes[0].Exporter = func(v any, i int) any {\n\t\t\tswitch v := v.(*CartItem); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_demo_proto_msgTypes[1].Exporter = func(v any, i int) any {\n\t\t\tswitch v := v.(*AddItemRequest); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_demo_proto_msgTypes[2].Exporter = func(v any, i int) any {\n\t\t\tswitch v := v.(*EmptyCartRequest); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_demo_proto_msgTypes[3].Exporter = func(v any, i int) any {\n\t\t\tswitch v := v.(*GetCartRequest); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_demo_proto_msgTypes[4].Exporter = func(v any, i int) any {\n\t\t\tswitch v := v.(*Cart); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_demo_proto_msgTypes[5].Exporter = func(v any, i int) any {\n\t\t\tswitch v := v.(*Empty); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_demo_proto_msgTypes[6].Exporter = func(v any, i int) any {\n\t\t\tswitch v := v.(*ListRecommendationsRequest); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_demo_proto_msgTypes[7].Exporter = func(v any, i int) any {\n\t\t\tswitch v := v.(*ListRecommendationsResponse); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_demo_proto_msgTypes[8].Exporter = func(v any, i int) any {\n\t\t\tswitch v := v.(*Product); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_demo_proto_msgTypes[9].Exporter = func(v any, i int) any {\n\t\t\tswitch v := v.(*ListProductsResponse); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_demo_proto_msgTypes[10].Exporter = func(v any, i int) any {\n\t\t\tswitch v := v.(*GetProductRequest); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_demo_proto_msgTypes[11].Exporter = func(v any, i int) any {\n\t\t\tswitch v := v.(*SearchProductsRequest); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_demo_proto_msgTypes[12].Exporter = func(v any, i int) any {\n\t\t\tswitch v := v.(*SearchProductsResponse); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_demo_proto_msgTypes[13].Exporter = func(v any, i int) any {\n\t\t\tswitch v := v.(*GetQuoteRequest); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_demo_proto_msgTypes[14].Exporter = func(v any, i int) any {\n\t\t\tswitch v := v.(*GetQuoteResponse); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_demo_proto_msgTypes[15].Exporter = func(v any, i int) any {\n\t\t\tswitch v := v.(*ShipOrderRequest); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_demo_proto_msgTypes[16].Exporter = func(v any, i int) any {\n\t\t\tswitch v := v.(*ShipOrderResponse); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_demo_proto_msgTypes[17].Exporter = func(v any, i int) any {\n\t\t\tswitch v := v.(*Address); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_demo_proto_msgTypes[18].Exporter = func(v any, i int) any {\n\t\t\tswitch v := v.(*Money); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_demo_proto_msgTypes[19].Exporter = func(v any, i int) any {\n\t\t\tswitch v := v.(*GetSupportedCurrenciesResponse); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_demo_proto_msgTypes[20].Exporter = func(v any, i int) any {\n\t\t\tswitch v := v.(*CurrencyConversionRequest); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_demo_proto_msgTypes[21].Exporter = func(v any, i int) any {\n\t\t\tswitch v := v.(*CreditCardInfo); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_demo_proto_msgTypes[22].Exporter = func(v any, i int) any {\n\t\t\tswitch v := v.(*ChargeRequest); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_demo_proto_msgTypes[23].Exporter = func(v any, i int) any {\n\t\t\tswitch v := v.(*ChargeResponse); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_demo_proto_msgTypes[24].Exporter = func(v any, i int) any {\n\t\t\tswitch v := v.(*OrderItem); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_demo_proto_msgTypes[25].Exporter = func(v any, i int) any {\n\t\t\tswitch v := v.(*OrderResult); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_demo_proto_msgTypes[26].Exporter = func(v any, i int) any {\n\t\t\tswitch v := v.(*SendOrderConfirmationRequest); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_demo_proto_msgTypes[27].Exporter = func(v any, i int) any {\n\t\t\tswitch v := v.(*PlaceOrderRequest); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_demo_proto_msgTypes[28].Exporter = func(v any, i int) any {\n\t\t\tswitch v := v.(*PlaceOrderResponse); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_demo_proto_msgTypes[29].Exporter = func(v any, i int) any {\n\t\t\tswitch v := v.(*AdRequest); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_demo_proto_msgTypes[30].Exporter = func(v any, i int) any {\n\t\t\tswitch v := v.(*AdResponse); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_demo_proto_msgTypes[31].Exporter = func(v any, i int) any {\n\t\t\tswitch v := v.(*Ad); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: file_demo_proto_rawDesc,\n\t\t\tNumEnums:      0,\n\t\t\tNumMessages:   32,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   9,\n\t\t},\n\t\tGoTypes:           file_demo_proto_goTypes,\n\t\tDependencyIndexes: file_demo_proto_depIdxs,\n\t\tMessageInfos:      file_demo_proto_msgTypes,\n\t}.Build()\n\tFile_demo_proto = out.File\n\tfile_demo_proto_rawDesc = nil\n\tfile_demo_proto_goTypes = nil\n\tfile_demo_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "src/checkoutservice/genproto/demo_grpc.pb.go",
    "content": "// Copyright 2020 Google LLC\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// Code generated by protoc-gen-go-grpc. DO NOT EDIT.\n// versions:\n// - protoc-gen-go-grpc v1.5.1\n// - protoc             v3.6.1\n// source: demo.proto\n\npackage hipstershop\n\nimport (\n\tcontext \"context\"\n\tgrpc \"google.golang.org/grpc\"\n\tcodes \"google.golang.org/grpc/codes\"\n\tstatus \"google.golang.org/grpc/status\"\n)\n\n// This is a compile-time assertion to ensure that this generated file\n// is compatible with the grpc package it is being compiled against.\n// Requires gRPC-Go v1.64.0 or later.\nconst _ = grpc.SupportPackageIsVersion9\n\nconst (\n\tCartService_AddItem_FullMethodName   = \"/hipstershop.CartService/AddItem\"\n\tCartService_GetCart_FullMethodName   = \"/hipstershop.CartService/GetCart\"\n\tCartService_EmptyCart_FullMethodName = \"/hipstershop.CartService/EmptyCart\"\n)\n\n// CartServiceClient is the client API for CartService service.\n//\n// 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.\ntype CartServiceClient interface {\n\tAddItem(ctx context.Context, in *AddItemRequest, opts ...grpc.CallOption) (*Empty, error)\n\tGetCart(ctx context.Context, in *GetCartRequest, opts ...grpc.CallOption) (*Cart, error)\n\tEmptyCart(ctx context.Context, in *EmptyCartRequest, opts ...grpc.CallOption) (*Empty, error)\n}\n\ntype cartServiceClient struct {\n\tcc grpc.ClientConnInterface\n}\n\nfunc NewCartServiceClient(cc grpc.ClientConnInterface) CartServiceClient {\n\treturn &cartServiceClient{cc}\n}\n\nfunc (c *cartServiceClient) AddItem(ctx context.Context, in *AddItemRequest, opts ...grpc.CallOption) (*Empty, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(Empty)\n\terr := c.cc.Invoke(ctx, CartService_AddItem_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *cartServiceClient) GetCart(ctx context.Context, in *GetCartRequest, opts ...grpc.CallOption) (*Cart, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(Cart)\n\terr := c.cc.Invoke(ctx, CartService_GetCart_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *cartServiceClient) EmptyCart(ctx context.Context, in *EmptyCartRequest, opts ...grpc.CallOption) (*Empty, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(Empty)\n\terr := c.cc.Invoke(ctx, CartService_EmptyCart_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\n// CartServiceServer is the server API for CartService service.\n// All implementations must embed UnimplementedCartServiceServer\n// for forward compatibility.\ntype CartServiceServer interface {\n\tAddItem(context.Context, *AddItemRequest) (*Empty, error)\n\tGetCart(context.Context, *GetCartRequest) (*Cart, error)\n\tEmptyCart(context.Context, *EmptyCartRequest) (*Empty, error)\n\tmustEmbedUnimplementedCartServiceServer()\n}\n\n// UnimplementedCartServiceServer must be embedded to have\n// forward compatible implementations.\n//\n// NOTE: this should be embedded by value instead of pointer to avoid a nil\n// pointer dereference when methods are called.\ntype UnimplementedCartServiceServer struct{}\n\nfunc (UnimplementedCartServiceServer) AddItem(context.Context, *AddItemRequest) (*Empty, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method AddItem not implemented\")\n}\nfunc (UnimplementedCartServiceServer) GetCart(context.Context, *GetCartRequest) (*Cart, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method GetCart not implemented\")\n}\nfunc (UnimplementedCartServiceServer) EmptyCart(context.Context, *EmptyCartRequest) (*Empty, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method EmptyCart not implemented\")\n}\nfunc (UnimplementedCartServiceServer) mustEmbedUnimplementedCartServiceServer() {}\nfunc (UnimplementedCartServiceServer) testEmbeddedByValue()                     {}\n\n// UnsafeCartServiceServer may be embedded to opt out of forward compatibility for this service.\n// Use of this interface is not recommended, as added methods to CartServiceServer will\n// result in compilation errors.\ntype UnsafeCartServiceServer interface {\n\tmustEmbedUnimplementedCartServiceServer()\n}\n\nfunc RegisterCartServiceServer(s grpc.ServiceRegistrar, srv CartServiceServer) {\n\t// If the following call pancis, it indicates UnimplementedCartServiceServer was\n\t// embedded by pointer and is nil.  This will cause panics if an\n\t// unimplemented method is ever invoked, so we test this at initialization\n\t// time to prevent it from happening at runtime later due to I/O.\n\tif t, ok := srv.(interface{ testEmbeddedByValue() }); ok {\n\t\tt.testEmbeddedByValue()\n\t}\n\ts.RegisterService(&CartService_ServiceDesc, srv)\n}\n\nfunc _CartService_AddItem_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(AddItemRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(CartServiceServer).AddItem(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: CartService_AddItem_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(CartServiceServer).AddItem(ctx, req.(*AddItemRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _CartService_GetCart_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(GetCartRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(CartServiceServer).GetCart(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: CartService_GetCart_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(CartServiceServer).GetCart(ctx, req.(*GetCartRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _CartService_EmptyCart_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(EmptyCartRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(CartServiceServer).EmptyCart(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: CartService_EmptyCart_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(CartServiceServer).EmptyCart(ctx, req.(*EmptyCartRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\n// CartService_ServiceDesc is the grpc.ServiceDesc for CartService service.\n// It's only intended for direct use with grpc.RegisterService,\n// and not to be introspected or modified (even as a copy)\nvar CartService_ServiceDesc = grpc.ServiceDesc{\n\tServiceName: \"hipstershop.CartService\",\n\tHandlerType: (*CartServiceServer)(nil),\n\tMethods: []grpc.MethodDesc{\n\t\t{\n\t\t\tMethodName: \"AddItem\",\n\t\t\tHandler:    _CartService_AddItem_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"GetCart\",\n\t\t\tHandler:    _CartService_GetCart_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"EmptyCart\",\n\t\t\tHandler:    _CartService_EmptyCart_Handler,\n\t\t},\n\t},\n\tStreams:  []grpc.StreamDesc{},\n\tMetadata: \"demo.proto\",\n}\n\nconst (\n\tRecommendationService_ListRecommendations_FullMethodName = \"/hipstershop.RecommendationService/ListRecommendations\"\n)\n\n// RecommendationServiceClient is the client API for RecommendationService service.\n//\n// 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.\ntype RecommendationServiceClient interface {\n\tListRecommendations(ctx context.Context, in *ListRecommendationsRequest, opts ...grpc.CallOption) (*ListRecommendationsResponse, error)\n}\n\ntype recommendationServiceClient struct {\n\tcc grpc.ClientConnInterface\n}\n\nfunc NewRecommendationServiceClient(cc grpc.ClientConnInterface) RecommendationServiceClient {\n\treturn &recommendationServiceClient{cc}\n}\n\nfunc (c *recommendationServiceClient) ListRecommendations(ctx context.Context, in *ListRecommendationsRequest, opts ...grpc.CallOption) (*ListRecommendationsResponse, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(ListRecommendationsResponse)\n\terr := c.cc.Invoke(ctx, RecommendationService_ListRecommendations_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\n// RecommendationServiceServer is the server API for RecommendationService service.\n// All implementations must embed UnimplementedRecommendationServiceServer\n// for forward compatibility.\ntype RecommendationServiceServer interface {\n\tListRecommendations(context.Context, *ListRecommendationsRequest) (*ListRecommendationsResponse, error)\n\tmustEmbedUnimplementedRecommendationServiceServer()\n}\n\n// UnimplementedRecommendationServiceServer must be embedded to have\n// forward compatible implementations.\n//\n// NOTE: this should be embedded by value instead of pointer to avoid a nil\n// pointer dereference when methods are called.\ntype UnimplementedRecommendationServiceServer struct{}\n\nfunc (UnimplementedRecommendationServiceServer) ListRecommendations(context.Context, *ListRecommendationsRequest) (*ListRecommendationsResponse, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method ListRecommendations not implemented\")\n}\nfunc (UnimplementedRecommendationServiceServer) mustEmbedUnimplementedRecommendationServiceServer() {}\nfunc (UnimplementedRecommendationServiceServer) testEmbeddedByValue()                               {}\n\n// UnsafeRecommendationServiceServer may be embedded to opt out of forward compatibility for this service.\n// Use of this interface is not recommended, as added methods to RecommendationServiceServer will\n// result in compilation errors.\ntype UnsafeRecommendationServiceServer interface {\n\tmustEmbedUnimplementedRecommendationServiceServer()\n}\n\nfunc RegisterRecommendationServiceServer(s grpc.ServiceRegistrar, srv RecommendationServiceServer) {\n\t// If the following call pancis, it indicates UnimplementedRecommendationServiceServer was\n\t// embedded by pointer and is nil.  This will cause panics if an\n\t// unimplemented method is ever invoked, so we test this at initialization\n\t// time to prevent it from happening at runtime later due to I/O.\n\tif t, ok := srv.(interface{ testEmbeddedByValue() }); ok {\n\t\tt.testEmbeddedByValue()\n\t}\n\ts.RegisterService(&RecommendationService_ServiceDesc, srv)\n}\n\nfunc _RecommendationService_ListRecommendations_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(ListRecommendationsRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(RecommendationServiceServer).ListRecommendations(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: RecommendationService_ListRecommendations_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(RecommendationServiceServer).ListRecommendations(ctx, req.(*ListRecommendationsRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\n// RecommendationService_ServiceDesc is the grpc.ServiceDesc for RecommendationService service.\n// It's only intended for direct use with grpc.RegisterService,\n// and not to be introspected or modified (even as a copy)\nvar RecommendationService_ServiceDesc = grpc.ServiceDesc{\n\tServiceName: \"hipstershop.RecommendationService\",\n\tHandlerType: (*RecommendationServiceServer)(nil),\n\tMethods: []grpc.MethodDesc{\n\t\t{\n\t\t\tMethodName: \"ListRecommendations\",\n\t\t\tHandler:    _RecommendationService_ListRecommendations_Handler,\n\t\t},\n\t},\n\tStreams:  []grpc.StreamDesc{},\n\tMetadata: \"demo.proto\",\n}\n\nconst (\n\tProductCatalogService_ListProducts_FullMethodName   = \"/hipstershop.ProductCatalogService/ListProducts\"\n\tProductCatalogService_GetProduct_FullMethodName     = \"/hipstershop.ProductCatalogService/GetProduct\"\n\tProductCatalogService_SearchProducts_FullMethodName = \"/hipstershop.ProductCatalogService/SearchProducts\"\n)\n\n// ProductCatalogServiceClient is the client API for ProductCatalogService service.\n//\n// 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.\ntype ProductCatalogServiceClient interface {\n\tListProducts(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*ListProductsResponse, error)\n\tGetProduct(ctx context.Context, in *GetProductRequest, opts ...grpc.CallOption) (*Product, error)\n\tSearchProducts(ctx context.Context, in *SearchProductsRequest, opts ...grpc.CallOption) (*SearchProductsResponse, error)\n}\n\ntype productCatalogServiceClient struct {\n\tcc grpc.ClientConnInterface\n}\n\nfunc NewProductCatalogServiceClient(cc grpc.ClientConnInterface) ProductCatalogServiceClient {\n\treturn &productCatalogServiceClient{cc}\n}\n\nfunc (c *productCatalogServiceClient) ListProducts(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*ListProductsResponse, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(ListProductsResponse)\n\terr := c.cc.Invoke(ctx, ProductCatalogService_ListProducts_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *productCatalogServiceClient) GetProduct(ctx context.Context, in *GetProductRequest, opts ...grpc.CallOption) (*Product, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(Product)\n\terr := c.cc.Invoke(ctx, ProductCatalogService_GetProduct_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *productCatalogServiceClient) SearchProducts(ctx context.Context, in *SearchProductsRequest, opts ...grpc.CallOption) (*SearchProductsResponse, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(SearchProductsResponse)\n\terr := c.cc.Invoke(ctx, ProductCatalogService_SearchProducts_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\n// ProductCatalogServiceServer is the server API for ProductCatalogService service.\n// All implementations must embed UnimplementedProductCatalogServiceServer\n// for forward compatibility.\ntype ProductCatalogServiceServer interface {\n\tListProducts(context.Context, *Empty) (*ListProductsResponse, error)\n\tGetProduct(context.Context, *GetProductRequest) (*Product, error)\n\tSearchProducts(context.Context, *SearchProductsRequest) (*SearchProductsResponse, error)\n\tmustEmbedUnimplementedProductCatalogServiceServer()\n}\n\n// UnimplementedProductCatalogServiceServer must be embedded to have\n// forward compatible implementations.\n//\n// NOTE: this should be embedded by value instead of pointer to avoid a nil\n// pointer dereference when methods are called.\ntype UnimplementedProductCatalogServiceServer struct{}\n\nfunc (UnimplementedProductCatalogServiceServer) ListProducts(context.Context, *Empty) (*ListProductsResponse, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method ListProducts not implemented\")\n}\nfunc (UnimplementedProductCatalogServiceServer) GetProduct(context.Context, *GetProductRequest) (*Product, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method GetProduct not implemented\")\n}\nfunc (UnimplementedProductCatalogServiceServer) SearchProducts(context.Context, *SearchProductsRequest) (*SearchProductsResponse, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method SearchProducts not implemented\")\n}\nfunc (UnimplementedProductCatalogServiceServer) mustEmbedUnimplementedProductCatalogServiceServer() {}\nfunc (UnimplementedProductCatalogServiceServer) testEmbeddedByValue()                               {}\n\n// UnsafeProductCatalogServiceServer may be embedded to opt out of forward compatibility for this service.\n// Use of this interface is not recommended, as added methods to ProductCatalogServiceServer will\n// result in compilation errors.\ntype UnsafeProductCatalogServiceServer interface {\n\tmustEmbedUnimplementedProductCatalogServiceServer()\n}\n\nfunc RegisterProductCatalogServiceServer(s grpc.ServiceRegistrar, srv ProductCatalogServiceServer) {\n\t// If the following call pancis, it indicates UnimplementedProductCatalogServiceServer was\n\t// embedded by pointer and is nil.  This will cause panics if an\n\t// unimplemented method is ever invoked, so we test this at initialization\n\t// time to prevent it from happening at runtime later due to I/O.\n\tif t, ok := srv.(interface{ testEmbeddedByValue() }); ok {\n\t\tt.testEmbeddedByValue()\n\t}\n\ts.RegisterService(&ProductCatalogService_ServiceDesc, srv)\n}\n\nfunc _ProductCatalogService_ListProducts_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(Empty)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ProductCatalogServiceServer).ListProducts(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: ProductCatalogService_ListProducts_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ProductCatalogServiceServer).ListProducts(ctx, req.(*Empty))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _ProductCatalogService_GetProduct_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(GetProductRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ProductCatalogServiceServer).GetProduct(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: ProductCatalogService_GetProduct_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ProductCatalogServiceServer).GetProduct(ctx, req.(*GetProductRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _ProductCatalogService_SearchProducts_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(SearchProductsRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ProductCatalogServiceServer).SearchProducts(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: ProductCatalogService_SearchProducts_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ProductCatalogServiceServer).SearchProducts(ctx, req.(*SearchProductsRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\n// ProductCatalogService_ServiceDesc is the grpc.ServiceDesc for ProductCatalogService service.\n// It's only intended for direct use with grpc.RegisterService,\n// and not to be introspected or modified (even as a copy)\nvar ProductCatalogService_ServiceDesc = grpc.ServiceDesc{\n\tServiceName: \"hipstershop.ProductCatalogService\",\n\tHandlerType: (*ProductCatalogServiceServer)(nil),\n\tMethods: []grpc.MethodDesc{\n\t\t{\n\t\t\tMethodName: \"ListProducts\",\n\t\t\tHandler:    _ProductCatalogService_ListProducts_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"GetProduct\",\n\t\t\tHandler:    _ProductCatalogService_GetProduct_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"SearchProducts\",\n\t\t\tHandler:    _ProductCatalogService_SearchProducts_Handler,\n\t\t},\n\t},\n\tStreams:  []grpc.StreamDesc{},\n\tMetadata: \"demo.proto\",\n}\n\nconst (\n\tShippingService_GetQuote_FullMethodName  = \"/hipstershop.ShippingService/GetQuote\"\n\tShippingService_ShipOrder_FullMethodName = \"/hipstershop.ShippingService/ShipOrder\"\n)\n\n// ShippingServiceClient is the client API for ShippingService service.\n//\n// 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.\ntype ShippingServiceClient interface {\n\tGetQuote(ctx context.Context, in *GetQuoteRequest, opts ...grpc.CallOption) (*GetQuoteResponse, error)\n\tShipOrder(ctx context.Context, in *ShipOrderRequest, opts ...grpc.CallOption) (*ShipOrderResponse, error)\n}\n\ntype shippingServiceClient struct {\n\tcc grpc.ClientConnInterface\n}\n\nfunc NewShippingServiceClient(cc grpc.ClientConnInterface) ShippingServiceClient {\n\treturn &shippingServiceClient{cc}\n}\n\nfunc (c *shippingServiceClient) GetQuote(ctx context.Context, in *GetQuoteRequest, opts ...grpc.CallOption) (*GetQuoteResponse, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(GetQuoteResponse)\n\terr := c.cc.Invoke(ctx, ShippingService_GetQuote_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *shippingServiceClient) ShipOrder(ctx context.Context, in *ShipOrderRequest, opts ...grpc.CallOption) (*ShipOrderResponse, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(ShipOrderResponse)\n\terr := c.cc.Invoke(ctx, ShippingService_ShipOrder_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\n// ShippingServiceServer is the server API for ShippingService service.\n// All implementations must embed UnimplementedShippingServiceServer\n// for forward compatibility.\ntype ShippingServiceServer interface {\n\tGetQuote(context.Context, *GetQuoteRequest) (*GetQuoteResponse, error)\n\tShipOrder(context.Context, *ShipOrderRequest) (*ShipOrderResponse, error)\n\tmustEmbedUnimplementedShippingServiceServer()\n}\n\n// UnimplementedShippingServiceServer must be embedded to have\n// forward compatible implementations.\n//\n// NOTE: this should be embedded by value instead of pointer to avoid a nil\n// pointer dereference when methods are called.\ntype UnimplementedShippingServiceServer struct{}\n\nfunc (UnimplementedShippingServiceServer) GetQuote(context.Context, *GetQuoteRequest) (*GetQuoteResponse, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method GetQuote not implemented\")\n}\nfunc (UnimplementedShippingServiceServer) ShipOrder(context.Context, *ShipOrderRequest) (*ShipOrderResponse, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method ShipOrder not implemented\")\n}\nfunc (UnimplementedShippingServiceServer) mustEmbedUnimplementedShippingServiceServer() {}\nfunc (UnimplementedShippingServiceServer) testEmbeddedByValue()                         {}\n\n// UnsafeShippingServiceServer may be embedded to opt out of forward compatibility for this service.\n// Use of this interface is not recommended, as added methods to ShippingServiceServer will\n// result in compilation errors.\ntype UnsafeShippingServiceServer interface {\n\tmustEmbedUnimplementedShippingServiceServer()\n}\n\nfunc RegisterShippingServiceServer(s grpc.ServiceRegistrar, srv ShippingServiceServer) {\n\t// If the following call pancis, it indicates UnimplementedShippingServiceServer was\n\t// embedded by pointer and is nil.  This will cause panics if an\n\t// unimplemented method is ever invoked, so we test this at initialization\n\t// time to prevent it from happening at runtime later due to I/O.\n\tif t, ok := srv.(interface{ testEmbeddedByValue() }); ok {\n\t\tt.testEmbeddedByValue()\n\t}\n\ts.RegisterService(&ShippingService_ServiceDesc, srv)\n}\n\nfunc _ShippingService_GetQuote_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(GetQuoteRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ShippingServiceServer).GetQuote(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: ShippingService_GetQuote_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ShippingServiceServer).GetQuote(ctx, req.(*GetQuoteRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _ShippingService_ShipOrder_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(ShipOrderRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ShippingServiceServer).ShipOrder(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: ShippingService_ShipOrder_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ShippingServiceServer).ShipOrder(ctx, req.(*ShipOrderRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\n// ShippingService_ServiceDesc is the grpc.ServiceDesc for ShippingService service.\n// It's only intended for direct use with grpc.RegisterService,\n// and not to be introspected or modified (even as a copy)\nvar ShippingService_ServiceDesc = grpc.ServiceDesc{\n\tServiceName: \"hipstershop.ShippingService\",\n\tHandlerType: (*ShippingServiceServer)(nil),\n\tMethods: []grpc.MethodDesc{\n\t\t{\n\t\t\tMethodName: \"GetQuote\",\n\t\t\tHandler:    _ShippingService_GetQuote_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"ShipOrder\",\n\t\t\tHandler:    _ShippingService_ShipOrder_Handler,\n\t\t},\n\t},\n\tStreams:  []grpc.StreamDesc{},\n\tMetadata: \"demo.proto\",\n}\n\nconst (\n\tCurrencyService_GetSupportedCurrencies_FullMethodName = \"/hipstershop.CurrencyService/GetSupportedCurrencies\"\n\tCurrencyService_Convert_FullMethodName                = \"/hipstershop.CurrencyService/Convert\"\n)\n\n// CurrencyServiceClient is the client API for CurrencyService service.\n//\n// 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.\ntype CurrencyServiceClient interface {\n\tGetSupportedCurrencies(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*GetSupportedCurrenciesResponse, error)\n\tConvert(ctx context.Context, in *CurrencyConversionRequest, opts ...grpc.CallOption) (*Money, error)\n}\n\ntype currencyServiceClient struct {\n\tcc grpc.ClientConnInterface\n}\n\nfunc NewCurrencyServiceClient(cc grpc.ClientConnInterface) CurrencyServiceClient {\n\treturn &currencyServiceClient{cc}\n}\n\nfunc (c *currencyServiceClient) GetSupportedCurrencies(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*GetSupportedCurrenciesResponse, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(GetSupportedCurrenciesResponse)\n\terr := c.cc.Invoke(ctx, CurrencyService_GetSupportedCurrencies_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *currencyServiceClient) Convert(ctx context.Context, in *CurrencyConversionRequest, opts ...grpc.CallOption) (*Money, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(Money)\n\terr := c.cc.Invoke(ctx, CurrencyService_Convert_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\n// CurrencyServiceServer is the server API for CurrencyService service.\n// All implementations must embed UnimplementedCurrencyServiceServer\n// for forward compatibility.\ntype CurrencyServiceServer interface {\n\tGetSupportedCurrencies(context.Context, *Empty) (*GetSupportedCurrenciesResponse, error)\n\tConvert(context.Context, *CurrencyConversionRequest) (*Money, error)\n\tmustEmbedUnimplementedCurrencyServiceServer()\n}\n\n// UnimplementedCurrencyServiceServer must be embedded to have\n// forward compatible implementations.\n//\n// NOTE: this should be embedded by value instead of pointer to avoid a nil\n// pointer dereference when methods are called.\ntype UnimplementedCurrencyServiceServer struct{}\n\nfunc (UnimplementedCurrencyServiceServer) GetSupportedCurrencies(context.Context, *Empty) (*GetSupportedCurrenciesResponse, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method GetSupportedCurrencies not implemented\")\n}\nfunc (UnimplementedCurrencyServiceServer) Convert(context.Context, *CurrencyConversionRequest) (*Money, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method Convert not implemented\")\n}\nfunc (UnimplementedCurrencyServiceServer) mustEmbedUnimplementedCurrencyServiceServer() {}\nfunc (UnimplementedCurrencyServiceServer) testEmbeddedByValue()                         {}\n\n// UnsafeCurrencyServiceServer may be embedded to opt out of forward compatibility for this service.\n// Use of this interface is not recommended, as added methods to CurrencyServiceServer will\n// result in compilation errors.\ntype UnsafeCurrencyServiceServer interface {\n\tmustEmbedUnimplementedCurrencyServiceServer()\n}\n\nfunc RegisterCurrencyServiceServer(s grpc.ServiceRegistrar, srv CurrencyServiceServer) {\n\t// If the following call pancis, it indicates UnimplementedCurrencyServiceServer was\n\t// embedded by pointer and is nil.  This will cause panics if an\n\t// unimplemented method is ever invoked, so we test this at initialization\n\t// time to prevent it from happening at runtime later due to I/O.\n\tif t, ok := srv.(interface{ testEmbeddedByValue() }); ok {\n\t\tt.testEmbeddedByValue()\n\t}\n\ts.RegisterService(&CurrencyService_ServiceDesc, srv)\n}\n\nfunc _CurrencyService_GetSupportedCurrencies_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(Empty)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(CurrencyServiceServer).GetSupportedCurrencies(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: CurrencyService_GetSupportedCurrencies_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(CurrencyServiceServer).GetSupportedCurrencies(ctx, req.(*Empty))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _CurrencyService_Convert_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(CurrencyConversionRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(CurrencyServiceServer).Convert(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: CurrencyService_Convert_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(CurrencyServiceServer).Convert(ctx, req.(*CurrencyConversionRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\n// CurrencyService_ServiceDesc is the grpc.ServiceDesc for CurrencyService service.\n// It's only intended for direct use with grpc.RegisterService,\n// and not to be introspected or modified (even as a copy)\nvar CurrencyService_ServiceDesc = grpc.ServiceDesc{\n\tServiceName: \"hipstershop.CurrencyService\",\n\tHandlerType: (*CurrencyServiceServer)(nil),\n\tMethods: []grpc.MethodDesc{\n\t\t{\n\t\t\tMethodName: \"GetSupportedCurrencies\",\n\t\t\tHandler:    _CurrencyService_GetSupportedCurrencies_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"Convert\",\n\t\t\tHandler:    _CurrencyService_Convert_Handler,\n\t\t},\n\t},\n\tStreams:  []grpc.StreamDesc{},\n\tMetadata: \"demo.proto\",\n}\n\nconst (\n\tPaymentService_Charge_FullMethodName = \"/hipstershop.PaymentService/Charge\"\n)\n\n// PaymentServiceClient is the client API for PaymentService service.\n//\n// 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.\ntype PaymentServiceClient interface {\n\tCharge(ctx context.Context, in *ChargeRequest, opts ...grpc.CallOption) (*ChargeResponse, error)\n}\n\ntype paymentServiceClient struct {\n\tcc grpc.ClientConnInterface\n}\n\nfunc NewPaymentServiceClient(cc grpc.ClientConnInterface) PaymentServiceClient {\n\treturn &paymentServiceClient{cc}\n}\n\nfunc (c *paymentServiceClient) Charge(ctx context.Context, in *ChargeRequest, opts ...grpc.CallOption) (*ChargeResponse, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(ChargeResponse)\n\terr := c.cc.Invoke(ctx, PaymentService_Charge_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\n// PaymentServiceServer is the server API for PaymentService service.\n// All implementations must embed UnimplementedPaymentServiceServer\n// for forward compatibility.\ntype PaymentServiceServer interface {\n\tCharge(context.Context, *ChargeRequest) (*ChargeResponse, error)\n\tmustEmbedUnimplementedPaymentServiceServer()\n}\n\n// UnimplementedPaymentServiceServer must be embedded to have\n// forward compatible implementations.\n//\n// NOTE: this should be embedded by value instead of pointer to avoid a nil\n// pointer dereference when methods are called.\ntype UnimplementedPaymentServiceServer struct{}\n\nfunc (UnimplementedPaymentServiceServer) Charge(context.Context, *ChargeRequest) (*ChargeResponse, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method Charge not implemented\")\n}\nfunc (UnimplementedPaymentServiceServer) mustEmbedUnimplementedPaymentServiceServer() {}\nfunc (UnimplementedPaymentServiceServer) testEmbeddedByValue()                        {}\n\n// UnsafePaymentServiceServer may be embedded to opt out of forward compatibility for this service.\n// Use of this interface is not recommended, as added methods to PaymentServiceServer will\n// result in compilation errors.\ntype UnsafePaymentServiceServer interface {\n\tmustEmbedUnimplementedPaymentServiceServer()\n}\n\nfunc RegisterPaymentServiceServer(s grpc.ServiceRegistrar, srv PaymentServiceServer) {\n\t// If the following call pancis, it indicates UnimplementedPaymentServiceServer was\n\t// embedded by pointer and is nil.  This will cause panics if an\n\t// unimplemented method is ever invoked, so we test this at initialization\n\t// time to prevent it from happening at runtime later due to I/O.\n\tif t, ok := srv.(interface{ testEmbeddedByValue() }); ok {\n\t\tt.testEmbeddedByValue()\n\t}\n\ts.RegisterService(&PaymentService_ServiceDesc, srv)\n}\n\nfunc _PaymentService_Charge_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(ChargeRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(PaymentServiceServer).Charge(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: PaymentService_Charge_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(PaymentServiceServer).Charge(ctx, req.(*ChargeRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\n// PaymentService_ServiceDesc is the grpc.ServiceDesc for PaymentService service.\n// It's only intended for direct use with grpc.RegisterService,\n// and not to be introspected or modified (even as a copy)\nvar PaymentService_ServiceDesc = grpc.ServiceDesc{\n\tServiceName: \"hipstershop.PaymentService\",\n\tHandlerType: (*PaymentServiceServer)(nil),\n\tMethods: []grpc.MethodDesc{\n\t\t{\n\t\t\tMethodName: \"Charge\",\n\t\t\tHandler:    _PaymentService_Charge_Handler,\n\t\t},\n\t},\n\tStreams:  []grpc.StreamDesc{},\n\tMetadata: \"demo.proto\",\n}\n\nconst (\n\tEmailService_SendOrderConfirmation_FullMethodName = \"/hipstershop.EmailService/SendOrderConfirmation\"\n)\n\n// EmailServiceClient is the client API for EmailService service.\n//\n// 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.\ntype EmailServiceClient interface {\n\tSendOrderConfirmation(ctx context.Context, in *SendOrderConfirmationRequest, opts ...grpc.CallOption) (*Empty, error)\n}\n\ntype emailServiceClient struct {\n\tcc grpc.ClientConnInterface\n}\n\nfunc NewEmailServiceClient(cc grpc.ClientConnInterface) EmailServiceClient {\n\treturn &emailServiceClient{cc}\n}\n\nfunc (c *emailServiceClient) SendOrderConfirmation(ctx context.Context, in *SendOrderConfirmationRequest, opts ...grpc.CallOption) (*Empty, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(Empty)\n\terr := c.cc.Invoke(ctx, EmailService_SendOrderConfirmation_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\n// EmailServiceServer is the server API for EmailService service.\n// All implementations must embed UnimplementedEmailServiceServer\n// for forward compatibility.\ntype EmailServiceServer interface {\n\tSendOrderConfirmation(context.Context, *SendOrderConfirmationRequest) (*Empty, error)\n\tmustEmbedUnimplementedEmailServiceServer()\n}\n\n// UnimplementedEmailServiceServer must be embedded to have\n// forward compatible implementations.\n//\n// NOTE: this should be embedded by value instead of pointer to avoid a nil\n// pointer dereference when methods are called.\ntype UnimplementedEmailServiceServer struct{}\n\nfunc (UnimplementedEmailServiceServer) SendOrderConfirmation(context.Context, *SendOrderConfirmationRequest) (*Empty, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method SendOrderConfirmation not implemented\")\n}\nfunc (UnimplementedEmailServiceServer) mustEmbedUnimplementedEmailServiceServer() {}\nfunc (UnimplementedEmailServiceServer) testEmbeddedByValue()                      {}\n\n// UnsafeEmailServiceServer may be embedded to opt out of forward compatibility for this service.\n// Use of this interface is not recommended, as added methods to EmailServiceServer will\n// result in compilation errors.\ntype UnsafeEmailServiceServer interface {\n\tmustEmbedUnimplementedEmailServiceServer()\n}\n\nfunc RegisterEmailServiceServer(s grpc.ServiceRegistrar, srv EmailServiceServer) {\n\t// If the following call pancis, it indicates UnimplementedEmailServiceServer was\n\t// embedded by pointer and is nil.  This will cause panics if an\n\t// unimplemented method is ever invoked, so we test this at initialization\n\t// time to prevent it from happening at runtime later due to I/O.\n\tif t, ok := srv.(interface{ testEmbeddedByValue() }); ok {\n\t\tt.testEmbeddedByValue()\n\t}\n\ts.RegisterService(&EmailService_ServiceDesc, srv)\n}\n\nfunc _EmailService_SendOrderConfirmation_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(SendOrderConfirmationRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(EmailServiceServer).SendOrderConfirmation(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: EmailService_SendOrderConfirmation_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(EmailServiceServer).SendOrderConfirmation(ctx, req.(*SendOrderConfirmationRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\n// EmailService_ServiceDesc is the grpc.ServiceDesc for EmailService service.\n// It's only intended for direct use with grpc.RegisterService,\n// and not to be introspected or modified (even as a copy)\nvar EmailService_ServiceDesc = grpc.ServiceDesc{\n\tServiceName: \"hipstershop.EmailService\",\n\tHandlerType: (*EmailServiceServer)(nil),\n\tMethods: []grpc.MethodDesc{\n\t\t{\n\t\t\tMethodName: \"SendOrderConfirmation\",\n\t\t\tHandler:    _EmailService_SendOrderConfirmation_Handler,\n\t\t},\n\t},\n\tStreams:  []grpc.StreamDesc{},\n\tMetadata: \"demo.proto\",\n}\n\nconst (\n\tCheckoutService_PlaceOrder_FullMethodName = \"/hipstershop.CheckoutService/PlaceOrder\"\n)\n\n// CheckoutServiceClient is the client API for CheckoutService service.\n//\n// 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.\ntype CheckoutServiceClient interface {\n\tPlaceOrder(ctx context.Context, in *PlaceOrderRequest, opts ...grpc.CallOption) (*PlaceOrderResponse, error)\n}\n\ntype checkoutServiceClient struct {\n\tcc grpc.ClientConnInterface\n}\n\nfunc NewCheckoutServiceClient(cc grpc.ClientConnInterface) CheckoutServiceClient {\n\treturn &checkoutServiceClient{cc}\n}\n\nfunc (c *checkoutServiceClient) PlaceOrder(ctx context.Context, in *PlaceOrderRequest, opts ...grpc.CallOption) (*PlaceOrderResponse, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(PlaceOrderResponse)\n\terr := c.cc.Invoke(ctx, CheckoutService_PlaceOrder_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\n// CheckoutServiceServer is the server API for CheckoutService service.\n// All implementations must embed UnimplementedCheckoutServiceServer\n// for forward compatibility.\ntype CheckoutServiceServer interface {\n\tPlaceOrder(context.Context, *PlaceOrderRequest) (*PlaceOrderResponse, error)\n\tmustEmbedUnimplementedCheckoutServiceServer()\n}\n\n// UnimplementedCheckoutServiceServer must be embedded to have\n// forward compatible implementations.\n//\n// NOTE: this should be embedded by value instead of pointer to avoid a nil\n// pointer dereference when methods are called.\ntype UnimplementedCheckoutServiceServer struct{}\n\nfunc (UnimplementedCheckoutServiceServer) PlaceOrder(context.Context, *PlaceOrderRequest) (*PlaceOrderResponse, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method PlaceOrder not implemented\")\n}\nfunc (UnimplementedCheckoutServiceServer) mustEmbedUnimplementedCheckoutServiceServer() {}\nfunc (UnimplementedCheckoutServiceServer) testEmbeddedByValue()                         {}\n\n// UnsafeCheckoutServiceServer may be embedded to opt out of forward compatibility for this service.\n// Use of this interface is not recommended, as added methods to CheckoutServiceServer will\n// result in compilation errors.\ntype UnsafeCheckoutServiceServer interface {\n\tmustEmbedUnimplementedCheckoutServiceServer()\n}\n\nfunc RegisterCheckoutServiceServer(s grpc.ServiceRegistrar, srv CheckoutServiceServer) {\n\t// If the following call pancis, it indicates UnimplementedCheckoutServiceServer was\n\t// embedded by pointer and is nil.  This will cause panics if an\n\t// unimplemented method is ever invoked, so we test this at initialization\n\t// time to prevent it from happening at runtime later due to I/O.\n\tif t, ok := srv.(interface{ testEmbeddedByValue() }); ok {\n\t\tt.testEmbeddedByValue()\n\t}\n\ts.RegisterService(&CheckoutService_ServiceDesc, srv)\n}\n\nfunc _CheckoutService_PlaceOrder_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(PlaceOrderRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(CheckoutServiceServer).PlaceOrder(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: CheckoutService_PlaceOrder_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(CheckoutServiceServer).PlaceOrder(ctx, req.(*PlaceOrderRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\n// CheckoutService_ServiceDesc is the grpc.ServiceDesc for CheckoutService service.\n// It's only intended for direct use with grpc.RegisterService,\n// and not to be introspected or modified (even as a copy)\nvar CheckoutService_ServiceDesc = grpc.ServiceDesc{\n\tServiceName: \"hipstershop.CheckoutService\",\n\tHandlerType: (*CheckoutServiceServer)(nil),\n\tMethods: []grpc.MethodDesc{\n\t\t{\n\t\t\tMethodName: \"PlaceOrder\",\n\t\t\tHandler:    _CheckoutService_PlaceOrder_Handler,\n\t\t},\n\t},\n\tStreams:  []grpc.StreamDesc{},\n\tMetadata: \"demo.proto\",\n}\n\nconst (\n\tAdService_GetAds_FullMethodName = \"/hipstershop.AdService/GetAds\"\n)\n\n// AdServiceClient is the client API for AdService service.\n//\n// 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.\ntype AdServiceClient interface {\n\tGetAds(ctx context.Context, in *AdRequest, opts ...grpc.CallOption) (*AdResponse, error)\n}\n\ntype adServiceClient struct {\n\tcc grpc.ClientConnInterface\n}\n\nfunc NewAdServiceClient(cc grpc.ClientConnInterface) AdServiceClient {\n\treturn &adServiceClient{cc}\n}\n\nfunc (c *adServiceClient) GetAds(ctx context.Context, in *AdRequest, opts ...grpc.CallOption) (*AdResponse, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(AdResponse)\n\terr := c.cc.Invoke(ctx, AdService_GetAds_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\n// AdServiceServer is the server API for AdService service.\n// All implementations must embed UnimplementedAdServiceServer\n// for forward compatibility.\ntype AdServiceServer interface {\n\tGetAds(context.Context, *AdRequest) (*AdResponse, error)\n\tmustEmbedUnimplementedAdServiceServer()\n}\n\n// UnimplementedAdServiceServer must be embedded to have\n// forward compatible implementations.\n//\n// NOTE: this should be embedded by value instead of pointer to avoid a nil\n// pointer dereference when methods are called.\ntype UnimplementedAdServiceServer struct{}\n\nfunc (UnimplementedAdServiceServer) GetAds(context.Context, *AdRequest) (*AdResponse, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method GetAds not implemented\")\n}\nfunc (UnimplementedAdServiceServer) mustEmbedUnimplementedAdServiceServer() {}\nfunc (UnimplementedAdServiceServer) testEmbeddedByValue()                   {}\n\n// UnsafeAdServiceServer may be embedded to opt out of forward compatibility for this service.\n// Use of this interface is not recommended, as added methods to AdServiceServer will\n// result in compilation errors.\ntype UnsafeAdServiceServer interface {\n\tmustEmbedUnimplementedAdServiceServer()\n}\n\nfunc RegisterAdServiceServer(s grpc.ServiceRegistrar, srv AdServiceServer) {\n\t// If the following call pancis, it indicates UnimplementedAdServiceServer was\n\t// embedded by pointer and is nil.  This will cause panics if an\n\t// unimplemented method is ever invoked, so we test this at initialization\n\t// time to prevent it from happening at runtime later due to I/O.\n\tif t, ok := srv.(interface{ testEmbeddedByValue() }); ok {\n\t\tt.testEmbeddedByValue()\n\t}\n\ts.RegisterService(&AdService_ServiceDesc, srv)\n}\n\nfunc _AdService_GetAds_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(AdRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(AdServiceServer).GetAds(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: AdService_GetAds_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(AdServiceServer).GetAds(ctx, req.(*AdRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\n// AdService_ServiceDesc is the grpc.ServiceDesc for AdService service.\n// It's only intended for direct use with grpc.RegisterService,\n// and not to be introspected or modified (even as a copy)\nvar AdService_ServiceDesc = grpc.ServiceDesc{\n\tServiceName: \"hipstershop.AdService\",\n\tHandlerType: (*AdServiceServer)(nil),\n\tMethods: []grpc.MethodDesc{\n\t\t{\n\t\t\tMethodName: \"GetAds\",\n\t\t\tHandler:    _AdService_GetAds_Handler,\n\t\t},\n\t},\n\tStreams:  []grpc.StreamDesc{},\n\tMetadata: \"demo.proto\",\n}\n"
  },
  {
    "path": "src/checkoutservice/genproto.sh",
    "content": "#!/bin/bash -eu\n#\n# Copyright 2018 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# [START gke_checkoutservice_genproto]\n\nPATH=$PATH:$(go env GOPATH)/bin\nprotodir=../../protos\noutdir=./genproto\n\nprotoc --proto_path=$protodir --go_out=./$outdir --go_opt=paths=source_relative --go-grpc_out=./$outdir --go-grpc_opt=paths=source_relative $protodir/demo.proto\n\n# [END gke_checkoutservice_genproto]"
  },
  {
    "path": "src/checkoutservice/go.mod",
    "content": "module github.com/GoogleCloudPlatform/microservices-demo/src/checkoutservice\n\ngo 1.25.0\n\ntoolchain go1.26.1\n\nrequire (\n\tcloud.google.com/go/profiler v0.4.3\n\tgithub.com/google/uuid v1.6.0\n\tgithub.com/pkg/errors v0.9.1\n\tgithub.com/sirupsen/logrus v1.9.4\n\tgo.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0\n\tgo.opentelemetry.io/otel v1.42.0\n\tgo.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.42.0\n\tgo.opentelemetry.io/otel/sdk v1.42.0\n\tgoogle.golang.org/grpc v1.79.2\n\tgoogle.golang.org/protobuf v1.36.11\n)\n\nrequire (\n\tcloud.google.com/go v0.123.0 // indirect\n\tcloud.google.com/go/auth v0.17.0 // indirect\n\tcloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect\n\tcloud.google.com/go/compute/metadata v0.9.0 // indirect\n\tgithub.com/cenkalti/backoff/v5 v5.0.3 // indirect\n\tgithub.com/cespare/xxhash/v2 v2.3.0 // indirect\n\tgithub.com/go-logr/logr v1.4.3 // indirect\n\tgithub.com/go-logr/stdr v1.2.2 // indirect\n\tgithub.com/google/pprof v0.0.0-20251114195745-4902fdda35c8 // indirect\n\tgithub.com/google/s2a-go v0.1.9 // indirect\n\tgithub.com/googleapis/enterprise-certificate-proxy v0.3.7 // indirect\n\tgithub.com/googleapis/gax-go/v2 v2.15.0 // indirect\n\tgithub.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 // indirect\n\tgo.opentelemetry.io/auto/sdk v1.2.1 // indirect\n\tgo.opentelemetry.io/otel/exporters/otlp/otlptrace v1.42.0 // indirect\n\tgo.opentelemetry.io/otel/metric v1.42.0 // indirect\n\tgo.opentelemetry.io/otel/trace v1.42.0 // indirect\n\tgo.opentelemetry.io/proto/otlp v1.9.0 // indirect\n\tgolang.org/x/crypto v0.48.0 // indirect\n\tgolang.org/x/net v0.51.0 // indirect\n\tgolang.org/x/oauth2 v0.35.0 // indirect\n\tgolang.org/x/sync v0.19.0 // indirect\n\tgolang.org/x/sys v0.41.0 // indirect\n\tgolang.org/x/text v0.34.0 // indirect\n\tgolang.org/x/time v0.14.0 // indirect\n\tgoogle.golang.org/api v0.256.0 // indirect\n\tgoogle.golang.org/genproto v0.0.0-20251124214823-79d6a2a48846 // indirect\n\tgoogle.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57 // indirect\n\tgoogle.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 // indirect\n)\n"
  },
  {
    "path": "src/checkoutservice/go.sum",
    "content": "cel.dev/expr v0.24.0 h1:56OvJKSH3hDGL0ml5uSxZmz3/3Pq4tJ+fb1unVLAFcY=\ncel.dev/expr v0.24.0/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw=\ncloud.google.com/go v0.123.0 h1:2NAUJwPR47q+E35uaJeYoNhuNEM9kM8SjgRgdeOJUSE=\ncloud.google.com/go v0.123.0/go.mod h1:xBoMV08QcqUGuPW65Qfm1o9Y4zKZBpGS+7bImXLTAZU=\ncloud.google.com/go/auth v0.17.0 h1:74yCm7hCj2rUyyAocqnFzsAYXgJhrG26XCFimrc/Kz4=\ncloud.google.com/go/auth v0.17.0/go.mod h1:6wv/t5/6rOPAX4fJiRjKkJCvswLwdet7G8+UGXt7nCQ=\ncloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc=\ncloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c=\ncloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs=\ncloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10=\ncloud.google.com/go/iam v1.5.3 h1:+vMINPiDF2ognBJ97ABAYYwRgsaqxPbQDlMnbHMjolc=\ncloud.google.com/go/iam v1.5.3/go.mod h1:MR3v9oLkZCTlaqljW6Eb2d3HGDGK5/bDv93jhfISFvU=\ncloud.google.com/go/monitoring v1.24.3 h1:dde+gMNc0UhPZD1Azu6at2e79bfdztVDS5lvhOdsgaE=\ncloud.google.com/go/monitoring v1.24.3/go.mod h1:nYP6W0tm3N9H/bOw8am7t62YTzZY+zUeQ+Bi6+2eonI=\ncloud.google.com/go/profiler v0.4.3 h1:IY3QNKlr8VbXwGWHcZbJQsMA/83ZTH6uAHf8jYyj7OI=\ncloud.google.com/go/profiler v0.4.3/go.mod h1:3xFodugWfPIQZWFcXdUmfa+yTiiyQ8fWrdT+d2Sg4J0=\ncloud.google.com/go/storage v1.56.0 h1:iixmq2Fse2tqxMbWhLWC9HfBj1qdxqAmiK8/eqtsLxI=\ncloud.google.com/go/storage v1.56.0/go.mod h1:Tpuj6t4NweCLzlNbw9Z9iwxEkrSem20AetIeH/shgVU=\ngithub.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0 h1:sBEjpZlNHzK1voKq9695PJSX2o5NEXl7/OL3coiIY0c=\ngithub.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0/go.mod h1:P4WPRUkOhJC13W//jWpyfJNDAIpvRbAUIYLX/4jtlE0=\ngithub.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.53.0 h1:owcC2UnmsZycprQ5RfRgjydWhuoxg71LUfyiQdijZuM=\ngithub.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.53.0/go.mod h1:ZPpqegjbE99EPKsu3iUWV22A04wzGPcAY/ziSIQEEgs=\ngithub.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.53.0 h1:Ron4zCA/yk6U7WOBXhTJcDpsUBG9npumK6xw2auFltQ=\ngithub.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.53.0/go.mod h1:cSgYe11MCNYunTnRXrKiR/tHc0eoKjICUuWpNZoVCOo=\ngithub.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM=\ngithub.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=\ngithub.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=\ngithub.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=\ngithub.com/cncf/xds/go v0.0.0-20251022180443-0feb69152e9f h1:Y8xYupdHxryycyPlc9Y+bSQAYZnetRJ70VMVKm5CKI0=\ngithub.com/cncf/xds/go v0.0.0-20251022180443-0feb69152e9f/go.mod h1:HlzOvOjVBOfTGSRXRyY0OiCS/3J1akRGQQpRO/7zyF4=\ngithub.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=\ngithub.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/envoyproxy/go-control-plane v0.13.5-0.20251024222203-75eaa193e329 h1:K+fnvUM0VZ7ZFJf0n4L/BRlnsb9pL/GuDG6FqaH+PwM=\ngithub.com/envoyproxy/go-control-plane/envoy v1.35.0 h1:ixjkELDE+ru6idPxcHLj8LBVc2bFP7iBytj353BoHUo=\ngithub.com/envoyproxy/go-control-plane/envoy v1.35.0/go.mod h1:09qwbGVuSWWAyN5t/b3iyVfz5+z8QWGrzkoqm/8SbEs=\ngithub.com/envoyproxy/protoc-gen-validate v1.2.1 h1:DEo3O99U8j4hBFwbJfrz9VtgcDfUKS7KJ7spH3d86P8=\ngithub.com/envoyproxy/protoc-gen-validate v1.2.1/go.mod h1:d/C80l/jxXLdfEIhX1W2TmLfsJ31lvEjwamM4DxlWXU=\ngithub.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=\ngithub.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=\ngithub.com/go-jose/go-jose/v4 v4.1.3 h1:CVLmWDhDVRa6Mi/IgCgaopNosCaHz7zrMeF9MlZRkrs=\ngithub.com/go-jose/go-jose/v4 v4.1.3/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=\ngithub.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=\ngithub.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=\ngithub.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=\ngithub.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=\ngithub.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=\ngithub.com/golang/mock v1.7.0-rc.1 h1:YojYx61/OLFsiv6Rw1Z96LpldJIy31o+UHmwAUMJ6/U=\ngithub.com/golang/mock v1.7.0-rc.1/go.mod h1:s42URUywIqd+OcERslBJvOjepvNymP31m3q8d/GkuRs=\ngithub.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=\ngithub.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=\ngithub.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=\ngithub.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=\ngithub.com/google/pprof v0.0.0-20251114195745-4902fdda35c8 h1:3DsUAV+VNEQa2CUVLxCY3f87278uWfIDhJnbdvDjvmE=\ngithub.com/google/pprof v0.0.0-20251114195745-4902fdda35c8/go.mod h1:I6V7YzU0XDpsHqbsyrghnFZLO1gwK6NPTNvmetQIk9U=\ngithub.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0=\ngithub.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM=\ngithub.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=\ngithub.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=\ngithub.com/googleapis/enterprise-certificate-proxy v0.3.7 h1:zrn2Ee/nWmHulBx5sAVrGgAa0f2/R35S4DJwfFaUPFQ=\ngithub.com/googleapis/enterprise-certificate-proxy v0.3.7/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA=\ngithub.com/googleapis/gax-go/v2 v2.15.0 h1:SyjDc1mGgZU5LncH8gimWo9lW1DtIfPibOG81vgd/bo=\ngithub.com/googleapis/gax-go/v2 v2.15.0/go.mod h1:zVVkkxAQHa1RQpg9z2AUCMnKhi0Qld9rcmyfL1OZhoc=\ngithub.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 h1:NmZ1PKzSTQbuGHw9DGPFomqkkLWMC+vZCkfs+FHv1Vg=\ngithub.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3/go.mod h1:zQrxl1YP88HQlA6i9c63DSVPFklWpGX4OWAc9bFuaH4=\ngithub.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 h1:HWRh5R2+9EifMyIHV7ZV+MIZqgz+PMpZ14Jynv3O2Zs=\ngithub.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0/go.mod h1:JfhWUomR1baixubs02l85lZYYOm7LV6om4ceouMv45c=\ngithub.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=\ngithub.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\ngithub.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo=\ngithub.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8=\ngithub.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=\ngithub.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=\ngithub.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=\ngithub.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w=\ngithub.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g=\ngithub.com/spiffe/go-spiffe/v2 v2.6.0 h1:l+DolpxNWYgruGQVV0xsfeya3CsC7m8iBzDnMpsbLuo=\ngithub.com/spiffe/go-spiffe/v2 v2.6.0/go.mod h1:gm2SeUoMZEtpnzPNs2Csc0D/gX33k1xIx7lEzqblHEs=\ngithub.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngithub.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=\ngithub.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=\ngo.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=\ngo.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=\ngo.opentelemetry.io/contrib/detectors/gcp v1.38.0 h1:ZoYbqX7OaA/TAikspPl3ozPI6iY6LiIY9I8cUfm+pJs=\ngo.opentelemetry.io/contrib/detectors/gcp v1.38.0/go.mod h1:SU+iU7nu5ud4oCb3LQOhIZ3nRLj6FNVrKgtflbaf2ts=\ngo.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0 h1:YH4g8lQroajqUwWbq/tr2QX1JFmEXaDLgG+ew9bLMWo=\ngo.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0/go.mod h1:fvPi2qXDqFs8M4B4fmJhE92TyQs9Ydjlg3RvfUp+NbQ=\ngo.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.64.0 h1:RN3ifU8y4prNWeEnQp2kRRHz8UwonAEYZl8tUzHEXAk=\ngo.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.64.0/go.mod h1:habDz3tEWiFANTo6oUE99EmaFUrCNYAAg3wiVmusm70=\ngo.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0 h1:yI1/OhfEPy7J9eoa6Sj051C7n5dvpj0QX8g4sRchg04=\ngo.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0/go.mod h1:NoUCKYWK+3ecatC4HjkRktREheMeEtrXoQxrqYFeHSc=\ngo.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus=\ngo.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q=\ngo.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8=\ngo.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM=\ngo.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48=\ngo.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8=\ngo.opentelemetry.io/otel v1.42.0 h1:lSQGzTgVR3+sgJDAU/7/ZMjN9Z+vUip7leaqBKy4sho=\ngo.opentelemetry.io/otel v1.42.0/go.mod h1:lJNsdRMxCUIWuMlVJWzecSMuNjE7dOYyWlqOXWkdqCc=\ngo.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 h1:GqRJVj7UmLjCVyVJ3ZFLdPRmhDUp2zFmQe3RHIOsw24=\ngo.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0/go.mod h1:ri3aaHSmCTVYu2AWv44YMauwAQc0aqI9gHKIcSbI1pU=\ngo.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0 h1:f0cb2XPmrqn4XMy9PNliTgRKJgS5WcL/u0/WRYGz4t0=\ngo.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0/go.mod h1:vnakAaFckOMiMtOIhFI2MNH4FYrZzXCYxmb1LlhoGz8=\ngo.opentelemetry.io/otel/exporters/otlp/otlptrace v1.42.0 h1:THuZiwpQZuHPul65w4WcwEnkX2QIuMT+UFoOrygtoJw=\ngo.opentelemetry.io/otel/exporters/otlp/otlptrace v1.42.0/go.mod h1:J2pvYM5NGHofZ2/Ru6zw/TNWnEQp5crgyDeSrYpXkAw=\ngo.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 h1:lwI4Dc5leUqENgGuQImwLo4WnuXFPetmPpkLi2IrX54=\ngo.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0/go.mod h1:Kz/oCE7z5wuyhPxsXDuaPteSWqjSBD5YaSdbxZYGbGk=\ngo.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0 h1:in9O8ESIOlwJAEGTkkf34DesGRAc/Pn8qJ7k3r/42LM=\ngo.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0/go.mod h1:Rp0EXBm5tfnv0WL+ARyO/PHBEaEAT8UUHQ6AGJcSq6c=\ngo.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.42.0 h1:zWWrB1U6nqhS/k6zYB74CjRpuiitRtLLi68VcgmOEto=\ngo.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.42.0/go.mod h1:2qXPNBX1OVRC0IwOnfo1ljoid+RD0QK3443EaqVlsOU=\ngo.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA=\ngo.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI=\ngo.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0=\ngo.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs=\ngo.opentelemetry.io/otel/metric v1.42.0 h1:2jXG+3oZLNXEPfNmnpxKDeZsFI5o4J+nz6xUlaFdF/4=\ngo.opentelemetry.io/otel/metric v1.42.0/go.mod h1:RlUN/7vTU7Ao/diDkEpQpnz3/92J9ko05BIwxYa2SSI=\ngo.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E=\ngo.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg=\ngo.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18=\ngo.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE=\ngo.opentelemetry.io/otel/sdk v1.42.0 h1:LyC8+jqk6UJwdrI/8VydAq/hvkFKNHZVIWuslJXYsDo=\ngo.opentelemetry.io/otel/sdk v1.42.0/go.mod h1:rGHCAxd9DAph0joO4W6OPwxjNTYWghRWmkHuGbayMts=\ngo.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM=\ngo.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA=\ngo.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE=\ngo.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs=\ngo.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI=\ngo.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA=\ngo.opentelemetry.io/otel/trace v1.42.0 h1:OUCgIPt+mzOnaUTpOQcBiM/PLQ/Op7oq6g4LenLmOYY=\ngo.opentelemetry.io/otel/trace v1.42.0/go.mod h1:f3K9S+IFqnumBkKhRJMeaZeNk9epyhnCmQh/EysQCdc=\ngo.opentelemetry.io/proto/otlp v1.9.0 h1:l706jCMITVouPOqEnii2fIAuO3IVGBRPV5ICjceRb/A=\ngo.opentelemetry.io/proto/otlp v1.9.0/go.mod h1:xE+Cx5E/eEHw+ISFkwPLwCZefwVjY+pqKg1qcK03+/4=\ngo.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=\ngo.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=\ngolang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=\ngolang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=\ngolang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=\ngolang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=\ngolang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=\ngolang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=\ngolang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=\ngolang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=\ngolang.org/x/oauth2 v0.33.0 h1:4Q+qn+E5z8gPRJfmRy7C2gGG3T4jIprK6aSYgTXGRpo=\ngolang.org/x/oauth2 v0.33.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=\ngolang.org/x/oauth2 v0.35.0 h1:Mv2mzuHuZuY2+bkyWXIHMfhNdJAdwW3FuWeCPYN5GVQ=\ngolang.org/x/oauth2 v0.35.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=\ngolang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=\ngolang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=\ngolang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=\ngolang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=\ngolang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=\ngolang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=\ngolang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=\ngolang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=\ngolang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=\ngolang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=\ngolang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=\ngolang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=\ngolang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=\ngolang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=\ngolang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=\ngolang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=\ngonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=\ngonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=\ngoogle.golang.org/api v0.256.0 h1:u6Khm8+F9sxbCTYNoBHg6/Hwv0N/i+V94MvkOSor6oI=\ngoogle.golang.org/api v0.256.0/go.mod h1:KIgPhksXADEKJlnEoRa9qAII4rXcy40vfI8HRqcU964=\ngoogle.golang.org/genproto v0.0.0-20251124214823-79d6a2a48846 h1:dDbsTLIK7EzwUq36kCSAsk0slouq/S0tWHeeGi97cD8=\ngoogle.golang.org/genproto v0.0.0-20251124214823-79d6a2a48846/go.mod h1:PP0g88Dz3C7hRAfbQCQggeWAXjuqGsNPLE4s7jh0RGU=\ngoogle.golang.org/genproto/googleapis/api v0.0.0-20251124214823-79d6a2a48846 h1:ZdyUkS9po3H7G0tuh955QVyyotWvOD4W0aEapeGeUYk=\ngoogle.golang.org/genproto/googleapis/api v0.0.0-20251124214823-79d6a2a48846/go.mod h1:Fk4kyraUvqD7i5H6S43sj2W98fbZa75lpZz/eUyhfO0=\ngoogle.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 h1:fCvbg86sFXwdrl5LgVcTEvNC+2txB5mgROGmRL5mrls=\ngoogle.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:+rXWjjaukWZun3mLfjmVnQi18E1AsFbDN9QdJ5YXLto=\ngoogle.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57 h1:JLQynH/LBHfCTSbDWl+py8C+Rg/k1OVH3xfcaiANuF0=\ngoogle.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57/go.mod h1:kSJwQxqmFXeo79zOmbrALdflXQeAYcUbgS7PbpMknCY=\ngoogle.golang.org/genproto/googleapis/rpc v0.0.0-20251124214823-79d6a2a48846 h1:Wgl1rcDNThT+Zn47YyCXOXyX/COgMTIdhJ717F0l4xk=\ngoogle.golang.org/genproto/googleapis/rpc v0.0.0-20251124214823-79d6a2a48846/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=\ngoogle.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 h1:gRkg/vSppuSQoDjxyiGfN4Upv/h/DQmIR10ZU8dh4Ww=\ngoogle.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=\ngoogle.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 h1:ggcbiqK8WWh6l1dnltU4BgWGIGo+EVYxCaAPih/zQXQ=\ngoogle.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=\ngoogle.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM=\ngoogle.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig=\ngoogle.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc=\ngoogle.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U=\ngoogle.golang.org/grpc v1.79.2 h1:fRMD94s2tITpyJGtBBn7MkMseNpOZU8ZxgC3MMBaXRU=\ngoogle.golang.org/grpc v1.79.2/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=\ngoogle.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=\ngoogle.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=\ngoogle.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=\ngoogle.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=\ngopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\n"
  },
  {
    "path": "src/checkoutservice/main.go",
    "content": "// Copyright 2018 Google LLC\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net\"\n\t\"os\"\n\t\"time\"\n\n\t\"cloud.google.com/go/profiler\"\n\t\"github.com/google/uuid\"\n\t\"github.com/pkg/errors\"\n\t\"github.com/sirupsen/logrus\"\n\t\"google.golang.org/grpc\"\n\t\"google.golang.org/grpc/codes\"\n\t\"google.golang.org/grpc/credentials/insecure\"\n\t\"google.golang.org/grpc/health\"\n\t\"google.golang.org/grpc/status\"\n\n\tpb \"github.com/GoogleCloudPlatform/microservices-demo/src/checkoutservice/genproto\"\n\tmoney \"github.com/GoogleCloudPlatform/microservices-demo/src/checkoutservice/money\"\n\thealthpb \"google.golang.org/grpc/health/grpc_health_v1\"\n\n\t\"go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc\"\n\t\"go.opentelemetry.io/otel\"\n\t\"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc\"\n\t\"go.opentelemetry.io/otel/propagation\"\n\tsdktrace \"go.opentelemetry.io/otel/sdk/trace\"\n)\n\nconst (\n\tlistenPort  = \"5050\"\n\tusdCurrency = \"USD\"\n)\n\nvar log *logrus.Logger\n\nfunc init() {\n\tlog = logrus.New()\n\tlog.Level = logrus.DebugLevel\n\tlog.Formatter = &logrus.JSONFormatter{\n\t\tFieldMap: logrus.FieldMap{\n\t\t\tlogrus.FieldKeyTime:  \"timestamp\",\n\t\t\tlogrus.FieldKeyLevel: \"severity\",\n\t\t\tlogrus.FieldKeyMsg:   \"message\",\n\t\t},\n\t\tTimestampFormat: time.RFC3339Nano,\n\t}\n\tlog.Out = os.Stdout\n}\n\ntype checkoutService struct {\n\tpb.UnimplementedCheckoutServiceServer\n\n\tproductCatalogSvcAddr string\n\tproductCatalogSvcConn *grpc.ClientConn\n\n\tcartSvcAddr string\n\tcartSvcConn *grpc.ClientConn\n\n\tcurrencySvcAddr string\n\tcurrencySvcConn *grpc.ClientConn\n\n\tshippingSvcAddr string\n\tshippingSvcConn *grpc.ClientConn\n\n\temailSvcAddr string\n\temailSvcConn *grpc.ClientConn\n\n\tpaymentSvcAddr string\n\tpaymentSvcConn *grpc.ClientConn\n}\n\nfunc main() {\n\tctx := context.Background()\n\tif os.Getenv(\"ENABLE_TRACING\") == \"1\" {\n\t\tlog.Info(\"Tracing enabled.\")\n\t\tinitTracing()\n\n\t} else {\n\t\tlog.Info(\"Tracing disabled.\")\n\t}\n\n\tif os.Getenv(\"ENABLE_PROFILER\") == \"1\" {\n\t\tlog.Info(\"Profiling enabled.\")\n\t\tgo initProfiling(\"checkoutservice\", \"1.0.0\")\n\t} else {\n\t\tlog.Info(\"Profiling disabled.\")\n\t}\n\n\tport := listenPort\n\tif os.Getenv(\"PORT\") != \"\" {\n\t\tport = os.Getenv(\"PORT\")\n\t}\n\n\tsvc := new(checkoutService)\n\tmustMapEnv(&svc.shippingSvcAddr, \"SHIPPING_SERVICE_ADDR\")\n\tmustMapEnv(&svc.productCatalogSvcAddr, \"PRODUCT_CATALOG_SERVICE_ADDR\")\n\tmustMapEnv(&svc.cartSvcAddr, \"CART_SERVICE_ADDR\")\n\tmustMapEnv(&svc.currencySvcAddr, \"CURRENCY_SERVICE_ADDR\")\n\tmustMapEnv(&svc.emailSvcAddr, \"EMAIL_SERVICE_ADDR\")\n\tmustMapEnv(&svc.paymentSvcAddr, \"PAYMENT_SERVICE_ADDR\")\n\n\tmustConnGRPC(ctx, &svc.shippingSvcConn, svc.shippingSvcAddr)\n\tmustConnGRPC(ctx, &svc.productCatalogSvcConn, svc.productCatalogSvcAddr)\n\tmustConnGRPC(ctx, &svc.cartSvcConn, svc.cartSvcAddr)\n\tmustConnGRPC(ctx, &svc.currencySvcConn, svc.currencySvcAddr)\n\tmustConnGRPC(ctx, &svc.emailSvcConn, svc.emailSvcAddr)\n\tmustConnGRPC(ctx, &svc.paymentSvcConn, svc.paymentSvcAddr)\n\n\tlog.Infof(\"service config: %+v\", svc)\n\n\tlis, err := net.Listen(\"tcp\", fmt.Sprintf(\":%s\", port))\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\tvar srv *grpc.Server\n\n\t// Propagate trace context always\n\totel.SetTextMapPropagator(\n\t\tpropagation.NewCompositeTextMapPropagator(\n\t\t\tpropagation.TraceContext{}, propagation.Baggage{}))\n\tsrv = grpc.NewServer(\n\t\tgrpc.StatsHandler(otelgrpc.NewServerHandler()),\n\t)\n\n\tpb.RegisterCheckoutServiceServer(srv, svc)\n\thealthcheck := health.NewServer()\n\thealthpb.RegisterHealthServer(srv, healthcheck)\n\tlog.Infof(\"starting to listen on tcp: %q\", lis.Addr().String())\n\terr = srv.Serve(lis)\n\tlog.Fatal(err)\n}\n\nfunc initStats() {\n\t//TODO(arbrown) Implement OpenTelemetry stats\n}\n\nfunc initTracing() {\n\tvar (\n\t\tcollectorAddr string\n\t\tcollectorConn *grpc.ClientConn\n\t)\n\n\tctx := context.Background()\n\tctx, cancel := context.WithTimeout(ctx, time.Second*3)\n\tdefer cancel()\n\n\tmustMapEnv(&collectorAddr, \"COLLECTOR_SERVICE_ADDR\")\n\tmustConnGRPC(ctx, &collectorConn, collectorAddr)\n\n\texporter, err := otlptracegrpc.New(\n\t\tctx,\n\t\totlptracegrpc.WithGRPCConn(collectorConn))\n\tif err != nil {\n\t\tlog.Warnf(\"warn: Failed to create trace exporter: %v\", err)\n\t}\n\ttp := sdktrace.NewTracerProvider(\n\t\tsdktrace.WithBatcher(exporter),\n\t\tsdktrace.WithSampler(sdktrace.AlwaysSample()))\n\totel.SetTracerProvider(tp)\n\n}\n\nfunc initProfiling(service, version string) {\n\t// TODO(ahmetb) this method is duplicated in other microservices using Go\n\t// since they are not sharing packages.\n\tfor i := 1; i <= 3; i++ {\n\t\tif err := profiler.Start(profiler.Config{\n\t\t\tService:        service,\n\t\t\tServiceVersion: version,\n\t\t\t// ProjectID must be set if not running on GCP.\n\t\t\t// ProjectID: \"my-project\",\n\t\t}); err != nil {\n\t\t\tlog.Warnf(\"failed to start profiler: %+v\", err)\n\t\t} else {\n\t\t\tlog.Info(\"started Stackdriver profiler\")\n\t\t\treturn\n\t\t}\n\t\td := time.Second * 10 * time.Duration(i)\n\t\tlog.Infof(\"sleeping %v to retry initializing Stackdriver profiler\", d)\n\t\ttime.Sleep(d)\n\t}\n\tlog.Warn(\"could not initialize Stackdriver profiler after retrying, giving up\")\n}\n\nfunc mustMapEnv(target *string, envKey string) {\n\tv := os.Getenv(envKey)\n\tif v == \"\" {\n\t\tpanic(fmt.Sprintf(\"environment variable %q not set\", envKey))\n\t}\n\t*target = v\n}\n\nfunc mustConnGRPC(ctx context.Context, conn **grpc.ClientConn, addr string) {\n\tvar err error\n\t_, cancel := context.WithTimeout(ctx, time.Second*3)\n\tdefer cancel()\n\t*conn, err = grpc.NewClient(addr,\n\t\tgrpc.WithTransportCredentials(insecure.NewCredentials()),\n\t\tgrpc.WithStatsHandler(otelgrpc.NewClientHandler()))\n\tif err != nil {\n\t\tpanic(errors.Wrapf(err, \"grpc: failed to connect %s\", addr))\n\t}\n}\n\nfunc (cs *checkoutService) Check(ctx context.Context, req *healthpb.HealthCheckRequest) (*healthpb.HealthCheckResponse, error) {\n\treturn &healthpb.HealthCheckResponse{Status: healthpb.HealthCheckResponse_SERVING}, nil\n}\n\nfunc (cs *checkoutService) Watch(req *healthpb.HealthCheckRequest, ws healthpb.Health_WatchServer) error {\n\treturn status.Errorf(codes.Unimplemented, \"health check via Watch not implemented\")\n}\n\nfunc (cs *checkoutService) PlaceOrder(ctx context.Context, req *pb.PlaceOrderRequest) (*pb.PlaceOrderResponse, error) {\n\tlog.Infof(\"[PlaceOrder] user_id=%q user_currency=%q\", req.UserId, req.UserCurrency)\n\n\torderID, err := uuid.NewUUID()\n\tif err != nil {\n\t\treturn nil, status.Errorf(codes.Internal, \"failed to generate order uuid\")\n\t}\n\n\tprep, err := cs.prepareOrderItemsAndShippingQuoteFromCart(ctx, req.UserId, req.UserCurrency, req.Address)\n\tif err != nil {\n\t\treturn nil, status.Errorf(codes.Internal, err.Error())\n\t}\n\n\ttotal := pb.Money{CurrencyCode: req.UserCurrency,\n\t\tUnits: 0,\n\t\tNanos: 0}\n\ttotal = money.Must(money.Sum(total, *prep.shippingCostLocalized))\n\tfor _, it := range prep.orderItems {\n\t\tmultPrice := money.MultiplySlow(*it.Cost, uint32(it.GetItem().GetQuantity()))\n\t\ttotal = money.Must(money.Sum(total, multPrice))\n\t}\n\n\ttxID, err := cs.chargeCard(ctx, &total, req.CreditCard)\n\tif err != nil {\n\t\treturn nil, status.Errorf(codes.Internal, \"failed to charge card: %+v\", err)\n\t}\n\tlog.Infof(\"payment went through (transaction_id: %s)\", txID)\n\n\tshippingTrackingID, err := cs.shipOrder(ctx, req.Address, prep.cartItems)\n\tif err != nil {\n\t\treturn nil, status.Errorf(codes.Unavailable, \"shipping error: %+v\", err)\n\t}\n\n\t_ = cs.emptyUserCart(ctx, req.UserId)\n\n\torderResult := &pb.OrderResult{\n\t\tOrderId:            orderID.String(),\n\t\tShippingTrackingId: shippingTrackingID,\n\t\tShippingCost:       prep.shippingCostLocalized,\n\t\tShippingAddress:    req.Address,\n\t\tItems:              prep.orderItems,\n\t}\n\n\tif err := cs.sendOrderConfirmation(ctx, req.Email, orderResult); err != nil {\n\t\tlog.Warnf(\"failed to send order confirmation to %q: %+v\", req.Email, err)\n\t} else {\n\t\tlog.Infof(\"order confirmation email sent to %q\", req.Email)\n\t}\n\tresp := &pb.PlaceOrderResponse{Order: orderResult}\n\treturn resp, nil\n}\n\ntype orderPrep struct {\n\torderItems            []*pb.OrderItem\n\tcartItems             []*pb.CartItem\n\tshippingCostLocalized *pb.Money\n}\n\nfunc (cs *checkoutService) prepareOrderItemsAndShippingQuoteFromCart(ctx context.Context, userID, userCurrency string, address *pb.Address) (orderPrep, error) {\n\tvar out orderPrep\n\tcartItems, err := cs.getUserCart(ctx, userID)\n\tif err != nil {\n\t\treturn out, fmt.Errorf(\"cart failure: %+v\", err)\n\t}\n\torderItems, err := cs.prepOrderItems(ctx, cartItems, userCurrency)\n\tif err != nil {\n\t\treturn out, fmt.Errorf(\"failed to prepare order: %+v\", err)\n\t}\n\tshippingUSD, err := cs.quoteShipping(ctx, address, cartItems)\n\tif err != nil {\n\t\treturn out, fmt.Errorf(\"shipping quote failure: %+v\", err)\n\t}\n\tshippingPrice, err := cs.convertCurrency(ctx, shippingUSD, userCurrency)\n\tif err != nil {\n\t\treturn out, fmt.Errorf(\"failed to convert shipping cost to currency: %+v\", err)\n\t}\n\n\tout.shippingCostLocalized = shippingPrice\n\tout.cartItems = cartItems\n\tout.orderItems = orderItems\n\treturn out, nil\n}\n\nfunc (cs *checkoutService) quoteShipping(ctx context.Context, address *pb.Address, items []*pb.CartItem) (*pb.Money, error) {\n\tshippingQuote, err := pb.NewShippingServiceClient(cs.shippingSvcConn).\n\t\tGetQuote(ctx, &pb.GetQuoteRequest{\n\t\t\tAddress: address,\n\t\t\tItems:   items})\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to get shipping quote: %+v\", err)\n\t}\n\treturn shippingQuote.GetCostUsd(), nil\n}\n\nfunc (cs *checkoutService) getUserCart(ctx context.Context, userID string) ([]*pb.CartItem, error) {\n\tcart, err := pb.NewCartServiceClient(cs.cartSvcConn).GetCart(ctx, &pb.GetCartRequest{UserId: userID})\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to get user cart during checkout: %+v\", err)\n\t}\n\treturn cart.GetItems(), nil\n}\n\nfunc (cs *checkoutService) emptyUserCart(ctx context.Context, userID string) error {\n\tif _, err := pb.NewCartServiceClient(cs.cartSvcConn).EmptyCart(ctx, &pb.EmptyCartRequest{UserId: userID}); err != nil {\n\t\treturn fmt.Errorf(\"failed to empty user cart during checkout: %+v\", err)\n\t}\n\treturn nil\n}\n\nfunc (cs *checkoutService) prepOrderItems(ctx context.Context, items []*pb.CartItem, userCurrency string) ([]*pb.OrderItem, error) {\n\tout := make([]*pb.OrderItem, len(items))\n\tcl := pb.NewProductCatalogServiceClient(cs.productCatalogSvcConn)\n\n\tfor i, item := range items {\n\t\tproduct, err := cl.GetProduct(ctx, &pb.GetProductRequest{Id: item.GetProductId()})\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"failed to get product #%q\", item.GetProductId())\n\t\t}\n\t\tprice, err := cs.convertCurrency(ctx, product.GetPriceUsd(), userCurrency)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"failed to convert price of %q to %s\", item.GetProductId(), userCurrency)\n\t\t}\n\t\tout[i] = &pb.OrderItem{\n\t\t\tItem: item,\n\t\t\tCost: price}\n\t}\n\treturn out, nil\n}\n\nfunc (cs *checkoutService) convertCurrency(ctx context.Context, from *pb.Money, toCurrency string) (*pb.Money, error) {\n\tresult, err := pb.NewCurrencyServiceClient(cs.currencySvcConn).Convert(context.TODO(), &pb.CurrencyConversionRequest{\n\t\tFrom:   from,\n\t\tToCode: toCurrency})\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to convert currency: %+v\", err)\n\t}\n\treturn result, err\n}\n\nfunc (cs *checkoutService) chargeCard(ctx context.Context, amount *pb.Money, paymentInfo *pb.CreditCardInfo) (string, error) {\n\tpaymentResp, err := pb.NewPaymentServiceClient(cs.paymentSvcConn).Charge(ctx, &pb.ChargeRequest{\n\t\tAmount:     amount,\n\t\tCreditCard: paymentInfo})\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"could not charge the card: %+v\", err)\n\t}\n\treturn paymentResp.GetTransactionId(), nil\n}\n\nfunc (cs *checkoutService) sendOrderConfirmation(ctx context.Context, email string, order *pb.OrderResult) error {\n\t_, err := pb.NewEmailServiceClient(cs.emailSvcConn).SendOrderConfirmation(ctx, &pb.SendOrderConfirmationRequest{\n\t\tEmail: email,\n\t\tOrder: order})\n\treturn err\n}\n\nfunc (cs *checkoutService) shipOrder(ctx context.Context, address *pb.Address, items []*pb.CartItem) (string, error) {\n\tresp, err := pb.NewShippingServiceClient(cs.shippingSvcConn).ShipOrder(ctx, &pb.ShipOrderRequest{\n\t\tAddress: address,\n\t\tItems:   items})\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"shipment failed: %+v\", err)\n\t}\n\treturn resp.GetTrackingId(), nil\n}\n"
  },
  {
    "path": "src/checkoutservice/money/money.go",
    "content": "// Copyright 2018 Google LLC\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage money\n\nimport (\n\t\"errors\"\n\n\tpb \"github.com/GoogleCloudPlatform/microservices-demo/src/checkoutservice/genproto\"\n)\n\nconst (\n\tnanosMin = -999999999\n\tnanosMax = +999999999\n\tnanosMod = 1000000000\n)\n\nvar (\n\tErrInvalidValue        = errors.New(\"one of the specified money values is invalid\")\n\tErrMismatchingCurrency = errors.New(\"mismatching currency codes\")\n)\n\n// IsValid checks if specified value has a valid units/nanos signs and ranges.\nfunc IsValid(m pb.Money) bool {\n\treturn signMatches(m) && validNanos(m.GetNanos())\n}\n\nfunc signMatches(m pb.Money) bool {\n\treturn m.GetNanos() == 0 || m.GetUnits() == 0 || (m.GetNanos() < 0) == (m.GetUnits() < 0)\n}\n\nfunc validNanos(nanos int32) bool { return nanosMin <= nanos && nanos <= nanosMax }\n\n// IsZero returns true if the specified money value is equal to zero.\nfunc IsZero(m pb.Money) bool { return m.GetUnits() == 0 && m.GetNanos() == 0 }\n\n// IsPositive returns true if the specified money value is valid and is\n// positive.\nfunc IsPositive(m pb.Money) bool {\n\treturn IsValid(m) && m.GetUnits() > 0 || (m.GetUnits() == 0 && m.GetNanos() > 0)\n}\n\n// IsNegative returns true if the specified money value is valid and is\n// negative.\nfunc IsNegative(m pb.Money) bool {\n\treturn IsValid(m) && m.GetUnits() < 0 || (m.GetUnits() == 0 && m.GetNanos() < 0)\n}\n\n// AreSameCurrency returns true if values l and r have a currency code and\n// they are the same values.\nfunc AreSameCurrency(l, r pb.Money) bool {\n\treturn l.GetCurrencyCode() == r.GetCurrencyCode() && l.GetCurrencyCode() != \"\"\n}\n\n// AreEquals returns true if values l and r are the equal, including the\n// currency. This does not check validity of the provided values.\nfunc AreEquals(l, r pb.Money) bool {\n\treturn l.GetCurrencyCode() == r.GetCurrencyCode() &&\n\t\tl.GetUnits() == r.GetUnits() && l.GetNanos() == r.GetNanos()\n}\n\n// Negate returns the same amount with the sign negated.\nfunc Negate(m pb.Money) pb.Money {\n\treturn pb.Money{\n\t\tUnits:        -m.GetUnits(),\n\t\tNanos:        -m.GetNanos(),\n\t\tCurrencyCode: m.GetCurrencyCode()}\n}\n\n// Must panics if the given error is not nil. This can be used with other\n// functions like: \"m := Must(Sum(a,b))\".\nfunc Must(v pb.Money, err error) pb.Money {\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn v\n}\n\n// Sum adds two values. Returns an error if one of the values are invalid or\n// currency codes are not matching (unless currency code is unspecified for\n// both).\nfunc Sum(l, r pb.Money) (pb.Money, error) {\n\tif !IsValid(l) || !IsValid(r) {\n\t\treturn pb.Money{}, ErrInvalidValue\n\t} else if l.GetCurrencyCode() != r.GetCurrencyCode() {\n\t\treturn pb.Money{}, ErrMismatchingCurrency\n\t}\n\tunits := l.GetUnits() + r.GetUnits()\n\tnanos := l.GetNanos() + r.GetNanos()\n\n\tif (units == 0 && nanos == 0) || (units > 0 && nanos >= 0) || (units < 0 && nanos <= 0) {\n\t\t// same sign <units, nanos>\n\t\tunits += int64(nanos / nanosMod)\n\t\tnanos = nanos % nanosMod\n\t} else {\n\t\t// different sign. nanos guaranteed to not to go over the limit\n\t\tif units > 0 {\n\t\t\tunits--\n\t\t\tnanos += nanosMod\n\t\t} else {\n\t\t\tunits++\n\t\t\tnanos -= nanosMod\n\t\t}\n\t}\n\n\treturn pb.Money{\n\t\tUnits:        units,\n\t\tNanos:        nanos,\n\t\tCurrencyCode: l.GetCurrencyCode()}, nil\n}\n\n// MultiplySlow is a slow multiplication operation done through adding the value\n// to itself n-1 times.\nfunc MultiplySlow(m pb.Money, n uint32) pb.Money {\n\tout := m\n\tfor n > 1 {\n\t\tout = Must(Sum(out, m))\n\t\tn--\n\t}\n\treturn out\n}\n"
  },
  {
    "path": "src/checkoutservice/money/money_test.go",
    "content": "// Copyright 2018 Google LLC\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage money\n\nimport (\n\t\"fmt\"\n\t\"reflect\"\n\t\"testing\"\n\n\tpb \"github.com/GoogleCloudPlatform/microservices-demo/src/checkoutservice/genproto\"\n)\n\nfunc mmc(u int64, n int32, c string) pb.Money { return pb.Money{Units: u, Nanos: n, CurrencyCode: c} }\nfunc mm(u int64, n int32) pb.Money            { return mmc(u, n, \"\") }\n\nfunc TestIsValid(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tin   pb.Money\n\t\twant bool\n\t}{\n\t\t{\"valid -/-\", mm(-981273891273, -999999999), true},\n\t\t{\"invalid -/+\", mm(-981273891273, +999999999), false},\n\t\t{\"valid +/+\", mm(981273891273, 999999999), true},\n\t\t{\"invalid +/-\", mm(981273891273, -999999999), false},\n\t\t{\"invalid +/+overflow\", mm(3, 1000000000), false},\n\t\t{\"invalid +/-overflow\", mm(3, -1000000000), false},\n\t\t{\"invalid -/+overflow\", mm(-3, 1000000000), false},\n\t\t{\"invalid -/-overflow\", mm(-3, -1000000000), false},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tif got := IsValid(tt.in); got != tt.want {\n\t\t\t\tt.Errorf(\"IsValid(%v) = %v, want %v\", tt.in, got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestIsZero(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tin   pb.Money\n\t\twant bool\n\t}{\n\t\t{\"zero\", mm(0, 0), true},\n\t\t{\"not-zero (-/+)\", mm(-1, +1), false},\n\t\t{\"not-zero (-/-)\", mm(-1, -1), false},\n\t\t{\"not-zero (+/+)\", mm(+1, +1), false},\n\t\t{\"not-zero (+/-)\", mm(+1, -1), false},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tif got := IsZero(tt.in); got != tt.want {\n\t\t\t\tt.Errorf(\"IsZero(%v) = %v, want %v\", tt.in, got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestIsPositive(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tin   pb.Money\n\t\twant bool\n\t}{\n\t\t{\"zero\", mm(0, 0), false},\n\t\t{\"positive (+/+)\", mm(+1, +1), true},\n\t\t{\"invalid (-/+)\", mm(-1, +1), false},\n\t\t{\"negative (-/-)\", mm(-1, -1), false},\n\t\t{\"invalid (+/-)\", mm(+1, -1), false},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tif got := IsPositive(tt.in); got != tt.want {\n\t\t\t\tt.Errorf(\"IsPositive(%v) = %v, want %v\", tt.in, got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestIsNegative(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tin   pb.Money\n\t\twant bool\n\t}{\n\t\t{\"zero\", mm(0, 0), false},\n\t\t{\"positive (+/+)\", mm(+1, +1), false},\n\t\t{\"invalid (-/+)\", mm(-1, +1), false},\n\t\t{\"negative (-/-)\", mm(-1, -1), true},\n\t\t{\"invalid (+/-)\", mm(+1, -1), false},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tif got := IsNegative(tt.in); got != tt.want {\n\t\t\t\tt.Errorf(\"IsNegative(%v) = %v, want %v\", tt.in, got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestAreSameCurrency(t *testing.T) {\n\ttype args struct {\n\t\tl pb.Money\n\t\tr pb.Money\n\t}\n\ttests := []struct {\n\t\tname string\n\t\targs args\n\t\twant bool\n\t}{\n\t\t{\"both empty currency\", args{mmc(1, 0, \"\"), mmc(2, 0, \"\")}, false},\n\t\t{\"left empty currency\", args{mmc(1, 0, \"\"), mmc(2, 0, \"USD\")}, false},\n\t\t{\"right empty currency\", args{mmc(1, 0, \"USD\"), mmc(2, 0, \"\")}, false},\n\t\t{\"mismatching\", args{mmc(1, 0, \"USD\"), mmc(2, 0, \"CAD\")}, false},\n\t\t{\"matching\", args{mmc(1, 0, \"USD\"), mmc(2, 0, \"USD\")}, true},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tif got := AreSameCurrency(tt.args.l, tt.args.r); got != tt.want {\n\t\t\t\tt.Errorf(\"AreSameCurrency([%v],[%v]) = %v, want %v\", tt.args.l, tt.args.r, got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestAreEquals(t *testing.T) {\n\ttype args struct {\n\t\tl pb.Money\n\t\tr pb.Money\n\t}\n\ttests := []struct {\n\t\tname string\n\t\targs args\n\t\twant bool\n\t}{\n\t\t{\"equals\", args{mmc(1, 2, \"USD\"), mmc(1, 2, \"USD\")}, true},\n\t\t{\"mismatching currency\", args{mmc(1, 2, \"USD\"), mmc(1, 2, \"CAD\")}, false},\n\t\t{\"mismatching units\", args{mmc(10, 20, \"USD\"), mmc(1, 20, \"USD\")}, false},\n\t\t{\"mismatching nanos\", args{mmc(1, 2, \"USD\"), mmc(1, 20, \"USD\")}, false},\n\t\t{\"negated\", args{mmc(1, 2, \"USD\"), mmc(-1, -2, \"USD\")}, false},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tif got := AreEquals(tt.args.l, tt.args.r); got != tt.want {\n\t\t\t\tt.Errorf(\"AreEquals([%v],[%v]) = %v, want %v\", tt.args.l, tt.args.r, got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestNegate(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tin   pb.Money\n\t\twant pb.Money\n\t}{\n\t\t{\"zero\", mm(0, 0), mm(0, 0)},\n\t\t{\"negative\", mm(-1, -200), mm(1, 200)},\n\t\t{\"positive\", mm(1, 200), mm(-1, -200)},\n\t\t{\"carries currency code\", mmc(0, 0, \"XXX\"), mmc(0, 0, \"XXX\")},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tif got := Negate(tt.in); !AreEquals(got, tt.want) {\n\t\t\t\tt.Errorf(\"Negate([%v]) = %v, want %v\", tt.in, got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestMust_pass(t *testing.T) {\n\tv := Must(mm(2, 3), nil)\n\tif !AreEquals(v, mm(2, 3)) {\n\t\tt.Errorf(\"returned the wrong value: %v\", v)\n\t}\n}\n\nfunc TestMust_panic(t *testing.T) {\n\tdefer func() {\n\t\tif r := recover(); r != nil {\n\t\t\tt.Logf(\"panic captured: %v\", r)\n\t\t}\n\t}()\n\tMust(mm(2, 3), fmt.Errorf(\"some error\"))\n\tt.Fatal(\"this should not have executed due to the panic above\")\n}\n\nfunc TestSum(t *testing.T) {\n\ttype args struct {\n\t\tl pb.Money\n\t\tr pb.Money\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\targs    args\n\t\twant    pb.Money\n\t\twantErr error\n\t}{\n\t\t{\"0+0=0\", args{mm(0, 0), mm(0, 0)}, mm(0, 0), nil},\n\t\t{\"Error: currency code on left\", args{mmc(0, 0, \"XXX\"), mm(0, 0)}, mm(0, 0), ErrMismatchingCurrency},\n\t\t{\"Error: currency code on right\", args{mm(0, 0), mmc(0, 0, \"YYY\")}, mm(0, 0), ErrMismatchingCurrency},\n\t\t{\"Error: currency code mismatch\", args{mmc(0, 0, \"AAA\"), mmc(0, 0, \"BBB\")}, mm(0, 0), ErrMismatchingCurrency},\n\t\t{\"Error: invalid +/-\", args{mm(+1, -1), mm(0, 0)}, mm(0, 0), ErrInvalidValue},\n\t\t{\"Error: invalid -/+\", args{mm(0, 0), mm(-1, +2)}, mm(0, 0), ErrInvalidValue},\n\t\t{\"Error: invalid nanos\", args{mm(0, 1000000000), mm(1, 0)}, mm(0, 0), ErrInvalidValue},\n\t\t{\"both positive (no carry)\", args{mm(2, 200000000), mm(2, 200000000)}, mm(4, 400000000), nil},\n\t\t{\"both positive (nanos=max)\", args{mm(2, 111111111), mm(2, 888888888)}, mm(4, 999999999), nil},\n\t\t{\"both positive (carry)\", args{mm(2, 200000000), mm(2, 900000000)}, mm(5, 100000000), nil},\n\t\t{\"both negative (no carry)\", args{mm(-2, -200000000), mm(-2, -200000000)}, mm(-4, -400000000), nil},\n\t\t{\"both negative (carry)\", args{mm(-2, -200000000), mm(-2, -900000000)}, mm(-5, -100000000), nil},\n\t\t{\"mixed (larger positive, just decimals)\", args{mm(11, 0), mm(-2, 0)}, mm(9, 0), nil},\n\t\t{\"mixed (larger negative, just decimals)\", args{mm(-11, 0), mm(2, 0)}, mm(-9, 0), nil},\n\t\t{\"mixed (larger positive, no borrow)\", args{mm(11, 100000000), mm(-2, -100000000)}, mm(9, 0), nil},\n\t\t{\"mixed (larger positive, with borrow)\", args{mm(11, 100000000), mm(-2, -9000000 /*.09*/)}, mm(9, 91000000 /*.091*/), nil},\n\t\t{\"mixed (larger negative, no borrow)\", args{mm(-11, -100000000), mm(2, 100000000)}, mm(-9, 0), nil},\n\t\t{\"mixed (larger negative, with borrow)\", args{mm(-11, -100000000), mm(2, 9000000 /*.09*/)}, mm(-9, -91000000 /*.091*/), nil},\n\t\t{\"0+negative\", args{mm(0, 0), mm(-2, -100000000)}, mm(-2, -100000000), nil},\n\t\t{\"negative+0\", args{mm(-2, -100000000), mm(0, 0)}, mm(-2, -100000000), nil},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := Sum(tt.args.l, tt.args.r)\n\t\t\tif err != tt.wantErr {\n\t\t\t\tt.Errorf(\"Sum([%v],[%v]): expected err=\\\"%v\\\" got=\\\"%v\\\"\", tt.args.l, tt.args.r, tt.wantErr, err)\n\t\t\t}\n\t\t\tif !reflect.DeepEqual(got, tt.want) {\n\t\t\t\tt.Errorf(\"Sum([%v],[%v]) = %v, want %v\", tt.args.l, tt.args.r, got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "src/currencyservice/.dockerignore",
    "content": "client.js\nnode_modules/\n"
  },
  {
    "path": "src/currencyservice/.gitignore",
    "content": "node_modules/\n"
  },
  {
    "path": "src/currencyservice/Dockerfile",
    "content": "# Copyright 2020 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# Define a default value so it's not empty if the builder fails to provide it\nARG BUILDPLATFORM=linux/amd64\n\nFROM --platform=$BUILDPLATFORM node:20.20.1-alpine@sha256:b88333c42c23fbd91596ebd7fd10de239cedab9617de04142dde7315e3bc0afa AS builder\n\n# Some packages (e.g. @google-cloud/profiler) require additional\n# deps for post-install scripts\nRUN apk add --update --no-cache \\\n    python3 \\\n    make \\\n    g++\n\nWORKDIR /usr/src/app\n\nCOPY package*.json ./\n\nRUN npm install --only=production\n\nFROM alpine:3.23.3@sha256:25109184c71bdad752c8312a8623239686a9a2071e8825f20acb8f2198c3f659\n\nRUN apk add --no-cache nodejs\n\nWORKDIR /usr/src/app\n\nCOPY --from=builder /usr/src/app/node_modules ./node_modules\n\nCOPY . .\n\nEXPOSE 7000\n\nENTRYPOINT [ \"node\", \"server.js\" ]\n"
  },
  {
    "path": "src/currencyservice/client.js",
    "content": "/*\n *\n * Copyright 2015 gRPC authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\nrequire('@google-cloud/trace-agent').start();\n\nconst path = require('path');\nconst grpc = require('grpc');\nconst pino = require('pino');\n\nconst PROTO_PATH = path.join(__dirname, './proto/demo.proto');\nconst PORT = 7000;\n\nconst shopProto = grpc.load(PROTO_PATH).hipstershop;\nconst client = new shopProto.CurrencyService(`localhost:${PORT}`,\n  grpc.credentials.createInsecure());\n\nconst logger = pino({\n  name: 'currencyservice-client',\n  messageKey: 'message',\n  formatters: {\n    level (logLevelString, logLevelNum) {\n      return { severity: logLevelString }\n    }\n  }\n});\n\nconst request = {\n  from: {\n    currency_code: 'CHF',\n    units: 300,\n    nanos: 0\n  },\n  to_code: 'EUR'\n};\n\nfunction _moneyToString (m) {\n  return `${m.units}.${m.nanos.toString().padStart(9,'0')} ${m.currency_code}`;\n}\n\nclient.getSupportedCurrencies({}, (err, response) => {\n  if (err) {\n    logger.error(`Error in getSupportedCurrencies: ${err}`);\n  } else {\n    logger.info(`Currency codes: ${response.currency_codes}`);\n  }\n});\n\nclient.convert(request, (err, response) => {\n  if (err) {\n    logger.error(`Error in convert: ${err}`);\n  } else {\n    logger.log(`Convert: ${_moneyToString(request.from)} to ${_moneyToString(response)}`);\n  }\n});\n"
  },
  {
    "path": "src/currencyservice/data/currency_conversion.json",
    "content": "{\n  \"EUR\": \"1.0\",\n  \"USD\": \"1.1305\",\n  \"JPY\": \"126.40\",\n  \"BGN\": \"1.9558\",\n  \"CZK\": \"25.592\",\n  \"DKK\": \"7.4609\",\n  \"GBP\": \"0.85970\",\n  \"HUF\": \"315.51\",\n  \"PLN\": \"4.2996\",\n  \"RON\": \"4.7463\",\n  \"SEK\": \"10.5375\",\n  \"CHF\": \"1.1360\",\n  \"ISK\": \"136.80\",\n  \"NOK\": \"9.8040\",\n  \"HRK\": \"7.4210\",\n  \"RUB\": \"74.4208\",\n  \"TRY\": \"6.1247\",\n  \"AUD\": \"1.6072\",\n  \"BRL\": \"4.2682\",\n  \"CAD\": \"1.5128\",\n  \"CNY\": \"7.5857\",\n  \"HKD\": \"8.8743\",\n  \"IDR\": \"15999.40\",\n  \"ILS\": \"4.0875\",\n  \"INR\": \"79.4320\",\n  \"KRW\": \"1275.05\",\n  \"MXN\": \"21.7999\",\n  \"MYR\": \"4.6289\",\n  \"NZD\": \"1.6679\",\n  \"PHP\": \"59.083\",\n  \"SGD\": \"1.5349\",\n  \"THB\": \"36.012\",\n  \"ZAR\": \"16.0583\"\n}"
  },
  {
    "path": "src/currencyservice/genproto.sh",
    "content": "#!/bin/bash -eu\n#\n# Copyright 2018 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# [START gke_currencyservice_genproto]\n\n# protos are loaded dynamically for node, simply copies over the proto.\nmkdir -p proto\ncp -r ../../protos/* ./proto\n\n# [END gke_currencyservice_genproto]"
  },
  {
    "path": "src/currencyservice/package.json",
    "content": "{\n  \"name\": \"grpc-currency-service\",\n  \"version\": \"0.1.0\",\n  \"description\": \"A gRPC currency conversion microservice\",\n  \"repository\": \"https://github.com/GoogleCloudPlatform/microservices-demo\",\n  \"scripts\": {\n    \"test\": \"echo \\\"Error: no test specified\\\" && exit 1\"\n  },\n  \"license\": \"Apache-2.0\",\n  \"dependencies\": {\n    \"@google-cloud/profiler\": \"6.0.4\",\n    \"@google-cloud/trace-agent\": \"8.0.0\",\n    \"@grpc/grpc-js\": \"1.14.3\",\n    \"@grpc/proto-loader\": \"0.8.0\",\n    \"async\": \"3.2.6\",\n    \"google-protobuf\": \"4.0.2\",\n    \"@opentelemetry/api\": \"1.9.0\",\n    \"@opentelemetry/exporter-otlp-grpc\": \"0.26.0\",\n    \"@opentelemetry/instrumentation-grpc\": \"0.213.0\",\n    \"@opentelemetry/resources\": \"2.6.0\",\n    \"@opentelemetry/semantic-conventions\": \"1.40.0\",\n    \"@opentelemetry/sdk-trace-base\": \"2.6.0\",\n    \"@opentelemetry/sdk-node\": \"0.213.0\",\n    \"pino\": \"10.3.1\",\n    \"xml2js\": \"0.6.2\"\n  }\n}\n"
  },
  {
    "path": "src/currencyservice/proto/demo.proto",
    "content": "// Copyright 2020 Google LLC\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\nsyntax = \"proto3\";\n\npackage hipstershop;\n\n// -----------------Cart service-----------------\n\nservice CartService {\n    rpc AddItem(AddItemRequest) returns (Empty) {}\n    rpc GetCart(GetCartRequest) returns (Cart) {}\n    rpc EmptyCart(EmptyCartRequest) returns (Empty) {}\n}\n\nmessage CartItem {\n    string product_id = 1;\n    int32  quantity = 2;\n}\n\nmessage AddItemRequest {\n    string user_id = 1;\n    CartItem item = 2;\n}\n\nmessage EmptyCartRequest {\n    string user_id = 1;\n}\n\nmessage GetCartRequest {\n    string user_id = 1;\n}\n\nmessage Cart {\n    string user_id = 1;\n    repeated CartItem items = 2;\n}\n\nmessage Empty {}\n\n// ---------------Recommendation service----------\n\nservice RecommendationService {\n  rpc ListRecommendations(ListRecommendationsRequest) returns (ListRecommendationsResponse){}\n}\n\nmessage ListRecommendationsRequest {\n    string user_id = 1;\n    repeated string product_ids = 2;\n}\n\nmessage ListRecommendationsResponse {\n    repeated string product_ids = 1;\n}\n\n// ---------------Product Catalog----------------\n\nservice ProductCatalogService {\n    rpc ListProducts(Empty) returns (ListProductsResponse) {}\n    rpc GetProduct(GetProductRequest) returns (Product) {}\n    rpc SearchProducts(SearchProductsRequest) returns (SearchProductsResponse) {}\n}\n\nmessage Product {\n    string id = 1;\n    string name = 2;\n    string description = 3;\n    string picture = 4;\n    Money price_usd = 5;\n\n    // Categories such as \"clothing\" or \"kitchen\" that can be used to look up\n    // other related products.\n    repeated string categories = 6;\n}\n\nmessage ListProductsResponse {\n    repeated Product products = 1;\n}\n\nmessage GetProductRequest {\n    string id = 1;\n}\n\nmessage SearchProductsRequest {\n    string query = 1;\n}\n\nmessage SearchProductsResponse {\n    repeated Product results = 1;\n}\n\n// ---------------Shipping Service----------\n\nservice ShippingService {\n    rpc GetQuote(GetQuoteRequest) returns (GetQuoteResponse) {}\n    rpc ShipOrder(ShipOrderRequest) returns (ShipOrderResponse) {}\n}\n\nmessage GetQuoteRequest {\n    Address address = 1;\n    repeated CartItem items = 2;\n}\n\nmessage GetQuoteResponse {\n    Money cost_usd = 1;\n}\n\nmessage ShipOrderRequest {\n    Address address = 1;\n    repeated CartItem items = 2;\n}\n\nmessage ShipOrderResponse {\n    string tracking_id = 1;\n}\n\nmessage Address {\n    string street_address = 1;\n    string city = 2;\n    string state = 3;\n    string country = 4;\n    int32 zip_code = 5;\n}\n\n// -----------------Currency service-----------------\n\nservice CurrencyService {\n    rpc GetSupportedCurrencies(Empty) returns (GetSupportedCurrenciesResponse) {}\n    rpc Convert(CurrencyConversionRequest) returns (Money) {}\n}\n\n// Represents an amount of money with its currency type.\nmessage Money {\n    // The 3-letter currency code defined in ISO 4217.\n    string currency_code = 1;\n\n    // The whole units of the amount.\n    // For example if `currencyCode` is `\"USD\"`, then 1 unit is one US dollar.\n    int64 units = 2;\n\n    // Number of nano (10^-9) units of the amount.\n    // The value must be between -999,999,999 and +999,999,999 inclusive.\n    // If `units` is positive, `nanos` must be positive or zero.\n    // If `units` is zero, `nanos` can be positive, zero, or negative.\n    // If `units` is negative, `nanos` must be negative or zero.\n    // For example $-1.75 is represented as `units`=-1 and `nanos`=-750,000,000.\n    int32 nanos = 3;\n}\n\nmessage GetSupportedCurrenciesResponse {\n    // The 3-letter currency code defined in ISO 4217.\n    repeated string currency_codes = 1;\n}\n\nmessage CurrencyConversionRequest {\n    Money from = 1;\n\n    // The 3-letter currency code defined in ISO 4217.\n    string to_code = 2;\n}\n\n// -------------Payment service-----------------\n\nservice PaymentService {\n    rpc Charge(ChargeRequest) returns (ChargeResponse) {}\n}\n\nmessage CreditCardInfo {\n    string credit_card_number = 1;\n    int32 credit_card_cvv = 2;\n    int32 credit_card_expiration_year = 3;\n    int32 credit_card_expiration_month = 4;\n}\n\nmessage ChargeRequest {\n    Money amount = 1;\n    CreditCardInfo credit_card = 2;\n}\n\nmessage ChargeResponse {\n    string transaction_id = 1;\n}\n\n// -------------Email service-----------------\n\nservice EmailService {\n    rpc SendOrderConfirmation(SendOrderConfirmationRequest) returns (Empty) {}\n}\n\nmessage OrderItem {\n    CartItem item = 1;\n    Money cost = 2;\n}\n\nmessage OrderResult {\n    string   order_id = 1;\n    string   shipping_tracking_id = 2;\n    Money shipping_cost = 3;\n    Address  shipping_address = 4;\n    repeated OrderItem items = 5;\n}\n\nmessage SendOrderConfirmationRequest {\n    string email = 1;\n    OrderResult order = 2;\n}\n\n\n// -------------Checkout service-----------------\n\nservice CheckoutService {\n    rpc PlaceOrder(PlaceOrderRequest) returns (PlaceOrderResponse) {}\n}\n\nmessage PlaceOrderRequest {\n    string user_id = 1;\n    string user_currency = 2;\n\n    Address address = 3;\n    string email = 5;\n    CreditCardInfo credit_card = 6;\n}\n\nmessage PlaceOrderResponse {\n    OrderResult order = 1;\n}\n\n// ------------Ad service------------------\n\nservice AdService {\n    rpc GetAds(AdRequest) returns (AdResponse) {}\n}\n\nmessage AdRequest {\n    // List of important key words from the current page describing the context.\n    repeated string context_keys = 1;\n}\n\nmessage AdResponse {\n    repeated Ad ads = 1;\n}\n\nmessage Ad {\n    // url to redirect to when an ad is clicked.\n    string redirect_url = 1;\n\n    // short advertisement text to display.\n    string text = 2;\n}\n"
  },
  {
    "path": "src/currencyservice/proto/grpc/health/v1/health.proto",
    "content": "// Copyright 2015 The gRPC Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// The canonical version of this proto can be found at\n// https://github.com/grpc/grpc-proto/blob/master/grpc/health/v1/health.proto\n\nsyntax = \"proto3\";\n\npackage grpc.health.v1;\n\noption csharp_namespace = \"Grpc.Health.V1\";\noption go_package = \"google.golang.org/grpc/health/grpc_health_v1\";\noption java_multiple_files = true;\noption java_outer_classname = \"HealthProto\";\noption java_package = \"io.grpc.health.v1\";\n\nmessage HealthCheckRequest {\n  string service = 1;\n}\n\nmessage HealthCheckResponse {\n  enum ServingStatus {\n    UNKNOWN = 0;\n    SERVING = 1;\n    NOT_SERVING = 2;\n  }\n  ServingStatus status = 1;\n}\n\nservice Health {\n  rpc Check(HealthCheckRequest) returns (HealthCheckResponse);\n}\n"
  },
  {
    "path": "src/currencyservice/server.js",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nconst pino = require('pino');\nconst logger = pino({\n  name: 'currencyservice-server',\n  messageKey: 'message',\n  formatters: {\n    level (logLevelString, logLevelNum) {\n      return { severity: logLevelString }\n    }\n  }\n});\n\nif(process.env.DISABLE_PROFILER) {\n  logger.info(\"Profiler disabled.\")\n}\nelse {\n  logger.info(\"Profiler enabled.\")\n  require('@google-cloud/profiler').start({\n    serviceContext: {\n      service: 'currencyservice',\n      version: '1.0.0'\n    }\n  });\n}\n\n// Register GRPC OTel Instrumentation for trace propagation\n// regardless of whether tracing is emitted.\nconst { GrpcInstrumentation } = require('@opentelemetry/instrumentation-grpc');\nconst { registerInstrumentations } = require('@opentelemetry/instrumentation');\n\nregisterInstrumentations({\n  instrumentations: [new GrpcInstrumentation()]\n});\n\nif(process.env.ENABLE_TRACING == \"1\") {\n  logger.info(\"Tracing enabled.\")\n\n  const { resourceFromAttributes } = require('@opentelemetry/resources');\n\n  const { ATTR_SERVICE_NAME } = require('@opentelemetry/semantic-conventions');\n\n  const opentelemetry = require('@opentelemetry/sdk-node');\n\n  const { OTLPTraceExporter } = require('@opentelemetry/exporter-otlp-grpc');\n\n  const collectorUrl = process.env.COLLECTOR_SERVICE_ADDR;\n  const traceExporter = new OTLPTraceExporter({url: collectorUrl});\n  const sdk = new opentelemetry.NodeSDK({\n    resource: resourceFromAttributes({\n      [ ATTR_SERVICE_NAME ]: process.env.OTEL_SERVICE_NAME || 'currencyservice',\n    }),\n    traceExporter: traceExporter,\n  });\n\n  sdk.start()\n}\nelse {\n  logger.info(\"Tracing disabled.\")\n}\n\nconst path = require('path');\nconst grpc = require('@grpc/grpc-js');\nconst protoLoader = require('@grpc/proto-loader');\n\nconst MAIN_PROTO_PATH = path.join(__dirname, './proto/demo.proto');\nconst HEALTH_PROTO_PATH = path.join(__dirname, './proto/grpc/health/v1/health.proto');\n\nconst PORT = process.env.PORT;\n\nconst shopProto = _loadProto(MAIN_PROTO_PATH).hipstershop;\nconst healthProto = _loadProto(HEALTH_PROTO_PATH).grpc.health.v1;\n\n/**\n * Helper function that loads a protobuf file.\n */\nfunction _loadProto (path) {\n  const packageDefinition = protoLoader.loadSync(\n    path,\n    {\n      keepCase: true,\n      longs: String,\n      enums: String,\n      defaults: true,\n      oneofs: true\n    }\n  );\n  return grpc.loadPackageDefinition(packageDefinition);\n}\n\n/**\n * Helper function that gets currency data from a stored JSON file\n * Uses public data from European Central Bank\n */\nfunction _getCurrencyData (callback) {\n  const data = require('./data/currency_conversion.json');\n  callback(data);\n}\n\n/**\n * Helper function that handles decimal/fractional carrying\n */\nfunction _carry (amount) {\n  const fractionSize = Math.pow(10, 9);\n  amount.nanos += (amount.units % 1) * fractionSize;\n  amount.units = Math.floor(amount.units) + Math.floor(amount.nanos / fractionSize);\n  amount.nanos = amount.nanos % fractionSize;\n  return amount;\n}\n\n/**\n * Lists the supported currencies\n */\nfunction getSupportedCurrencies (call, callback) {\n  logger.info('Getting supported currencies...');\n  _getCurrencyData((data) => {\n    callback(null, {currency_codes: Object.keys(data)});\n  });\n}\n\n/**\n * Converts between currencies\n */\nfunction convert (call, callback) {\n  try {\n    _getCurrencyData((data) => {\n      const request = call.request;\n\n      // Convert: from_currency --> EUR\n      const from = request.from;\n      const euros = _carry({\n        units: from.units / data[from.currency_code],\n        nanos: from.nanos / data[from.currency_code]\n      });\n\n      euros.nanos = Math.round(euros.nanos);\n\n      // Convert: EUR --> to_currency\n      const result = _carry({\n        units: euros.units * data[request.to_code],\n        nanos: euros.nanos * data[request.to_code]\n      });\n\n      result.units = Math.floor(result.units);\n      result.nanos = Math.floor(result.nanos);\n      result.currency_code = request.to_code;\n\n      logger.info(`conversion request successful`);\n      callback(null, result);\n    });\n  } catch (err) {\n    logger.error(`conversion request failed: ${err}`);\n    callback(err.message);\n  }\n}\n\n/**\n * Endpoint for health checks\n */\nfunction check (call, callback) {\n  callback(null, { status: 'SERVING' });\n}\n\n/**\n * Starts an RPC server that receives requests for the\n * CurrencyConverter service at the sample server port\n */\nfunction main () {\n  logger.info(`Starting gRPC server on port ${PORT}...`);\n  const server = new grpc.Server();\n  server.addService(shopProto.CurrencyService.service, {getSupportedCurrencies, convert});\n  server.addService(healthProto.Health.service, {check});\n\n  server.bindAsync(\n    `[::]:${PORT}`,\n    grpc.ServerCredentials.createInsecure(),\n    function() {\n      logger.info(`CurrencyService gRPC server started on port ${PORT}`);\n      server.start();\n    },\n   );\n}\n\nmain();\n"
  },
  {
    "path": "src/emailservice/Dockerfile",
    "content": "# Copyright 2020 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# Define a default value so it's not empty if the builder fails to provide it\nARG BUILDPLATFORM=linux/amd64\n\nFROM --platform=$BUILDPLATFORM python:3.14.3-alpine@sha256:faee120f7885a06fcc9677922331391fa690d911c020abb9e8025ff3d908e510 AS base\n\nFROM base AS builder\n\nENV PYTHONDONTWRITEBYTECODE=1\nENV PYTHONUNBUFFERED=1\n\nRUN apk update \\\n    && apk add --no-cache g++ linux-headers \\\n    && rm -rf /var/cache/apk/*\n\n# get packages\nCOPY requirements.txt .\nRUN pip install -r requirements.txt\n\nFROM base\n\nENV PYTHONDONTWRITEBYTECODE=1\nENV PYTHONUNBUFFERED=1\n\n# Enable Profiler\nENV ENABLE_PROFILER=1\n\nRUN apk update \\\n    && apk add --no-cache libstdc++ \\\n    && rm -rf /var/cache/apk/*\n\nWORKDIR /email_server\n\n# Grab packages from builder\nCOPY --from=builder /usr/local/lib/python3.14/ /usr/local/lib/python3.14/\n\n# Add the application\nCOPY . .\n\nEXPOSE 8080\nENTRYPOINT [ \"python\", \"email_server.py\" ]\n"
  },
  {
    "path": "src/emailservice/demo_pb2.py",
    "content": "#!/usr/bin/python\n#\n# Copyright 2018 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      https://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# -*- coding: utf-8 -*-\n# Generated by the protocol buffer compiler.  DO NOT EDIT!\n# source: demo.proto\n\"\"\"Generated protocol buffer code.\"\"\"\nfrom google.protobuf.internal import builder as _builder\nfrom google.protobuf import descriptor as _descriptor\nfrom google.protobuf import descriptor_pool as _descriptor_pool\nfrom google.protobuf import symbol_database as _symbol_database\n# @@protoc_insertion_point(imports)\n\n_sym_db = _symbol_database.Default()\n\n\n\n\nDESCRIPTOR = _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')\n\n_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals())\n_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'demo_pb2', globals())\nif _descriptor._USE_C_DESCRIPTORS == False:\n\n  DESCRIPTOR._options = None\n  _CARTITEM._serialized_start=27\n  _CARTITEM._serialized_end=75\n  _ADDITEMREQUEST._serialized_start=77\n  _ADDITEMREQUEST._serialized_end=147\n  _EMPTYCARTREQUEST._serialized_start=149\n  _EMPTYCARTREQUEST._serialized_end=184\n  _GETCARTREQUEST._serialized_start=186\n  _GETCARTREQUEST._serialized_end=219\n  _CART._serialized_start=221\n  _CART._serialized_end=282\n  _EMPTY._serialized_start=284\n  _EMPTY._serialized_end=291\n  _LISTRECOMMENDATIONSREQUEST._serialized_start=293\n  _LISTRECOMMENDATIONSREQUEST._serialized_end=359\n  _LISTRECOMMENDATIONSRESPONSE._serialized_start=361\n  _LISTRECOMMENDATIONSRESPONSE._serialized_end=411\n  _PRODUCT._serialized_start=414\n  _PRODUCT._serialized_end=546\n  _LISTPRODUCTSRESPONSE._serialized_start=548\n  _LISTPRODUCTSRESPONSE._serialized_end=610\n  _GETPRODUCTREQUEST._serialized_start=612\n  _GETPRODUCTREQUEST._serialized_end=643\n  _SEARCHPRODUCTSREQUEST._serialized_start=645\n  _SEARCHPRODUCTSREQUEST._serialized_end=683\n  _SEARCHPRODUCTSRESPONSE._serialized_start=685\n  _SEARCHPRODUCTSRESPONSE._serialized_end=748\n  _GETQUOTEREQUEST._serialized_start=750\n  _GETQUOTEREQUEST._serialized_end=844\n  _GETQUOTERESPONSE._serialized_start=846\n  _GETQUOTERESPONSE._serialized_end=902\n  _SHIPORDERREQUEST._serialized_start=904\n  _SHIPORDERREQUEST._serialized_end=999\n  _SHIPORDERRESPONSE._serialized_start=1001\n  _SHIPORDERRESPONSE._serialized_end=1041\n  _ADDRESS._serialized_start=1043\n  _ADDRESS._serialized_end=1140\n  _MONEY._serialized_start=1142\n  _MONEY._serialized_end=1202\n  _GETSUPPORTEDCURRENCIESRESPONSE._serialized_start=1204\n  _GETSUPPORTEDCURRENCIESRESPONSE._serialized_end=1260\n  _CURRENCYCONVERSIONREQUEST._serialized_start=1262\n  _CURRENCYCONVERSIONREQUEST._serialized_end=1340\n  _CREDITCARDINFO._serialized_start=1343\n  _CREDITCARDINFO._serialized_end=1487\n  _CHARGEREQUEST._serialized_start=1489\n  _CHARGEREQUEST._serialized_end=1590\n  _CHARGERESPONSE._serialized_start=1592\n  _CHARGERESPONSE._serialized_end=1632\n  _ORDERITEM._serialized_start=1634\n  _ORDERITEM._serialized_end=1716\n  _ORDERRESULT._serialized_start=1719\n  _ORDERRESULT._serialized_end=1910\n  _SENDORDERCONFIRMATIONREQUEST._serialized_start=1912\n  _SENDORDERCONFIRMATIONREQUEST._serialized_end=1998\n  _PLACEORDERREQUEST._serialized_start=2001\n  _PLACEORDERREQUEST._serialized_end=2164\n  _PLACEORDERRESPONSE._serialized_start=2166\n  _PLACEORDERRESPONSE._serialized_end=2227\n  _ADREQUEST._serialized_start=2229\n  _ADREQUEST._serialized_end=2262\n  _ADRESPONSE._serialized_start=2264\n  _ADRESPONSE._serialized_end=2306\n  _AD._serialized_start=2308\n  _AD._serialized_end=2348\n  _CARTSERVICE._serialized_start=2351\n  _CARTSERVICE._serialized_end=2553\n  _RECOMMENDATIONSERVICE._serialized_start=2556\n  _RECOMMENDATIONSERVICE._serialized_end=2687\n  _PRODUCTCATALOGSERVICE._serialized_start=2690\n  _PRODUCTCATALOGSERVICE._serialized_end=2949\n  _SHIPPINGSERVICE._serialized_start=2952\n  _SHIPPINGSERVICE._serialized_end=3122\n  _CURRENCYSERVICE._serialized_start=3125\n  _CURRENCYSERVICE._serialized_end=3308\n  _PAYMENTSERVICE._serialized_start=3310\n  _PAYMENTSERVICE._serialized_end=3395\n  _EMAILSERVICE._serialized_start=3397\n  _EMAILSERVICE._serialized_end=3501\n  _CHECKOUTSERVICE._serialized_start=3503\n  _CHECKOUTSERVICE._serialized_end=3601\n  _ADSERVICE._serialized_start=3603\n  _ADSERVICE._serialized_end=3675\n# @@protoc_insertion_point(module_scope)\n"
  },
  {
    "path": "src/emailservice/demo_pb2_grpc.py",
    "content": "#!/usr/bin/python\n#\n# Copyright 2018 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      https://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT!\n\"\"\"Client and server classes corresponding to protobuf-defined services.\"\"\"\nimport grpc\n\nimport demo_pb2 as demo__pb2\n\n\nclass CartServiceStub(object):\n    \"\"\"-----------------Cart service-----------------\n\n    \"\"\"\n\n    def __init__(self, channel):\n        \"\"\"Constructor.\n\n        Args:\n            channel: A grpc.Channel.\n        \"\"\"\n        self.AddItem = channel.unary_unary(\n                '/hipstershop.CartService/AddItem',\n                request_serializer=demo__pb2.AddItemRequest.SerializeToString,\n                response_deserializer=demo__pb2.Empty.FromString,\n                )\n        self.GetCart = channel.unary_unary(\n                '/hipstershop.CartService/GetCart',\n                request_serializer=demo__pb2.GetCartRequest.SerializeToString,\n                response_deserializer=demo__pb2.Cart.FromString,\n                )\n        self.EmptyCart = channel.unary_unary(\n                '/hipstershop.CartService/EmptyCart',\n                request_serializer=demo__pb2.EmptyCartRequest.SerializeToString,\n                response_deserializer=demo__pb2.Empty.FromString,\n                )\n\n\nclass CartServiceServicer(object):\n    \"\"\"-----------------Cart service-----------------\n\n    \"\"\"\n\n    def AddItem(self, request, context):\n        \"\"\"Missing associated documentation comment in .proto file.\"\"\"\n        context.set_code(grpc.StatusCode.UNIMPLEMENTED)\n        context.set_details('Method not implemented!')\n        raise NotImplementedError('Method not implemented!')\n\n    def GetCart(self, request, context):\n        \"\"\"Missing associated documentation comment in .proto file.\"\"\"\n        context.set_code(grpc.StatusCode.UNIMPLEMENTED)\n        context.set_details('Method not implemented!')\n        raise NotImplementedError('Method not implemented!')\n\n    def EmptyCart(self, request, context):\n        \"\"\"Missing associated documentation comment in .proto file.\"\"\"\n        context.set_code(grpc.StatusCode.UNIMPLEMENTED)\n        context.set_details('Method not implemented!')\n        raise NotImplementedError('Method not implemented!')\n\n\ndef add_CartServiceServicer_to_server(servicer, server):\n    rpc_method_handlers = {\n            'AddItem': grpc.unary_unary_rpc_method_handler(\n                    servicer.AddItem,\n                    request_deserializer=demo__pb2.AddItemRequest.FromString,\n                    response_serializer=demo__pb2.Empty.SerializeToString,\n            ),\n            'GetCart': grpc.unary_unary_rpc_method_handler(\n                    servicer.GetCart,\n                    request_deserializer=demo__pb2.GetCartRequest.FromString,\n                    response_serializer=demo__pb2.Cart.SerializeToString,\n            ),\n            'EmptyCart': grpc.unary_unary_rpc_method_handler(\n                    servicer.EmptyCart,\n                    request_deserializer=demo__pb2.EmptyCartRequest.FromString,\n                    response_serializer=demo__pb2.Empty.SerializeToString,\n            ),\n    }\n    generic_handler = grpc.method_handlers_generic_handler(\n            'hipstershop.CartService', rpc_method_handlers)\n    server.add_generic_rpc_handlers((generic_handler,))\n\n\n # This class is part of an EXPERIMENTAL API.\nclass CartService(object):\n    \"\"\"-----------------Cart service-----------------\n\n    \"\"\"\n\n    @staticmethod\n    def AddItem(request,\n            target,\n            options=(),\n            channel_credentials=None,\n            call_credentials=None,\n            insecure=False,\n            compression=None,\n            wait_for_ready=None,\n            timeout=None,\n            metadata=None):\n        return grpc.experimental.unary_unary(request, target, '/hipstershop.CartService/AddItem',\n            demo__pb2.AddItemRequest.SerializeToString,\n            demo__pb2.Empty.FromString,\n            options, channel_credentials,\n            insecure, call_credentials, compression, wait_for_ready, timeout, metadata)\n\n    @staticmethod\n    def GetCart(request,\n            target,\n            options=(),\n            channel_credentials=None,\n            call_credentials=None,\n            insecure=False,\n            compression=None,\n            wait_for_ready=None,\n            timeout=None,\n            metadata=None):\n        return grpc.experimental.unary_unary(request, target, '/hipstershop.CartService/GetCart',\n            demo__pb2.GetCartRequest.SerializeToString,\n            demo__pb2.Cart.FromString,\n            options, channel_credentials,\n            insecure, call_credentials, compression, wait_for_ready, timeout, metadata)\n\n    @staticmethod\n    def EmptyCart(request,\n            target,\n            options=(),\n            channel_credentials=None,\n            call_credentials=None,\n            insecure=False,\n            compression=None,\n            wait_for_ready=None,\n            timeout=None,\n            metadata=None):\n        return grpc.experimental.unary_unary(request, target, '/hipstershop.CartService/EmptyCart',\n            demo__pb2.EmptyCartRequest.SerializeToString,\n            demo__pb2.Empty.FromString,\n            options, channel_credentials,\n            insecure, call_credentials, compression, wait_for_ready, timeout, metadata)\n\n\nclass RecommendationServiceStub(object):\n    \"\"\"---------------Recommendation service----------\n\n    \"\"\"\n\n    def __init__(self, channel):\n        \"\"\"Constructor.\n\n        Args:\n            channel: A grpc.Channel.\n        \"\"\"\n        self.ListRecommendations = channel.unary_unary(\n                '/hipstershop.RecommendationService/ListRecommendations',\n                request_serializer=demo__pb2.ListRecommendationsRequest.SerializeToString,\n                response_deserializer=demo__pb2.ListRecommendationsResponse.FromString,\n                )\n\n\nclass RecommendationServiceServicer(object):\n    \"\"\"---------------Recommendation service----------\n\n    \"\"\"\n\n    def ListRecommendations(self, request, context):\n        \"\"\"Missing associated documentation comment in .proto file.\"\"\"\n        context.set_code(grpc.StatusCode.UNIMPLEMENTED)\n        context.set_details('Method not implemented!')\n        raise NotImplementedError('Method not implemented!')\n\n\ndef add_RecommendationServiceServicer_to_server(servicer, server):\n    rpc_method_handlers = {\n            'ListRecommendations': grpc.unary_unary_rpc_method_handler(\n                    servicer.ListRecommendations,\n                    request_deserializer=demo__pb2.ListRecommendationsRequest.FromString,\n                    response_serializer=demo__pb2.ListRecommendationsResponse.SerializeToString,\n            ),\n    }\n    generic_handler = grpc.method_handlers_generic_handler(\n            'hipstershop.RecommendationService', rpc_method_handlers)\n    server.add_generic_rpc_handlers((generic_handler,))\n\n\n # This class is part of an EXPERIMENTAL API.\nclass RecommendationService(object):\n    \"\"\"---------------Recommendation service----------\n\n    \"\"\"\n\n    @staticmethod\n    def ListRecommendations(request,\n            target,\n            options=(),\n            channel_credentials=None,\n            call_credentials=None,\n            insecure=False,\n            compression=None,\n            wait_for_ready=None,\n            timeout=None,\n            metadata=None):\n        return grpc.experimental.unary_unary(request, target, '/hipstershop.RecommendationService/ListRecommendations',\n            demo__pb2.ListRecommendationsRequest.SerializeToString,\n            demo__pb2.ListRecommendationsResponse.FromString,\n            options, channel_credentials,\n            insecure, call_credentials, compression, wait_for_ready, timeout, metadata)\n\n\nclass ProductCatalogServiceStub(object):\n    \"\"\"---------------Product Catalog----------------\n\n    \"\"\"\n\n    def __init__(self, channel):\n        \"\"\"Constructor.\n\n        Args:\n            channel: A grpc.Channel.\n        \"\"\"\n        self.ListProducts = channel.unary_unary(\n                '/hipstershop.ProductCatalogService/ListProducts',\n                request_serializer=demo__pb2.Empty.SerializeToString,\n                response_deserializer=demo__pb2.ListProductsResponse.FromString,\n                )\n        self.GetProduct = channel.unary_unary(\n                '/hipstershop.ProductCatalogService/GetProduct',\n                request_serializer=demo__pb2.GetProductRequest.SerializeToString,\n                response_deserializer=demo__pb2.Product.FromString,\n                )\n        self.SearchProducts = channel.unary_unary(\n                '/hipstershop.ProductCatalogService/SearchProducts',\n                request_serializer=demo__pb2.SearchProductsRequest.SerializeToString,\n                response_deserializer=demo__pb2.SearchProductsResponse.FromString,\n                )\n\n\nclass ProductCatalogServiceServicer(object):\n    \"\"\"---------------Product Catalog----------------\n\n    \"\"\"\n\n    def ListProducts(self, request, context):\n        \"\"\"Missing associated documentation comment in .proto file.\"\"\"\n        context.set_code(grpc.StatusCode.UNIMPLEMENTED)\n        context.set_details('Method not implemented!')\n        raise NotImplementedError('Method not implemented!')\n\n    def GetProduct(self, request, context):\n        \"\"\"Missing associated documentation comment in .proto file.\"\"\"\n        context.set_code(grpc.StatusCode.UNIMPLEMENTED)\n        context.set_details('Method not implemented!')\n        raise NotImplementedError('Method not implemented!')\n\n    def SearchProducts(self, request, context):\n        \"\"\"Missing associated documentation comment in .proto file.\"\"\"\n        context.set_code(grpc.StatusCode.UNIMPLEMENTED)\n        context.set_details('Method not implemented!')\n        raise NotImplementedError('Method not implemented!')\n\n\ndef add_ProductCatalogServiceServicer_to_server(servicer, server):\n    rpc_method_handlers = {\n            'ListProducts': grpc.unary_unary_rpc_method_handler(\n                    servicer.ListProducts,\n                    request_deserializer=demo__pb2.Empty.FromString,\n                    response_serializer=demo__pb2.ListProductsResponse.SerializeToString,\n            ),\n            'GetProduct': grpc.unary_unary_rpc_method_handler(\n                    servicer.GetProduct,\n                    request_deserializer=demo__pb2.GetProductRequest.FromString,\n                    response_serializer=demo__pb2.Product.SerializeToString,\n            ),\n            'SearchProducts': grpc.unary_unary_rpc_method_handler(\n                    servicer.SearchProducts,\n                    request_deserializer=demo__pb2.SearchProductsRequest.FromString,\n                    response_serializer=demo__pb2.SearchProductsResponse.SerializeToString,\n            ),\n    }\n    generic_handler = grpc.method_handlers_generic_handler(\n            'hipstershop.ProductCatalogService', rpc_method_handlers)\n    server.add_generic_rpc_handlers((generic_handler,))\n\n\n # This class is part of an EXPERIMENTAL API.\nclass ProductCatalogService(object):\n    \"\"\"---------------Product Catalog----------------\n\n    \"\"\"\n\n    @staticmethod\n    def ListProducts(request,\n            target,\n            options=(),\n            channel_credentials=None,\n            call_credentials=None,\n            insecure=False,\n            compression=None,\n            wait_for_ready=None,\n            timeout=None,\n            metadata=None):\n        return grpc.experimental.unary_unary(request, target, '/hipstershop.ProductCatalogService/ListProducts',\n            demo__pb2.Empty.SerializeToString,\n            demo__pb2.ListProductsResponse.FromString,\n            options, channel_credentials,\n            insecure, call_credentials, compression, wait_for_ready, timeout, metadata)\n\n    @staticmethod\n    def GetProduct(request,\n            target,\n            options=(),\n            channel_credentials=None,\n            call_credentials=None,\n            insecure=False,\n            compression=None,\n            wait_for_ready=None,\n            timeout=None,\n            metadata=None):\n        return grpc.experimental.unary_unary(request, target, '/hipstershop.ProductCatalogService/GetProduct',\n            demo__pb2.GetProductRequest.SerializeToString,\n            demo__pb2.Product.FromString,\n            options, channel_credentials,\n            insecure, call_credentials, compression, wait_for_ready, timeout, metadata)\n\n    @staticmethod\n    def SearchProducts(request,\n            target,\n            options=(),\n            channel_credentials=None,\n            call_credentials=None,\n            insecure=False,\n            compression=None,\n            wait_for_ready=None,\n            timeout=None,\n            metadata=None):\n        return grpc.experimental.unary_unary(request, target, '/hipstershop.ProductCatalogService/SearchProducts',\n            demo__pb2.SearchProductsRequest.SerializeToString,\n            demo__pb2.SearchProductsResponse.FromString,\n            options, channel_credentials,\n            insecure, call_credentials, compression, wait_for_ready, timeout, metadata)\n\n\nclass ShippingServiceStub(object):\n    \"\"\"---------------Shipping Service----------\n\n    \"\"\"\n\n    def __init__(self, channel):\n        \"\"\"Constructor.\n\n        Args:\n            channel: A grpc.Channel.\n        \"\"\"\n        self.GetQuote = channel.unary_unary(\n                '/hipstershop.ShippingService/GetQuote',\n                request_serializer=demo__pb2.GetQuoteRequest.SerializeToString,\n                response_deserializer=demo__pb2.GetQuoteResponse.FromString,\n                )\n        self.ShipOrder = channel.unary_unary(\n                '/hipstershop.ShippingService/ShipOrder',\n                request_serializer=demo__pb2.ShipOrderRequest.SerializeToString,\n                response_deserializer=demo__pb2.ShipOrderResponse.FromString,\n                )\n\n\nclass ShippingServiceServicer(object):\n    \"\"\"---------------Shipping Service----------\n\n    \"\"\"\n\n    def GetQuote(self, request, context):\n        \"\"\"Missing associated documentation comment in .proto file.\"\"\"\n        context.set_code(grpc.StatusCode.UNIMPLEMENTED)\n        context.set_details('Method not implemented!')\n        raise NotImplementedError('Method not implemented!')\n\n    def ShipOrder(self, request, context):\n        \"\"\"Missing associated documentation comment in .proto file.\"\"\"\n        context.set_code(grpc.StatusCode.UNIMPLEMENTED)\n        context.set_details('Method not implemented!')\n        raise NotImplementedError('Method not implemented!')\n\n\ndef add_ShippingServiceServicer_to_server(servicer, server):\n    rpc_method_handlers = {\n            'GetQuote': grpc.unary_unary_rpc_method_handler(\n                    servicer.GetQuote,\n                    request_deserializer=demo__pb2.GetQuoteRequest.FromString,\n                    response_serializer=demo__pb2.GetQuoteResponse.SerializeToString,\n            ),\n            'ShipOrder': grpc.unary_unary_rpc_method_handler(\n                    servicer.ShipOrder,\n                    request_deserializer=demo__pb2.ShipOrderRequest.FromString,\n                    response_serializer=demo__pb2.ShipOrderResponse.SerializeToString,\n            ),\n    }\n    generic_handler = grpc.method_handlers_generic_handler(\n            'hipstershop.ShippingService', rpc_method_handlers)\n    server.add_generic_rpc_handlers((generic_handler,))\n\n\n # This class is part of an EXPERIMENTAL API.\nclass ShippingService(object):\n    \"\"\"---------------Shipping Service----------\n\n    \"\"\"\n\n    @staticmethod\n    def GetQuote(request,\n            target,\n            options=(),\n            channel_credentials=None,\n            call_credentials=None,\n            insecure=False,\n            compression=None,\n            wait_for_ready=None,\n            timeout=None,\n            metadata=None):\n        return grpc.experimental.unary_unary(request, target, '/hipstershop.ShippingService/GetQuote',\n            demo__pb2.GetQuoteRequest.SerializeToString,\n            demo__pb2.GetQuoteResponse.FromString,\n            options, channel_credentials,\n            insecure, call_credentials, compression, wait_for_ready, timeout, metadata)\n\n    @staticmethod\n    def ShipOrder(request,\n            target,\n            options=(),\n            channel_credentials=None,\n            call_credentials=None,\n            insecure=False,\n            compression=None,\n            wait_for_ready=None,\n            timeout=None,\n            metadata=None):\n        return grpc.experimental.unary_unary(request, target, '/hipstershop.ShippingService/ShipOrder',\n            demo__pb2.ShipOrderRequest.SerializeToString,\n            demo__pb2.ShipOrderResponse.FromString,\n            options, channel_credentials,\n            insecure, call_credentials, compression, wait_for_ready, timeout, metadata)\n\n\nclass CurrencyServiceStub(object):\n    \"\"\"-----------------Currency service-----------------\n\n    \"\"\"\n\n    def __init__(self, channel):\n        \"\"\"Constructor.\n\n        Args:\n            channel: A grpc.Channel.\n        \"\"\"\n        self.GetSupportedCurrencies = channel.unary_unary(\n                '/hipstershop.CurrencyService/GetSupportedCurrencies',\n                request_serializer=demo__pb2.Empty.SerializeToString,\n                response_deserializer=demo__pb2.GetSupportedCurrenciesResponse.FromString,\n                )\n        self.Convert = channel.unary_unary(\n                '/hipstershop.CurrencyService/Convert',\n                request_serializer=demo__pb2.CurrencyConversionRequest.SerializeToString,\n                response_deserializer=demo__pb2.Money.FromString,\n                )\n\n\nclass CurrencyServiceServicer(object):\n    \"\"\"-----------------Currency service-----------------\n\n    \"\"\"\n\n    def GetSupportedCurrencies(self, request, context):\n        \"\"\"Missing associated documentation comment in .proto file.\"\"\"\n        context.set_code(grpc.StatusCode.UNIMPLEMENTED)\n        context.set_details('Method not implemented!')\n        raise NotImplementedError('Method not implemented!')\n\n    def Convert(self, request, context):\n        \"\"\"Missing associated documentation comment in .proto file.\"\"\"\n        context.set_code(grpc.StatusCode.UNIMPLEMENTED)\n        context.set_details('Method not implemented!')\n        raise NotImplementedError('Method not implemented!')\n\n\ndef add_CurrencyServiceServicer_to_server(servicer, server):\n    rpc_method_handlers = {\n            'GetSupportedCurrencies': grpc.unary_unary_rpc_method_handler(\n                    servicer.GetSupportedCurrencies,\n                    request_deserializer=demo__pb2.Empty.FromString,\n                    response_serializer=demo__pb2.GetSupportedCurrenciesResponse.SerializeToString,\n            ),\n            'Convert': grpc.unary_unary_rpc_method_handler(\n                    servicer.Convert,\n                    request_deserializer=demo__pb2.CurrencyConversionRequest.FromString,\n                    response_serializer=demo__pb2.Money.SerializeToString,\n            ),\n    }\n    generic_handler = grpc.method_handlers_generic_handler(\n            'hipstershop.CurrencyService', rpc_method_handlers)\n    server.add_generic_rpc_handlers((generic_handler,))\n\n\n # This class is part of an EXPERIMENTAL API.\nclass CurrencyService(object):\n    \"\"\"-----------------Currency service-----------------\n\n    \"\"\"\n\n    @staticmethod\n    def GetSupportedCurrencies(request,\n            target,\n            options=(),\n            channel_credentials=None,\n            call_credentials=None,\n            insecure=False,\n            compression=None,\n            wait_for_ready=None,\n            timeout=None,\n            metadata=None):\n        return grpc.experimental.unary_unary(request, target, '/hipstershop.CurrencyService/GetSupportedCurrencies',\n            demo__pb2.Empty.SerializeToString,\n            demo__pb2.GetSupportedCurrenciesResponse.FromString,\n            options, channel_credentials,\n            insecure, call_credentials, compression, wait_for_ready, timeout, metadata)\n\n    @staticmethod\n    def Convert(request,\n            target,\n            options=(),\n            channel_credentials=None,\n            call_credentials=None,\n            insecure=False,\n            compression=None,\n            wait_for_ready=None,\n            timeout=None,\n            metadata=None):\n        return grpc.experimental.unary_unary(request, target, '/hipstershop.CurrencyService/Convert',\n            demo__pb2.CurrencyConversionRequest.SerializeToString,\n            demo__pb2.Money.FromString,\n            options, channel_credentials,\n            insecure, call_credentials, compression, wait_for_ready, timeout, metadata)\n\n\nclass PaymentServiceStub(object):\n    \"\"\"-------------Payment service-----------------\n\n    \"\"\"\n\n    def __init__(self, channel):\n        \"\"\"Constructor.\n\n        Args:\n            channel: A grpc.Channel.\n        \"\"\"\n        self.Charge = channel.unary_unary(\n                '/hipstershop.PaymentService/Charge',\n                request_serializer=demo__pb2.ChargeRequest.SerializeToString,\n                response_deserializer=demo__pb2.ChargeResponse.FromString,\n                )\n\n\nclass PaymentServiceServicer(object):\n    \"\"\"-------------Payment service-----------------\n\n    \"\"\"\n\n    def Charge(self, request, context):\n        \"\"\"Missing associated documentation comment in .proto file.\"\"\"\n        context.set_code(grpc.StatusCode.UNIMPLEMENTED)\n        context.set_details('Method not implemented!')\n        raise NotImplementedError('Method not implemented!')\n\n\ndef add_PaymentServiceServicer_to_server(servicer, server):\n    rpc_method_handlers = {\n            'Charge': grpc.unary_unary_rpc_method_handler(\n                    servicer.Charge,\n                    request_deserializer=demo__pb2.ChargeRequest.FromString,\n                    response_serializer=demo__pb2.ChargeResponse.SerializeToString,\n            ),\n    }\n    generic_handler = grpc.method_handlers_generic_handler(\n            'hipstershop.PaymentService', rpc_method_handlers)\n    server.add_generic_rpc_handlers((generic_handler,))\n\n\n # This class is part of an EXPERIMENTAL API.\nclass PaymentService(object):\n    \"\"\"-------------Payment service-----------------\n\n    \"\"\"\n\n    @staticmethod\n    def Charge(request,\n            target,\n            options=(),\n            channel_credentials=None,\n            call_credentials=None,\n            insecure=False,\n            compression=None,\n            wait_for_ready=None,\n            timeout=None,\n            metadata=None):\n        return grpc.experimental.unary_unary(request, target, '/hipstershop.PaymentService/Charge',\n            demo__pb2.ChargeRequest.SerializeToString,\n            demo__pb2.ChargeResponse.FromString,\n            options, channel_credentials,\n            insecure, call_credentials, compression, wait_for_ready, timeout, metadata)\n\n\nclass EmailServiceStub(object):\n    \"\"\"-------------Email service-----------------\n\n    \"\"\"\n\n    def __init__(self, channel):\n        \"\"\"Constructor.\n\n        Args:\n            channel: A grpc.Channel.\n        \"\"\"\n        self.SendOrderConfirmation = channel.unary_unary(\n                '/hipstershop.EmailService/SendOrderConfirmation',\n                request_serializer=demo__pb2.SendOrderConfirmationRequest.SerializeToString,\n                response_deserializer=demo__pb2.Empty.FromString,\n                )\n\n\nclass EmailServiceServicer(object):\n    \"\"\"-------------Email service-----------------\n\n    \"\"\"\n\n    def SendOrderConfirmation(self, request, context):\n        \"\"\"Missing associated documentation comment in .proto file.\"\"\"\n        context.set_code(grpc.StatusCode.UNIMPLEMENTED)\n        context.set_details('Method not implemented!')\n        raise NotImplementedError('Method not implemented!')\n\n\ndef add_EmailServiceServicer_to_server(servicer, server):\n    rpc_method_handlers = {\n            'SendOrderConfirmation': grpc.unary_unary_rpc_method_handler(\n                    servicer.SendOrderConfirmation,\n                    request_deserializer=demo__pb2.SendOrderConfirmationRequest.FromString,\n                    response_serializer=demo__pb2.Empty.SerializeToString,\n            ),\n    }\n    generic_handler = grpc.method_handlers_generic_handler(\n            'hipstershop.EmailService', rpc_method_handlers)\n    server.add_generic_rpc_handlers((generic_handler,))\n\n\n # This class is part of an EXPERIMENTAL API.\nclass EmailService(object):\n    \"\"\"-------------Email service-----------------\n\n    \"\"\"\n\n    @staticmethod\n    def SendOrderConfirmation(request,\n            target,\n            options=(),\n            channel_credentials=None,\n            call_credentials=None,\n            insecure=False,\n            compression=None,\n            wait_for_ready=None,\n            timeout=None,\n            metadata=None):\n        return grpc.experimental.unary_unary(request, target, '/hipstershop.EmailService/SendOrderConfirmation',\n            demo__pb2.SendOrderConfirmationRequest.SerializeToString,\n            demo__pb2.Empty.FromString,\n            options, channel_credentials,\n            insecure, call_credentials, compression, wait_for_ready, timeout, metadata)\n\n\nclass CheckoutServiceStub(object):\n    \"\"\"-------------Checkout service-----------------\n\n    \"\"\"\n\n    def __init__(self, channel):\n        \"\"\"Constructor.\n\n        Args:\n            channel: A grpc.Channel.\n        \"\"\"\n        self.PlaceOrder = channel.unary_unary(\n                '/hipstershop.CheckoutService/PlaceOrder',\n                request_serializer=demo__pb2.PlaceOrderRequest.SerializeToString,\n                response_deserializer=demo__pb2.PlaceOrderResponse.FromString,\n                )\n\n\nclass CheckoutServiceServicer(object):\n    \"\"\"-------------Checkout service-----------------\n\n    \"\"\"\n\n    def PlaceOrder(self, request, context):\n        \"\"\"Missing associated documentation comment in .proto file.\"\"\"\n        context.set_code(grpc.StatusCode.UNIMPLEMENTED)\n        context.set_details('Method not implemented!')\n        raise NotImplementedError('Method not implemented!')\n\n\ndef add_CheckoutServiceServicer_to_server(servicer, server):\n    rpc_method_handlers = {\n            'PlaceOrder': grpc.unary_unary_rpc_method_handler(\n                    servicer.PlaceOrder,\n                    request_deserializer=demo__pb2.PlaceOrderRequest.FromString,\n                    response_serializer=demo__pb2.PlaceOrderResponse.SerializeToString,\n            ),\n    }\n    generic_handler = grpc.method_handlers_generic_handler(\n            'hipstershop.CheckoutService', rpc_method_handlers)\n    server.add_generic_rpc_handlers((generic_handler,))\n\n\n # This class is part of an EXPERIMENTAL API.\nclass CheckoutService(object):\n    \"\"\"-------------Checkout service-----------------\n\n    \"\"\"\n\n    @staticmethod\n    def PlaceOrder(request,\n            target,\n            options=(),\n            channel_credentials=None,\n            call_credentials=None,\n            insecure=False,\n            compression=None,\n            wait_for_ready=None,\n            timeout=None,\n            metadata=None):\n        return grpc.experimental.unary_unary(request, target, '/hipstershop.CheckoutService/PlaceOrder',\n            demo__pb2.PlaceOrderRequest.SerializeToString,\n            demo__pb2.PlaceOrderResponse.FromString,\n            options, channel_credentials,\n            insecure, call_credentials, compression, wait_for_ready, timeout, metadata)\n\n\nclass AdServiceStub(object):\n    \"\"\"------------Ad service------------------\n\n    \"\"\"\n\n    def __init__(self, channel):\n        \"\"\"Constructor.\n\n        Args:\n            channel: A grpc.Channel.\n        \"\"\"\n        self.GetAds = channel.unary_unary(\n                '/hipstershop.AdService/GetAds',\n                request_serializer=demo__pb2.AdRequest.SerializeToString,\n                response_deserializer=demo__pb2.AdResponse.FromString,\n                )\n\n\nclass AdServiceServicer(object):\n    \"\"\"------------Ad service------------------\n\n    \"\"\"\n\n    def GetAds(self, request, context):\n        \"\"\"Missing associated documentation comment in .proto file.\"\"\"\n        context.set_code(grpc.StatusCode.UNIMPLEMENTED)\n        context.set_details('Method not implemented!')\n        raise NotImplementedError('Method not implemented!')\n\n\ndef add_AdServiceServicer_to_server(servicer, server):\n    rpc_method_handlers = {\n            'GetAds': grpc.unary_unary_rpc_method_handler(\n                    servicer.GetAds,\n                    request_deserializer=demo__pb2.AdRequest.FromString,\n                    response_serializer=demo__pb2.AdResponse.SerializeToString,\n            ),\n    }\n    generic_handler = grpc.method_handlers_generic_handler(\n            'hipstershop.AdService', rpc_method_handlers)\n    server.add_generic_rpc_handlers((generic_handler,))\n\n\n # This class is part of an EXPERIMENTAL API.\nclass AdService(object):\n    \"\"\"------------Ad service------------------\n\n    \"\"\"\n\n    @staticmethod\n    def GetAds(request,\n            target,\n            options=(),\n            channel_credentials=None,\n            call_credentials=None,\n            insecure=False,\n            compression=None,\n            wait_for_ready=None,\n            timeout=None,\n            metadata=None):\n        return grpc.experimental.unary_unary(request, target, '/hipstershop.AdService/GetAds',\n            demo__pb2.AdRequest.SerializeToString,\n            demo__pb2.AdResponse.FromString,\n            options, channel_credentials,\n            insecure, call_credentials, compression, wait_for_ready, timeout, metadata)\n"
  },
  {
    "path": "src/emailservice/email_client.py",
    "content": "#!/usr/bin/python\n#\n# Copyright 2018 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport grpc\n\nimport demo_pb2\nimport demo_pb2_grpc\n\nfrom logger import getJSONLogger\nlogger = getJSONLogger('emailservice-client')\n\ndef send_confirmation_email(email, order):\n  channel = grpc.insecure_channel('[::]:8080')\n  stub = demo_pb2_grpc.EmailServiceStub(channel)\n  try:\n    response = stub.SendOrderConfirmation(demo_pb2.SendOrderConfirmationRequest(\n      email = email,\n      order = order\n    ))\n    logger.info('Request sent.')\n  except grpc.RpcError as err:\n    logger.error(err.details())\n    logger.error('{}, {}'.format(err.code().name, err.code().value))\n\nif __name__ == '__main__':\n  logger.info('Client for email service.')\n"
  },
  {
    "path": "src/emailservice/email_server.py",
    "content": "#!/usr/bin/python\n#\n# Copyright 2018 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nfrom concurrent import futures\nimport argparse\nimport os\nimport sys\nimport time\nimport grpc\nimport traceback\nfrom jinja2 import Environment, FileSystemLoader, select_autoescape, TemplateError\nfrom google.api_core.exceptions import GoogleAPICallError\nfrom google.auth.exceptions import DefaultCredentialsError\n\nimport demo_pb2\nimport demo_pb2_grpc\nfrom grpc_health.v1 import health_pb2\nfrom grpc_health.v1 import health_pb2_grpc\n\nfrom opentelemetry import trace\nfrom opentelemetry.instrumentation.grpc import GrpcInstrumentorServer\nfrom opentelemetry.sdk.trace import TracerProvider\nfrom opentelemetry.sdk.trace.export import BatchSpanProcessor\nfrom opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter\n\n# @TODO: Temporarily removed in https://github.com/GoogleCloudPlatform/microservices-demo/pull/3196\n# import googlecloudprofiler\n\nfrom logger import getJSONLogger\nlogger = getJSONLogger('emailservice-server')\n\n# Loads confirmation email template from file\nenv = Environment(\n    loader=FileSystemLoader('templates'),\n    autoescape=select_autoescape(['html', 'xml'])\n)\ntemplate = env.get_template('confirmation.html')\n\nclass BaseEmailService(demo_pb2_grpc.EmailServiceServicer):\n  def Check(self, request, context):\n    return health_pb2.HealthCheckResponse(\n      status=health_pb2.HealthCheckResponse.SERVING)\n  \n  def Watch(self, request, context):\n    return health_pb2.HealthCheckResponse(\n      status=health_pb2.HealthCheckResponse.UNIMPLEMENTED)\n\nclass EmailService(BaseEmailService):\n  def __init__(self):\n    raise Exception('cloud mail client not implemented')\n    super().__init__()\n\n  @staticmethod\n  def send_email(client, email_address, content):\n    response = client.send_message(\n      sender = client.sender_path(project_id, region, sender_id),\n      envelope_from_authority = '',\n      header_from_authority = '',\n      envelope_from_address = from_address,\n      simple_message = {\n        \"from\": {\n          \"address_spec\": from_address,\n        },\n        \"to\": [{\n          \"address_spec\": email_address\n        }],\n        \"subject\": \"Your Confirmation Email\",\n        \"html_body\": content\n      }\n    )\n    logger.info(\"Message sent: {}\".format(response.rfc822_message_id))\n\n  def SendOrderConfirmation(self, request, context):\n    email = request.email\n    order = request.order\n\n    try:\n      confirmation = template.render(order = order)\n    except TemplateError as err:\n      context.set_details(\"An error occurred when preparing the confirmation mail.\")\n      logger.error(err.message)\n      context.set_code(grpc.StatusCode.INTERNAL)\n      return demo_pb2.Empty()\n\n    try:\n      EmailService.send_email(self.client, email, confirmation)\n    except GoogleAPICallError as err:\n      context.set_details(\"An error occurred when sending the email.\")\n      print(err.message)\n      context.set_code(grpc.StatusCode.INTERNAL)\n      return demo_pb2.Empty()\n\n    return demo_pb2.Empty()\n\nclass DummyEmailService(BaseEmailService):\n  def SendOrderConfirmation(self, request, context):\n    logger.info('A request to send order confirmation email to {} has been received.'.format(request.email))\n    return demo_pb2.Empty()\n\nclass HealthCheck():\n  def Check(self, request, context):\n    return health_pb2.HealthCheckResponse(\n      status=health_pb2.HealthCheckResponse.SERVING)\n\ndef start(dummy_mode):\n  server = grpc.server(futures.ThreadPoolExecutor(max_workers=10),)\n  service = None\n  if dummy_mode:\n    service = DummyEmailService()\n  else:\n    raise Exception('non-dummy mode not implemented yet')\n\n  demo_pb2_grpc.add_EmailServiceServicer_to_server(service, server)\n  health_pb2_grpc.add_HealthServicer_to_server(service, server)\n\n  port = os.environ.get('PORT', \"8080\")\n  logger.info(\"listening on port: \"+port)\n  server.add_insecure_port('[::]:'+port)\n  server.start()\n  try:\n    while True:\n      time.sleep(3600)\n  except KeyboardInterrupt:\n    server.stop(0)\n\ndef initStackdriverProfiling():\n  project_id = None\n  try:\n    project_id = os.environ[\"GCP_PROJECT_ID\"]\n  except KeyError:\n    # Environment variable not set\n    pass\n\n  # @TODO: Temporarily removed in https://github.com/GoogleCloudPlatform/microservices-demo/pull/3196\n  # for retry in range(1,4):\n  #   try:\n  #     if project_id:\n  #       googlecloudprofiler.start(service='email_server', service_version='1.0.0', verbose=0, project_id=project_id)\n  #     else:\n  #       googlecloudprofiler.start(service='email_server', service_version='1.0.0', verbose=0)\n  #     logger.info(\"Successfully started Stackdriver Profiler.\")\n  #     return\n  #   except (BaseException) as exc:\n  #     logger.info(\"Unable to start Stackdriver Profiler Python agent. \" + str(exc))\n  #     if (retry < 4):\n  #       logger.info(\"Sleeping %d to retry initializing Stackdriver Profiler\"%(retry*10))\n  #       time.sleep (1)\n  #     else:\n  #       logger.warning(\"Could not initialize Stackdriver Profiler after retrying, giving up\")\n  return\n\n\nif __name__ == '__main__':\n  logger.info('starting the email service in dummy mode.')\n\n  # Profiler\n  try:\n    if \"DISABLE_PROFILER\" in os.environ:\n      raise KeyError()\n    else:\n      logger.info(\"Profiler enabled.\")\n      initStackdriverProfiling()\n  except KeyError:\n      logger.info(\"Profiler disabled.\")\n\n  # Tracing\n  try:\n    if os.environ[\"ENABLE_TRACING\"] == \"1\":\n      otel_endpoint = os.getenv(\"COLLECTOR_SERVICE_ADDR\", \"localhost:4317\")\n      trace.set_tracer_provider(TracerProvider())\n      trace.get_tracer_provider().add_span_processor(\n        BatchSpanProcessor(\n            OTLPSpanExporter(\n            endpoint = otel_endpoint,\n            insecure = True\n          )\n        )\n      )\n    grpc_server_instrumentor = GrpcInstrumentorServer()\n    grpc_server_instrumentor.instrument()\n\n  except (KeyError, DefaultCredentialsError):\n      logger.info(\"Tracing disabled.\")\n  except Exception as e:\n      logger.warn(f\"Exception on Cloud Trace setup: {traceback.format_exc()}, tracing disabled.\") \n  \n  start(dummy_mode = True)\n"
  },
  {
    "path": "src/emailservice/genproto.sh",
    "content": "#!/bin/bash -eu\n#\n# Copyright 2018 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# [START gke_emailservice_genproto]\n\npython -m grpc_tools.protoc -I../../protos --python_out=. --grpc_python_out=. ../../protos/demo.proto\n\n# [END gke_emailservice_genproto]"
  },
  {
    "path": "src/emailservice/logger.py",
    "content": "#!/usr/bin/python\n#\n# Copyright 2018 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport logging\nimport sys\nfrom pythonjsonlogger import jsonlogger\n\n# TODO(yoshifumi) this class is duplicated since other Python services are\n# not sharing the modules for logging.\nclass CustomJsonFormatter(jsonlogger.JsonFormatter):\n  def add_fields(self, log_record, record, message_dict):\n    super(CustomJsonFormatter, self).add_fields(log_record, record, message_dict)\n    if not log_record.get('timestamp'):\n      log_record['timestamp'] = record.created\n    if log_record.get('severity'):\n      log_record['severity'] = log_record['severity'].upper()\n    else:\n      log_record['severity'] = record.levelname\n\ndef getJSONLogger(name):\n  logger = logging.getLogger(name)\n  handler = logging.StreamHandler(sys.stdout)\n  formatter = CustomJsonFormatter('%(timestamp)s %(severity)s %(name)s %(message)s')\n  handler.setFormatter(formatter)\n  logger.addHandler(handler)\n  logger.setLevel(logging.INFO)\n  logger.propagate = False\n  return logger\n"
  },
  {
    "path": "src/emailservice/requirements.in",
    "content": "google-api-core==2.28.1\ngrpcio-health-checking==1.76.0\ngrpcio==1.76.0\njinja2==3.1.6\npython-json-logger==4.0.0\ngoogle-cloud-trace==1.17.0\nrequests==2.32.5\nopentelemetry-distro==0.60b1\nopentelemetry-instrumentation-grpc==0.60b1\nopentelemetry-exporter-otlp-proto-grpc==1.39.1\n"
  },
  {
    "path": "src/emailservice/requirements.txt",
    "content": "# This file was autogenerated by uv via the following command:\n#    uv pip compile requirements.in -o requirements.txt\ncachetools==5.3.2\n    # via google-auth\ncertifi==2024.7.4\n    # via requests\ncharset-normalizer==3.3.2\n    # via requests\ngoogle-api-core[grpc]==2.28.1\n    # via\n    #   -r requirements.in\n    #   google-cloud-trace\ngoogle-auth==2.23.4\n    # via\n    #   google-api-core\n    #   google-cloud-trace\ngoogle-cloud-trace==1.17.0\n    # via -r requirements.in\ngoogleapis-common-protos==1.72.0\n    # via\n    #   google-api-core\n    #   grpcio-status\n    #   opentelemetry-exporter-otlp-proto-grpc\ngrpcio==1.76.0\n    # via\n    #   -r requirements.in\n    #   google-api-core\n    #   google-cloud-trace\n    #   grpcio-health-checking\n    #   grpcio-status\n    #   opentelemetry-exporter-otlp-proto-grpc\ngrpcio-health-checking==1.76.0\n    # via -r requirements.in\ngrpcio-status==1.76.0\n    # via google-api-core\nidna==3.7\n    # via requests\nimportlib-metadata==6.8.0\n    # via opentelemetry-api\njinja2==3.1.6\n    # via -r requirements.in\nmarkupsafe==2.1.3\n    # via jinja2\nopentelemetry-api==1.39.1\n    # via\n    #   opentelemetry-distro\n    #   opentelemetry-exporter-otlp-proto-grpc\n    #   opentelemetry-instrumentation\n    #   opentelemetry-instrumentation-grpc\n    #   opentelemetry-sdk\n    #   opentelemetry-semantic-conventions\nopentelemetry-distro==0.60b1\n    # via -r requirements.in\nopentelemetry-exporter-otlp-proto-common==1.39.1\n    # via opentelemetry-exporter-otlp-proto-grpc\nopentelemetry-exporter-otlp-proto-grpc==1.39.1\n    # via -r requirements.in\nopentelemetry-instrumentation==0.60b1\n    # via\n    #   opentelemetry-distro\n    #   opentelemetry-instrumentation-grpc\nopentelemetry-instrumentation-grpc==0.60b1\n    # via -r requirements.in\nopentelemetry-proto==1.39.1\n    # via\n    #   opentelemetry-exporter-otlp-proto-common\n    #   opentelemetry-exporter-otlp-proto-grpc\nopentelemetry-sdk==1.39.1\n    # via\n    #   opentelemetry-distro\n    #   opentelemetry-exporter-otlp-proto-grpc\nopentelemetry-semantic-conventions==0.60b1\n    # via\n    #   opentelemetry-instrumentation\n    #   opentelemetry-instrumentation-grpc\n    #   opentelemetry-sdk\npackaging==25.0\n    # via opentelemetry-instrumentation\nproto-plus==1.27.0\n    # via\n    #   google-api-core\n    #   google-cloud-trace\nprotobuf==6.33.5\n    # via\n    #   google-api-core\n    #   google-cloud-trace\n    #   googleapis-common-protos\n    #   grpcio-health-checking\n    #   grpcio-status\n    #   opentelemetry-proto\n    #   proto-plus\npyasn1==0.5.0\n    # via\n    #   pyasn1-modules\n    #   rsa\npyasn1-modules==0.3.0\n    # via google-auth\npython-json-logger==4.0.0\n    # via -r requirements.in\nrequests==2.32.5\n    # via\n    #   -r requirements.in\n    #   google-api-core\nrsa==4.9\n    # via google-auth\ntyping-extensions==4.15.0\n    # via\n    #   grpcio\n    #   opentelemetry-api\n    #   opentelemetry-exporter-otlp-proto-grpc\n    #   opentelemetry-sdk\n    #   opentelemetry-semantic-conventions\nurllib3==2.6.3\n    # via requests\nwrapt==1.16.0\n    # via\n    #   opentelemetry-instrumentation\n    #   opentelemetry-instrumentation-grpc\nzipp==3.19.1\n    # via importlib-metadata\n"
  },
  {
    "path": "src/emailservice/templates/confirmation.html",
    "content": "<!DOCTYPE html>\n<!--\n Copyright 2020 Google LLC\n\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n      http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n-->\n\n<html>\n  <head>\n    <title>Your Order Confirmation</title>\n    <link href=\"https://fonts.googleapis.com/css2?family=DM+Sans:ital,wght@0,400;0,700;1,400;1,700&display=swap\" rel=\"stylesheet\">\n  </head>\n  <style>\n    body{\n      font-family: 'DM Sans', sans-serif;\n    }\n  </style>\n  <body>\n    <h2>Your Order Confirmation</h2>\n    <p>Thanks for shopping with us!<p>\n    <h3>Order ID</h3>\n    <p>#{{ order.order_id }}</p>\n    <h3>Shipping</h3>\n    <p>#{{ order.shipping_tracking_id }}</p>\n    <p>{{ order.shipping_cost.units }}. {{ \"%02d\" | format(order.shipping_cost.nanos // 10000000) }} {{ order.shipping_cost.currency_code }}</p>\n    <p>{{ 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}}</p>\n    <h3>Items</h3>\n    <table style=\"width:100%\">\n        <tr>\n          <th>Item No.</th>\n          <th>Quantity</th> \n          <th>Price</th>\n        </tr>\n        {% for item in order.items %}\n        <tr>\n          <td>#{{ item.item.product_id }}</td>\n          <td>{{ item.item.quantity }}</td> \n          <td>{{ item.cost.units }}.{{ \"%02d\" | format(item.cost.nanos // 10000000) }} {{ item.cost.currency_code }}</td>\n        </tr>\n        {% endfor %}\n    </table>\n  </body>\n</html>\n"
  },
  {
    "path": "src/frontend/.dockerignore",
    "content": "vendor/\n"
  },
  {
    "path": "src/frontend/.gitkeep",
    "content": ""
  },
  {
    "path": "src/frontend/Dockerfile",
    "content": "# Copyright 2020 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# Define a default value so it's not empty if the builder fails to provide it\nARG BUILDPLATFORM=linux/amd64\n\nFROM --platform=$BUILDPLATFORM golang:1.26.1-alpine@sha256:2389ebfa5b7f43eeafbd6be0c3700cc46690ef842ad962f6c5bd6be49ed82039 AS builder\nARG TARGETOS=linux\nARG TARGETARCH=amd64\nWORKDIR /src\n\n# restore dependencies\nCOPY go.mod go.sum ./\nRUN go mod download\nCOPY . .\n\n# Skaffold passes in debug-oriented compiler flags\nARG SKAFFOLD_GO_GCFLAGS\nRUN GOOS=${TARGETOS} GOARCH=${TARGETARCH} CGO_ENABLED=0 go build -ldflags=\"-s -w\" -gcflags=\"${SKAFFOLD_GO_GCFLAGS}\" -o /go/bin/frontend .\n\nFROM gcr.io/distroless/static\nWORKDIR /src\nCOPY --from=builder /go/bin/frontend /src/server\nCOPY ./templates ./templates\nCOPY ./static ./static\n\n# Definition of this variable is used by 'skaffold debug' to identify a golang binary.\n# Default behavior - a failure prints a stack trace for the current goroutine.\n# See https://golang.org/pkg/runtime/\nENV GOTRACEBACK=single\n\nEXPOSE 8080\nENTRYPOINT [\"/src/server\"]\n"
  },
  {
    "path": "src/frontend/README.md",
    "content": "# frontend\n\nRun the following command to restore dependencies to `vendor/` directory:\n\n    dep ensure --vendor-only\n"
  },
  {
    "path": "src/frontend/deployment_details.go",
    "content": "package main\n\nimport (\n\t\"net/http\"\n\t\"os\"\n\t\"time\"\n\n\t\"cloud.google.com/go/compute/metadata\"\n\t\"github.com/sirupsen/logrus\"\n)\n\nvar deploymentDetailsMap map[string]string\nvar log *logrus.Logger\n\nfunc init() {\n\tinitializeLogger()\n\t// Use a goroutine to ensure loadDeploymentDetails()'s GCP API\n\t// calls don't block non-GCP deployments. See issue #685.\n\tgo loadDeploymentDetails()\n}\n\nfunc initializeLogger() {\n\tlog = logrus.New()\n\tlog.Level = logrus.DebugLevel\n\tlog.Formatter = &logrus.JSONFormatter{\n\t\tFieldMap: logrus.FieldMap{\n\t\t\tlogrus.FieldKeyTime:  \"timestamp\",\n\t\t\tlogrus.FieldKeyLevel: \"severity\",\n\t\t\tlogrus.FieldKeyMsg:   \"message\",\n\t\t},\n\t\tTimestampFormat: time.RFC3339Nano,\n\t}\n\tlog.Out = os.Stdout\n}\n\nfunc loadDeploymentDetails() {\n\tdeploymentDetailsMap = make(map[string]string)\n\tvar metaServerClient = metadata.NewClient(&http.Client{})\n\n\tpodHostname, err := os.Hostname()\n\tif err != nil {\n\t\tlog.Error(\"Failed to fetch the hostname for the Pod\", err)\n\t}\n\n\tpodCluster, err := metaServerClient.InstanceAttributeValue(\"cluster-name\")\n\tif err != nil {\n\t\tlog.Error(\"Failed to fetch the name of the cluster in which the pod is running\", err)\n\t}\n\n\tpodZone, err := metaServerClient.Zone()\n\tif err != nil {\n\t\tlog.Error(\"Failed to fetch the Zone of the node where the pod is scheduled\", err)\n\t}\n\n\tdeploymentDetailsMap[\"HOSTNAME\"] = podHostname\n\tdeploymentDetailsMap[\"CLUSTERNAME\"] = podCluster\n\tdeploymentDetailsMap[\"ZONE\"] = podZone\n\n\tlog.WithFields(logrus.Fields{\n\t\t\"cluster\":  podCluster,\n\t\t\"zone\":     podZone,\n\t\t\"hostname\": podHostname,\n\t}).Debug(\"Loaded deployment details\")\n}\n"
  },
  {
    "path": "src/frontend/genproto/demo.pb.go",
    "content": "// Copyright 2020 Google LLC\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.34.2\n// \tprotoc        v3.6.1\n// source: demo.proto\n\npackage hipstershop\n\nimport (\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\treflect \"reflect\"\n\tsync \"sync\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\ntype CartItem struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tProductId string `protobuf:\"bytes,1,opt,name=product_id,json=productId,proto3\" json:\"product_id,omitempty\"`\n\tQuantity  int32  `protobuf:\"varint,2,opt,name=quantity,proto3\" json:\"quantity,omitempty\"`\n}\n\nfunc (x *CartItem) Reset() {\n\t*x = CartItem{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_demo_proto_msgTypes[0]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *CartItem) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*CartItem) ProtoMessage() {}\n\nfunc (x *CartItem) ProtoReflect() protoreflect.Message {\n\tmi := &file_demo_proto_msgTypes[0]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use CartItem.ProtoReflect.Descriptor instead.\nfunc (*CartItem) Descriptor() ([]byte, []int) {\n\treturn file_demo_proto_rawDescGZIP(), []int{0}\n}\n\nfunc (x *CartItem) GetProductId() string {\n\tif x != nil {\n\t\treturn x.ProductId\n\t}\n\treturn \"\"\n}\n\nfunc (x *CartItem) GetQuantity() int32 {\n\tif x != nil {\n\t\treturn x.Quantity\n\t}\n\treturn 0\n}\n\ntype AddItemRequest struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tUserId string    `protobuf:\"bytes,1,opt,name=user_id,json=userId,proto3\" json:\"user_id,omitempty\"`\n\tItem   *CartItem `protobuf:\"bytes,2,opt,name=item,proto3\" json:\"item,omitempty\"`\n}\n\nfunc (x *AddItemRequest) Reset() {\n\t*x = AddItemRequest{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_demo_proto_msgTypes[1]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *AddItemRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*AddItemRequest) ProtoMessage() {}\n\nfunc (x *AddItemRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_demo_proto_msgTypes[1]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use AddItemRequest.ProtoReflect.Descriptor instead.\nfunc (*AddItemRequest) Descriptor() ([]byte, []int) {\n\treturn file_demo_proto_rawDescGZIP(), []int{1}\n}\n\nfunc (x *AddItemRequest) GetUserId() string {\n\tif x != nil {\n\t\treturn x.UserId\n\t}\n\treturn \"\"\n}\n\nfunc (x *AddItemRequest) GetItem() *CartItem {\n\tif x != nil {\n\t\treturn x.Item\n\t}\n\treturn nil\n}\n\ntype EmptyCartRequest struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tUserId string `protobuf:\"bytes,1,opt,name=user_id,json=userId,proto3\" json:\"user_id,omitempty\"`\n}\n\nfunc (x *EmptyCartRequest) Reset() {\n\t*x = EmptyCartRequest{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_demo_proto_msgTypes[2]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *EmptyCartRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*EmptyCartRequest) ProtoMessage() {}\n\nfunc (x *EmptyCartRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_demo_proto_msgTypes[2]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use EmptyCartRequest.ProtoReflect.Descriptor instead.\nfunc (*EmptyCartRequest) Descriptor() ([]byte, []int) {\n\treturn file_demo_proto_rawDescGZIP(), []int{2}\n}\n\nfunc (x *EmptyCartRequest) GetUserId() string {\n\tif x != nil {\n\t\treturn x.UserId\n\t}\n\treturn \"\"\n}\n\ntype GetCartRequest struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tUserId string `protobuf:\"bytes,1,opt,name=user_id,json=userId,proto3\" json:\"user_id,omitempty\"`\n}\n\nfunc (x *GetCartRequest) Reset() {\n\t*x = GetCartRequest{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_demo_proto_msgTypes[3]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *GetCartRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*GetCartRequest) ProtoMessage() {}\n\nfunc (x *GetCartRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_demo_proto_msgTypes[3]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use GetCartRequest.ProtoReflect.Descriptor instead.\nfunc (*GetCartRequest) Descriptor() ([]byte, []int) {\n\treturn file_demo_proto_rawDescGZIP(), []int{3}\n}\n\nfunc (x *GetCartRequest) GetUserId() string {\n\tif x != nil {\n\t\treturn x.UserId\n\t}\n\treturn \"\"\n}\n\ntype Cart struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tUserId string      `protobuf:\"bytes,1,opt,name=user_id,json=userId,proto3\" json:\"user_id,omitempty\"`\n\tItems  []*CartItem `protobuf:\"bytes,2,rep,name=items,proto3\" json:\"items,omitempty\"`\n}\n\nfunc (x *Cart) Reset() {\n\t*x = Cart{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_demo_proto_msgTypes[4]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *Cart) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Cart) ProtoMessage() {}\n\nfunc (x *Cart) ProtoReflect() protoreflect.Message {\n\tmi := &file_demo_proto_msgTypes[4]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Cart.ProtoReflect.Descriptor instead.\nfunc (*Cart) Descriptor() ([]byte, []int) {\n\treturn file_demo_proto_rawDescGZIP(), []int{4}\n}\n\nfunc (x *Cart) GetUserId() string {\n\tif x != nil {\n\t\treturn x.UserId\n\t}\n\treturn \"\"\n}\n\nfunc (x *Cart) GetItems() []*CartItem {\n\tif x != nil {\n\t\treturn x.Items\n\t}\n\treturn nil\n}\n\ntype Empty struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n}\n\nfunc (x *Empty) Reset() {\n\t*x = Empty{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_demo_proto_msgTypes[5]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *Empty) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Empty) ProtoMessage() {}\n\nfunc (x *Empty) ProtoReflect() protoreflect.Message {\n\tmi := &file_demo_proto_msgTypes[5]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Empty.ProtoReflect.Descriptor instead.\nfunc (*Empty) Descriptor() ([]byte, []int) {\n\treturn file_demo_proto_rawDescGZIP(), []int{5}\n}\n\ntype ListRecommendationsRequest struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tUserId     string   `protobuf:\"bytes,1,opt,name=user_id,json=userId,proto3\" json:\"user_id,omitempty\"`\n\tProductIds []string `protobuf:\"bytes,2,rep,name=product_ids,json=productIds,proto3\" json:\"product_ids,omitempty\"`\n}\n\nfunc (x *ListRecommendationsRequest) Reset() {\n\t*x = ListRecommendationsRequest{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_demo_proto_msgTypes[6]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *ListRecommendationsRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ListRecommendationsRequest) ProtoMessage() {}\n\nfunc (x *ListRecommendationsRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_demo_proto_msgTypes[6]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use ListRecommendationsRequest.ProtoReflect.Descriptor instead.\nfunc (*ListRecommendationsRequest) Descriptor() ([]byte, []int) {\n\treturn file_demo_proto_rawDescGZIP(), []int{6}\n}\n\nfunc (x *ListRecommendationsRequest) GetUserId() string {\n\tif x != nil {\n\t\treturn x.UserId\n\t}\n\treturn \"\"\n}\n\nfunc (x *ListRecommendationsRequest) GetProductIds() []string {\n\tif x != nil {\n\t\treturn x.ProductIds\n\t}\n\treturn nil\n}\n\ntype ListRecommendationsResponse struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tProductIds []string `protobuf:\"bytes,1,rep,name=product_ids,json=productIds,proto3\" json:\"product_ids,omitempty\"`\n}\n\nfunc (x *ListRecommendationsResponse) Reset() {\n\t*x = ListRecommendationsResponse{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_demo_proto_msgTypes[7]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *ListRecommendationsResponse) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ListRecommendationsResponse) ProtoMessage() {}\n\nfunc (x *ListRecommendationsResponse) ProtoReflect() protoreflect.Message {\n\tmi := &file_demo_proto_msgTypes[7]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use ListRecommendationsResponse.ProtoReflect.Descriptor instead.\nfunc (*ListRecommendationsResponse) Descriptor() ([]byte, []int) {\n\treturn file_demo_proto_rawDescGZIP(), []int{7}\n}\n\nfunc (x *ListRecommendationsResponse) GetProductIds() []string {\n\tif x != nil {\n\t\treturn x.ProductIds\n\t}\n\treturn nil\n}\n\ntype Product struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tId          string `protobuf:\"bytes,1,opt,name=id,proto3\" json:\"id,omitempty\"`\n\tName        string `protobuf:\"bytes,2,opt,name=name,proto3\" json:\"name,omitempty\"`\n\tDescription string `protobuf:\"bytes,3,opt,name=description,proto3\" json:\"description,omitempty\"`\n\tPicture     string `protobuf:\"bytes,4,opt,name=picture,proto3\" json:\"picture,omitempty\"`\n\tPriceUsd    *Money `protobuf:\"bytes,5,opt,name=price_usd,json=priceUsd,proto3\" json:\"price_usd,omitempty\"`\n\t// Categories such as \"clothing\" or \"kitchen\" that can be used to look up\n\t// other related products.\n\tCategories []string `protobuf:\"bytes,6,rep,name=categories,proto3\" json:\"categories,omitempty\"`\n}\n\nfunc (x *Product) Reset() {\n\t*x = Product{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_demo_proto_msgTypes[8]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *Product) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Product) ProtoMessage() {}\n\nfunc (x *Product) ProtoReflect() protoreflect.Message {\n\tmi := &file_demo_proto_msgTypes[8]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Product.ProtoReflect.Descriptor instead.\nfunc (*Product) Descriptor() ([]byte, []int) {\n\treturn file_demo_proto_rawDescGZIP(), []int{8}\n}\n\nfunc (x *Product) GetId() string {\n\tif x != nil {\n\t\treturn x.Id\n\t}\n\treturn \"\"\n}\n\nfunc (x *Product) GetName() string {\n\tif x != nil {\n\t\treturn x.Name\n\t}\n\treturn \"\"\n}\n\nfunc (x *Product) GetDescription() string {\n\tif x != nil {\n\t\treturn x.Description\n\t}\n\treturn \"\"\n}\n\nfunc (x *Product) GetPicture() string {\n\tif x != nil {\n\t\treturn x.Picture\n\t}\n\treturn \"\"\n}\n\nfunc (x *Product) GetPriceUsd() *Money {\n\tif x != nil {\n\t\treturn x.PriceUsd\n\t}\n\treturn nil\n}\n\nfunc (x *Product) GetCategories() []string {\n\tif x != nil {\n\t\treturn x.Categories\n\t}\n\treturn nil\n}\n\ntype ListProductsResponse struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tProducts []*Product `protobuf:\"bytes,1,rep,name=products,proto3\" json:\"products,omitempty\"`\n}\n\nfunc (x *ListProductsResponse) Reset() {\n\t*x = ListProductsResponse{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_demo_proto_msgTypes[9]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *ListProductsResponse) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ListProductsResponse) ProtoMessage() {}\n\nfunc (x *ListProductsResponse) ProtoReflect() protoreflect.Message {\n\tmi := &file_demo_proto_msgTypes[9]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use ListProductsResponse.ProtoReflect.Descriptor instead.\nfunc (*ListProductsResponse) Descriptor() ([]byte, []int) {\n\treturn file_demo_proto_rawDescGZIP(), []int{9}\n}\n\nfunc (x *ListProductsResponse) GetProducts() []*Product {\n\tif x != nil {\n\t\treturn x.Products\n\t}\n\treturn nil\n}\n\ntype GetProductRequest struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tId string `protobuf:\"bytes,1,opt,name=id,proto3\" json:\"id,omitempty\"`\n}\n\nfunc (x *GetProductRequest) Reset() {\n\t*x = GetProductRequest{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_demo_proto_msgTypes[10]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *GetProductRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*GetProductRequest) ProtoMessage() {}\n\nfunc (x *GetProductRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_demo_proto_msgTypes[10]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use GetProductRequest.ProtoReflect.Descriptor instead.\nfunc (*GetProductRequest) Descriptor() ([]byte, []int) {\n\treturn file_demo_proto_rawDescGZIP(), []int{10}\n}\n\nfunc (x *GetProductRequest) GetId() string {\n\tif x != nil {\n\t\treturn x.Id\n\t}\n\treturn \"\"\n}\n\ntype SearchProductsRequest struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tQuery string `protobuf:\"bytes,1,opt,name=query,proto3\" json:\"query,omitempty\"`\n}\n\nfunc (x *SearchProductsRequest) Reset() {\n\t*x = SearchProductsRequest{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_demo_proto_msgTypes[11]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *SearchProductsRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*SearchProductsRequest) ProtoMessage() {}\n\nfunc (x *SearchProductsRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_demo_proto_msgTypes[11]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use SearchProductsRequest.ProtoReflect.Descriptor instead.\nfunc (*SearchProductsRequest) Descriptor() ([]byte, []int) {\n\treturn file_demo_proto_rawDescGZIP(), []int{11}\n}\n\nfunc (x *SearchProductsRequest) GetQuery() string {\n\tif x != nil {\n\t\treturn x.Query\n\t}\n\treturn \"\"\n}\n\ntype SearchProductsResponse struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tResults []*Product `protobuf:\"bytes,1,rep,name=results,proto3\" json:\"results,omitempty\"`\n}\n\nfunc (x *SearchProductsResponse) Reset() {\n\t*x = SearchProductsResponse{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_demo_proto_msgTypes[12]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *SearchProductsResponse) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*SearchProductsResponse) ProtoMessage() {}\n\nfunc (x *SearchProductsResponse) ProtoReflect() protoreflect.Message {\n\tmi := &file_demo_proto_msgTypes[12]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use SearchProductsResponse.ProtoReflect.Descriptor instead.\nfunc (*SearchProductsResponse) Descriptor() ([]byte, []int) {\n\treturn file_demo_proto_rawDescGZIP(), []int{12}\n}\n\nfunc (x *SearchProductsResponse) GetResults() []*Product {\n\tif x != nil {\n\t\treturn x.Results\n\t}\n\treturn nil\n}\n\ntype GetQuoteRequest struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tAddress *Address    `protobuf:\"bytes,1,opt,name=address,proto3\" json:\"address,omitempty\"`\n\tItems   []*CartItem `protobuf:\"bytes,2,rep,name=items,proto3\" json:\"items,omitempty\"`\n}\n\nfunc (x *GetQuoteRequest) Reset() {\n\t*x = GetQuoteRequest{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_demo_proto_msgTypes[13]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *GetQuoteRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*GetQuoteRequest) ProtoMessage() {}\n\nfunc (x *GetQuoteRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_demo_proto_msgTypes[13]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use GetQuoteRequest.ProtoReflect.Descriptor instead.\nfunc (*GetQuoteRequest) Descriptor() ([]byte, []int) {\n\treturn file_demo_proto_rawDescGZIP(), []int{13}\n}\n\nfunc (x *GetQuoteRequest) GetAddress() *Address {\n\tif x != nil {\n\t\treturn x.Address\n\t}\n\treturn nil\n}\n\nfunc (x *GetQuoteRequest) GetItems() []*CartItem {\n\tif x != nil {\n\t\treturn x.Items\n\t}\n\treturn nil\n}\n\ntype GetQuoteResponse struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tCostUsd *Money `protobuf:\"bytes,1,opt,name=cost_usd,json=costUsd,proto3\" json:\"cost_usd,omitempty\"`\n}\n\nfunc (x *GetQuoteResponse) Reset() {\n\t*x = GetQuoteResponse{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_demo_proto_msgTypes[14]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *GetQuoteResponse) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*GetQuoteResponse) ProtoMessage() {}\n\nfunc (x *GetQuoteResponse) ProtoReflect() protoreflect.Message {\n\tmi := &file_demo_proto_msgTypes[14]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use GetQuoteResponse.ProtoReflect.Descriptor instead.\nfunc (*GetQuoteResponse) Descriptor() ([]byte, []int) {\n\treturn file_demo_proto_rawDescGZIP(), []int{14}\n}\n\nfunc (x *GetQuoteResponse) GetCostUsd() *Money {\n\tif x != nil {\n\t\treturn x.CostUsd\n\t}\n\treturn nil\n}\n\ntype ShipOrderRequest struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tAddress *Address    `protobuf:\"bytes,1,opt,name=address,proto3\" json:\"address,omitempty\"`\n\tItems   []*CartItem `protobuf:\"bytes,2,rep,name=items,proto3\" json:\"items,omitempty\"`\n}\n\nfunc (x *ShipOrderRequest) Reset() {\n\t*x = ShipOrderRequest{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_demo_proto_msgTypes[15]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *ShipOrderRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ShipOrderRequest) ProtoMessage() {}\n\nfunc (x *ShipOrderRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_demo_proto_msgTypes[15]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use ShipOrderRequest.ProtoReflect.Descriptor instead.\nfunc (*ShipOrderRequest) Descriptor() ([]byte, []int) {\n\treturn file_demo_proto_rawDescGZIP(), []int{15}\n}\n\nfunc (x *ShipOrderRequest) GetAddress() *Address {\n\tif x != nil {\n\t\treturn x.Address\n\t}\n\treturn nil\n}\n\nfunc (x *ShipOrderRequest) GetItems() []*CartItem {\n\tif x != nil {\n\t\treturn x.Items\n\t}\n\treturn nil\n}\n\ntype ShipOrderResponse struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tTrackingId string `protobuf:\"bytes,1,opt,name=tracking_id,json=trackingId,proto3\" json:\"tracking_id,omitempty\"`\n}\n\nfunc (x *ShipOrderResponse) Reset() {\n\t*x = ShipOrderResponse{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_demo_proto_msgTypes[16]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *ShipOrderResponse) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ShipOrderResponse) ProtoMessage() {}\n\nfunc (x *ShipOrderResponse) ProtoReflect() protoreflect.Message {\n\tmi := &file_demo_proto_msgTypes[16]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use ShipOrderResponse.ProtoReflect.Descriptor instead.\nfunc (*ShipOrderResponse) Descriptor() ([]byte, []int) {\n\treturn file_demo_proto_rawDescGZIP(), []int{16}\n}\n\nfunc (x *ShipOrderResponse) GetTrackingId() string {\n\tif x != nil {\n\t\treturn x.TrackingId\n\t}\n\treturn \"\"\n}\n\ntype Address struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tStreetAddress string `protobuf:\"bytes,1,opt,name=street_address,json=streetAddress,proto3\" json:\"street_address,omitempty\"`\n\tCity          string `protobuf:\"bytes,2,opt,name=city,proto3\" json:\"city,omitempty\"`\n\tState         string `protobuf:\"bytes,3,opt,name=state,proto3\" json:\"state,omitempty\"`\n\tCountry       string `protobuf:\"bytes,4,opt,name=country,proto3\" json:\"country,omitempty\"`\n\tZipCode       int32  `protobuf:\"varint,5,opt,name=zip_code,json=zipCode,proto3\" json:\"zip_code,omitempty\"`\n}\n\nfunc (x *Address) Reset() {\n\t*x = Address{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_demo_proto_msgTypes[17]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *Address) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Address) ProtoMessage() {}\n\nfunc (x *Address) ProtoReflect() protoreflect.Message {\n\tmi := &file_demo_proto_msgTypes[17]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Address.ProtoReflect.Descriptor instead.\nfunc (*Address) Descriptor() ([]byte, []int) {\n\treturn file_demo_proto_rawDescGZIP(), []int{17}\n}\n\nfunc (x *Address) GetStreetAddress() string {\n\tif x != nil {\n\t\treturn x.StreetAddress\n\t}\n\treturn \"\"\n}\n\nfunc (x *Address) GetCity() string {\n\tif x != nil {\n\t\treturn x.City\n\t}\n\treturn \"\"\n}\n\nfunc (x *Address) GetState() string {\n\tif x != nil {\n\t\treturn x.State\n\t}\n\treturn \"\"\n}\n\nfunc (x *Address) GetCountry() string {\n\tif x != nil {\n\t\treturn x.Country\n\t}\n\treturn \"\"\n}\n\nfunc (x *Address) GetZipCode() int32 {\n\tif x != nil {\n\t\treturn x.ZipCode\n\t}\n\treturn 0\n}\n\n// Represents an amount of money with its currency type.\ntype Money struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// The 3-letter currency code defined in ISO 4217.\n\tCurrencyCode string `protobuf:\"bytes,1,opt,name=currency_code,json=currencyCode,proto3\" json:\"currency_code,omitempty\"`\n\t// The whole units of the amount.\n\t// For example if `currencyCode` is `\"USD\"`, then 1 unit is one US dollar.\n\tUnits int64 `protobuf:\"varint,2,opt,name=units,proto3\" json:\"units,omitempty\"`\n\t// Number of nano (10^-9) units of the amount.\n\t// The value must be between -999,999,999 and +999,999,999 inclusive.\n\t// If `units` is positive, `nanos` must be positive or zero.\n\t// If `units` is zero, `nanos` can be positive, zero, or negative.\n\t// If `units` is negative, `nanos` must be negative or zero.\n\t// For example $-1.75 is represented as `units`=-1 and `nanos`=-750,000,000.\n\tNanos int32 `protobuf:\"varint,3,opt,name=nanos,proto3\" json:\"nanos,omitempty\"`\n}\n\nfunc (x *Money) Reset() {\n\t*x = Money{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_demo_proto_msgTypes[18]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *Money) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Money) ProtoMessage() {}\n\nfunc (x *Money) ProtoReflect() protoreflect.Message {\n\tmi := &file_demo_proto_msgTypes[18]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Money.ProtoReflect.Descriptor instead.\nfunc (*Money) Descriptor() ([]byte, []int) {\n\treturn file_demo_proto_rawDescGZIP(), []int{18}\n}\n\nfunc (x *Money) GetCurrencyCode() string {\n\tif x != nil {\n\t\treturn x.CurrencyCode\n\t}\n\treturn \"\"\n}\n\nfunc (x *Money) GetUnits() int64 {\n\tif x != nil {\n\t\treturn x.Units\n\t}\n\treturn 0\n}\n\nfunc (x *Money) GetNanos() int32 {\n\tif x != nil {\n\t\treturn x.Nanos\n\t}\n\treturn 0\n}\n\ntype GetSupportedCurrenciesResponse struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// The 3-letter currency code defined in ISO 4217.\n\tCurrencyCodes []string `protobuf:\"bytes,1,rep,name=currency_codes,json=currencyCodes,proto3\" json:\"currency_codes,omitempty\"`\n}\n\nfunc (x *GetSupportedCurrenciesResponse) Reset() {\n\t*x = GetSupportedCurrenciesResponse{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_demo_proto_msgTypes[19]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *GetSupportedCurrenciesResponse) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*GetSupportedCurrenciesResponse) ProtoMessage() {}\n\nfunc (x *GetSupportedCurrenciesResponse) ProtoReflect() protoreflect.Message {\n\tmi := &file_demo_proto_msgTypes[19]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use GetSupportedCurrenciesResponse.ProtoReflect.Descriptor instead.\nfunc (*GetSupportedCurrenciesResponse) Descriptor() ([]byte, []int) {\n\treturn file_demo_proto_rawDescGZIP(), []int{19}\n}\n\nfunc (x *GetSupportedCurrenciesResponse) GetCurrencyCodes() []string {\n\tif x != nil {\n\t\treturn x.CurrencyCodes\n\t}\n\treturn nil\n}\n\ntype CurrencyConversionRequest struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tFrom *Money `protobuf:\"bytes,1,opt,name=from,proto3\" json:\"from,omitempty\"`\n\t// The 3-letter currency code defined in ISO 4217.\n\tToCode string `protobuf:\"bytes,2,opt,name=to_code,json=toCode,proto3\" json:\"to_code,omitempty\"`\n}\n\nfunc (x *CurrencyConversionRequest) Reset() {\n\t*x = CurrencyConversionRequest{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_demo_proto_msgTypes[20]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *CurrencyConversionRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*CurrencyConversionRequest) ProtoMessage() {}\n\nfunc (x *CurrencyConversionRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_demo_proto_msgTypes[20]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use CurrencyConversionRequest.ProtoReflect.Descriptor instead.\nfunc (*CurrencyConversionRequest) Descriptor() ([]byte, []int) {\n\treturn file_demo_proto_rawDescGZIP(), []int{20}\n}\n\nfunc (x *CurrencyConversionRequest) GetFrom() *Money {\n\tif x != nil {\n\t\treturn x.From\n\t}\n\treturn nil\n}\n\nfunc (x *CurrencyConversionRequest) GetToCode() string {\n\tif x != nil {\n\t\treturn x.ToCode\n\t}\n\treturn \"\"\n}\n\ntype CreditCardInfo struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tCreditCardNumber          string `protobuf:\"bytes,1,opt,name=credit_card_number,json=creditCardNumber,proto3\" json:\"credit_card_number,omitempty\"`\n\tCreditCardCvv             int32  `protobuf:\"varint,2,opt,name=credit_card_cvv,json=creditCardCvv,proto3\" json:\"credit_card_cvv,omitempty\"`\n\tCreditCardExpirationYear  int32  `protobuf:\"varint,3,opt,name=credit_card_expiration_year,json=creditCardExpirationYear,proto3\" json:\"credit_card_expiration_year,omitempty\"`\n\tCreditCardExpirationMonth int32  `protobuf:\"varint,4,opt,name=credit_card_expiration_month,json=creditCardExpirationMonth,proto3\" json:\"credit_card_expiration_month,omitempty\"`\n}\n\nfunc (x *CreditCardInfo) Reset() {\n\t*x = CreditCardInfo{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_demo_proto_msgTypes[21]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *CreditCardInfo) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*CreditCardInfo) ProtoMessage() {}\n\nfunc (x *CreditCardInfo) ProtoReflect() protoreflect.Message {\n\tmi := &file_demo_proto_msgTypes[21]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use CreditCardInfo.ProtoReflect.Descriptor instead.\nfunc (*CreditCardInfo) Descriptor() ([]byte, []int) {\n\treturn file_demo_proto_rawDescGZIP(), []int{21}\n}\n\nfunc (x *CreditCardInfo) GetCreditCardNumber() string {\n\tif x != nil {\n\t\treturn x.CreditCardNumber\n\t}\n\treturn \"\"\n}\n\nfunc (x *CreditCardInfo) GetCreditCardCvv() int32 {\n\tif x != nil {\n\t\treturn x.CreditCardCvv\n\t}\n\treturn 0\n}\n\nfunc (x *CreditCardInfo) GetCreditCardExpirationYear() int32 {\n\tif x != nil {\n\t\treturn x.CreditCardExpirationYear\n\t}\n\treturn 0\n}\n\nfunc (x *CreditCardInfo) GetCreditCardExpirationMonth() int32 {\n\tif x != nil {\n\t\treturn x.CreditCardExpirationMonth\n\t}\n\treturn 0\n}\n\ntype ChargeRequest struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tAmount     *Money          `protobuf:\"bytes,1,opt,name=amount,proto3\" json:\"amount,omitempty\"`\n\tCreditCard *CreditCardInfo `protobuf:\"bytes,2,opt,name=credit_card,json=creditCard,proto3\" json:\"credit_card,omitempty\"`\n}\n\nfunc (x *ChargeRequest) Reset() {\n\t*x = ChargeRequest{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_demo_proto_msgTypes[22]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *ChargeRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ChargeRequest) ProtoMessage() {}\n\nfunc (x *ChargeRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_demo_proto_msgTypes[22]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use ChargeRequest.ProtoReflect.Descriptor instead.\nfunc (*ChargeRequest) Descriptor() ([]byte, []int) {\n\treturn file_demo_proto_rawDescGZIP(), []int{22}\n}\n\nfunc (x *ChargeRequest) GetAmount() *Money {\n\tif x != nil {\n\t\treturn x.Amount\n\t}\n\treturn nil\n}\n\nfunc (x *ChargeRequest) GetCreditCard() *CreditCardInfo {\n\tif x != nil {\n\t\treturn x.CreditCard\n\t}\n\treturn nil\n}\n\ntype ChargeResponse struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tTransactionId string `protobuf:\"bytes,1,opt,name=transaction_id,json=transactionId,proto3\" json:\"transaction_id,omitempty\"`\n}\n\nfunc (x *ChargeResponse) Reset() {\n\t*x = ChargeResponse{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_demo_proto_msgTypes[23]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *ChargeResponse) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ChargeResponse) ProtoMessage() {}\n\nfunc (x *ChargeResponse) ProtoReflect() protoreflect.Message {\n\tmi := &file_demo_proto_msgTypes[23]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use ChargeResponse.ProtoReflect.Descriptor instead.\nfunc (*ChargeResponse) Descriptor() ([]byte, []int) {\n\treturn file_demo_proto_rawDescGZIP(), []int{23}\n}\n\nfunc (x *ChargeResponse) GetTransactionId() string {\n\tif x != nil {\n\t\treturn x.TransactionId\n\t}\n\treturn \"\"\n}\n\ntype OrderItem struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tItem *CartItem `protobuf:\"bytes,1,opt,name=item,proto3\" json:\"item,omitempty\"`\n\tCost *Money    `protobuf:\"bytes,2,opt,name=cost,proto3\" json:\"cost,omitempty\"`\n}\n\nfunc (x *OrderItem) Reset() {\n\t*x = OrderItem{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_demo_proto_msgTypes[24]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *OrderItem) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*OrderItem) ProtoMessage() {}\n\nfunc (x *OrderItem) ProtoReflect() protoreflect.Message {\n\tmi := &file_demo_proto_msgTypes[24]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use OrderItem.ProtoReflect.Descriptor instead.\nfunc (*OrderItem) Descriptor() ([]byte, []int) {\n\treturn file_demo_proto_rawDescGZIP(), []int{24}\n}\n\nfunc (x *OrderItem) GetItem() *CartItem {\n\tif x != nil {\n\t\treturn x.Item\n\t}\n\treturn nil\n}\n\nfunc (x *OrderItem) GetCost() *Money {\n\tif x != nil {\n\t\treturn x.Cost\n\t}\n\treturn nil\n}\n\ntype OrderResult struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tOrderId            string       `protobuf:\"bytes,1,opt,name=order_id,json=orderId,proto3\" json:\"order_id,omitempty\"`\n\tShippingTrackingId string       `protobuf:\"bytes,2,opt,name=shipping_tracking_id,json=shippingTrackingId,proto3\" json:\"shipping_tracking_id,omitempty\"`\n\tShippingCost       *Money       `protobuf:\"bytes,3,opt,name=shipping_cost,json=shippingCost,proto3\" json:\"shipping_cost,omitempty\"`\n\tShippingAddress    *Address     `protobuf:\"bytes,4,opt,name=shipping_address,json=shippingAddress,proto3\" json:\"shipping_address,omitempty\"`\n\tItems              []*OrderItem `protobuf:\"bytes,5,rep,name=items,proto3\" json:\"items,omitempty\"`\n}\n\nfunc (x *OrderResult) Reset() {\n\t*x = OrderResult{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_demo_proto_msgTypes[25]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *OrderResult) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*OrderResult) ProtoMessage() {}\n\nfunc (x *OrderResult) ProtoReflect() protoreflect.Message {\n\tmi := &file_demo_proto_msgTypes[25]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use OrderResult.ProtoReflect.Descriptor instead.\nfunc (*OrderResult) Descriptor() ([]byte, []int) {\n\treturn file_demo_proto_rawDescGZIP(), []int{25}\n}\n\nfunc (x *OrderResult) GetOrderId() string {\n\tif x != nil {\n\t\treturn x.OrderId\n\t}\n\treturn \"\"\n}\n\nfunc (x *OrderResult) GetShippingTrackingId() string {\n\tif x != nil {\n\t\treturn x.ShippingTrackingId\n\t}\n\treturn \"\"\n}\n\nfunc (x *OrderResult) GetShippingCost() *Money {\n\tif x != nil {\n\t\treturn x.ShippingCost\n\t}\n\treturn nil\n}\n\nfunc (x *OrderResult) GetShippingAddress() *Address {\n\tif x != nil {\n\t\treturn x.ShippingAddress\n\t}\n\treturn nil\n}\n\nfunc (x *OrderResult) GetItems() []*OrderItem {\n\tif x != nil {\n\t\treturn x.Items\n\t}\n\treturn nil\n}\n\ntype SendOrderConfirmationRequest struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tEmail string       `protobuf:\"bytes,1,opt,name=email,proto3\" json:\"email,omitempty\"`\n\tOrder *OrderResult `protobuf:\"bytes,2,opt,name=order,proto3\" json:\"order,omitempty\"`\n}\n\nfunc (x *SendOrderConfirmationRequest) Reset() {\n\t*x = SendOrderConfirmationRequest{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_demo_proto_msgTypes[26]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *SendOrderConfirmationRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*SendOrderConfirmationRequest) ProtoMessage() {}\n\nfunc (x *SendOrderConfirmationRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_demo_proto_msgTypes[26]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use SendOrderConfirmationRequest.ProtoReflect.Descriptor instead.\nfunc (*SendOrderConfirmationRequest) Descriptor() ([]byte, []int) {\n\treturn file_demo_proto_rawDescGZIP(), []int{26}\n}\n\nfunc (x *SendOrderConfirmationRequest) GetEmail() string {\n\tif x != nil {\n\t\treturn x.Email\n\t}\n\treturn \"\"\n}\n\nfunc (x *SendOrderConfirmationRequest) GetOrder() *OrderResult {\n\tif x != nil {\n\t\treturn x.Order\n\t}\n\treturn nil\n}\n\ntype PlaceOrderRequest struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tUserId       string          `protobuf:\"bytes,1,opt,name=user_id,json=userId,proto3\" json:\"user_id,omitempty\"`\n\tUserCurrency string          `protobuf:\"bytes,2,opt,name=user_currency,json=userCurrency,proto3\" json:\"user_currency,omitempty\"`\n\tAddress      *Address        `protobuf:\"bytes,3,opt,name=address,proto3\" json:\"address,omitempty\"`\n\tEmail        string          `protobuf:\"bytes,5,opt,name=email,proto3\" json:\"email,omitempty\"`\n\tCreditCard   *CreditCardInfo `protobuf:\"bytes,6,opt,name=credit_card,json=creditCard,proto3\" json:\"credit_card,omitempty\"`\n}\n\nfunc (x *PlaceOrderRequest) Reset() {\n\t*x = PlaceOrderRequest{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_demo_proto_msgTypes[27]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *PlaceOrderRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*PlaceOrderRequest) ProtoMessage() {}\n\nfunc (x *PlaceOrderRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_demo_proto_msgTypes[27]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use PlaceOrderRequest.ProtoReflect.Descriptor instead.\nfunc (*PlaceOrderRequest) Descriptor() ([]byte, []int) {\n\treturn file_demo_proto_rawDescGZIP(), []int{27}\n}\n\nfunc (x *PlaceOrderRequest) GetUserId() string {\n\tif x != nil {\n\t\treturn x.UserId\n\t}\n\treturn \"\"\n}\n\nfunc (x *PlaceOrderRequest) GetUserCurrency() string {\n\tif x != nil {\n\t\treturn x.UserCurrency\n\t}\n\treturn \"\"\n}\n\nfunc (x *PlaceOrderRequest) GetAddress() *Address {\n\tif x != nil {\n\t\treturn x.Address\n\t}\n\treturn nil\n}\n\nfunc (x *PlaceOrderRequest) GetEmail() string {\n\tif x != nil {\n\t\treturn x.Email\n\t}\n\treturn \"\"\n}\n\nfunc (x *PlaceOrderRequest) GetCreditCard() *CreditCardInfo {\n\tif x != nil {\n\t\treturn x.CreditCard\n\t}\n\treturn nil\n}\n\ntype PlaceOrderResponse struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tOrder *OrderResult `protobuf:\"bytes,1,opt,name=order,proto3\" json:\"order,omitempty\"`\n}\n\nfunc (x *PlaceOrderResponse) Reset() {\n\t*x = PlaceOrderResponse{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_demo_proto_msgTypes[28]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *PlaceOrderResponse) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*PlaceOrderResponse) ProtoMessage() {}\n\nfunc (x *PlaceOrderResponse) ProtoReflect() protoreflect.Message {\n\tmi := &file_demo_proto_msgTypes[28]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use PlaceOrderResponse.ProtoReflect.Descriptor instead.\nfunc (*PlaceOrderResponse) Descriptor() ([]byte, []int) {\n\treturn file_demo_proto_rawDescGZIP(), []int{28}\n}\n\nfunc (x *PlaceOrderResponse) GetOrder() *OrderResult {\n\tif x != nil {\n\t\treturn x.Order\n\t}\n\treturn nil\n}\n\ntype AdRequest struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// List of important key words from the current page describing the context.\n\tContextKeys []string `protobuf:\"bytes,1,rep,name=context_keys,json=contextKeys,proto3\" json:\"context_keys,omitempty\"`\n}\n\nfunc (x *AdRequest) Reset() {\n\t*x = AdRequest{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_demo_proto_msgTypes[29]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *AdRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*AdRequest) ProtoMessage() {}\n\nfunc (x *AdRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_demo_proto_msgTypes[29]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use AdRequest.ProtoReflect.Descriptor instead.\nfunc (*AdRequest) Descriptor() ([]byte, []int) {\n\treturn file_demo_proto_rawDescGZIP(), []int{29}\n}\n\nfunc (x *AdRequest) GetContextKeys() []string {\n\tif x != nil {\n\t\treturn x.ContextKeys\n\t}\n\treturn nil\n}\n\ntype AdResponse struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tAds []*Ad `protobuf:\"bytes,1,rep,name=ads,proto3\" json:\"ads,omitempty\"`\n}\n\nfunc (x *AdResponse) Reset() {\n\t*x = AdResponse{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_demo_proto_msgTypes[30]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *AdResponse) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*AdResponse) ProtoMessage() {}\n\nfunc (x *AdResponse) ProtoReflect() protoreflect.Message {\n\tmi := &file_demo_proto_msgTypes[30]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use AdResponse.ProtoReflect.Descriptor instead.\nfunc (*AdResponse) Descriptor() ([]byte, []int) {\n\treturn file_demo_proto_rawDescGZIP(), []int{30}\n}\n\nfunc (x *AdResponse) GetAds() []*Ad {\n\tif x != nil {\n\t\treturn x.Ads\n\t}\n\treturn nil\n}\n\ntype Ad struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// url to redirect to when an ad is clicked.\n\tRedirectUrl string `protobuf:\"bytes,1,opt,name=redirect_url,json=redirectUrl,proto3\" json:\"redirect_url,omitempty\"`\n\t// short advertisement text to display.\n\tText string `protobuf:\"bytes,2,opt,name=text,proto3\" json:\"text,omitempty\"`\n}\n\nfunc (x *Ad) Reset() {\n\t*x = Ad{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_demo_proto_msgTypes[31]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *Ad) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Ad) ProtoMessage() {}\n\nfunc (x *Ad) ProtoReflect() protoreflect.Message {\n\tmi := &file_demo_proto_msgTypes[31]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Ad.ProtoReflect.Descriptor instead.\nfunc (*Ad) Descriptor() ([]byte, []int) {\n\treturn file_demo_proto_rawDescGZIP(), []int{31}\n}\n\nfunc (x *Ad) GetRedirectUrl() string {\n\tif x != nil {\n\t\treturn x.RedirectUrl\n\t}\n\treturn \"\"\n}\n\nfunc (x *Ad) GetText() string {\n\tif x != nil {\n\t\treturn x.Text\n\t}\n\treturn \"\"\n}\n\nvar File_demo_proto protoreflect.FileDescriptor\n\nvar file_demo_proto_rawDesc = []byte{\n\t0x0a, 0x0a, 0x64, 0x65, 0x6d, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0b, 0x68, 0x69,\n\t0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x22, 0x45, 0x0a, 0x08, 0x43, 0x61, 0x72,\n\t0x74, 0x49, 0x74, 0x65, 0x6d, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74,\n\t0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x70, 0x72, 0x6f, 0x64, 0x75,\n\t0x63, 0x74, 0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x71, 0x75, 0x61, 0x6e, 0x74, 0x69, 0x74, 0x79,\n\t0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x71, 0x75, 0x61, 0x6e, 0x74, 0x69, 0x74, 0x79,\n\t0x22, 0x54, 0x0a, 0x0e, 0x41, 0x64, 0x64, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65,\n\t0x73, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20,\n\t0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x29, 0x0a, 0x04, 0x69,\n\t0x74, 0x65, 0x6d, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x68, 0x69, 0x70, 0x73,\n\t0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x43, 0x61, 0x72, 0x74, 0x49, 0x74, 0x65, 0x6d,\n\t0x52, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x22, 0x2b, 0x0a, 0x10, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x43,\n\t0x61, 0x72, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73,\n\t0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65,\n\t0x72, 0x49, 0x64, 0x22, 0x29, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x43, 0x61, 0x72, 0x74, 0x52, 0x65,\n\t0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64,\n\t0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0x4c,\n\t0x0a, 0x04, 0x43, 0x61, 0x72, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69,\n\t0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12,\n\t0x2b, 0x0a, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15,\n\t0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x43, 0x61, 0x72,\n\t0x74, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x22, 0x07, 0x0a, 0x05,\n\t0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x56, 0x0a, 0x1a, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x63,\n\t0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75,\n\t0x65, 0x73, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01,\n\t0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b,\n\t0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28,\n\t0x09, 0x52, 0x0a, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x49, 0x64, 0x73, 0x22, 0x3e, 0x0a,\n\t0x1b, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x64, 0x61, 0x74,\n\t0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1f, 0x0a, 0x0b,\n\t0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28,\n\t0x09, 0x52, 0x0a, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x49, 0x64, 0x73, 0x22, 0xba, 0x01,\n\t0x0a, 0x07, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18,\n\t0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d,\n\t0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x20, 0x0a,\n\t0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01,\n\t0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12,\n\t0x18, 0x0a, 0x07, 0x70, 0x69, 0x63, 0x74, 0x75, 0x72, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09,\n\t0x52, 0x07, 0x70, 0x69, 0x63, 0x74, 0x75, 0x72, 0x65, 0x12, 0x2f, 0x0a, 0x09, 0x70, 0x72, 0x69,\n\t0x63, 0x65, 0x5f, 0x75, 0x73, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x68,\n\t0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x4d, 0x6f, 0x6e, 0x65, 0x79,\n\t0x52, 0x08, 0x70, 0x72, 0x69, 0x63, 0x65, 0x55, 0x73, 0x64, 0x12, 0x1e, 0x0a, 0x0a, 0x63, 0x61,\n\t0x74, 0x65, 0x67, 0x6f, 0x72, 0x69, 0x65, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a,\n\t0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x69, 0x65, 0x73, 0x22, 0x48, 0x0a, 0x14, 0x4c, 0x69,\n\t0x73, 0x74, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,\n\t0x73, 0x65, 0x12, 0x30, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x73, 0x18, 0x01,\n\t0x20, 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68,\n\t0x6f, 0x70, 0x2e, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x64,\n\t0x75, 0x63, 0x74, 0x73, 0x22, 0x23, 0x0a, 0x11, 0x47, 0x65, 0x74, 0x50, 0x72, 0x6f, 0x64, 0x75,\n\t0x63, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18,\n\t0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x22, 0x2d, 0x0a, 0x15, 0x53, 0x65, 0x61,\n\t0x72, 0x63, 0x68, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65,\n\t0x73, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28,\n\t0x09, 0x52, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x22, 0x48, 0x0a, 0x16, 0x53, 0x65, 0x61, 0x72,\n\t0x63, 0x68, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,\n\t0x73, 0x65, 0x12, 0x2e, 0x0a, 0x07, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x18, 0x01, 0x20,\n\t0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f,\n\t0x70, 0x2e, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x52, 0x07, 0x72, 0x65, 0x73, 0x75, 0x6c,\n\t0x74, 0x73, 0x22, 0x6e, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x51, 0x75, 0x6f, 0x74, 0x65, 0x52, 0x65,\n\t0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73,\n\t0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72,\n\t0x73, 0x68, 0x6f, 0x70, 0x2e, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, 0x07, 0x61, 0x64,\n\t0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x2b, 0x0a, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x18, 0x02,\n\t0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68,\n\t0x6f, 0x70, 0x2e, 0x43, 0x61, 0x72, 0x74, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x05, 0x69, 0x74, 0x65,\n\t0x6d, 0x73, 0x22, 0x41, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x51, 0x75, 0x6f, 0x74, 0x65, 0x52, 0x65,\n\t0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2d, 0x0a, 0x08, 0x63, 0x6f, 0x73, 0x74, 0x5f, 0x75,\n\t0x73, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74,\n\t0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x4d, 0x6f, 0x6e, 0x65, 0x79, 0x52, 0x07, 0x63, 0x6f,\n\t0x73, 0x74, 0x55, 0x73, 0x64, 0x22, 0x6f, 0x0a, 0x10, 0x53, 0x68, 0x69, 0x70, 0x4f, 0x72, 0x64,\n\t0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x07, 0x61, 0x64, 0x64,\n\t0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x68, 0x69, 0x70,\n\t0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73,\n\t0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x2b, 0x0a, 0x05, 0x69, 0x74, 0x65,\n\t0x6d, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74,\n\t0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x43, 0x61, 0x72, 0x74, 0x49, 0x74, 0x65, 0x6d, 0x52,\n\t0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x22, 0x34, 0x0a, 0x11, 0x53, 0x68, 0x69, 0x70, 0x4f, 0x72,\n\t0x64, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x74,\n\t0x72, 0x61, 0x63, 0x6b, 0x69, 0x6e, 0x67, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,\n\t0x52, 0x0a, 0x74, 0x72, 0x61, 0x63, 0x6b, 0x69, 0x6e, 0x67, 0x49, 0x64, 0x22, 0x8f, 0x01, 0x0a,\n\t0x07, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x25, 0x0a, 0x0e, 0x73, 0x74, 0x72, 0x65,\n\t0x65, 0x74, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,\n\t0x52, 0x0d, 0x73, 0x74, 0x72, 0x65, 0x65, 0x74, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12,\n\t0x12, 0x0a, 0x04, 0x63, 0x69, 0x74, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x63,\n\t0x69, 0x74, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01,\n\t0x28, 0x09, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x75,\n\t0x6e, 0x74, 0x72, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x6f, 0x75, 0x6e,\n\t0x74, 0x72, 0x79, 0x12, 0x19, 0x0a, 0x08, 0x7a, 0x69, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18,\n\t0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x07, 0x7a, 0x69, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x22, 0x58,\n\t0x0a, 0x05, 0x4d, 0x6f, 0x6e, 0x65, 0x79, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x75, 0x72, 0x72, 0x65,\n\t0x6e, 0x63, 0x79, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c,\n\t0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x14, 0x0a, 0x05,\n\t0x75, 0x6e, 0x69, 0x74, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x75, 0x6e, 0x69,\n\t0x74, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x6e, 0x61, 0x6e, 0x6f, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28,\n\t0x05, 0x52, 0x05, 0x6e, 0x61, 0x6e, 0x6f, 0x73, 0x22, 0x47, 0x0a, 0x1e, 0x47, 0x65, 0x74, 0x53,\n\t0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x69,\n\t0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x63, 0x75,\n\t0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03,\n\t0x28, 0x09, 0x52, 0x0d, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x43, 0x6f, 0x64, 0x65,\n\t0x73, 0x22, 0x5c, 0x0a, 0x19, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x43, 0x6f, 0x6e,\n\t0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x26,\n\t0x0a, 0x04, 0x66, 0x72, 0x6f, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x68,\n\t0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x4d, 0x6f, 0x6e, 0x65, 0x79,\n\t0x52, 0x04, 0x66, 0x72, 0x6f, 0x6d, 0x12, 0x17, 0x0a, 0x07, 0x74, 0x6f, 0x5f, 0x63, 0x6f, 0x64,\n\t0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x74, 0x6f, 0x43, 0x6f, 0x64, 0x65, 0x22,\n\t0xe6, 0x01, 0x0a, 0x0e, 0x43, 0x72, 0x65, 0x64, 0x69, 0x74, 0x43, 0x61, 0x72, 0x64, 0x49, 0x6e,\n\t0x66, 0x6f, 0x12, 0x2c, 0x0a, 0x12, 0x63, 0x72, 0x65, 0x64, 0x69, 0x74, 0x5f, 0x63, 0x61, 0x72,\n\t0x64, 0x5f, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10,\n\t0x63, 0x72, 0x65, 0x64, 0x69, 0x74, 0x43, 0x61, 0x72, 0x64, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72,\n\t0x12, 0x26, 0x0a, 0x0f, 0x63, 0x72, 0x65, 0x64, 0x69, 0x74, 0x5f, 0x63, 0x61, 0x72, 0x64, 0x5f,\n\t0x63, 0x76, 0x76, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0d, 0x63, 0x72, 0x65, 0x64, 0x69,\n\t0x74, 0x43, 0x61, 0x72, 0x64, 0x43, 0x76, 0x76, 0x12, 0x3d, 0x0a, 0x1b, 0x63, 0x72, 0x65, 0x64,\n\t0x69, 0x74, 0x5f, 0x63, 0x61, 0x72, 0x64, 0x5f, 0x65, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69,\n\t0x6f, 0x6e, 0x5f, 0x79, 0x65, 0x61, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x18, 0x63,\n\t0x72, 0x65, 0x64, 0x69, 0x74, 0x43, 0x61, 0x72, 0x64, 0x45, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74,\n\t0x69, 0x6f, 0x6e, 0x59, 0x65, 0x61, 0x72, 0x12, 0x3f, 0x0a, 0x1c, 0x63, 0x72, 0x65, 0x64, 0x69,\n\t0x74, 0x5f, 0x63, 0x61, 0x72, 0x64, 0x5f, 0x65, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f,\n\t0x6e, 0x5f, 0x6d, 0x6f, 0x6e, 0x74, 0x68, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x19, 0x63,\n\t0x72, 0x65, 0x64, 0x69, 0x74, 0x43, 0x61, 0x72, 0x64, 0x45, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74,\n\t0x69, 0x6f, 0x6e, 0x4d, 0x6f, 0x6e, 0x74, 0x68, 0x22, 0x79, 0x0a, 0x0d, 0x43, 0x68, 0x61, 0x72,\n\t0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2a, 0x0a, 0x06, 0x61, 0x6d, 0x6f,\n\t0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x68, 0x69, 0x70, 0x73,\n\t0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x4d, 0x6f, 0x6e, 0x65, 0x79, 0x52, 0x06, 0x61,\n\t0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x3c, 0x0a, 0x0b, 0x63, 0x72, 0x65, 0x64, 0x69, 0x74, 0x5f,\n\t0x63, 0x61, 0x72, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x68, 0x69, 0x70,\n\t0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x43, 0x72, 0x65, 0x64, 0x69, 0x74, 0x43,\n\t0x61, 0x72, 0x64, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x0a, 0x63, 0x72, 0x65, 0x64, 0x69, 0x74, 0x43,\n\t0x61, 0x72, 0x64, 0x22, 0x37, 0x0a, 0x0e, 0x43, 0x68, 0x61, 0x72, 0x67, 0x65, 0x52, 0x65, 0x73,\n\t0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63,\n\t0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x74,\n\t0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x22, 0x5e, 0x0a, 0x09,\n\t0x4f, 0x72, 0x64, 0x65, 0x72, 0x49, 0x74, 0x65, 0x6d, 0x12, 0x29, 0x0a, 0x04, 0x69, 0x74, 0x65,\n\t0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65,\n\t0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x43, 0x61, 0x72, 0x74, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x04,\n\t0x69, 0x74, 0x65, 0x6d, 0x12, 0x26, 0x0a, 0x04, 0x63, 0x6f, 0x73, 0x74, 0x18, 0x02, 0x20, 0x01,\n\t0x28, 0x0b, 0x32, 0x12, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70,\n\t0x2e, 0x4d, 0x6f, 0x6e, 0x65, 0x79, 0x52, 0x04, 0x63, 0x6f, 0x73, 0x74, 0x22, 0x82, 0x02, 0x0a,\n\t0x0b, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x19, 0x0a, 0x08,\n\t0x6f, 0x72, 0x64, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07,\n\t0x6f, 0x72, 0x64, 0x65, 0x72, 0x49, 0x64, 0x12, 0x30, 0x0a, 0x14, 0x73, 0x68, 0x69, 0x70, 0x70,\n\t0x69, 0x6e, 0x67, 0x5f, 0x74, 0x72, 0x61, 0x63, 0x6b, 0x69, 0x6e, 0x67, 0x5f, 0x69, 0x64, 0x18,\n\t0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x12, 0x73, 0x68, 0x69, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x54,\n\t0x72, 0x61, 0x63, 0x6b, 0x69, 0x6e, 0x67, 0x49, 0x64, 0x12, 0x37, 0x0a, 0x0d, 0x73, 0x68, 0x69,\n\t0x70, 0x70, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x6f, 0x73, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b,\n\t0x32, 0x12, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x4d,\n\t0x6f, 0x6e, 0x65, 0x79, 0x52, 0x0c, 0x73, 0x68, 0x69, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x43, 0x6f,\n\t0x73, 0x74, 0x12, 0x3f, 0x0a, 0x10, 0x73, 0x68, 0x69, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x5f, 0x61,\n\t0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x68,\n\t0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x41, 0x64, 0x64, 0x72, 0x65,\n\t0x73, 0x73, 0x52, 0x0f, 0x73, 0x68, 0x69, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x41, 0x64, 0x64, 0x72,\n\t0x65, 0x73, 0x73, 0x12, 0x2c, 0x0a, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x18, 0x05, 0x20, 0x03,\n\t0x28, 0x0b, 0x32, 0x16, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70,\n\t0x2e, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d,\n\t0x73, 0x22, 0x64, 0x0a, 0x1c, 0x53, 0x65, 0x6e, 0x64, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x43, 0x6f,\n\t0x6e, 0x66, 0x69, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,\n\t0x74, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,\n\t0x52, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x12, 0x2e, 0x0a, 0x05, 0x6f, 0x72, 0x64, 0x65, 0x72,\n\t0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72,\n\t0x73, 0x68, 0x6f, 0x70, 0x2e, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74,\n\t0x52, 0x05, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x22, 0xd5, 0x01, 0x0a, 0x11, 0x50, 0x6c, 0x61, 0x63,\n\t0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x17, 0x0a,\n\t0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06,\n\t0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x63,\n\t0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x75,\n\t0x73, 0x65, 0x72, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x12, 0x2e, 0x0a, 0x07, 0x61,\n\t0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x68,\n\t0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x41, 0x64, 0x64, 0x72, 0x65,\n\t0x73, 0x73, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x65,\n\t0x6d, 0x61, 0x69, 0x6c, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x6d, 0x61, 0x69,\n\t0x6c, 0x12, 0x3c, 0x0a, 0x0b, 0x63, 0x72, 0x65, 0x64, 0x69, 0x74, 0x5f, 0x63, 0x61, 0x72, 0x64,\n\t0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72,\n\t0x73, 0x68, 0x6f, 0x70, 0x2e, 0x43, 0x72, 0x65, 0x64, 0x69, 0x74, 0x43, 0x61, 0x72, 0x64, 0x49,\n\t0x6e, 0x66, 0x6f, 0x52, 0x0a, 0x63, 0x72, 0x65, 0x64, 0x69, 0x74, 0x43, 0x61, 0x72, 0x64, 0x22,\n\t0x44, 0x0a, 0x12, 0x50, 0x6c, 0x61, 0x63, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x73,\n\t0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2e, 0x0a, 0x05, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x18, 0x01,\n\t0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68,\n\t0x6f, 0x70, 0x2e, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x05,\n\t0x6f, 0x72, 0x64, 0x65, 0x72, 0x22, 0x2e, 0x0a, 0x09, 0x41, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65,\n\t0x73, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x5f, 0x6b, 0x65,\n\t0x79, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78,\n\t0x74, 0x4b, 0x65, 0x79, 0x73, 0x22, 0x2f, 0x0a, 0x0a, 0x41, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f,\n\t0x6e, 0x73, 0x65, 0x12, 0x21, 0x0a, 0x03, 0x61, 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b,\n\t0x32, 0x0f, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x41,\n\t0x64, 0x52, 0x03, 0x61, 0x64, 0x73, 0x22, 0x3b, 0x0a, 0x02, 0x41, 0x64, 0x12, 0x21, 0x0a, 0x0c,\n\t0x72, 0x65, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01,\n\t0x28, 0x09, 0x52, 0x0b, 0x72, 0x65, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x55, 0x72, 0x6c, 0x12,\n\t0x12, 0x0a, 0x04, 0x74, 0x65, 0x78, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74,\n\t0x65, 0x78, 0x74, 0x32, 0xca, 0x01, 0x0a, 0x0b, 0x43, 0x61, 0x72, 0x74, 0x53, 0x65, 0x72, 0x76,\n\t0x69, 0x63, 0x65, 0x12, 0x3c, 0x0a, 0x07, 0x41, 0x64, 0x64, 0x49, 0x74, 0x65, 0x6d, 0x12, 0x1b,\n\t0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x41, 0x64, 0x64,\n\t0x49, 0x74, 0x65, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x68, 0x69,\n\t0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22,\n\t0x00, 0x12, 0x3b, 0x0a, 0x07, 0x47, 0x65, 0x74, 0x43, 0x61, 0x72, 0x74, 0x12, 0x1b, 0x2e, 0x68,\n\t0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x61,\n\t0x72, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x11, 0x2e, 0x68, 0x69, 0x70, 0x73,\n\t0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x43, 0x61, 0x72, 0x74, 0x22, 0x00, 0x12, 0x40,\n\t0x0a, 0x09, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x43, 0x61, 0x72, 0x74, 0x12, 0x1d, 0x2e, 0x68, 0x69,\n\t0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x43,\n\t0x61, 0x72, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x68, 0x69, 0x70,\n\t0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00,\n\t0x32, 0x83, 0x01, 0x0a, 0x15, 0x52, 0x65, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x64, 0x61, 0x74,\n\t0x69, 0x6f, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x6a, 0x0a, 0x13, 0x4c, 0x69,\n\t0x73, 0x74, 0x52, 0x65, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e,\n\t0x73, 0x12, 0x27, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e,\n\t0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x64, 0x61, 0x74, 0x69,\n\t0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, 0x68, 0x69, 0x70,\n\t0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x63,\n\t0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70,\n\t0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x32, 0x83, 0x02, 0x0a, 0x15, 0x50, 0x72, 0x6f, 0x64, 0x75,\n\t0x63, 0x74, 0x43, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65,\n\t0x12, 0x47, 0x0a, 0x0c, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x73,\n\t0x12, 0x12, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x45,\n\t0x6d, 0x70, 0x74, 0x79, 0x1a, 0x21, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68,\n\t0x6f, 0x70, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x73, 0x52,\n\t0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x44, 0x0a, 0x0a, 0x47, 0x65, 0x74,\n\t0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x12, 0x1e, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65,\n\t0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x47, 0x65, 0x74, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74,\n\t0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65,\n\t0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x22, 0x00, 0x12,\n\t0x5b, 0x0a, 0x0e, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74,\n\t0x73, 0x12, 0x22, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e,\n\t0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x73, 0x52, 0x65,\n\t0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73,\n\t0x68, 0x6f, 0x70, 0x2e, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63,\n\t0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x32, 0xaa, 0x01, 0x0a,\n\t0x0f, 0x53, 0x68, 0x69, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65,\n\t0x12, 0x49, 0x0a, 0x08, 0x47, 0x65, 0x74, 0x51, 0x75, 0x6f, 0x74, 0x65, 0x12, 0x1c, 0x2e, 0x68,\n\t0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x47, 0x65, 0x74, 0x51, 0x75,\n\t0x6f, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x68, 0x69, 0x70,\n\t0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x47, 0x65, 0x74, 0x51, 0x75, 0x6f, 0x74,\n\t0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x4c, 0x0a, 0x09, 0x53,\n\t0x68, 0x69, 0x70, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x1d, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74,\n\t0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x53, 0x68, 0x69, 0x70, 0x4f, 0x72, 0x64, 0x65, 0x72,\n\t0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65,\n\t0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x53, 0x68, 0x69, 0x70, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52,\n\t0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x32, 0xb7, 0x01, 0x0a, 0x0f, 0x43, 0x75,\n\t0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x5b, 0x0a,\n\t0x16, 0x47, 0x65, 0x74, 0x53, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x43, 0x75, 0x72,\n\t0x72, 0x65, 0x6e, 0x63, 0x69, 0x65, 0x73, 0x12, 0x12, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65,\n\t0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x2b, 0x2e, 0x68, 0x69,\n\t0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x75, 0x70,\n\t0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x69, 0x65, 0x73,\n\t0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x47, 0x0a, 0x07, 0x43, 0x6f,\n\t0x6e, 0x76, 0x65, 0x72, 0x74, 0x12, 0x26, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73,\n\t0x68, 0x6f, 0x70, 0x2e, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x43, 0x6f, 0x6e, 0x76,\n\t0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e,\n\t0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x4d, 0x6f, 0x6e, 0x65,\n\t0x79, 0x22, 0x00, 0x32, 0x55, 0x0a, 0x0e, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x65,\n\t0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x43, 0x0a, 0x06, 0x43, 0x68, 0x61, 0x72, 0x67, 0x65, 0x12,\n\t0x1a, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x43, 0x68,\n\t0x61, 0x72, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x68, 0x69,\n\t0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x43, 0x68, 0x61, 0x72, 0x67, 0x65,\n\t0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x32, 0x68, 0x0a, 0x0c, 0x45, 0x6d,\n\t0x61, 0x69, 0x6c, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x58, 0x0a, 0x15, 0x53, 0x65,\n\t0x6e, 0x64, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x61, 0x74,\n\t0x69, 0x6f, 0x6e, 0x12, 0x29, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f,\n\t0x70, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69,\n\t0x72, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12,\n\t0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x45, 0x6d, 0x70,\n\t0x74, 0x79, 0x22, 0x00, 0x32, 0x62, 0x0a, 0x0f, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x6f, 0x75, 0x74,\n\t0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x4f, 0x0a, 0x0a, 0x50, 0x6c, 0x61, 0x63, 0x65,\n\t0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x1e, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73,\n\t0x68, 0x6f, 0x70, 0x2e, 0x50, 0x6c, 0x61, 0x63, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65,\n\t0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73,\n\t0x68, 0x6f, 0x70, 0x2e, 0x50, 0x6c, 0x61, 0x63, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65,\n\t0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x32, 0x48, 0x0a, 0x09, 0x41, 0x64, 0x53, 0x65,\n\t0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x3b, 0x0a, 0x06, 0x47, 0x65, 0x74, 0x41, 0x64, 0x73, 0x12,\n\t0x16, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x41, 0x64,\n\t0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65,\n\t0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x41, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,\n\t0x22, 0x00, 0x42, 0x40, 0x5a, 0x3e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d,\n\t0x2f, 0x47, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x43, 0x6c, 0x6f, 0x75, 0x64, 0x50, 0x6c, 0x61, 0x74,\n\t0x66, 0x6f, 0x72, 0x6d, 0x2f, 0x6d, 0x69, 0x73, 0x63, 0x72, 0x6f, 0x73, 0x65, 0x72, 0x76, 0x69,\n\t0x63, 0x65, 0x73, 0x2d, 0x64, 0x65, 0x6d, 0x6f, 0x2f, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72,\n\t0x73, 0x68, 0x6f, 0x70, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,\n}\n\nvar (\n\tfile_demo_proto_rawDescOnce sync.Once\n\tfile_demo_proto_rawDescData = file_demo_proto_rawDesc\n)\n\nfunc file_demo_proto_rawDescGZIP() []byte {\n\tfile_demo_proto_rawDescOnce.Do(func() {\n\t\tfile_demo_proto_rawDescData = protoimpl.X.CompressGZIP(file_demo_proto_rawDescData)\n\t})\n\treturn file_demo_proto_rawDescData\n}\n\nvar file_demo_proto_msgTypes = make([]protoimpl.MessageInfo, 32)\nvar file_demo_proto_goTypes = []any{\n\t(*CartItem)(nil),                       // 0: hipstershop.CartItem\n\t(*AddItemRequest)(nil),                 // 1: hipstershop.AddItemRequest\n\t(*EmptyCartRequest)(nil),               // 2: hipstershop.EmptyCartRequest\n\t(*GetCartRequest)(nil),                 // 3: hipstershop.GetCartRequest\n\t(*Cart)(nil),                           // 4: hipstershop.Cart\n\t(*Empty)(nil),                          // 5: hipstershop.Empty\n\t(*ListRecommendationsRequest)(nil),     // 6: hipstershop.ListRecommendationsRequest\n\t(*ListRecommendationsResponse)(nil),    // 7: hipstershop.ListRecommendationsResponse\n\t(*Product)(nil),                        // 8: hipstershop.Product\n\t(*ListProductsResponse)(nil),           // 9: hipstershop.ListProductsResponse\n\t(*GetProductRequest)(nil),              // 10: hipstershop.GetProductRequest\n\t(*SearchProductsRequest)(nil),          // 11: hipstershop.SearchProductsRequest\n\t(*SearchProductsResponse)(nil),         // 12: hipstershop.SearchProductsResponse\n\t(*GetQuoteRequest)(nil),                // 13: hipstershop.GetQuoteRequest\n\t(*GetQuoteResponse)(nil),               // 14: hipstershop.GetQuoteResponse\n\t(*ShipOrderRequest)(nil),               // 15: hipstershop.ShipOrderRequest\n\t(*ShipOrderResponse)(nil),              // 16: hipstershop.ShipOrderResponse\n\t(*Address)(nil),                        // 17: hipstershop.Address\n\t(*Money)(nil),                          // 18: hipstershop.Money\n\t(*GetSupportedCurrenciesResponse)(nil), // 19: hipstershop.GetSupportedCurrenciesResponse\n\t(*CurrencyConversionRequest)(nil),      // 20: hipstershop.CurrencyConversionRequest\n\t(*CreditCardInfo)(nil),                 // 21: hipstershop.CreditCardInfo\n\t(*ChargeRequest)(nil),                  // 22: hipstershop.ChargeRequest\n\t(*ChargeResponse)(nil),                 // 23: hipstershop.ChargeResponse\n\t(*OrderItem)(nil),                      // 24: hipstershop.OrderItem\n\t(*OrderResult)(nil),                    // 25: hipstershop.OrderResult\n\t(*SendOrderConfirmationRequest)(nil),   // 26: hipstershop.SendOrderConfirmationRequest\n\t(*PlaceOrderRequest)(nil),              // 27: hipstershop.PlaceOrderRequest\n\t(*PlaceOrderResponse)(nil),             // 28: hipstershop.PlaceOrderResponse\n\t(*AdRequest)(nil),                      // 29: hipstershop.AdRequest\n\t(*AdResponse)(nil),                     // 30: hipstershop.AdResponse\n\t(*Ad)(nil),                             // 31: hipstershop.Ad\n}\nvar file_demo_proto_depIdxs = []int32{\n\t0,  // 0: hipstershop.AddItemRequest.item:type_name -> hipstershop.CartItem\n\t0,  // 1: hipstershop.Cart.items:type_name -> hipstershop.CartItem\n\t18, // 2: hipstershop.Product.price_usd:type_name -> hipstershop.Money\n\t8,  // 3: hipstershop.ListProductsResponse.products:type_name -> hipstershop.Product\n\t8,  // 4: hipstershop.SearchProductsResponse.results:type_name -> hipstershop.Product\n\t17, // 5: hipstershop.GetQuoteRequest.address:type_name -> hipstershop.Address\n\t0,  // 6: hipstershop.GetQuoteRequest.items:type_name -> hipstershop.CartItem\n\t18, // 7: hipstershop.GetQuoteResponse.cost_usd:type_name -> hipstershop.Money\n\t17, // 8: hipstershop.ShipOrderRequest.address:type_name -> hipstershop.Address\n\t0,  // 9: hipstershop.ShipOrderRequest.items:type_name -> hipstershop.CartItem\n\t18, // 10: hipstershop.CurrencyConversionRequest.from:type_name -> hipstershop.Money\n\t18, // 11: hipstershop.ChargeRequest.amount:type_name -> hipstershop.Money\n\t21, // 12: hipstershop.ChargeRequest.credit_card:type_name -> hipstershop.CreditCardInfo\n\t0,  // 13: hipstershop.OrderItem.item:type_name -> hipstershop.CartItem\n\t18, // 14: hipstershop.OrderItem.cost:type_name -> hipstershop.Money\n\t18, // 15: hipstershop.OrderResult.shipping_cost:type_name -> hipstershop.Money\n\t17, // 16: hipstershop.OrderResult.shipping_address:type_name -> hipstershop.Address\n\t24, // 17: hipstershop.OrderResult.items:type_name -> hipstershop.OrderItem\n\t25, // 18: hipstershop.SendOrderConfirmationRequest.order:type_name -> hipstershop.OrderResult\n\t17, // 19: hipstershop.PlaceOrderRequest.address:type_name -> hipstershop.Address\n\t21, // 20: hipstershop.PlaceOrderRequest.credit_card:type_name -> hipstershop.CreditCardInfo\n\t25, // 21: hipstershop.PlaceOrderResponse.order:type_name -> hipstershop.OrderResult\n\t31, // 22: hipstershop.AdResponse.ads:type_name -> hipstershop.Ad\n\t1,  // 23: hipstershop.CartService.AddItem:input_type -> hipstershop.AddItemRequest\n\t3,  // 24: hipstershop.CartService.GetCart:input_type -> hipstershop.GetCartRequest\n\t2,  // 25: hipstershop.CartService.EmptyCart:input_type -> hipstershop.EmptyCartRequest\n\t6,  // 26: hipstershop.RecommendationService.ListRecommendations:input_type -> hipstershop.ListRecommendationsRequest\n\t5,  // 27: hipstershop.ProductCatalogService.ListProducts:input_type -> hipstershop.Empty\n\t10, // 28: hipstershop.ProductCatalogService.GetProduct:input_type -> hipstershop.GetProductRequest\n\t11, // 29: hipstershop.ProductCatalogService.SearchProducts:input_type -> hipstershop.SearchProductsRequest\n\t13, // 30: hipstershop.ShippingService.GetQuote:input_type -> hipstershop.GetQuoteRequest\n\t15, // 31: hipstershop.ShippingService.ShipOrder:input_type -> hipstershop.ShipOrderRequest\n\t5,  // 32: hipstershop.CurrencyService.GetSupportedCurrencies:input_type -> hipstershop.Empty\n\t20, // 33: hipstershop.CurrencyService.Convert:input_type -> hipstershop.CurrencyConversionRequest\n\t22, // 34: hipstershop.PaymentService.Charge:input_type -> hipstershop.ChargeRequest\n\t26, // 35: hipstershop.EmailService.SendOrderConfirmation:input_type -> hipstershop.SendOrderConfirmationRequest\n\t27, // 36: hipstershop.CheckoutService.PlaceOrder:input_type -> hipstershop.PlaceOrderRequest\n\t29, // 37: hipstershop.AdService.GetAds:input_type -> hipstershop.AdRequest\n\t5,  // 38: hipstershop.CartService.AddItem:output_type -> hipstershop.Empty\n\t4,  // 39: hipstershop.CartService.GetCart:output_type -> hipstershop.Cart\n\t5,  // 40: hipstershop.CartService.EmptyCart:output_type -> hipstershop.Empty\n\t7,  // 41: hipstershop.RecommendationService.ListRecommendations:output_type -> hipstershop.ListRecommendationsResponse\n\t9,  // 42: hipstershop.ProductCatalogService.ListProducts:output_type -> hipstershop.ListProductsResponse\n\t8,  // 43: hipstershop.ProductCatalogService.GetProduct:output_type -> hipstershop.Product\n\t12, // 44: hipstershop.ProductCatalogService.SearchProducts:output_type -> hipstershop.SearchProductsResponse\n\t14, // 45: hipstershop.ShippingService.GetQuote:output_type -> hipstershop.GetQuoteResponse\n\t16, // 46: hipstershop.ShippingService.ShipOrder:output_type -> hipstershop.ShipOrderResponse\n\t19, // 47: hipstershop.CurrencyService.GetSupportedCurrencies:output_type -> hipstershop.GetSupportedCurrenciesResponse\n\t18, // 48: hipstershop.CurrencyService.Convert:output_type -> hipstershop.Money\n\t23, // 49: hipstershop.PaymentService.Charge:output_type -> hipstershop.ChargeResponse\n\t5,  // 50: hipstershop.EmailService.SendOrderConfirmation:output_type -> hipstershop.Empty\n\t28, // 51: hipstershop.CheckoutService.PlaceOrder:output_type -> hipstershop.PlaceOrderResponse\n\t30, // 52: hipstershop.AdService.GetAds:output_type -> hipstershop.AdResponse\n\t38, // [38:53] is the sub-list for method output_type\n\t23, // [23:38] is the sub-list for method input_type\n\t23, // [23:23] is the sub-list for extension type_name\n\t23, // [23:23] is the sub-list for extension extendee\n\t0,  // [0:23] is the sub-list for field type_name\n}\n\nfunc init() { file_demo_proto_init() }\nfunc file_demo_proto_init() {\n\tif File_demo_proto != nil {\n\t\treturn\n\t}\n\tif !protoimpl.UnsafeEnabled {\n\t\tfile_demo_proto_msgTypes[0].Exporter = func(v any, i int) any {\n\t\t\tswitch v := v.(*CartItem); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_demo_proto_msgTypes[1].Exporter = func(v any, i int) any {\n\t\t\tswitch v := v.(*AddItemRequest); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_demo_proto_msgTypes[2].Exporter = func(v any, i int) any {\n\t\t\tswitch v := v.(*EmptyCartRequest); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_demo_proto_msgTypes[3].Exporter = func(v any, i int) any {\n\t\t\tswitch v := v.(*GetCartRequest); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_demo_proto_msgTypes[4].Exporter = func(v any, i int) any {\n\t\t\tswitch v := v.(*Cart); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_demo_proto_msgTypes[5].Exporter = func(v any, i int) any {\n\t\t\tswitch v := v.(*Empty); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_demo_proto_msgTypes[6].Exporter = func(v any, i int) any {\n\t\t\tswitch v := v.(*ListRecommendationsRequest); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_demo_proto_msgTypes[7].Exporter = func(v any, i int) any {\n\t\t\tswitch v := v.(*ListRecommendationsResponse); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_demo_proto_msgTypes[8].Exporter = func(v any, i int) any {\n\t\t\tswitch v := v.(*Product); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_demo_proto_msgTypes[9].Exporter = func(v any, i int) any {\n\t\t\tswitch v := v.(*ListProductsResponse); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_demo_proto_msgTypes[10].Exporter = func(v any, i int) any {\n\t\t\tswitch v := v.(*GetProductRequest); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_demo_proto_msgTypes[11].Exporter = func(v any, i int) any {\n\t\t\tswitch v := v.(*SearchProductsRequest); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_demo_proto_msgTypes[12].Exporter = func(v any, i int) any {\n\t\t\tswitch v := v.(*SearchProductsResponse); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_demo_proto_msgTypes[13].Exporter = func(v any, i int) any {\n\t\t\tswitch v := v.(*GetQuoteRequest); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_demo_proto_msgTypes[14].Exporter = func(v any, i int) any {\n\t\t\tswitch v := v.(*GetQuoteResponse); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_demo_proto_msgTypes[15].Exporter = func(v any, i int) any {\n\t\t\tswitch v := v.(*ShipOrderRequest); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_demo_proto_msgTypes[16].Exporter = func(v any, i int) any {\n\t\t\tswitch v := v.(*ShipOrderResponse); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_demo_proto_msgTypes[17].Exporter = func(v any, i int) any {\n\t\t\tswitch v := v.(*Address); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_demo_proto_msgTypes[18].Exporter = func(v any, i int) any {\n\t\t\tswitch v := v.(*Money); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_demo_proto_msgTypes[19].Exporter = func(v any, i int) any {\n\t\t\tswitch v := v.(*GetSupportedCurrenciesResponse); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_demo_proto_msgTypes[20].Exporter = func(v any, i int) any {\n\t\t\tswitch v := v.(*CurrencyConversionRequest); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_demo_proto_msgTypes[21].Exporter = func(v any, i int) any {\n\t\t\tswitch v := v.(*CreditCardInfo); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_demo_proto_msgTypes[22].Exporter = func(v any, i int) any {\n\t\t\tswitch v := v.(*ChargeRequest); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_demo_proto_msgTypes[23].Exporter = func(v any, i int) any {\n\t\t\tswitch v := v.(*ChargeResponse); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_demo_proto_msgTypes[24].Exporter = func(v any, i int) any {\n\t\t\tswitch v := v.(*OrderItem); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_demo_proto_msgTypes[25].Exporter = func(v any, i int) any {\n\t\t\tswitch v := v.(*OrderResult); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_demo_proto_msgTypes[26].Exporter = func(v any, i int) any {\n\t\t\tswitch v := v.(*SendOrderConfirmationRequest); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_demo_proto_msgTypes[27].Exporter = func(v any, i int) any {\n\t\t\tswitch v := v.(*PlaceOrderRequest); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_demo_proto_msgTypes[28].Exporter = func(v any, i int) any {\n\t\t\tswitch v := v.(*PlaceOrderResponse); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_demo_proto_msgTypes[29].Exporter = func(v any, i int) any {\n\t\t\tswitch v := v.(*AdRequest); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_demo_proto_msgTypes[30].Exporter = func(v any, i int) any {\n\t\t\tswitch v := v.(*AdResponse); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_demo_proto_msgTypes[31].Exporter = func(v any, i int) any {\n\t\t\tswitch v := v.(*Ad); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: file_demo_proto_rawDesc,\n\t\t\tNumEnums:      0,\n\t\t\tNumMessages:   32,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   9,\n\t\t},\n\t\tGoTypes:           file_demo_proto_goTypes,\n\t\tDependencyIndexes: file_demo_proto_depIdxs,\n\t\tMessageInfos:      file_demo_proto_msgTypes,\n\t}.Build()\n\tFile_demo_proto = out.File\n\tfile_demo_proto_rawDesc = nil\n\tfile_demo_proto_goTypes = nil\n\tfile_demo_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "src/frontend/genproto/demo_grpc.pb.go",
    "content": "// Copyright 2020 Google LLC\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// Code generated by protoc-gen-go-grpc. DO NOT EDIT.\n// versions:\n// - protoc-gen-go-grpc v1.5.1\n// - protoc             v3.6.1\n// source: demo.proto\n\npackage hipstershop\n\nimport (\n\tcontext \"context\"\n\tgrpc \"google.golang.org/grpc\"\n\tcodes \"google.golang.org/grpc/codes\"\n\tstatus \"google.golang.org/grpc/status\"\n)\n\n// This is a compile-time assertion to ensure that this generated file\n// is compatible with the grpc package it is being compiled against.\n// Requires gRPC-Go v1.64.0 or later.\nconst _ = grpc.SupportPackageIsVersion9\n\nconst (\n\tCartService_AddItem_FullMethodName   = \"/hipstershop.CartService/AddItem\"\n\tCartService_GetCart_FullMethodName   = \"/hipstershop.CartService/GetCart\"\n\tCartService_EmptyCart_FullMethodName = \"/hipstershop.CartService/EmptyCart\"\n)\n\n// CartServiceClient is the client API for CartService service.\n//\n// 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.\ntype CartServiceClient interface {\n\tAddItem(ctx context.Context, in *AddItemRequest, opts ...grpc.CallOption) (*Empty, error)\n\tGetCart(ctx context.Context, in *GetCartRequest, opts ...grpc.CallOption) (*Cart, error)\n\tEmptyCart(ctx context.Context, in *EmptyCartRequest, opts ...grpc.CallOption) (*Empty, error)\n}\n\ntype cartServiceClient struct {\n\tcc grpc.ClientConnInterface\n}\n\nfunc NewCartServiceClient(cc grpc.ClientConnInterface) CartServiceClient {\n\treturn &cartServiceClient{cc}\n}\n\nfunc (c *cartServiceClient) AddItem(ctx context.Context, in *AddItemRequest, opts ...grpc.CallOption) (*Empty, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(Empty)\n\terr := c.cc.Invoke(ctx, CartService_AddItem_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *cartServiceClient) GetCart(ctx context.Context, in *GetCartRequest, opts ...grpc.CallOption) (*Cart, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(Cart)\n\terr := c.cc.Invoke(ctx, CartService_GetCart_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *cartServiceClient) EmptyCart(ctx context.Context, in *EmptyCartRequest, opts ...grpc.CallOption) (*Empty, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(Empty)\n\terr := c.cc.Invoke(ctx, CartService_EmptyCart_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\n// CartServiceServer is the server API for CartService service.\n// All implementations must embed UnimplementedCartServiceServer\n// for forward compatibility.\ntype CartServiceServer interface {\n\tAddItem(context.Context, *AddItemRequest) (*Empty, error)\n\tGetCart(context.Context, *GetCartRequest) (*Cart, error)\n\tEmptyCart(context.Context, *EmptyCartRequest) (*Empty, error)\n\tmustEmbedUnimplementedCartServiceServer()\n}\n\n// UnimplementedCartServiceServer must be embedded to have\n// forward compatible implementations.\n//\n// NOTE: this should be embedded by value instead of pointer to avoid a nil\n// pointer dereference when methods are called.\ntype UnimplementedCartServiceServer struct{}\n\nfunc (UnimplementedCartServiceServer) AddItem(context.Context, *AddItemRequest) (*Empty, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method AddItem not implemented\")\n}\nfunc (UnimplementedCartServiceServer) GetCart(context.Context, *GetCartRequest) (*Cart, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method GetCart not implemented\")\n}\nfunc (UnimplementedCartServiceServer) EmptyCart(context.Context, *EmptyCartRequest) (*Empty, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method EmptyCart not implemented\")\n}\nfunc (UnimplementedCartServiceServer) mustEmbedUnimplementedCartServiceServer() {}\nfunc (UnimplementedCartServiceServer) testEmbeddedByValue()                     {}\n\n// UnsafeCartServiceServer may be embedded to opt out of forward compatibility for this service.\n// Use of this interface is not recommended, as added methods to CartServiceServer will\n// result in compilation errors.\ntype UnsafeCartServiceServer interface {\n\tmustEmbedUnimplementedCartServiceServer()\n}\n\nfunc RegisterCartServiceServer(s grpc.ServiceRegistrar, srv CartServiceServer) {\n\t// If the following call pancis, it indicates UnimplementedCartServiceServer was\n\t// embedded by pointer and is nil.  This will cause panics if an\n\t// unimplemented method is ever invoked, so we test this at initialization\n\t// time to prevent it from happening at runtime later due to I/O.\n\tif t, ok := srv.(interface{ testEmbeddedByValue() }); ok {\n\t\tt.testEmbeddedByValue()\n\t}\n\ts.RegisterService(&CartService_ServiceDesc, srv)\n}\n\nfunc _CartService_AddItem_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(AddItemRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(CartServiceServer).AddItem(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: CartService_AddItem_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(CartServiceServer).AddItem(ctx, req.(*AddItemRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _CartService_GetCart_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(GetCartRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(CartServiceServer).GetCart(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: CartService_GetCart_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(CartServiceServer).GetCart(ctx, req.(*GetCartRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _CartService_EmptyCart_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(EmptyCartRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(CartServiceServer).EmptyCart(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: CartService_EmptyCart_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(CartServiceServer).EmptyCart(ctx, req.(*EmptyCartRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\n// CartService_ServiceDesc is the grpc.ServiceDesc for CartService service.\n// It's only intended for direct use with grpc.RegisterService,\n// and not to be introspected or modified (even as a copy)\nvar CartService_ServiceDesc = grpc.ServiceDesc{\n\tServiceName: \"hipstershop.CartService\",\n\tHandlerType: (*CartServiceServer)(nil),\n\tMethods: []grpc.MethodDesc{\n\t\t{\n\t\t\tMethodName: \"AddItem\",\n\t\t\tHandler:    _CartService_AddItem_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"GetCart\",\n\t\t\tHandler:    _CartService_GetCart_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"EmptyCart\",\n\t\t\tHandler:    _CartService_EmptyCart_Handler,\n\t\t},\n\t},\n\tStreams:  []grpc.StreamDesc{},\n\tMetadata: \"demo.proto\",\n}\n\nconst (\n\tRecommendationService_ListRecommendations_FullMethodName = \"/hipstershop.RecommendationService/ListRecommendations\"\n)\n\n// RecommendationServiceClient is the client API for RecommendationService service.\n//\n// 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.\ntype RecommendationServiceClient interface {\n\tListRecommendations(ctx context.Context, in *ListRecommendationsRequest, opts ...grpc.CallOption) (*ListRecommendationsResponse, error)\n}\n\ntype recommendationServiceClient struct {\n\tcc grpc.ClientConnInterface\n}\n\nfunc NewRecommendationServiceClient(cc grpc.ClientConnInterface) RecommendationServiceClient {\n\treturn &recommendationServiceClient{cc}\n}\n\nfunc (c *recommendationServiceClient) ListRecommendations(ctx context.Context, in *ListRecommendationsRequest, opts ...grpc.CallOption) (*ListRecommendationsResponse, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(ListRecommendationsResponse)\n\terr := c.cc.Invoke(ctx, RecommendationService_ListRecommendations_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\n// RecommendationServiceServer is the server API for RecommendationService service.\n// All implementations must embed UnimplementedRecommendationServiceServer\n// for forward compatibility.\ntype RecommendationServiceServer interface {\n\tListRecommendations(context.Context, *ListRecommendationsRequest) (*ListRecommendationsResponse, error)\n\tmustEmbedUnimplementedRecommendationServiceServer()\n}\n\n// UnimplementedRecommendationServiceServer must be embedded to have\n// forward compatible implementations.\n//\n// NOTE: this should be embedded by value instead of pointer to avoid a nil\n// pointer dereference when methods are called.\ntype UnimplementedRecommendationServiceServer struct{}\n\nfunc (UnimplementedRecommendationServiceServer) ListRecommendations(context.Context, *ListRecommendationsRequest) (*ListRecommendationsResponse, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method ListRecommendations not implemented\")\n}\nfunc (UnimplementedRecommendationServiceServer) mustEmbedUnimplementedRecommendationServiceServer() {}\nfunc (UnimplementedRecommendationServiceServer) testEmbeddedByValue()                               {}\n\n// UnsafeRecommendationServiceServer may be embedded to opt out of forward compatibility for this service.\n// Use of this interface is not recommended, as added methods to RecommendationServiceServer will\n// result in compilation errors.\ntype UnsafeRecommendationServiceServer interface {\n\tmustEmbedUnimplementedRecommendationServiceServer()\n}\n\nfunc RegisterRecommendationServiceServer(s grpc.ServiceRegistrar, srv RecommendationServiceServer) {\n\t// If the following call pancis, it indicates UnimplementedRecommendationServiceServer was\n\t// embedded by pointer and is nil.  This will cause panics if an\n\t// unimplemented method is ever invoked, so we test this at initialization\n\t// time to prevent it from happening at runtime later due to I/O.\n\tif t, ok := srv.(interface{ testEmbeddedByValue() }); ok {\n\t\tt.testEmbeddedByValue()\n\t}\n\ts.RegisterService(&RecommendationService_ServiceDesc, srv)\n}\n\nfunc _RecommendationService_ListRecommendations_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(ListRecommendationsRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(RecommendationServiceServer).ListRecommendations(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: RecommendationService_ListRecommendations_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(RecommendationServiceServer).ListRecommendations(ctx, req.(*ListRecommendationsRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\n// RecommendationService_ServiceDesc is the grpc.ServiceDesc for RecommendationService service.\n// It's only intended for direct use with grpc.RegisterService,\n// and not to be introspected or modified (even as a copy)\nvar RecommendationService_ServiceDesc = grpc.ServiceDesc{\n\tServiceName: \"hipstershop.RecommendationService\",\n\tHandlerType: (*RecommendationServiceServer)(nil),\n\tMethods: []grpc.MethodDesc{\n\t\t{\n\t\t\tMethodName: \"ListRecommendations\",\n\t\t\tHandler:    _RecommendationService_ListRecommendations_Handler,\n\t\t},\n\t},\n\tStreams:  []grpc.StreamDesc{},\n\tMetadata: \"demo.proto\",\n}\n\nconst (\n\tProductCatalogService_ListProducts_FullMethodName   = \"/hipstershop.ProductCatalogService/ListProducts\"\n\tProductCatalogService_GetProduct_FullMethodName     = \"/hipstershop.ProductCatalogService/GetProduct\"\n\tProductCatalogService_SearchProducts_FullMethodName = \"/hipstershop.ProductCatalogService/SearchProducts\"\n)\n\n// ProductCatalogServiceClient is the client API for ProductCatalogService service.\n//\n// 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.\ntype ProductCatalogServiceClient interface {\n\tListProducts(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*ListProductsResponse, error)\n\tGetProduct(ctx context.Context, in *GetProductRequest, opts ...grpc.CallOption) (*Product, error)\n\tSearchProducts(ctx context.Context, in *SearchProductsRequest, opts ...grpc.CallOption) (*SearchProductsResponse, error)\n}\n\ntype productCatalogServiceClient struct {\n\tcc grpc.ClientConnInterface\n}\n\nfunc NewProductCatalogServiceClient(cc grpc.ClientConnInterface) ProductCatalogServiceClient {\n\treturn &productCatalogServiceClient{cc}\n}\n\nfunc (c *productCatalogServiceClient) ListProducts(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*ListProductsResponse, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(ListProductsResponse)\n\terr := c.cc.Invoke(ctx, ProductCatalogService_ListProducts_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *productCatalogServiceClient) GetProduct(ctx context.Context, in *GetProductRequest, opts ...grpc.CallOption) (*Product, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(Product)\n\terr := c.cc.Invoke(ctx, ProductCatalogService_GetProduct_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *productCatalogServiceClient) SearchProducts(ctx context.Context, in *SearchProductsRequest, opts ...grpc.CallOption) (*SearchProductsResponse, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(SearchProductsResponse)\n\terr := c.cc.Invoke(ctx, ProductCatalogService_SearchProducts_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\n// ProductCatalogServiceServer is the server API for ProductCatalogService service.\n// All implementations must embed UnimplementedProductCatalogServiceServer\n// for forward compatibility.\ntype ProductCatalogServiceServer interface {\n\tListProducts(context.Context, *Empty) (*ListProductsResponse, error)\n\tGetProduct(context.Context, *GetProductRequest) (*Product, error)\n\tSearchProducts(context.Context, *SearchProductsRequest) (*SearchProductsResponse, error)\n\tmustEmbedUnimplementedProductCatalogServiceServer()\n}\n\n// UnimplementedProductCatalogServiceServer must be embedded to have\n// forward compatible implementations.\n//\n// NOTE: this should be embedded by value instead of pointer to avoid a nil\n// pointer dereference when methods are called.\ntype UnimplementedProductCatalogServiceServer struct{}\n\nfunc (UnimplementedProductCatalogServiceServer) ListProducts(context.Context, *Empty) (*ListProductsResponse, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method ListProducts not implemented\")\n}\nfunc (UnimplementedProductCatalogServiceServer) GetProduct(context.Context, *GetProductRequest) (*Product, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method GetProduct not implemented\")\n}\nfunc (UnimplementedProductCatalogServiceServer) SearchProducts(context.Context, *SearchProductsRequest) (*SearchProductsResponse, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method SearchProducts not implemented\")\n}\nfunc (UnimplementedProductCatalogServiceServer) mustEmbedUnimplementedProductCatalogServiceServer() {}\nfunc (UnimplementedProductCatalogServiceServer) testEmbeddedByValue()                               {}\n\n// UnsafeProductCatalogServiceServer may be embedded to opt out of forward compatibility for this service.\n// Use of this interface is not recommended, as added methods to ProductCatalogServiceServer will\n// result in compilation errors.\ntype UnsafeProductCatalogServiceServer interface {\n\tmustEmbedUnimplementedProductCatalogServiceServer()\n}\n\nfunc RegisterProductCatalogServiceServer(s grpc.ServiceRegistrar, srv ProductCatalogServiceServer) {\n\t// If the following call pancis, it indicates UnimplementedProductCatalogServiceServer was\n\t// embedded by pointer and is nil.  This will cause panics if an\n\t// unimplemented method is ever invoked, so we test this at initialization\n\t// time to prevent it from happening at runtime later due to I/O.\n\tif t, ok := srv.(interface{ testEmbeddedByValue() }); ok {\n\t\tt.testEmbeddedByValue()\n\t}\n\ts.RegisterService(&ProductCatalogService_ServiceDesc, srv)\n}\n\nfunc _ProductCatalogService_ListProducts_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(Empty)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ProductCatalogServiceServer).ListProducts(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: ProductCatalogService_ListProducts_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ProductCatalogServiceServer).ListProducts(ctx, req.(*Empty))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _ProductCatalogService_GetProduct_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(GetProductRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ProductCatalogServiceServer).GetProduct(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: ProductCatalogService_GetProduct_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ProductCatalogServiceServer).GetProduct(ctx, req.(*GetProductRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _ProductCatalogService_SearchProducts_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(SearchProductsRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ProductCatalogServiceServer).SearchProducts(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: ProductCatalogService_SearchProducts_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ProductCatalogServiceServer).SearchProducts(ctx, req.(*SearchProductsRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\n// ProductCatalogService_ServiceDesc is the grpc.ServiceDesc for ProductCatalogService service.\n// It's only intended for direct use with grpc.RegisterService,\n// and not to be introspected or modified (even as a copy)\nvar ProductCatalogService_ServiceDesc = grpc.ServiceDesc{\n\tServiceName: \"hipstershop.ProductCatalogService\",\n\tHandlerType: (*ProductCatalogServiceServer)(nil),\n\tMethods: []grpc.MethodDesc{\n\t\t{\n\t\t\tMethodName: \"ListProducts\",\n\t\t\tHandler:    _ProductCatalogService_ListProducts_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"GetProduct\",\n\t\t\tHandler:    _ProductCatalogService_GetProduct_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"SearchProducts\",\n\t\t\tHandler:    _ProductCatalogService_SearchProducts_Handler,\n\t\t},\n\t},\n\tStreams:  []grpc.StreamDesc{},\n\tMetadata: \"demo.proto\",\n}\n\nconst (\n\tShippingService_GetQuote_FullMethodName  = \"/hipstershop.ShippingService/GetQuote\"\n\tShippingService_ShipOrder_FullMethodName = \"/hipstershop.ShippingService/ShipOrder\"\n)\n\n// ShippingServiceClient is the client API for ShippingService service.\n//\n// 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.\ntype ShippingServiceClient interface {\n\tGetQuote(ctx context.Context, in *GetQuoteRequest, opts ...grpc.CallOption) (*GetQuoteResponse, error)\n\tShipOrder(ctx context.Context, in *ShipOrderRequest, opts ...grpc.CallOption) (*ShipOrderResponse, error)\n}\n\ntype shippingServiceClient struct {\n\tcc grpc.ClientConnInterface\n}\n\nfunc NewShippingServiceClient(cc grpc.ClientConnInterface) ShippingServiceClient {\n\treturn &shippingServiceClient{cc}\n}\n\nfunc (c *shippingServiceClient) GetQuote(ctx context.Context, in *GetQuoteRequest, opts ...grpc.CallOption) (*GetQuoteResponse, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(GetQuoteResponse)\n\terr := c.cc.Invoke(ctx, ShippingService_GetQuote_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *shippingServiceClient) ShipOrder(ctx context.Context, in *ShipOrderRequest, opts ...grpc.CallOption) (*ShipOrderResponse, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(ShipOrderResponse)\n\terr := c.cc.Invoke(ctx, ShippingService_ShipOrder_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\n// ShippingServiceServer is the server API for ShippingService service.\n// All implementations must embed UnimplementedShippingServiceServer\n// for forward compatibility.\ntype ShippingServiceServer interface {\n\tGetQuote(context.Context, *GetQuoteRequest) (*GetQuoteResponse, error)\n\tShipOrder(context.Context, *ShipOrderRequest) (*ShipOrderResponse, error)\n\tmustEmbedUnimplementedShippingServiceServer()\n}\n\n// UnimplementedShippingServiceServer must be embedded to have\n// forward compatible implementations.\n//\n// NOTE: this should be embedded by value instead of pointer to avoid a nil\n// pointer dereference when methods are called.\ntype UnimplementedShippingServiceServer struct{}\n\nfunc (UnimplementedShippingServiceServer) GetQuote(context.Context, *GetQuoteRequest) (*GetQuoteResponse, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method GetQuote not implemented\")\n}\nfunc (UnimplementedShippingServiceServer) ShipOrder(context.Context, *ShipOrderRequest) (*ShipOrderResponse, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method ShipOrder not implemented\")\n}\nfunc (UnimplementedShippingServiceServer) mustEmbedUnimplementedShippingServiceServer() {}\nfunc (UnimplementedShippingServiceServer) testEmbeddedByValue()                         {}\n\n// UnsafeShippingServiceServer may be embedded to opt out of forward compatibility for this service.\n// Use of this interface is not recommended, as added methods to ShippingServiceServer will\n// result in compilation errors.\ntype UnsafeShippingServiceServer interface {\n\tmustEmbedUnimplementedShippingServiceServer()\n}\n\nfunc RegisterShippingServiceServer(s grpc.ServiceRegistrar, srv ShippingServiceServer) {\n\t// If the following call pancis, it indicates UnimplementedShippingServiceServer was\n\t// embedded by pointer and is nil.  This will cause panics if an\n\t// unimplemented method is ever invoked, so we test this at initialization\n\t// time to prevent it from happening at runtime later due to I/O.\n\tif t, ok := srv.(interface{ testEmbeddedByValue() }); ok {\n\t\tt.testEmbeddedByValue()\n\t}\n\ts.RegisterService(&ShippingService_ServiceDesc, srv)\n}\n\nfunc _ShippingService_GetQuote_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(GetQuoteRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ShippingServiceServer).GetQuote(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: ShippingService_GetQuote_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ShippingServiceServer).GetQuote(ctx, req.(*GetQuoteRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _ShippingService_ShipOrder_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(ShipOrderRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ShippingServiceServer).ShipOrder(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: ShippingService_ShipOrder_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ShippingServiceServer).ShipOrder(ctx, req.(*ShipOrderRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\n// ShippingService_ServiceDesc is the grpc.ServiceDesc for ShippingService service.\n// It's only intended for direct use with grpc.RegisterService,\n// and not to be introspected or modified (even as a copy)\nvar ShippingService_ServiceDesc = grpc.ServiceDesc{\n\tServiceName: \"hipstershop.ShippingService\",\n\tHandlerType: (*ShippingServiceServer)(nil),\n\tMethods: []grpc.MethodDesc{\n\t\t{\n\t\t\tMethodName: \"GetQuote\",\n\t\t\tHandler:    _ShippingService_GetQuote_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"ShipOrder\",\n\t\t\tHandler:    _ShippingService_ShipOrder_Handler,\n\t\t},\n\t},\n\tStreams:  []grpc.StreamDesc{},\n\tMetadata: \"demo.proto\",\n}\n\nconst (\n\tCurrencyService_GetSupportedCurrencies_FullMethodName = \"/hipstershop.CurrencyService/GetSupportedCurrencies\"\n\tCurrencyService_Convert_FullMethodName                = \"/hipstershop.CurrencyService/Convert\"\n)\n\n// CurrencyServiceClient is the client API for CurrencyService service.\n//\n// 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.\ntype CurrencyServiceClient interface {\n\tGetSupportedCurrencies(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*GetSupportedCurrenciesResponse, error)\n\tConvert(ctx context.Context, in *CurrencyConversionRequest, opts ...grpc.CallOption) (*Money, error)\n}\n\ntype currencyServiceClient struct {\n\tcc grpc.ClientConnInterface\n}\n\nfunc NewCurrencyServiceClient(cc grpc.ClientConnInterface) CurrencyServiceClient {\n\treturn &currencyServiceClient{cc}\n}\n\nfunc (c *currencyServiceClient) GetSupportedCurrencies(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*GetSupportedCurrenciesResponse, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(GetSupportedCurrenciesResponse)\n\terr := c.cc.Invoke(ctx, CurrencyService_GetSupportedCurrencies_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *currencyServiceClient) Convert(ctx context.Context, in *CurrencyConversionRequest, opts ...grpc.CallOption) (*Money, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(Money)\n\terr := c.cc.Invoke(ctx, CurrencyService_Convert_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\n// CurrencyServiceServer is the server API for CurrencyService service.\n// All implementations must embed UnimplementedCurrencyServiceServer\n// for forward compatibility.\ntype CurrencyServiceServer interface {\n\tGetSupportedCurrencies(context.Context, *Empty) (*GetSupportedCurrenciesResponse, error)\n\tConvert(context.Context, *CurrencyConversionRequest) (*Money, error)\n\tmustEmbedUnimplementedCurrencyServiceServer()\n}\n\n// UnimplementedCurrencyServiceServer must be embedded to have\n// forward compatible implementations.\n//\n// NOTE: this should be embedded by value instead of pointer to avoid a nil\n// pointer dereference when methods are called.\ntype UnimplementedCurrencyServiceServer struct{}\n\nfunc (UnimplementedCurrencyServiceServer) GetSupportedCurrencies(context.Context, *Empty) (*GetSupportedCurrenciesResponse, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method GetSupportedCurrencies not implemented\")\n}\nfunc (UnimplementedCurrencyServiceServer) Convert(context.Context, *CurrencyConversionRequest) (*Money, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method Convert not implemented\")\n}\nfunc (UnimplementedCurrencyServiceServer) mustEmbedUnimplementedCurrencyServiceServer() {}\nfunc (UnimplementedCurrencyServiceServer) testEmbeddedByValue()                         {}\n\n// UnsafeCurrencyServiceServer may be embedded to opt out of forward compatibility for this service.\n// Use of this interface is not recommended, as added methods to CurrencyServiceServer will\n// result in compilation errors.\ntype UnsafeCurrencyServiceServer interface {\n\tmustEmbedUnimplementedCurrencyServiceServer()\n}\n\nfunc RegisterCurrencyServiceServer(s grpc.ServiceRegistrar, srv CurrencyServiceServer) {\n\t// If the following call pancis, it indicates UnimplementedCurrencyServiceServer was\n\t// embedded by pointer and is nil.  This will cause panics if an\n\t// unimplemented method is ever invoked, so we test this at initialization\n\t// time to prevent it from happening at runtime later due to I/O.\n\tif t, ok := srv.(interface{ testEmbeddedByValue() }); ok {\n\t\tt.testEmbeddedByValue()\n\t}\n\ts.RegisterService(&CurrencyService_ServiceDesc, srv)\n}\n\nfunc _CurrencyService_GetSupportedCurrencies_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(Empty)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(CurrencyServiceServer).GetSupportedCurrencies(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: CurrencyService_GetSupportedCurrencies_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(CurrencyServiceServer).GetSupportedCurrencies(ctx, req.(*Empty))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _CurrencyService_Convert_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(CurrencyConversionRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(CurrencyServiceServer).Convert(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: CurrencyService_Convert_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(CurrencyServiceServer).Convert(ctx, req.(*CurrencyConversionRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\n// CurrencyService_ServiceDesc is the grpc.ServiceDesc for CurrencyService service.\n// It's only intended for direct use with grpc.RegisterService,\n// and not to be introspected or modified (even as a copy)\nvar CurrencyService_ServiceDesc = grpc.ServiceDesc{\n\tServiceName: \"hipstershop.CurrencyService\",\n\tHandlerType: (*CurrencyServiceServer)(nil),\n\tMethods: []grpc.MethodDesc{\n\t\t{\n\t\t\tMethodName: \"GetSupportedCurrencies\",\n\t\t\tHandler:    _CurrencyService_GetSupportedCurrencies_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"Convert\",\n\t\t\tHandler:    _CurrencyService_Convert_Handler,\n\t\t},\n\t},\n\tStreams:  []grpc.StreamDesc{},\n\tMetadata: \"demo.proto\",\n}\n\nconst (\n\tPaymentService_Charge_FullMethodName = \"/hipstershop.PaymentService/Charge\"\n)\n\n// PaymentServiceClient is the client API for PaymentService service.\n//\n// 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.\ntype PaymentServiceClient interface {\n\tCharge(ctx context.Context, in *ChargeRequest, opts ...grpc.CallOption) (*ChargeResponse, error)\n}\n\ntype paymentServiceClient struct {\n\tcc grpc.ClientConnInterface\n}\n\nfunc NewPaymentServiceClient(cc grpc.ClientConnInterface) PaymentServiceClient {\n\treturn &paymentServiceClient{cc}\n}\n\nfunc (c *paymentServiceClient) Charge(ctx context.Context, in *ChargeRequest, opts ...grpc.CallOption) (*ChargeResponse, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(ChargeResponse)\n\terr := c.cc.Invoke(ctx, PaymentService_Charge_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\n// PaymentServiceServer is the server API for PaymentService service.\n// All implementations must embed UnimplementedPaymentServiceServer\n// for forward compatibility.\ntype PaymentServiceServer interface {\n\tCharge(context.Context, *ChargeRequest) (*ChargeResponse, error)\n\tmustEmbedUnimplementedPaymentServiceServer()\n}\n\n// UnimplementedPaymentServiceServer must be embedded to have\n// forward compatible implementations.\n//\n// NOTE: this should be embedded by value instead of pointer to avoid a nil\n// pointer dereference when methods are called.\ntype UnimplementedPaymentServiceServer struct{}\n\nfunc (UnimplementedPaymentServiceServer) Charge(context.Context, *ChargeRequest) (*ChargeResponse, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method Charge not implemented\")\n}\nfunc (UnimplementedPaymentServiceServer) mustEmbedUnimplementedPaymentServiceServer() {}\nfunc (UnimplementedPaymentServiceServer) testEmbeddedByValue()                        {}\n\n// UnsafePaymentServiceServer may be embedded to opt out of forward compatibility for this service.\n// Use of this interface is not recommended, as added methods to PaymentServiceServer will\n// result in compilation errors.\ntype UnsafePaymentServiceServer interface {\n\tmustEmbedUnimplementedPaymentServiceServer()\n}\n\nfunc RegisterPaymentServiceServer(s grpc.ServiceRegistrar, srv PaymentServiceServer) {\n\t// If the following call pancis, it indicates UnimplementedPaymentServiceServer was\n\t// embedded by pointer and is nil.  This will cause panics if an\n\t// unimplemented method is ever invoked, so we test this at initialization\n\t// time to prevent it from happening at runtime later due to I/O.\n\tif t, ok := srv.(interface{ testEmbeddedByValue() }); ok {\n\t\tt.testEmbeddedByValue()\n\t}\n\ts.RegisterService(&PaymentService_ServiceDesc, srv)\n}\n\nfunc _PaymentService_Charge_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(ChargeRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(PaymentServiceServer).Charge(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: PaymentService_Charge_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(PaymentServiceServer).Charge(ctx, req.(*ChargeRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\n// PaymentService_ServiceDesc is the grpc.ServiceDesc for PaymentService service.\n// It's only intended for direct use with grpc.RegisterService,\n// and not to be introspected or modified (even as a copy)\nvar PaymentService_ServiceDesc = grpc.ServiceDesc{\n\tServiceName: \"hipstershop.PaymentService\",\n\tHandlerType: (*PaymentServiceServer)(nil),\n\tMethods: []grpc.MethodDesc{\n\t\t{\n\t\t\tMethodName: \"Charge\",\n\t\t\tHandler:    _PaymentService_Charge_Handler,\n\t\t},\n\t},\n\tStreams:  []grpc.StreamDesc{},\n\tMetadata: \"demo.proto\",\n}\n\nconst (\n\tEmailService_SendOrderConfirmation_FullMethodName = \"/hipstershop.EmailService/SendOrderConfirmation\"\n)\n\n// EmailServiceClient is the client API for EmailService service.\n//\n// 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.\ntype EmailServiceClient interface {\n\tSendOrderConfirmation(ctx context.Context, in *SendOrderConfirmationRequest, opts ...grpc.CallOption) (*Empty, error)\n}\n\ntype emailServiceClient struct {\n\tcc grpc.ClientConnInterface\n}\n\nfunc NewEmailServiceClient(cc grpc.ClientConnInterface) EmailServiceClient {\n\treturn &emailServiceClient{cc}\n}\n\nfunc (c *emailServiceClient) SendOrderConfirmation(ctx context.Context, in *SendOrderConfirmationRequest, opts ...grpc.CallOption) (*Empty, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(Empty)\n\terr := c.cc.Invoke(ctx, EmailService_SendOrderConfirmation_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\n// EmailServiceServer is the server API for EmailService service.\n// All implementations must embed UnimplementedEmailServiceServer\n// for forward compatibility.\ntype EmailServiceServer interface {\n\tSendOrderConfirmation(context.Context, *SendOrderConfirmationRequest) (*Empty, error)\n\tmustEmbedUnimplementedEmailServiceServer()\n}\n\n// UnimplementedEmailServiceServer must be embedded to have\n// forward compatible implementations.\n//\n// NOTE: this should be embedded by value instead of pointer to avoid a nil\n// pointer dereference when methods are called.\ntype UnimplementedEmailServiceServer struct{}\n\nfunc (UnimplementedEmailServiceServer) SendOrderConfirmation(context.Context, *SendOrderConfirmationRequest) (*Empty, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method SendOrderConfirmation not implemented\")\n}\nfunc (UnimplementedEmailServiceServer) mustEmbedUnimplementedEmailServiceServer() {}\nfunc (UnimplementedEmailServiceServer) testEmbeddedByValue()                      {}\n\n// UnsafeEmailServiceServer may be embedded to opt out of forward compatibility for this service.\n// Use of this interface is not recommended, as added methods to EmailServiceServer will\n// result in compilation errors.\ntype UnsafeEmailServiceServer interface {\n\tmustEmbedUnimplementedEmailServiceServer()\n}\n\nfunc RegisterEmailServiceServer(s grpc.ServiceRegistrar, srv EmailServiceServer) {\n\t// If the following call pancis, it indicates UnimplementedEmailServiceServer was\n\t// embedded by pointer and is nil.  This will cause panics if an\n\t// unimplemented method is ever invoked, so we test this at initialization\n\t// time to prevent it from happening at runtime later due to I/O.\n\tif t, ok := srv.(interface{ testEmbeddedByValue() }); ok {\n\t\tt.testEmbeddedByValue()\n\t}\n\ts.RegisterService(&EmailService_ServiceDesc, srv)\n}\n\nfunc _EmailService_SendOrderConfirmation_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(SendOrderConfirmationRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(EmailServiceServer).SendOrderConfirmation(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: EmailService_SendOrderConfirmation_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(EmailServiceServer).SendOrderConfirmation(ctx, req.(*SendOrderConfirmationRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\n// EmailService_ServiceDesc is the grpc.ServiceDesc for EmailService service.\n// It's only intended for direct use with grpc.RegisterService,\n// and not to be introspected or modified (even as a copy)\nvar EmailService_ServiceDesc = grpc.ServiceDesc{\n\tServiceName: \"hipstershop.EmailService\",\n\tHandlerType: (*EmailServiceServer)(nil),\n\tMethods: []grpc.MethodDesc{\n\t\t{\n\t\t\tMethodName: \"SendOrderConfirmation\",\n\t\t\tHandler:    _EmailService_SendOrderConfirmation_Handler,\n\t\t},\n\t},\n\tStreams:  []grpc.StreamDesc{},\n\tMetadata: \"demo.proto\",\n}\n\nconst (\n\tCheckoutService_PlaceOrder_FullMethodName = \"/hipstershop.CheckoutService/PlaceOrder\"\n)\n\n// CheckoutServiceClient is the client API for CheckoutService service.\n//\n// 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.\ntype CheckoutServiceClient interface {\n\tPlaceOrder(ctx context.Context, in *PlaceOrderRequest, opts ...grpc.CallOption) (*PlaceOrderResponse, error)\n}\n\ntype checkoutServiceClient struct {\n\tcc grpc.ClientConnInterface\n}\n\nfunc NewCheckoutServiceClient(cc grpc.ClientConnInterface) CheckoutServiceClient {\n\treturn &checkoutServiceClient{cc}\n}\n\nfunc (c *checkoutServiceClient) PlaceOrder(ctx context.Context, in *PlaceOrderRequest, opts ...grpc.CallOption) (*PlaceOrderResponse, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(PlaceOrderResponse)\n\terr := c.cc.Invoke(ctx, CheckoutService_PlaceOrder_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\n// CheckoutServiceServer is the server API for CheckoutService service.\n// All implementations must embed UnimplementedCheckoutServiceServer\n// for forward compatibility.\ntype CheckoutServiceServer interface {\n\tPlaceOrder(context.Context, *PlaceOrderRequest) (*PlaceOrderResponse, error)\n\tmustEmbedUnimplementedCheckoutServiceServer()\n}\n\n// UnimplementedCheckoutServiceServer must be embedded to have\n// forward compatible implementations.\n//\n// NOTE: this should be embedded by value instead of pointer to avoid a nil\n// pointer dereference when methods are called.\ntype UnimplementedCheckoutServiceServer struct{}\n\nfunc (UnimplementedCheckoutServiceServer) PlaceOrder(context.Context, *PlaceOrderRequest) (*PlaceOrderResponse, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method PlaceOrder not implemented\")\n}\nfunc (UnimplementedCheckoutServiceServer) mustEmbedUnimplementedCheckoutServiceServer() {}\nfunc (UnimplementedCheckoutServiceServer) testEmbeddedByValue()                         {}\n\n// UnsafeCheckoutServiceServer may be embedded to opt out of forward compatibility for this service.\n// Use of this interface is not recommended, as added methods to CheckoutServiceServer will\n// result in compilation errors.\ntype UnsafeCheckoutServiceServer interface {\n\tmustEmbedUnimplementedCheckoutServiceServer()\n}\n\nfunc RegisterCheckoutServiceServer(s grpc.ServiceRegistrar, srv CheckoutServiceServer) {\n\t// If the following call pancis, it indicates UnimplementedCheckoutServiceServer was\n\t// embedded by pointer and is nil.  This will cause panics if an\n\t// unimplemented method is ever invoked, so we test this at initialization\n\t// time to prevent it from happening at runtime later due to I/O.\n\tif t, ok := srv.(interface{ testEmbeddedByValue() }); ok {\n\t\tt.testEmbeddedByValue()\n\t}\n\ts.RegisterService(&CheckoutService_ServiceDesc, srv)\n}\n\nfunc _CheckoutService_PlaceOrder_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(PlaceOrderRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(CheckoutServiceServer).PlaceOrder(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: CheckoutService_PlaceOrder_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(CheckoutServiceServer).PlaceOrder(ctx, req.(*PlaceOrderRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\n// CheckoutService_ServiceDesc is the grpc.ServiceDesc for CheckoutService service.\n// It's only intended for direct use with grpc.RegisterService,\n// and not to be introspected or modified (even as a copy)\nvar CheckoutService_ServiceDesc = grpc.ServiceDesc{\n\tServiceName: \"hipstershop.CheckoutService\",\n\tHandlerType: (*CheckoutServiceServer)(nil),\n\tMethods: []grpc.MethodDesc{\n\t\t{\n\t\t\tMethodName: \"PlaceOrder\",\n\t\t\tHandler:    _CheckoutService_PlaceOrder_Handler,\n\t\t},\n\t},\n\tStreams:  []grpc.StreamDesc{},\n\tMetadata: \"demo.proto\",\n}\n\nconst (\n\tAdService_GetAds_FullMethodName = \"/hipstershop.AdService/GetAds\"\n)\n\n// AdServiceClient is the client API for AdService service.\n//\n// 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.\ntype AdServiceClient interface {\n\tGetAds(ctx context.Context, in *AdRequest, opts ...grpc.CallOption) (*AdResponse, error)\n}\n\ntype adServiceClient struct {\n\tcc grpc.ClientConnInterface\n}\n\nfunc NewAdServiceClient(cc grpc.ClientConnInterface) AdServiceClient {\n\treturn &adServiceClient{cc}\n}\n\nfunc (c *adServiceClient) GetAds(ctx context.Context, in *AdRequest, opts ...grpc.CallOption) (*AdResponse, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(AdResponse)\n\terr := c.cc.Invoke(ctx, AdService_GetAds_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\n// AdServiceServer is the server API for AdService service.\n// All implementations must embed UnimplementedAdServiceServer\n// for forward compatibility.\ntype AdServiceServer interface {\n\tGetAds(context.Context, *AdRequest) (*AdResponse, error)\n\tmustEmbedUnimplementedAdServiceServer()\n}\n\n// UnimplementedAdServiceServer must be embedded to have\n// forward compatible implementations.\n//\n// NOTE: this should be embedded by value instead of pointer to avoid a nil\n// pointer dereference when methods are called.\ntype UnimplementedAdServiceServer struct{}\n\nfunc (UnimplementedAdServiceServer) GetAds(context.Context, *AdRequest) (*AdResponse, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method GetAds not implemented\")\n}\nfunc (UnimplementedAdServiceServer) mustEmbedUnimplementedAdServiceServer() {}\nfunc (UnimplementedAdServiceServer) testEmbeddedByValue()                   {}\n\n// UnsafeAdServiceServer may be embedded to opt out of forward compatibility for this service.\n// Use of this interface is not recommended, as added methods to AdServiceServer will\n// result in compilation errors.\ntype UnsafeAdServiceServer interface {\n\tmustEmbedUnimplementedAdServiceServer()\n}\n\nfunc RegisterAdServiceServer(s grpc.ServiceRegistrar, srv AdServiceServer) {\n\t// If the following call pancis, it indicates UnimplementedAdServiceServer was\n\t// embedded by pointer and is nil.  This will cause panics if an\n\t// unimplemented method is ever invoked, so we test this at initialization\n\t// time to prevent it from happening at runtime later due to I/O.\n\tif t, ok := srv.(interface{ testEmbeddedByValue() }); ok {\n\t\tt.testEmbeddedByValue()\n\t}\n\ts.RegisterService(&AdService_ServiceDesc, srv)\n}\n\nfunc _AdService_GetAds_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(AdRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(AdServiceServer).GetAds(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: AdService_GetAds_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(AdServiceServer).GetAds(ctx, req.(*AdRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\n// AdService_ServiceDesc is the grpc.ServiceDesc for AdService service.\n// It's only intended for direct use with grpc.RegisterService,\n// and not to be introspected or modified (even as a copy)\nvar AdService_ServiceDesc = grpc.ServiceDesc{\n\tServiceName: \"hipstershop.AdService\",\n\tHandlerType: (*AdServiceServer)(nil),\n\tMethods: []grpc.MethodDesc{\n\t\t{\n\t\t\tMethodName: \"GetAds\",\n\t\t\tHandler:    _AdService_GetAds_Handler,\n\t\t},\n\t},\n\tStreams:  []grpc.StreamDesc{},\n\tMetadata: \"demo.proto\",\n}\n"
  },
  {
    "path": "src/frontend/genproto.sh",
    "content": "#!/bin/bash -eu\n#\n# Copyright 2018 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# [START gke_frontend_genproto]\n\nPATH=$PATH:$(go env GOPATH)/bin\nprotodir=../../protos\noutdir=./genproto\n\nprotoc --proto_path=$protodir --go_out=./$outdir --go_opt=paths=source_relative --go-grpc_out=./$outdir --go-grpc_opt=paths=source_relative $protodir/demo.proto\n\n# [END gke_frontend_genproto]"
  },
  {
    "path": "src/frontend/go.mod",
    "content": "module github.com/GoogleCloudPlatform/microservices-demo/src/frontend\n\ngo 1.25.0\n\ntoolchain go1.26.1\n\nrequire (\n\tcloud.google.com/go/compute/metadata v0.9.0\n\tcloud.google.com/go/profiler v0.4.3\n\tgithub.com/go-playground/validator/v10 v10.30.1\n\tgithub.com/google/uuid v1.6.0\n\tgithub.com/gorilla/mux v1.8.1\n\tgithub.com/pkg/errors v0.9.1\n\tgithub.com/sirupsen/logrus v1.9.4\n\tgo.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0\n\tgo.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0\n\tgo.opentelemetry.io/otel v1.42.0\n\tgo.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.42.0\n\tgo.opentelemetry.io/otel/sdk v1.42.0\n\tgoogle.golang.org/grpc v1.79.2\n\tgoogle.golang.org/protobuf v1.36.11\n)\n\nrequire (\n\tcloud.google.com/go v0.123.0 // indirect\n\tcloud.google.com/go/auth v0.17.0 // indirect\n\tcloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect\n\tgithub.com/cenkalti/backoff/v5 v5.0.3 // indirect\n\tgithub.com/cespare/xxhash/v2 v2.3.0 // indirect\n\tgithub.com/felixge/httpsnoop v1.0.4 // indirect\n\tgithub.com/gabriel-vasile/mimetype v1.4.12 // indirect\n\tgithub.com/go-logr/logr v1.4.3 // indirect\n\tgithub.com/go-logr/stdr v1.2.2 // indirect\n\tgithub.com/go-playground/locales v0.14.1 // indirect\n\tgithub.com/go-playground/universal-translator v0.18.1 // indirect\n\tgithub.com/google/pprof v0.0.0-20251114195745-4902fdda35c8 // indirect\n\tgithub.com/google/s2a-go v0.1.9 // indirect\n\tgithub.com/googleapis/enterprise-certificate-proxy v0.3.7 // indirect\n\tgithub.com/googleapis/gax-go/v2 v2.15.0 // indirect\n\tgithub.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 // indirect\n\tgithub.com/leodido/go-urn v1.4.0 // indirect\n\tgo.opentelemetry.io/auto/sdk v1.2.1 // indirect\n\tgo.opentelemetry.io/otel/exporters/otlp/otlptrace v1.42.0 // indirect\n\tgo.opentelemetry.io/otel/metric v1.42.0 // indirect\n\tgo.opentelemetry.io/otel/trace v1.42.0 // indirect\n\tgo.opentelemetry.io/proto/otlp v1.9.0 // indirect\n\tgolang.org/x/crypto v0.48.0 // indirect\n\tgolang.org/x/net v0.51.0 // indirect\n\tgolang.org/x/oauth2 v0.35.0 // indirect\n\tgolang.org/x/sync v0.19.0 // indirect\n\tgolang.org/x/sys v0.41.0 // indirect\n\tgolang.org/x/text v0.34.0 // indirect\n\tgolang.org/x/time v0.14.0 // indirect\n\tgoogle.golang.org/api v0.256.0 // indirect\n\tgoogle.golang.org/genproto v0.0.0-20251124214823-79d6a2a48846 // indirect\n\tgoogle.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57 // indirect\n\tgoogle.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 // indirect\n)\n"
  },
  {
    "path": "src/frontend/go.sum",
    "content": "cel.dev/expr v0.24.0 h1:56OvJKSH3hDGL0ml5uSxZmz3/3Pq4tJ+fb1unVLAFcY=\ncel.dev/expr v0.24.0/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw=\ncloud.google.com/go v0.123.0 h1:2NAUJwPR47q+E35uaJeYoNhuNEM9kM8SjgRgdeOJUSE=\ncloud.google.com/go v0.123.0/go.mod h1:xBoMV08QcqUGuPW65Qfm1o9Y4zKZBpGS+7bImXLTAZU=\ncloud.google.com/go/auth v0.17.0 h1:74yCm7hCj2rUyyAocqnFzsAYXgJhrG26XCFimrc/Kz4=\ncloud.google.com/go/auth v0.17.0/go.mod h1:6wv/t5/6rOPAX4fJiRjKkJCvswLwdet7G8+UGXt7nCQ=\ncloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc=\ncloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c=\ncloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs=\ncloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10=\ncloud.google.com/go/iam v1.5.3 h1:+vMINPiDF2ognBJ97ABAYYwRgsaqxPbQDlMnbHMjolc=\ncloud.google.com/go/iam v1.5.3/go.mod h1:MR3v9oLkZCTlaqljW6Eb2d3HGDGK5/bDv93jhfISFvU=\ncloud.google.com/go/monitoring v1.24.3 h1:dde+gMNc0UhPZD1Azu6at2e79bfdztVDS5lvhOdsgaE=\ncloud.google.com/go/monitoring v1.24.3/go.mod h1:nYP6W0tm3N9H/bOw8am7t62YTzZY+zUeQ+Bi6+2eonI=\ncloud.google.com/go/profiler v0.4.3 h1:IY3QNKlr8VbXwGWHcZbJQsMA/83ZTH6uAHf8jYyj7OI=\ncloud.google.com/go/profiler v0.4.3/go.mod h1:3xFodugWfPIQZWFcXdUmfa+yTiiyQ8fWrdT+d2Sg4J0=\ncloud.google.com/go/storage v1.56.0 h1:iixmq2Fse2tqxMbWhLWC9HfBj1qdxqAmiK8/eqtsLxI=\ncloud.google.com/go/storage v1.56.0/go.mod h1:Tpuj6t4NweCLzlNbw9Z9iwxEkrSem20AetIeH/shgVU=\ngithub.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0 h1:sBEjpZlNHzK1voKq9695PJSX2o5NEXl7/OL3coiIY0c=\ngithub.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0/go.mod h1:P4WPRUkOhJC13W//jWpyfJNDAIpvRbAUIYLX/4jtlE0=\ngithub.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.53.0 h1:owcC2UnmsZycprQ5RfRgjydWhuoxg71LUfyiQdijZuM=\ngithub.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.53.0/go.mod h1:ZPpqegjbE99EPKsu3iUWV22A04wzGPcAY/ziSIQEEgs=\ngithub.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.53.0 h1:Ron4zCA/yk6U7WOBXhTJcDpsUBG9npumK6xw2auFltQ=\ngithub.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.53.0/go.mod h1:cSgYe11MCNYunTnRXrKiR/tHc0eoKjICUuWpNZoVCOo=\ngithub.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM=\ngithub.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=\ngithub.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=\ngithub.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=\ngithub.com/cncf/xds/go v0.0.0-20251022180443-0feb69152e9f h1:Y8xYupdHxryycyPlc9Y+bSQAYZnetRJ70VMVKm5CKI0=\ngithub.com/cncf/xds/go v0.0.0-20251022180443-0feb69152e9f/go.mod h1:HlzOvOjVBOfTGSRXRyY0OiCS/3J1akRGQQpRO/7zyF4=\ngithub.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=\ngithub.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/envoyproxy/go-control-plane v0.13.5-0.20251024222203-75eaa193e329 h1:K+fnvUM0VZ7ZFJf0n4L/BRlnsb9pL/GuDG6FqaH+PwM=\ngithub.com/envoyproxy/go-control-plane/envoy v1.35.0 h1:ixjkELDE+ru6idPxcHLj8LBVc2bFP7iBytj353BoHUo=\ngithub.com/envoyproxy/go-control-plane/envoy v1.35.0/go.mod h1:09qwbGVuSWWAyN5t/b3iyVfz5+z8QWGrzkoqm/8SbEs=\ngithub.com/envoyproxy/protoc-gen-validate v1.2.1 h1:DEo3O99U8j4hBFwbJfrz9VtgcDfUKS7KJ7spH3d86P8=\ngithub.com/envoyproxy/protoc-gen-validate v1.2.1/go.mod h1:d/C80l/jxXLdfEIhX1W2TmLfsJ31lvEjwamM4DxlWXU=\ngithub.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=\ngithub.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=\ngithub.com/gabriel-vasile/mimetype v1.4.11 h1:AQvxbp830wPhHTqc1u7nzoLT+ZFxGY7emj5DR5DYFik=\ngithub.com/gabriel-vasile/mimetype v1.4.11/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=\ngithub.com/gabriel-vasile/mimetype v1.4.12 h1:e9hWvmLYvtp846tLHam2o++qitpguFiYCKbn0w9jyqw=\ngithub.com/gabriel-vasile/mimetype v1.4.12/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=\ngithub.com/go-jose/go-jose/v4 v4.1.3 h1:CVLmWDhDVRa6Mi/IgCgaopNosCaHz7zrMeF9MlZRkrs=\ngithub.com/go-jose/go-jose/v4 v4.1.3/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=\ngithub.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=\ngithub.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=\ngithub.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=\ngithub.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=\ngithub.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=\ngithub.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=\ngithub.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=\ngithub.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=\ngithub.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=\ngithub.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=\ngithub.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=\ngithub.com/go-playground/validator/v10 v10.28.0 h1:Q7ibns33JjyW48gHkuFT91qX48KG0ktULL6FgHdG688=\ngithub.com/go-playground/validator/v10 v10.28.0/go.mod h1:GoI6I1SjPBh9p7ykNE/yj3fFYbyDOpwMn5KXd+m2hUU=\ngithub.com/go-playground/validator/v10 v10.30.0 h1:5YBPNs273uzsZJD1I8uiB4Aqg9sN6sMDVX3s6LxmhWU=\ngithub.com/go-playground/validator/v10 v10.30.0/go.mod h1:oSuBIQzuJxL//3MelwSLD5hc2Tu889bF0Idm9Dg26cM=\ngithub.com/go-playground/validator/v10 v10.30.1 h1:f3zDSN/zOma+w6+1Wswgd9fLkdwy06ntQJp0BBvFG0w=\ngithub.com/go-playground/validator/v10 v10.30.1/go.mod h1:oSuBIQzuJxL//3MelwSLD5hc2Tu889bF0Idm9Dg26cM=\ngithub.com/golang/mock v1.7.0-rc.1 h1:YojYx61/OLFsiv6Rw1Z96LpldJIy31o+UHmwAUMJ6/U=\ngithub.com/golang/mock v1.7.0-rc.1/go.mod h1:s42URUywIqd+OcERslBJvOjepvNymP31m3q8d/GkuRs=\ngithub.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=\ngithub.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=\ngithub.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=\ngithub.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=\ngithub.com/google/pprof v0.0.0-20251114195745-4902fdda35c8 h1:3DsUAV+VNEQa2CUVLxCY3f87278uWfIDhJnbdvDjvmE=\ngithub.com/google/pprof v0.0.0-20251114195745-4902fdda35c8/go.mod h1:I6V7YzU0XDpsHqbsyrghnFZLO1gwK6NPTNvmetQIk9U=\ngithub.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0=\ngithub.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM=\ngithub.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=\ngithub.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=\ngithub.com/googleapis/enterprise-certificate-proxy v0.3.7 h1:zrn2Ee/nWmHulBx5sAVrGgAa0f2/R35S4DJwfFaUPFQ=\ngithub.com/googleapis/enterprise-certificate-proxy v0.3.7/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA=\ngithub.com/googleapis/gax-go/v2 v2.15.0 h1:SyjDc1mGgZU5LncH8gimWo9lW1DtIfPibOG81vgd/bo=\ngithub.com/googleapis/gax-go/v2 v2.15.0/go.mod h1:zVVkkxAQHa1RQpg9z2AUCMnKhi0Qld9rcmyfL1OZhoc=\ngithub.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=\ngithub.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=\ngithub.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 h1:NmZ1PKzSTQbuGHw9DGPFomqkkLWMC+vZCkfs+FHv1Vg=\ngithub.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3/go.mod h1:zQrxl1YP88HQlA6i9c63DSVPFklWpGX4OWAc9bFuaH4=\ngithub.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 h1:HWRh5R2+9EifMyIHV7ZV+MIZqgz+PMpZ14Jynv3O2Zs=\ngithub.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0/go.mod h1:JfhWUomR1baixubs02l85lZYYOm7LV6om4ceouMv45c=\ngithub.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=\ngithub.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=\ngithub.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=\ngithub.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\ngithub.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo=\ngithub.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8=\ngithub.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=\ngithub.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=\ngithub.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=\ngithub.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w=\ngithub.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g=\ngithub.com/spiffe/go-spiffe/v2 v2.6.0 h1:l+DolpxNWYgruGQVV0xsfeya3CsC7m8iBzDnMpsbLuo=\ngithub.com/spiffe/go-spiffe/v2 v2.6.0/go.mod h1:gm2SeUoMZEtpnzPNs2Csc0D/gX33k1xIx7lEzqblHEs=\ngithub.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngithub.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=\ngithub.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=\ngo.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=\ngo.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=\ngo.opentelemetry.io/contrib/detectors/gcp v1.38.0 h1:ZoYbqX7OaA/TAikspPl3ozPI6iY6LiIY9I8cUfm+pJs=\ngo.opentelemetry.io/contrib/detectors/gcp v1.38.0/go.mod h1:SU+iU7nu5ud4oCb3LQOhIZ3nRLj6FNVrKgtflbaf2ts=\ngo.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0 h1:YH4g8lQroajqUwWbq/tr2QX1JFmEXaDLgG+ew9bLMWo=\ngo.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0/go.mod h1:fvPi2qXDqFs8M4B4fmJhE92TyQs9Ydjlg3RvfUp+NbQ=\ngo.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.64.0 h1:RN3ifU8y4prNWeEnQp2kRRHz8UwonAEYZl8tUzHEXAk=\ngo.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.64.0/go.mod h1:habDz3tEWiFANTo6oUE99EmaFUrCNYAAg3wiVmusm70=\ngo.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0 h1:yI1/OhfEPy7J9eoa6Sj051C7n5dvpj0QX8g4sRchg04=\ngo.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0/go.mod h1:NoUCKYWK+3ecatC4HjkRktREheMeEtrXoQxrqYFeHSc=\ngo.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 h1:RbKq8BG0FI8OiXhBfcRtqqHcZcka+gU3cskNuf05R18=\ngo.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0/go.mod h1:h06DGIukJOevXaj/xrNjhi/2098RZzcLTbc0jDAUbsg=\ngo.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0 h1:ssfIgGNANqpVFCndZvcuyKbl0g+UAVcbBcqGkG28H0Y=\ngo.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0/go.mod h1:GQ/474YrbE4Jx8gZ4q5I4hrhUzM6UPzyrqJYV2AqPoQ=\ngo.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0 h1:OyrsyzuttWTSur2qN/Lm0m2a8yqyIjUVBZcxFPuXq2o=\ngo.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0/go.mod h1:C2NGBr+kAB4bk3xtMXfZ94gqFDtg/GkI7e9zqGh5Beg=\ngo.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8=\ngo.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM=\ngo.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48=\ngo.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8=\ngo.opentelemetry.io/otel v1.42.0 h1:lSQGzTgVR3+sgJDAU/7/ZMjN9Z+vUip7leaqBKy4sho=\ngo.opentelemetry.io/otel v1.42.0/go.mod h1:lJNsdRMxCUIWuMlVJWzecSMuNjE7dOYyWlqOXWkdqCc=\ngo.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 h1:GqRJVj7UmLjCVyVJ3ZFLdPRmhDUp2zFmQe3RHIOsw24=\ngo.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0/go.mod h1:ri3aaHSmCTVYu2AWv44YMauwAQc0aqI9gHKIcSbI1pU=\ngo.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0 h1:f0cb2XPmrqn4XMy9PNliTgRKJgS5WcL/u0/WRYGz4t0=\ngo.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0/go.mod h1:vnakAaFckOMiMtOIhFI2MNH4FYrZzXCYxmb1LlhoGz8=\ngo.opentelemetry.io/otel/exporters/otlp/otlptrace v1.42.0 h1:THuZiwpQZuHPul65w4WcwEnkX2QIuMT+UFoOrygtoJw=\ngo.opentelemetry.io/otel/exporters/otlp/otlptrace v1.42.0/go.mod h1:J2pvYM5NGHofZ2/Ru6zw/TNWnEQp5crgyDeSrYpXkAw=\ngo.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 h1:lwI4Dc5leUqENgGuQImwLo4WnuXFPetmPpkLi2IrX54=\ngo.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0/go.mod h1:Kz/oCE7z5wuyhPxsXDuaPteSWqjSBD5YaSdbxZYGbGk=\ngo.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0 h1:in9O8ESIOlwJAEGTkkf34DesGRAc/Pn8qJ7k3r/42LM=\ngo.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0/go.mod h1:Rp0EXBm5tfnv0WL+ARyO/PHBEaEAT8UUHQ6AGJcSq6c=\ngo.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.42.0 h1:zWWrB1U6nqhS/k6zYB74CjRpuiitRtLLi68VcgmOEto=\ngo.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.42.0/go.mod h1:2qXPNBX1OVRC0IwOnfo1ljoid+RD0QK3443EaqVlsOU=\ngo.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA=\ngo.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI=\ngo.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0=\ngo.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs=\ngo.opentelemetry.io/otel/metric v1.42.0 h1:2jXG+3oZLNXEPfNmnpxKDeZsFI5o4J+nz6xUlaFdF/4=\ngo.opentelemetry.io/otel/metric v1.42.0/go.mod h1:RlUN/7vTU7Ao/diDkEpQpnz3/92J9ko05BIwxYa2SSI=\ngo.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E=\ngo.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg=\ngo.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18=\ngo.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE=\ngo.opentelemetry.io/otel/sdk v1.42.0 h1:LyC8+jqk6UJwdrI/8VydAq/hvkFKNHZVIWuslJXYsDo=\ngo.opentelemetry.io/otel/sdk v1.42.0/go.mod h1:rGHCAxd9DAph0joO4W6OPwxjNTYWghRWmkHuGbayMts=\ngo.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM=\ngo.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA=\ngo.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE=\ngo.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs=\ngo.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI=\ngo.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA=\ngo.opentelemetry.io/otel/trace v1.42.0 h1:OUCgIPt+mzOnaUTpOQcBiM/PLQ/Op7oq6g4LenLmOYY=\ngo.opentelemetry.io/otel/trace v1.42.0/go.mod h1:f3K9S+IFqnumBkKhRJMeaZeNk9epyhnCmQh/EysQCdc=\ngo.opentelemetry.io/proto/otlp v1.9.0 h1:l706jCMITVouPOqEnii2fIAuO3IVGBRPV5ICjceRb/A=\ngo.opentelemetry.io/proto/otlp v1.9.0/go.mod h1:xE+Cx5E/eEHw+ISFkwPLwCZefwVjY+pqKg1qcK03+/4=\ngo.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=\ngo.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=\ngolang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=\ngolang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=\ngolang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU=\ngolang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0=\ngolang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=\ngolang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=\ngolang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=\ngolang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=\ngolang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=\ngolang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=\ngolang.org/x/oauth2 v0.33.0 h1:4Q+qn+E5z8gPRJfmRy7C2gGG3T4jIprK6aSYgTXGRpo=\ngolang.org/x/oauth2 v0.33.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=\ngolang.org/x/oauth2 v0.35.0 h1:Mv2mzuHuZuY2+bkyWXIHMfhNdJAdwW3FuWeCPYN5GVQ=\ngolang.org/x/oauth2 v0.35.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=\ngolang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=\ngolang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=\ngolang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=\ngolang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=\ngolang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=\ngolang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=\ngolang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=\ngolang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=\ngolang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=\ngolang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=\ngolang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=\ngolang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=\ngolang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=\ngolang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=\ngolang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=\ngolang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=\ngolang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=\ngolang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=\ngonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=\ngonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=\ngoogle.golang.org/api v0.256.0 h1:u6Khm8+F9sxbCTYNoBHg6/Hwv0N/i+V94MvkOSor6oI=\ngoogle.golang.org/api v0.256.0/go.mod h1:KIgPhksXADEKJlnEoRa9qAII4rXcy40vfI8HRqcU964=\ngoogle.golang.org/genproto v0.0.0-20251124214823-79d6a2a48846 h1:dDbsTLIK7EzwUq36kCSAsk0slouq/S0tWHeeGi97cD8=\ngoogle.golang.org/genproto v0.0.0-20251124214823-79d6a2a48846/go.mod h1:PP0g88Dz3C7hRAfbQCQggeWAXjuqGsNPLE4s7jh0RGU=\ngoogle.golang.org/genproto/googleapis/api v0.0.0-20251124214823-79d6a2a48846 h1:ZdyUkS9po3H7G0tuh955QVyyotWvOD4W0aEapeGeUYk=\ngoogle.golang.org/genproto/googleapis/api v0.0.0-20251124214823-79d6a2a48846/go.mod h1:Fk4kyraUvqD7i5H6S43sj2W98fbZa75lpZz/eUyhfO0=\ngoogle.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 h1:fCvbg86sFXwdrl5LgVcTEvNC+2txB5mgROGmRL5mrls=\ngoogle.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:+rXWjjaukWZun3mLfjmVnQi18E1AsFbDN9QdJ5YXLto=\ngoogle.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57 h1:JLQynH/LBHfCTSbDWl+py8C+Rg/k1OVH3xfcaiANuF0=\ngoogle.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57/go.mod h1:kSJwQxqmFXeo79zOmbrALdflXQeAYcUbgS7PbpMknCY=\ngoogle.golang.org/genproto/googleapis/rpc v0.0.0-20251124214823-79d6a2a48846 h1:Wgl1rcDNThT+Zn47YyCXOXyX/COgMTIdhJ717F0l4xk=\ngoogle.golang.org/genproto/googleapis/rpc v0.0.0-20251124214823-79d6a2a48846/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=\ngoogle.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 h1:gRkg/vSppuSQoDjxyiGfN4Upv/h/DQmIR10ZU8dh4Ww=\ngoogle.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=\ngoogle.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 h1:ggcbiqK8WWh6l1dnltU4BgWGIGo+EVYxCaAPih/zQXQ=\ngoogle.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=\ngoogle.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM=\ngoogle.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig=\ngoogle.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc=\ngoogle.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U=\ngoogle.golang.org/grpc v1.79.2 h1:fRMD94s2tITpyJGtBBn7MkMseNpOZU8ZxgC3MMBaXRU=\ngoogle.golang.org/grpc v1.79.2/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=\ngoogle.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=\ngoogle.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=\ngoogle.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=\ngoogle.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=\ngopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\n"
  },
  {
    "path": "src/frontend/handlers.go",
    "content": "// Copyright 2018 Google LLC\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage main\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"html/template\"\n\t\"io\"\n\t\"math/rand\"\n\t\"net\"\n\t\"net/http\"\n\t\"os\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/gorilla/mux\"\n\t\"github.com/pkg/errors\"\n\t\"github.com/sirupsen/logrus\"\n\n\tpb \"github.com/GoogleCloudPlatform/microservices-demo/src/frontend/genproto\"\n\t\"github.com/GoogleCloudPlatform/microservices-demo/src/frontend/money\"\n\t\"github.com/GoogleCloudPlatform/microservices-demo/src/frontend/validator\"\n)\n\ntype platformDetails struct {\n\tcss      string\n\tprovider string\n}\n\nvar (\n\tfrontendMessage  = strings.TrimSpace(os.Getenv(\"FRONTEND_MESSAGE\"))\n\tisCymbalBrand    = \"true\" == strings.ToLower(os.Getenv(\"CYMBAL_BRANDING\"))\n\tassistantEnabled = \"true\" == strings.ToLower(os.Getenv(\"ENABLE_ASSISTANT\"))\n\ttemplates        = template.Must(template.New(\"\").\n\t\t\t\tFuncs(template.FuncMap{\n\t\t\t\"renderMoney\":        renderMoney,\n\t\t\t\"renderCurrencyLogo\": renderCurrencyLogo,\n\t\t}).ParseGlob(\"templates/*.html\"))\n\tplat platformDetails\n)\n\nvar validEnvs = []string{\"local\", \"gcp\", \"azure\", \"aws\", \"onprem\", \"alibaba\"}\n\nfunc (fe *frontendServer) homeHandler(w http.ResponseWriter, r *http.Request) {\n\tlog := r.Context().Value(ctxKeyLog{}).(logrus.FieldLogger)\n\tlog.WithField(\"currency\", currentCurrency(r)).Info(\"home\")\n\tcurrencies, err := fe.getCurrencies(r.Context())\n\tif err != nil {\n\t\trenderHTTPError(log, r, w, errors.Wrap(err, \"could not retrieve currencies\"), http.StatusInternalServerError)\n\t\treturn\n\t}\n\tproducts, err := fe.getProducts(r.Context())\n\tif err != nil {\n\t\trenderHTTPError(log, r, w, errors.Wrap(err, \"could not retrieve products\"), http.StatusInternalServerError)\n\t\treturn\n\t}\n\tcart, err := fe.getCart(r.Context(), sessionID(r))\n\tif err != nil {\n\t\trenderHTTPError(log, r, w, errors.Wrap(err, \"could not retrieve cart\"), http.StatusInternalServerError)\n\t\treturn\n\t}\n\n\ttype productView struct {\n\t\tItem  *pb.Product\n\t\tPrice *pb.Money\n\t}\n\tps := make([]productView, len(products))\n\tfor i, p := range products {\n\t\tprice, err := fe.convertCurrency(r.Context(), p.GetPriceUsd(), currentCurrency(r))\n\t\tif err != nil {\n\t\t\trenderHTTPError(log, r, w, errors.Wrapf(err, \"failed to do currency conversion for product %s\", p.GetId()), http.StatusInternalServerError)\n\t\t\treturn\n\t\t}\n\t\tps[i] = productView{p, price}\n\t}\n\n\t// Set ENV_PLATFORM (default to local if not set; use env var if set; otherwise detect GCP, which overrides env)_\n\tvar env = os.Getenv(\"ENV_PLATFORM\")\n\t// Only override from env variable if set + valid env\n\tif env == \"\" || stringinSlice(validEnvs, env) == false {\n\t\tfmt.Println(\"env platform is either empty or invalid\")\n\t\tenv = \"local\"\n\t}\n\t// Autodetect GCP\n\taddrs, err := net.LookupHost(\"metadata.google.internal.\")\n\tif err == nil && len(addrs) >= 0 {\n\t\tlog.Debugf(\"Detected Google metadata server: %v, setting ENV_PLATFORM to GCP.\", addrs)\n\t\tenv = \"gcp\"\n\t}\n\n\tlog.Debugf(\"ENV_PLATFORM is: %s\", env)\n\tplat = platformDetails{}\n\tplat.setPlatformDetails(strings.ToLower(env))\n\n\tif err := templates.ExecuteTemplate(w, \"home\", injectCommonTemplateData(r, map[string]interface{}{\n\t\t\"show_currency\": true,\n\t\t\"currencies\":    currencies,\n\t\t\"products\":      ps,\n\t\t\"cart_size\":     cartSize(cart),\n\t\t\"banner_color\":  os.Getenv(\"BANNER_COLOR\"), // illustrates canary deployments\n\t\t\"ad\":            fe.chooseAd(r.Context(), []string{}, log),\n\t})); err != nil {\n\t\tlog.Error(err)\n\t}\n}\n\nfunc (plat *platformDetails) setPlatformDetails(env string) {\n\tif env == \"aws\" {\n\t\tplat.provider = \"AWS\"\n\t\tplat.css = \"aws-platform\"\n\t} else if env == \"onprem\" {\n\t\tplat.provider = \"On-Premises\"\n\t\tplat.css = \"onprem-platform\"\n\t} else if env == \"azure\" {\n\t\tplat.provider = \"Azure\"\n\t\tplat.css = \"azure-platform\"\n\t} else if env == \"gcp\" {\n\t\tplat.provider = \"Google Cloud\"\n\t\tplat.css = \"gcp-platform\"\n\t} else if env == \"alibaba\" {\n\t\tplat.provider = \"Alibaba Cloud\"\n\t\tplat.css = \"alibaba-platform\"\n\t} else {\n\t\tplat.provider = \"local\"\n\t\tplat.css = \"local\"\n\t}\n}\n\nfunc (fe *frontendServer) productHandler(w http.ResponseWriter, r *http.Request) {\n\tlog := r.Context().Value(ctxKeyLog{}).(logrus.FieldLogger)\n\tid := mux.Vars(r)[\"id\"]\n\tif id == \"\" {\n\t\trenderHTTPError(log, r, w, errors.New(\"product id not specified\"), http.StatusBadRequest)\n\t\treturn\n\t}\n\tlog.WithField(\"id\", id).WithField(\"currency\", currentCurrency(r)).\n\t\tDebug(\"serving product page\")\n\n\tp, err := fe.getProduct(r.Context(), id)\n\tif err != nil {\n\t\trenderHTTPError(log, r, w, errors.Wrap(err, \"could not retrieve product\"), http.StatusInternalServerError)\n\t\treturn\n\t}\n\tcurrencies, err := fe.getCurrencies(r.Context())\n\tif err != nil {\n\t\trenderHTTPError(log, r, w, errors.Wrap(err, \"could not retrieve currencies\"), http.StatusInternalServerError)\n\t\treturn\n\t}\n\n\tcart, err := fe.getCart(r.Context(), sessionID(r))\n\tif err != nil {\n\t\trenderHTTPError(log, r, w, errors.Wrap(err, \"could not retrieve cart\"), http.StatusInternalServerError)\n\t\treturn\n\t}\n\n\tprice, err := fe.convertCurrency(r.Context(), p.GetPriceUsd(), currentCurrency(r))\n\tif err != nil {\n\t\trenderHTTPError(log, r, w, errors.Wrap(err, \"failed to convert currency\"), http.StatusInternalServerError)\n\t\treturn\n\t}\n\n\t// ignores the error retrieving recommendations since it is not critical\n\trecommendations, err := fe.getRecommendations(r.Context(), sessionID(r), []string{id})\n\tif err != nil {\n\t\tlog.WithField(\"error\", err).Warn(\"failed to get product recommendations\")\n\t}\n\n\tproduct := struct {\n\t\tItem  *pb.Product\n\t\tPrice *pb.Money\n\t}{p, price}\n\n\t// Fetch packaging info (weight/dimensions) of the product\n\t// The packaging service is an optional microservice you can run as part of a Google Cloud demo.\n\tvar packagingInfo *PackagingInfo = nil\n\tif isPackagingServiceConfigured() {\n\t\tpackagingInfo, err = httpGetPackagingInfo(id)\n\t\tif err != nil {\n\t\t\tfmt.Println(\"Failed to obtain product's packaging info:\", err)\n\t\t}\n\t}\n\n\tif err := templates.ExecuteTemplate(w, \"product\", injectCommonTemplateData(r, map[string]interface{}{\n\t\t\"ad\":              fe.chooseAd(r.Context(), p.Categories, log),\n\t\t\"show_currency\":   true,\n\t\t\"currencies\":      currencies,\n\t\t\"product\":         product,\n\t\t\"recommendations\": recommendations,\n\t\t\"cart_size\":       cartSize(cart),\n\t\t\"packagingInfo\":   packagingInfo,\n\t})); err != nil {\n\t\tlog.Println(err)\n\t}\n}\n\nfunc (fe *frontendServer) addToCartHandler(w http.ResponseWriter, r *http.Request) {\n\tlog := r.Context().Value(ctxKeyLog{}).(logrus.FieldLogger)\n\tquantity, _ := strconv.ParseUint(r.FormValue(\"quantity\"), 10, 32)\n\tproductID := r.FormValue(\"product_id\")\n\tpayload := validator.AddToCartPayload{\n\t\tQuantity:  quantity,\n\t\tProductID: productID,\n\t}\n\tif err := payload.Validate(); err != nil {\n\t\trenderHTTPError(log, r, w, validator.ValidationErrorResponse(err), http.StatusUnprocessableEntity)\n\t\treturn\n\t}\n\tlog.WithField(\"product\", payload.ProductID).WithField(\"quantity\", payload.Quantity).Debug(\"adding to cart\")\n\n\tp, err := fe.getProduct(r.Context(), payload.ProductID)\n\tif err != nil {\n\t\trenderHTTPError(log, r, w, errors.Wrap(err, \"could not retrieve product\"), http.StatusInternalServerError)\n\t\treturn\n\t}\n\n\tif err := fe.insertCart(r.Context(), sessionID(r), p.GetId(), int32(payload.Quantity)); err != nil {\n\t\trenderHTTPError(log, r, w, errors.Wrap(err, \"failed to add to cart\"), http.StatusInternalServerError)\n\t\treturn\n\t}\n\tw.Header().Set(\"location\", baseUrl + \"/cart\")\n\tw.WriteHeader(http.StatusFound)\n}\n\nfunc (fe *frontendServer) emptyCartHandler(w http.ResponseWriter, r *http.Request) {\n\tlog := r.Context().Value(ctxKeyLog{}).(logrus.FieldLogger)\n\tlog.Debug(\"emptying cart\")\n\n\tif err := fe.emptyCart(r.Context(), sessionID(r)); err != nil {\n\t\trenderHTTPError(log, r, w, errors.Wrap(err, \"failed to empty cart\"), http.StatusInternalServerError)\n\t\treturn\n\t}\n\tw.Header().Set(\"location\", baseUrl + \"/\")\n\tw.WriteHeader(http.StatusFound)\n}\n\nfunc (fe *frontendServer) viewCartHandler(w http.ResponseWriter, r *http.Request) {\n\tlog := r.Context().Value(ctxKeyLog{}).(logrus.FieldLogger)\n\tlog.Debug(\"view user cart\")\n\tcurrencies, err := fe.getCurrencies(r.Context())\n\tif err != nil {\n\t\trenderHTTPError(log, r, w, errors.Wrap(err, \"could not retrieve currencies\"), http.StatusInternalServerError)\n\t\treturn\n\t}\n\tcart, err := fe.getCart(r.Context(), sessionID(r))\n\tif err != nil {\n\t\trenderHTTPError(log, r, w, errors.Wrap(err, \"could not retrieve cart\"), http.StatusInternalServerError)\n\t\treturn\n\t}\n\n\t// ignores the error retrieving recommendations since it is not critical\n\trecommendations, err := fe.getRecommendations(r.Context(), sessionID(r), cartIDs(cart))\n\tif err != nil {\n\t\tlog.WithField(\"error\", err).Warn(\"failed to get product recommendations\")\n\t}\n\n\tshippingCost, err := fe.getShippingQuote(r.Context(), cart, currentCurrency(r))\n\tif err != nil {\n\t\trenderHTTPError(log, r, w, errors.Wrap(err, \"failed to get shipping quote\"), http.StatusInternalServerError)\n\t\treturn\n\t}\n\n\ttype cartItemView struct {\n\t\tItem     *pb.Product\n\t\tQuantity int32\n\t\tPrice    *pb.Money\n\t}\n\titems := make([]cartItemView, len(cart))\n\ttotalPrice := pb.Money{CurrencyCode: currentCurrency(r)}\n\tfor i, item := range cart {\n\t\tp, err := fe.getProduct(r.Context(), item.GetProductId())\n\t\tif err != nil {\n\t\t\trenderHTTPError(log, r, w, errors.Wrapf(err, \"could not retrieve product #%s\", item.GetProductId()), http.StatusInternalServerError)\n\t\t\treturn\n\t\t}\n\t\tprice, err := fe.convertCurrency(r.Context(), p.GetPriceUsd(), currentCurrency(r))\n\t\tif err != nil {\n\t\t\trenderHTTPError(log, r, w, errors.Wrapf(err, \"could not convert currency for product #%s\", item.GetProductId()), http.StatusInternalServerError)\n\t\t\treturn\n\t\t}\n\n\t\tmultPrice := money.MultiplySlow(*price, uint32(item.GetQuantity()))\n\t\titems[i] = cartItemView{\n\t\t\tItem:     p,\n\t\t\tQuantity: item.GetQuantity(),\n\t\t\tPrice:    &multPrice}\n\t\ttotalPrice = money.Must(money.Sum(totalPrice, multPrice))\n\t}\n\ttotalPrice = money.Must(money.Sum(totalPrice, *shippingCost))\n\tyear := time.Now().Year()\n\n\tif err := templates.ExecuteTemplate(w, \"cart\", injectCommonTemplateData(r, map[string]interface{}{\n\t\t\"currencies\":       currencies,\n\t\t\"recommendations\":  recommendations,\n\t\t\"cart_size\":        cartSize(cart),\n\t\t\"shipping_cost\":    shippingCost,\n\t\t\"show_currency\":    true,\n\t\t\"total_cost\":       totalPrice,\n\t\t\"items\":            items,\n\t\t\"expiration_years\": []int{year, year + 1, year + 2, year + 3, year + 4},\n\t})); err != nil {\n\t\tlog.Println(err)\n\t}\n}\n\nfunc (fe *frontendServer) placeOrderHandler(w http.ResponseWriter, r *http.Request) {\n\tlog := r.Context().Value(ctxKeyLog{}).(logrus.FieldLogger)\n\tlog.Debug(\"placing order\")\n\n\tvar (\n\t\temail         = r.FormValue(\"email\")\n\t\tstreetAddress = r.FormValue(\"street_address\")\n\t\tzipCode, _    = strconv.ParseInt(r.FormValue(\"zip_code\"), 10, 32)\n\t\tcity          = r.FormValue(\"city\")\n\t\tstate         = r.FormValue(\"state\")\n\t\tcountry       = r.FormValue(\"country\")\n\t\tccNumber      = r.FormValue(\"credit_card_number\")\n\t\tccMonth, _    = strconv.ParseInt(r.FormValue(\"credit_card_expiration_month\"), 10, 32)\n\t\tccYear, _     = strconv.ParseInt(r.FormValue(\"credit_card_expiration_year\"), 10, 32)\n\t\tccCVV, _      = strconv.ParseInt(r.FormValue(\"credit_card_cvv\"), 10, 32)\n\t)\n\n\tpayload := validator.PlaceOrderPayload{\n\t\tEmail:         email,\n\t\tStreetAddress: streetAddress,\n\t\tZipCode:       zipCode,\n\t\tCity:          city,\n\t\tState:         state,\n\t\tCountry:       country,\n\t\tCcNumber:      ccNumber,\n\t\tCcMonth:       ccMonth,\n\t\tCcYear:        ccYear,\n\t\tCcCVV:         ccCVV,\n\t}\n\tif err := payload.Validate(); err != nil {\n\t\trenderHTTPError(log, r, w, validator.ValidationErrorResponse(err), http.StatusUnprocessableEntity)\n\t\treturn\n\t}\n\n\torder, err := pb.NewCheckoutServiceClient(fe.checkoutSvcConn).\n\t\tPlaceOrder(r.Context(), &pb.PlaceOrderRequest{\n\t\t\tEmail: payload.Email,\n\t\t\tCreditCard: &pb.CreditCardInfo{\n\t\t\t\tCreditCardNumber:          payload.CcNumber,\n\t\t\t\tCreditCardExpirationMonth: int32(payload.CcMonth),\n\t\t\t\tCreditCardExpirationYear:  int32(payload.CcYear),\n\t\t\t\tCreditCardCvv:             int32(payload.CcCVV)},\n\t\t\tUserId:       sessionID(r),\n\t\t\tUserCurrency: currentCurrency(r),\n\t\t\tAddress: &pb.Address{\n\t\t\t\tStreetAddress: payload.StreetAddress,\n\t\t\t\tCity:          payload.City,\n\t\t\t\tState:         payload.State,\n\t\t\t\tZipCode:       int32(payload.ZipCode),\n\t\t\t\tCountry:       payload.Country},\n\t\t})\n\tif err != nil {\n\t\trenderHTTPError(log, r, w, errors.Wrap(err, \"failed to complete the order\"), http.StatusInternalServerError)\n\t\treturn\n\t}\n\tlog.WithField(\"order\", order.GetOrder().GetOrderId()).Info(\"order placed\")\n\n\torder.GetOrder().GetItems()\n\trecommendations, _ := fe.getRecommendations(r.Context(), sessionID(r), nil)\n\n\ttotalPaid := *order.GetOrder().GetShippingCost()\n\tfor _, v := range order.GetOrder().GetItems() {\n\t\tmultPrice := money.MultiplySlow(*v.GetCost(), uint32(v.GetItem().GetQuantity()))\n\t\ttotalPaid = money.Must(money.Sum(totalPaid, multPrice))\n\t}\n\n\tcurrencies, err := fe.getCurrencies(r.Context())\n\tif err != nil {\n\t\trenderHTTPError(log, r, w, errors.Wrap(err, \"could not retrieve currencies\"), http.StatusInternalServerError)\n\t\treturn\n\t}\n\n\tif err := templates.ExecuteTemplate(w, \"order\", injectCommonTemplateData(r, map[string]interface{}{\n\t\t\"show_currency\":   false,\n\t\t\"currencies\":      currencies,\n\t\t\"order\":           order.GetOrder(),\n\t\t\"total_paid\":      &totalPaid,\n\t\t\"recommendations\": recommendations,\n\t})); err != nil {\n\t\tlog.Println(err)\n\t}\n}\n\nfunc (fe *frontendServer) assistantHandler(w http.ResponseWriter, r *http.Request) {\n\tcurrencies, err := fe.getCurrencies(r.Context())\n\tif err != nil {\n\t\trenderHTTPError(log, r, w, errors.Wrap(err, \"could not retrieve currencies\"), http.StatusInternalServerError)\n\t\treturn\n\t}\n\n\tif err := templates.ExecuteTemplate(w, \"assistant\", injectCommonTemplateData(r, map[string]interface{}{\n\t\t\"show_currency\": false,\n\t\t\"currencies\":    currencies,\n\t})); err != nil {\n\t\tlog.Println(err)\n\t}\n}\n\nfunc (fe *frontendServer) logoutHandler(w http.ResponseWriter, r *http.Request) {\n\tlog := r.Context().Value(ctxKeyLog{}).(logrus.FieldLogger)\n\tlog.Debug(\"logging out\")\n\tfor _, c := range r.Cookies() {\n\t\tc.Expires = time.Now().Add(-time.Hour * 24 * 365)\n\t\tc.MaxAge = -1\n\t\thttp.SetCookie(w, c)\n\t}\n\tw.Header().Set(\"Location\", baseUrl + \"/\")\n\tw.WriteHeader(http.StatusFound)\n}\n\nfunc (fe *frontendServer) getProductByID(w http.ResponseWriter, r *http.Request) {\n\tid := mux.Vars(r)[\"ids\"]\n\tif id == \"\" {\n\t\treturn\n\t}\n\n\tp, err := fe.getProduct(r.Context(), id)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tjsonData, err := json.Marshal(p)\n\tif err != nil {\n\t\tfmt.Println(err)\n\t\treturn\n\t}\n\n\tw.Write(jsonData)\n\tw.WriteHeader(http.StatusOK)\n}\n\nfunc (fe *frontendServer) chatBotHandler(w http.ResponseWriter, r *http.Request) {\n\tlog := r.Context().Value(ctxKeyLog{}).(logrus.FieldLogger)\n\ttype Response struct {\n\t\tMessage string `json:\"message\"`\n\t}\n\n\ttype LLMResponse struct {\n\t\tContent string         `json:\"content\"`\n\t\tDetails map[string]any `json:\"details\"`\n\t}\n\n\tvar response LLMResponse\n\n\turl := \"http://\" + fe.shoppingAssistantSvcAddr\n\treq, err := http.NewRequest(http.MethodPost, url, r.Body)\n\tif err != nil {\n\t\trenderHTTPError(log, r, w, errors.Wrap(err, \"failed to create request\"), http.StatusInternalServerError)\n\t\treturn\n\t}\n\treq.Header.Set(\"Content-Type\", \"application/json\")\n\treq.Header.Set(\"Accept\", \"application/json\")\n\tres, err := http.DefaultClient.Do(req)\n\tif err != nil {\n\t\trenderHTTPError(log, r, w, errors.Wrap(err, \"failed to send request\"), http.StatusInternalServerError)\n\t\treturn\n\t}\n\n\tbody, err := io.ReadAll(res.Body)\n\tif err != nil {\n\t\trenderHTTPError(log, r, w, errors.Wrap(err, \"failed to read response\"), http.StatusInternalServerError)\n\t\treturn\n\t}\n\n\tfmt.Printf(\"%+v\\n\", body)\n\tfmt.Printf(\"%+v\\n\", res)\n\n\terr = json.Unmarshal(body, &response)\n\tif err != nil {\n\t\trenderHTTPError(log, r, w, errors.Wrap(err, \"failed to unmarshal body\"), http.StatusInternalServerError)\n\t\treturn\n\t}\n\n\t// respond with the same message\n\tjson.NewEncoder(w).Encode(Response{Message: response.Content})\n\n\tw.WriteHeader(http.StatusOK)\n}\n\nfunc (fe *frontendServer) setCurrencyHandler(w http.ResponseWriter, r *http.Request) {\n\tlog := r.Context().Value(ctxKeyLog{}).(logrus.FieldLogger)\n\tcur := r.FormValue(\"currency_code\")\n\tpayload := validator.SetCurrencyPayload{Currency: cur}\n\tif err := payload.Validate(); err != nil {\n\t\trenderHTTPError(log, r, w, validator.ValidationErrorResponse(err), http.StatusUnprocessableEntity)\n\t\treturn\n\t}\n\tlog.WithField(\"curr.new\", payload.Currency).WithField(\"curr.old\", currentCurrency(r)).\n\t\tDebug(\"setting currency\")\n\n\tif payload.Currency != \"\" {\n\t\thttp.SetCookie(w, &http.Cookie{\n\t\t\tName:   cookieCurrency,\n\t\t\tValue:  payload.Currency,\n\t\t\tMaxAge: cookieMaxAge,\n\t\t})\n\t}\n\treferer := r.Header.Get(\"referer\")\n\tif referer == \"\" {\n\t\treferer = baseUrl + \"/\"\n\t}\n\tw.Header().Set(\"Location\", referer)\n\tw.WriteHeader(http.StatusFound)\n}\n\n// chooseAd queries for advertisements available and randomly chooses one, if\n// available. It ignores the error retrieving the ad since it is not critical.\nfunc (fe *frontendServer) chooseAd(ctx context.Context, ctxKeys []string, log logrus.FieldLogger) *pb.Ad {\n\tads, err := fe.getAd(ctx, ctxKeys)\n\tif err != nil {\n\t\tlog.WithField(\"error\", err).Warn(\"failed to retrieve ads\")\n\t\treturn nil\n\t}\n\treturn ads[rand.Intn(len(ads))]\n}\n\nfunc renderHTTPError(log logrus.FieldLogger, r *http.Request, w http.ResponseWriter, err error, code int) {\n\tlog.WithField(\"error\", err).Error(\"request error\")\n\terrMsg := fmt.Sprintf(\"%+v\", err)\n\n\tw.WriteHeader(code)\n\n\tif templateErr := templates.ExecuteTemplate(w, \"error\", injectCommonTemplateData(r, map[string]interface{}{\n\t\t\"error\":       errMsg,\n\t\t\"status_code\": code,\n\t\t\"status\":      http.StatusText(code),\n\t})); templateErr != nil {\n\t\tlog.Println(templateErr)\n\t}\n}\n\nfunc injectCommonTemplateData(r *http.Request, payload map[string]interface{}) map[string]interface{} {\n\tdata := map[string]interface{}{\n\t\t\"session_id\":        sessionID(r),\n\t\t\"request_id\":        r.Context().Value(ctxKeyRequestID{}),\n\t\t\"user_currency\":     currentCurrency(r),\n\t\t\"platform_css\":      plat.css,\n\t\t\"platform_name\":     plat.provider,\n\t\t\"is_cymbal_brand\":   isCymbalBrand,\n\t\t\"assistant_enabled\": assistantEnabled,\n\t\t\"deploymentDetails\": deploymentDetailsMap,\n\t\t\"frontendMessage\":   frontendMessage,\n\t\t\"currentYear\":       time.Now().Year(),\n\t\t\"baseUrl\":           baseUrl,\n\t}\n\n\tfor k, v := range payload {\n\t\tdata[k] = v\n\t}\n\n\treturn data\n}\n\nfunc currentCurrency(r *http.Request) string {\n\tc, _ := r.Cookie(cookieCurrency)\n\tif c != nil {\n\t\treturn c.Value\n\t}\n\treturn defaultCurrency\n}\n\nfunc sessionID(r *http.Request) string {\n\tv := r.Context().Value(ctxKeySessionID{})\n\tif v != nil {\n\t\treturn v.(string)\n\t}\n\treturn \"\"\n}\n\nfunc cartIDs(c []*pb.CartItem) []string {\n\tout := make([]string, len(c))\n\tfor i, v := range c {\n\t\tout[i] = v.GetProductId()\n\t}\n\treturn out\n}\n\n// get total # of items in cart\nfunc cartSize(c []*pb.CartItem) int {\n\tcartSize := 0\n\tfor _, item := range c {\n\t\tcartSize += int(item.GetQuantity())\n\t}\n\treturn cartSize\n}\n\nfunc renderMoney(money pb.Money) string {\n\tcurrencyLogo := renderCurrencyLogo(money.GetCurrencyCode())\n\treturn fmt.Sprintf(\"%s%d.%02d\", currencyLogo, money.GetUnits(), money.GetNanos()/10000000)\n}\n\nfunc renderCurrencyLogo(currencyCode string) string {\n\tlogos := map[string]string{\n\t\t\"USD\": \"$\",\n\t\t\"CAD\": \"$\",\n\t\t\"JPY\": \"¥\",\n\t\t\"EUR\": \"€\",\n\t\t\"TRY\": \"₺\",\n\t\t\"GBP\": \"£\",\n\t}\n\n\tlogo := \"$\" //default\n\tif val, ok := logos[currencyCode]; ok {\n\t\tlogo = val\n\t}\n\treturn logo\n}\n\nfunc stringinSlice(slice []string, val string) bool {\n\tfor _, item := range slice {\n\t\tif item == val {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n"
  },
  {
    "path": "src/frontend/main.go",
    "content": "// Copyright 2018 Google LLC\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"os\"\n\t\"time\"\n\n\t\"cloud.google.com/go/profiler\"\n\t\"github.com/gorilla/mux\"\n\t\"github.com/pkg/errors\"\n\t\"github.com/sirupsen/logrus\"\n\t\"go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc\"\n\t\"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp\"\n\t\"go.opentelemetry.io/otel\"\n\t\"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc\"\n\t\"go.opentelemetry.io/otel/propagation\"\n\tsdktrace \"go.opentelemetry.io/otel/sdk/trace\"\n\t\"google.golang.org/grpc\"\n\t\"google.golang.org/grpc/credentials/insecure\"\n)\n\nconst (\n\tport            = \"8080\"\n\tdefaultCurrency = \"USD\"\n\tcookieMaxAge    = 60 * 60 * 48\n\n\tcookiePrefix    = \"shop_\"\n\tcookieSessionID = cookiePrefix + \"session-id\"\n\tcookieCurrency  = cookiePrefix + \"currency\"\n)\n\nvar (\n\twhitelistedCurrencies = map[string]bool{\n\t\t\"USD\": true,\n\t\t\"EUR\": true,\n\t\t\"CAD\": true,\n\t\t\"JPY\": true,\n\t\t\"GBP\": true,\n\t\t\"TRY\": true,\n\t}\n\n\tbaseUrl = \"\"\n)\n\ntype ctxKeySessionID struct{}\n\ntype frontendServer struct {\n\tproductCatalogSvcAddr string\n\tproductCatalogSvcConn *grpc.ClientConn\n\n\tcurrencySvcAddr string\n\tcurrencySvcConn *grpc.ClientConn\n\n\tcartSvcAddr string\n\tcartSvcConn *grpc.ClientConn\n\n\trecommendationSvcAddr string\n\trecommendationSvcConn *grpc.ClientConn\n\n\tcheckoutSvcAddr string\n\tcheckoutSvcConn *grpc.ClientConn\n\n\tshippingSvcAddr string\n\tshippingSvcConn *grpc.ClientConn\n\n\tadSvcAddr string\n\tadSvcConn *grpc.ClientConn\n\n\tcollectorAddr string\n\tcollectorConn *grpc.ClientConn\n\n\tshoppingAssistantSvcAddr string\n}\n\nfunc main() {\n\tctx := context.Background()\n\tlog := logrus.New()\n\tlog.Level = logrus.DebugLevel\n\tlog.Formatter = &logrus.JSONFormatter{\n\t\tFieldMap: logrus.FieldMap{\n\t\t\tlogrus.FieldKeyTime:  \"timestamp\",\n\t\t\tlogrus.FieldKeyLevel: \"severity\",\n\t\t\tlogrus.FieldKeyMsg:   \"message\",\n\t\t},\n\t\tTimestampFormat: time.RFC3339Nano,\n\t}\n\tlog.Out = os.Stdout\n\n\tsvc := new(frontendServer)\n\n\totel.SetTextMapPropagator(\n\t\tpropagation.NewCompositeTextMapPropagator(\n\t\t\tpropagation.TraceContext{}, propagation.Baggage{}))\n\n\tbaseUrl = os.Getenv(\"BASE_URL\")\n\n\tif os.Getenv(\"ENABLE_TRACING\") == \"1\" {\n\t\tlog.Info(\"Tracing enabled.\")\n\t\tinitTracing(log, ctx, svc)\n\t} else {\n\t\tlog.Info(\"Tracing disabled.\")\n\t}\n\n\tif os.Getenv(\"ENABLE_PROFILER\") == \"1\" {\n\t\tlog.Info(\"Profiling enabled.\")\n\t\tgo initProfiling(log, \"frontend\", \"1.0.0\")\n\t} else {\n\t\tlog.Info(\"Profiling disabled.\")\n\t}\n\n\tsrvPort := port\n\tif os.Getenv(\"PORT\") != \"\" {\n\t\tsrvPort = os.Getenv(\"PORT\")\n\t}\n\taddr := os.Getenv(\"LISTEN_ADDR\")\n\tmustMapEnv(&svc.productCatalogSvcAddr, \"PRODUCT_CATALOG_SERVICE_ADDR\")\n\tmustMapEnv(&svc.currencySvcAddr, \"CURRENCY_SERVICE_ADDR\")\n\tmustMapEnv(&svc.cartSvcAddr, \"CART_SERVICE_ADDR\")\n\tmustMapEnv(&svc.recommendationSvcAddr, \"RECOMMENDATION_SERVICE_ADDR\")\n\tmustMapEnv(&svc.checkoutSvcAddr, \"CHECKOUT_SERVICE_ADDR\")\n\tmustMapEnv(&svc.shippingSvcAddr, \"SHIPPING_SERVICE_ADDR\")\n\tmustMapEnv(&svc.adSvcAddr, \"AD_SERVICE_ADDR\")\n\tmustMapEnv(&svc.shoppingAssistantSvcAddr, \"SHOPPING_ASSISTANT_SERVICE_ADDR\")\n\n\tmustConnGRPC(ctx, &svc.currencySvcConn, svc.currencySvcAddr)\n\tmustConnGRPC(ctx, &svc.productCatalogSvcConn, svc.productCatalogSvcAddr)\n\tmustConnGRPC(ctx, &svc.cartSvcConn, svc.cartSvcAddr)\n\tmustConnGRPC(ctx, &svc.recommendationSvcConn, svc.recommendationSvcAddr)\n\tmustConnGRPC(ctx, &svc.shippingSvcConn, svc.shippingSvcAddr)\n\tmustConnGRPC(ctx, &svc.checkoutSvcConn, svc.checkoutSvcAddr)\n\tmustConnGRPC(ctx, &svc.adSvcConn, svc.adSvcAddr)\n\n\tr := mux.NewRouter()\n\tr.HandleFunc(baseUrl+\"/\", svc.homeHandler).Methods(http.MethodGet, http.MethodHead)\n\tr.HandleFunc(baseUrl+\"/product/{id}\", svc.productHandler).Methods(http.MethodGet, http.MethodHead)\n\tr.HandleFunc(baseUrl+\"/cart\", svc.viewCartHandler).Methods(http.MethodGet, http.MethodHead)\n\tr.HandleFunc(baseUrl+\"/cart\", svc.addToCartHandler).Methods(http.MethodPost)\n\tr.HandleFunc(baseUrl+\"/cart/empty\", svc.emptyCartHandler).Methods(http.MethodPost)\n\tr.HandleFunc(baseUrl+\"/setCurrency\", svc.setCurrencyHandler).Methods(http.MethodPost)\n\tr.HandleFunc(baseUrl+\"/logout\", svc.logoutHandler).Methods(http.MethodGet)\n\tr.HandleFunc(baseUrl+\"/cart/checkout\", svc.placeOrderHandler).Methods(http.MethodPost)\n\tr.HandleFunc(baseUrl+\"/assistant\", svc.assistantHandler).Methods(http.MethodGet)\n\tr.PathPrefix(baseUrl + \"/static/\").Handler(http.StripPrefix(baseUrl+\"/static/\", http.FileServer(http.Dir(\"./static/\"))))\n\tr.HandleFunc(baseUrl+\"/robots.txt\", func(w http.ResponseWriter, _ *http.Request) { fmt.Fprint(w, \"User-agent: *\\nDisallow: /\") })\n\tr.HandleFunc(baseUrl+\"/_healthz\", func(w http.ResponseWriter, _ *http.Request) { fmt.Fprint(w, \"ok\") })\n\tr.HandleFunc(baseUrl+\"/product-meta/{ids}\", svc.getProductByID).Methods(http.MethodGet)\n\tr.HandleFunc(baseUrl+\"/bot\", svc.chatBotHandler).Methods(http.MethodPost)\n\n\tvar handler http.Handler = r\n\thandler = &logHandler{log: log, next: handler}     // add logging\n\thandler = ensureSessionID(handler)                 // add session ID\n\thandler = otelhttp.NewHandler(handler, \"frontend\") // add OTel tracing\n\n\tlog.Infof(\"starting server on %s:%s\", addr, srvPort)\n\tlog.Fatal(http.ListenAndServe(addr+\":\"+srvPort, handler))\n}\nfunc initStats(log logrus.FieldLogger) {\n\t// TODO(arbrown) Implement OpenTelemtry stats\n}\n\nfunc initTracing(log logrus.FieldLogger, ctx context.Context, svc *frontendServer) (*sdktrace.TracerProvider, error) {\n\tmustMapEnv(&svc.collectorAddr, \"COLLECTOR_SERVICE_ADDR\")\n\tmustConnGRPC(ctx, &svc.collectorConn, svc.collectorAddr)\n\texporter, err := otlptracegrpc.New(\n\t\tctx,\n\t\totlptracegrpc.WithGRPCConn(svc.collectorConn))\n\tif err != nil {\n\t\tlog.Warnf(\"warn: Failed to create trace exporter: %v\", err)\n\t}\n\ttp := sdktrace.NewTracerProvider(\n\t\tsdktrace.WithBatcher(exporter),\n\t\tsdktrace.WithSampler(sdktrace.AlwaysSample()))\n\totel.SetTracerProvider(tp)\n\n\treturn tp, err\n}\n\nfunc initProfiling(log logrus.FieldLogger, service, version string) {\n\t// TODO(ahmetb) this method is duplicated in other microservices using Go\n\t// since they are not sharing packages.\n\tfor i := 1; i <= 3; i++ {\n\t\tlog = log.WithField(\"retry\", i)\n\t\tif err := profiler.Start(profiler.Config{\n\t\t\tService:        service,\n\t\t\tServiceVersion: version,\n\t\t\t// ProjectID must be set if not running on GCP.\n\t\t\t// ProjectID: \"my-project\",\n\t\t}); err != nil {\n\t\t\tlog.Warnf(\"warn: failed to start profiler: %+v\", err)\n\t\t} else {\n\t\t\tlog.Info(\"started Stackdriver profiler\")\n\t\t\treturn\n\t\t}\n\t\td := time.Second * 10 * time.Duration(i)\n\t\tlog.Debugf(\"sleeping %v to retry initializing Stackdriver profiler\", d)\n\t\ttime.Sleep(d)\n\t}\n\tlog.Warn(\"warning: could not initialize Stackdriver profiler after retrying, giving up\")\n}\n\nfunc mustMapEnv(target *string, envKey string) {\n\tv := os.Getenv(envKey)\n\tif v == \"\" {\n\t\tpanic(fmt.Sprintf(\"environment variable %q not set\", envKey))\n\t}\n\t*target = v\n}\n\nfunc mustConnGRPC(ctx context.Context, conn **grpc.ClientConn, addr string) {\n\tvar err error\n\t_, cancel := context.WithTimeout(ctx, time.Second*3)\n\tdefer cancel()\n\t*conn, err = grpc.NewClient(addr,\n\t\tgrpc.WithTransportCredentials(insecure.NewCredentials()),\n\t\tgrpc.WithStatsHandler(otelgrpc.NewClientHandler()))\n\tif err != nil {\n\t\tpanic(errors.Wrapf(err, \"grpc: failed to connect %s\", addr))\n\t}\n}\n"
  },
  {
    "path": "src/frontend/middleware.go",
    "content": "// Copyright 2018 Google LLC\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage main\n\nimport (\n\t\"context\"\n\t\"net/http\"\n\t\"time\"\n\t\"os\"\n\n\t\"github.com/google/uuid\"\n\t\"github.com/sirupsen/logrus\"\n)\n\ntype ctxKeyLog struct{}\ntype ctxKeyRequestID struct{}\n\ntype logHandler struct {\n\tlog  *logrus.Logger\n\tnext http.Handler\n}\n\ntype responseRecorder struct {\n\tb      int\n\tstatus int\n\tw      http.ResponseWriter\n}\n\nfunc (r *responseRecorder) Header() http.Header { return r.w.Header() }\n\nfunc (r *responseRecorder) Write(p []byte) (int, error) {\n\tif r.status == 0 {\n\t\tr.status = http.StatusOK\n\t}\n\tn, err := r.w.Write(p)\n\tr.b += n\n\treturn n, err\n}\n\nfunc (r *responseRecorder) WriteHeader(statusCode int) {\n\tr.status = statusCode\n\tr.w.WriteHeader(statusCode)\n}\n\nfunc (lh *logHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {\n\tctx := r.Context()\n\trequestID, _ := uuid.NewRandom()\n\tctx = context.WithValue(ctx, ctxKeyRequestID{}, requestID.String())\n\n\tstart := time.Now()\n\trr := &responseRecorder{w: w}\n\tlog := lh.log.WithFields(logrus.Fields{\n\t\t\"http.req.path\":   r.URL.Path,\n\t\t\"http.req.method\": r.Method,\n\t\t\"http.req.id\":     requestID.String(),\n\t})\n\tif v, ok := r.Context().Value(ctxKeySessionID{}).(string); ok {\n\t\tlog = log.WithField(\"session\", v)\n\t}\n\tlog.Debug(\"request started\")\n\tdefer func() {\n\t\tlog.WithFields(logrus.Fields{\n\t\t\t\"http.resp.took_ms\": int64(time.Since(start) / time.Millisecond),\n\t\t\t\"http.resp.status\":  rr.status,\n\t\t\t\"http.resp.bytes\":   rr.b}).Debugf(\"request complete\")\n\t}()\n\n\tctx = context.WithValue(ctx, ctxKeyLog{}, log)\n\tr = r.WithContext(ctx)\n\tlh.next.ServeHTTP(rr, r)\n}\n\nfunc ensureSessionID(next http.Handler) http.HandlerFunc {\n\treturn func(w http.ResponseWriter, r *http.Request) {\n\t\tvar sessionID string\n\t\tc, err := r.Cookie(cookieSessionID)\n\t\tif err == http.ErrNoCookie {\n\t\t\tif os.Getenv(\"ENABLE_SINGLE_SHARED_SESSION\") == \"true\" {\n\t\t\t\t// Hard coded user id, shared across sessions\n\t\t\t\tsessionID = \"12345678-1234-1234-1234-123456789123\"\n\t\t\t} else {\n\t\t\t\tu, _ := uuid.NewRandom()\n\t\t\t\tsessionID = u.String()\n\t\t\t}\n\t\t\thttp.SetCookie(w, &http.Cookie{\n\t\t\t\tName:   cookieSessionID,\n\t\t\t\tValue:  sessionID,\n\t\t\t\tMaxAge: cookieMaxAge,\n\t\t\t})\n\t\t} else if err != nil {\n\t\t\treturn\n\t\t} else {\n\t\t\tsessionID = c.Value\n\t\t}\n\t\tctx := context.WithValue(r.Context(), ctxKeySessionID{}, sessionID)\n\t\tr = r.WithContext(ctx)\n\t\tnext.ServeHTTP(w, r)\n\t}\n}\n"
  },
  {
    "path": "src/frontend/money/money.go",
    "content": "// Copyright 2018 Google LLC\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage money\n\nimport (\n\t\"errors\"\n\n\tpb \"github.com/GoogleCloudPlatform/microservices-demo/src/frontend/genproto\"\n)\n\nconst (\n\tnanosMin = -999999999\n\tnanosMax = +999999999\n\tnanosMod = 1000000000\n)\n\nvar (\n\tErrInvalidValue        = errors.New(\"one of the specified money values is invalid\")\n\tErrMismatchingCurrency = errors.New(\"mismatching currency codes\")\n)\n\n// IsValid checks if specified value has a valid units/nanos signs and ranges.\nfunc IsValid(m pb.Money) bool {\n\treturn signMatches(m) && validNanos(m.GetNanos())\n}\n\nfunc signMatches(m pb.Money) bool {\n\treturn m.GetNanos() == 0 || m.GetUnits() == 0 || (m.GetNanos() < 0) == (m.GetUnits() < 0)\n}\n\nfunc validNanos(nanos int32) bool { return nanosMin <= nanos && nanos <= nanosMax }\n\n// IsZero returns true if the specified money value is equal to zero.\nfunc IsZero(m pb.Money) bool { return m.GetUnits() == 0 && m.GetNanos() == 0 }\n\n// IsPositive returns true if the specified money value is valid and is\n// positive.\nfunc IsPositive(m pb.Money) bool {\n\treturn IsValid(m) && m.GetUnits() > 0 || (m.GetUnits() == 0 && m.GetNanos() > 0)\n}\n\n// IsNegative returns true if the specified money value is valid and is\n// negative.\nfunc IsNegative(m pb.Money) bool {\n\treturn IsValid(m) && m.GetUnits() < 0 || (m.GetUnits() == 0 && m.GetNanos() < 0)\n}\n\n// AreSameCurrency returns true if values l and r have a currency code and\n// they are the same values.\nfunc AreSameCurrency(l, r pb.Money) bool {\n\treturn l.GetCurrencyCode() == r.GetCurrencyCode() && l.GetCurrencyCode() != \"\"\n}\n\n// AreEquals returns true if values l and r are the equal, including the\n// currency. This does not check validity of the provided values.\nfunc AreEquals(l, r pb.Money) bool {\n\treturn l.GetCurrencyCode() == r.GetCurrencyCode() &&\n\t\tl.GetUnits() == r.GetUnits() && l.GetNanos() == r.GetNanos()\n}\n\n// Negate returns the same amount with the sign negated.\nfunc Negate(m pb.Money) pb.Money {\n\treturn pb.Money{\n\t\tUnits:        -m.GetUnits(),\n\t\tNanos:        -m.GetNanos(),\n\t\tCurrencyCode: m.GetCurrencyCode()}\n}\n\n// Must panics if the given error is not nil. This can be used with other\n// functions like: \"m := Must(Sum(a,b))\".\nfunc Must(v pb.Money, err error) pb.Money {\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn v\n}\n\n// Sum adds two values. Returns an error if one of the values are invalid or\n// currency codes are not matching (unless currency code is unspecified for\n// both).\nfunc Sum(l, r pb.Money) (pb.Money, error) {\n\tif !IsValid(l) || !IsValid(r) {\n\t\treturn pb.Money{}, ErrInvalidValue\n\t} else if l.GetCurrencyCode() != r.GetCurrencyCode() {\n\t\treturn pb.Money{}, ErrMismatchingCurrency\n\t}\n\tunits := l.GetUnits() + r.GetUnits()\n\tnanos := l.GetNanos() + r.GetNanos()\n\n\tif (units == 0 && nanos == 0) || (units > 0 && nanos >= 0) || (units < 0 && nanos <= 0) {\n\t\t// same sign <units, nanos>\n\t\tunits += int64(nanos / nanosMod)\n\t\tnanos = nanos % nanosMod\n\t} else {\n\t\t// different sign. nanos guaranteed to not to go over the limit\n\t\tif units > 0 {\n\t\t\tunits--\n\t\t\tnanos += nanosMod\n\t\t} else {\n\t\t\tunits++\n\t\t\tnanos -= nanosMod\n\t\t}\n\t}\n\n\treturn pb.Money{\n\t\tUnits:        units,\n\t\tNanos:        nanos,\n\t\tCurrencyCode: l.GetCurrencyCode()}, nil\n}\n\n// MultiplySlow is a slow multiplication operation done through adding the value\n// to itself n-1 times.\nfunc MultiplySlow(m pb.Money, n uint32) pb.Money {\n\tout := m\n\tfor n > 1 {\n\t\tout = Must(Sum(out, m))\n\t\tn--\n\t}\n\treturn out\n}\n"
  },
  {
    "path": "src/frontend/money/money_test.go",
    "content": "// Copyright 2018 Google LLC\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage money\n\nimport (\n\t\"fmt\"\n\t\"reflect\"\n\t\"testing\"\n\n\tpb \"github.com/GoogleCloudPlatform/microservices-demo/src/frontend/genproto\"\n)\n\nfunc mmc(u int64, n int32, c string) pb.Money { return pb.Money{Units: u, Nanos: n, CurrencyCode: c} }\nfunc mm(u int64, n int32) pb.Money            { return mmc(u, n, \"\") }\n\nfunc TestIsValid(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tin   pb.Money\n\t\twant bool\n\t}{\n\t\t{\"valid -/-\", mm(-981273891273, -999999999), true},\n\t\t{\"invalid -/+\", mm(-981273891273, +999999999), false},\n\t\t{\"valid +/+\", mm(981273891273, 999999999), true},\n\t\t{\"invalid +/-\", mm(981273891273, -999999999), false},\n\t\t{\"invalid +/+overflow\", mm(3, 1000000000), false},\n\t\t{\"invalid +/-overflow\", mm(3, -1000000000), false},\n\t\t{\"invalid -/+overflow\", mm(-3, 1000000000), false},\n\t\t{\"invalid -/-overflow\", mm(-3, -1000000000), false},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tif got := IsValid(tt.in); got != tt.want {\n\t\t\t\tt.Errorf(\"IsValid(%v) = %v, want %v\", tt.in, got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestIsZero(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tin   pb.Money\n\t\twant bool\n\t}{\n\t\t{\"zero\", mm(0, 0), true},\n\t\t{\"not-zero (-/+)\", mm(-1, +1), false},\n\t\t{\"not-zero (-/-)\", mm(-1, -1), false},\n\t\t{\"not-zero (+/+)\", mm(+1, +1), false},\n\t\t{\"not-zero (+/-)\", mm(+1, -1), false},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tif got := IsZero(tt.in); got != tt.want {\n\t\t\t\tt.Errorf(\"IsZero(%v) = %v, want %v\", tt.in, got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestIsPositive(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tin   pb.Money\n\t\twant bool\n\t}{\n\t\t{\"zero\", mm(0, 0), false},\n\t\t{\"positive (+/+)\", mm(+1, +1), true},\n\t\t{\"invalid (-/+)\", mm(-1, +1), false},\n\t\t{\"negative (-/-)\", mm(-1, -1), false},\n\t\t{\"invalid (+/-)\", mm(+1, -1), false},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tif got := IsPositive(tt.in); got != tt.want {\n\t\t\t\tt.Errorf(\"IsPositive(%v) = %v, want %v\", tt.in, got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestIsNegative(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tin   pb.Money\n\t\twant bool\n\t}{\n\t\t{\"zero\", mm(0, 0), false},\n\t\t{\"positive (+/+)\", mm(+1, +1), false},\n\t\t{\"invalid (-/+)\", mm(-1, +1), false},\n\t\t{\"negative (-/-)\", mm(-1, -1), true},\n\t\t{\"invalid (+/-)\", mm(+1, -1), false},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tif got := IsNegative(tt.in); got != tt.want {\n\t\t\t\tt.Errorf(\"IsNegative(%v) = %v, want %v\", tt.in, got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestAreSameCurrency(t *testing.T) {\n\ttype args struct {\n\t\tl pb.Money\n\t\tr pb.Money\n\t}\n\ttests := []struct {\n\t\tname string\n\t\targs args\n\t\twant bool\n\t}{\n\t\t{\"both empty currency\", args{mmc(1, 0, \"\"), mmc(2, 0, \"\")}, false},\n\t\t{\"left empty currency\", args{mmc(1, 0, \"\"), mmc(2, 0, \"USD\")}, false},\n\t\t{\"right empty currency\", args{mmc(1, 0, \"USD\"), mmc(2, 0, \"\")}, false},\n\t\t{\"mismatching\", args{mmc(1, 0, \"USD\"), mmc(2, 0, \"CAD\")}, false},\n\t\t{\"matching\", args{mmc(1, 0, \"USD\"), mmc(2, 0, \"USD\")}, true},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tif got := AreSameCurrency(tt.args.l, tt.args.r); got != tt.want {\n\t\t\t\tt.Errorf(\"AreSameCurrency([%v],[%v]) = %v, want %v\", tt.args.l, tt.args.r, got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestAreEquals(t *testing.T) {\n\ttype args struct {\n\t\tl pb.Money\n\t\tr pb.Money\n\t}\n\ttests := []struct {\n\t\tname string\n\t\targs args\n\t\twant bool\n\t}{\n\t\t{\"equals\", args{mmc(1, 2, \"USD\"), mmc(1, 2, \"USD\")}, true},\n\t\t{\"mismatching currency\", args{mmc(1, 2, \"USD\"), mmc(1, 2, \"CAD\")}, false},\n\t\t{\"mismatching units\", args{mmc(10, 20, \"USD\"), mmc(1, 20, \"USD\")}, false},\n\t\t{\"mismatching nanos\", args{mmc(1, 2, \"USD\"), mmc(1, 20, \"USD\")}, false},\n\t\t{\"negated\", args{mmc(1, 2, \"USD\"), mmc(-1, -2, \"USD\")}, false},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tif got := AreEquals(tt.args.l, tt.args.r); got != tt.want {\n\t\t\t\tt.Errorf(\"AreEquals([%v],[%v]) = %v, want %v\", tt.args.l, tt.args.r, got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestNegate(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tin   pb.Money\n\t\twant pb.Money\n\t}{\n\t\t{\"zero\", mm(0, 0), mm(0, 0)},\n\t\t{\"negative\", mm(-1, -200), mm(1, 200)},\n\t\t{\"positive\", mm(1, 200), mm(-1, -200)},\n\t\t{\"carries currency code\", mmc(0, 0, \"XXX\"), mmc(0, 0, \"XXX\")},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tif got := Negate(tt.in); !AreEquals(got, tt.want) {\n\t\t\t\tt.Errorf(\"Negate([%v]) = %v, want %v\", tt.in, got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestMust_pass(t *testing.T) {\n\tv := Must(mm(2, 3), nil)\n\tif !AreEquals(v, mm(2, 3)) {\n\t\tt.Errorf(\"returned the wrong value: %v\", v)\n\t}\n}\n\nfunc TestMust_panic(t *testing.T) {\n\tdefer func() {\n\t\tif r := recover(); r != nil {\n\t\t\tt.Logf(\"panic captured: %v\", r)\n\t\t}\n\t}()\n\tMust(mm(2, 3), fmt.Errorf(\"some error\"))\n\tt.Fatal(\"this should not have executed due to the panic above\")\n}\n\nfunc TestSum(t *testing.T) {\n\ttype args struct {\n\t\tl pb.Money\n\t\tr pb.Money\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\targs    args\n\t\twant    pb.Money\n\t\twantErr error\n\t}{\n\t\t{\"0+0=0\", args{mm(0, 0), mm(0, 0)}, mm(0, 0), nil},\n\t\t{\"Error: currency code on left\", args{mmc(0, 0, \"XXX\"), mm(0, 0)}, mm(0, 0), ErrMismatchingCurrency},\n\t\t{\"Error: currency code on right\", args{mm(0, 0), mmc(0, 0, \"YYY\")}, mm(0, 0), ErrMismatchingCurrency},\n\t\t{\"Error: currency code mismatch\", args{mmc(0, 0, \"AAA\"), mmc(0, 0, \"BBB\")}, mm(0, 0), ErrMismatchingCurrency},\n\t\t{\"Error: invalid +/-\", args{mm(+1, -1), mm(0, 0)}, mm(0, 0), ErrInvalidValue},\n\t\t{\"Error: invalid -/+\", args{mm(0, 0), mm(-1, +2)}, mm(0, 0), ErrInvalidValue},\n\t\t{\"Error: invalid nanos\", args{mm(0, 1000000000), mm(1, 0)}, mm(0, 0), ErrInvalidValue},\n\t\t{\"both positive (no carry)\", args{mm(2, 200000000), mm(2, 200000000)}, mm(4, 400000000), nil},\n\t\t{\"both positive (nanos=max)\", args{mm(2, 111111111), mm(2, 888888888)}, mm(4, 999999999), nil},\n\t\t{\"both positive (carry)\", args{mm(2, 200000000), mm(2, 900000000)}, mm(5, 100000000), nil},\n\t\t{\"both negative (no carry)\", args{mm(-2, -200000000), mm(-2, -200000000)}, mm(-4, -400000000), nil},\n\t\t{\"both negative (carry)\", args{mm(-2, -200000000), mm(-2, -900000000)}, mm(-5, -100000000), nil},\n\t\t{\"mixed (larger positive, just decimals)\", args{mm(11, 0), mm(-2, 0)}, mm(9, 0), nil},\n\t\t{\"mixed (larger negative, just decimals)\", args{mm(-11, 0), mm(2, 0)}, mm(-9, 0), nil},\n\t\t{\"mixed (larger positive, no borrow)\", args{mm(11, 100000000), mm(-2, -100000000)}, mm(9, 0), nil},\n\t\t{\"mixed (larger positive, with borrow)\", args{mm(11, 100000000), mm(-2, -9000000 /*.09*/)}, mm(9, 91000000 /*.091*/), nil},\n\t\t{\"mixed (larger negative, no borrow)\", args{mm(-11, -100000000), mm(2, 100000000)}, mm(-9, 0), nil},\n\t\t{\"mixed (larger negative, with borrow)\", args{mm(-11, -100000000), mm(2, 9000000 /*.09*/)}, mm(-9, -91000000 /*.091*/), nil},\n\t\t{\"0+negative\", args{mm(0, 0), mm(-2, -100000000)}, mm(-2, -100000000), nil},\n\t\t{\"negative+0\", args{mm(-2, -100000000), mm(0, 0)}, mm(-2, -100000000), nil},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := Sum(tt.args.l, tt.args.r)\n\t\t\tif err != tt.wantErr {\n\t\t\t\tt.Errorf(\"Sum([%v],[%v]): expected err=\\\"%v\\\" got=\\\"%v\\\"\", tt.args.l, tt.args.r, tt.wantErr, err)\n\t\t\t}\n\t\t\tif !reflect.DeepEqual(got, tt.want) {\n\t\t\t\tt.Errorf(\"Sum([%v],[%v]) = %v, want %v\", tt.args.l, tt.args.r, got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "src/frontend/packaging_info.go",
    "content": "// Copyright 2023 Google LLC\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage main\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io/ioutil\"\n\t\"net/http\"\n\t\"os\"\n)\n\n/*\nAs part of an optional Google Cloud demo, you can run an additional \"packaging\" microservice (HTTP server).\nThis file contains code related to the frontend and the \"packaging\" microservice.\n*/\n\nvar (\n\tpackagingServiceUrl string\n)\n\ntype PackagingInfo struct {\n\tWeight float32 `json:\"weight\"`\n\tWidth  float32 `json:\"width\"`\n\tHeight float32 `json:\"height\"`\n\tDepth  float32 `json:\"depth\"`\n}\n\n// init() is a special function in Golang that will run when this package is imported.\nfunc init() {\n\tpackagingServiceUrl = os.Getenv(\"PACKAGING_SERVICE_URL\")\n}\n\nfunc isPackagingServiceConfigured() bool {\n\treturn packagingServiceUrl != \"\"\n}\n\nfunc httpGetPackagingInfo(productId string) (*PackagingInfo, error) {\n\t// Make the GET request\n\turl := packagingServiceUrl + \"/\" + productId\n\tfmt.Println(\"Requesting packaging info from URL: \", url)\n\tresp, err := http.Get(url)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer resp.Body.Close()\n\n\t// Check the response status code\n\tif resp.StatusCode != http.StatusOK {\n\t\treturn nil, fmt.Errorf(\"Unexpected status code: %d\", resp.StatusCode)\n\t}\n\n\t// Read the JSON response body\n\tresponseBody, err := ioutil.ReadAll(resp.Body)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// Decode the JSON response into a PackagingInfo struct\n\tvar packagingInfo PackagingInfo\n\terr = json.Unmarshal(responseBody, &packagingInfo)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &packagingInfo, nil\n}\n"
  },
  {
    "path": "src/frontend/rpc.go",
    "content": "// Copyright 2018 Google LLC\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage main\n\nimport (\n\t\"context\"\n\t\"time\"\n\n\tpb \"github.com/GoogleCloudPlatform/microservices-demo/src/frontend/genproto\"\n\n\t\"github.com/pkg/errors\"\n)\n\nconst (\n\tavoidNoopCurrencyConversionRPC = false\n)\n\nfunc (fe *frontendServer) getCurrencies(ctx context.Context) ([]string, error) {\n\tcurrs, err := pb.NewCurrencyServiceClient(fe.currencySvcConn).\n\t\tGetSupportedCurrencies(ctx, &pb.Empty{})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tvar out []string\n\tfor _, c := range currs.CurrencyCodes {\n\t\tif _, ok := whitelistedCurrencies[c]; ok {\n\t\t\tout = append(out, c)\n\t\t}\n\t}\n\treturn out, nil\n}\n\nfunc (fe *frontendServer) getProducts(ctx context.Context) ([]*pb.Product, error) {\n\tresp, err := pb.NewProductCatalogServiceClient(fe.productCatalogSvcConn).\n\t\tListProducts(ctx, &pb.Empty{})\n\treturn resp.GetProducts(), err\n}\n\nfunc (fe *frontendServer) getProduct(ctx context.Context, id string) (*pb.Product, error) {\n\tresp, err := pb.NewProductCatalogServiceClient(fe.productCatalogSvcConn).\n\t\tGetProduct(ctx, &pb.GetProductRequest{Id: id})\n\treturn resp, err\n}\n\nfunc (fe *frontendServer) getCart(ctx context.Context, userID string) ([]*pb.CartItem, error) {\n\tresp, err := pb.NewCartServiceClient(fe.cartSvcConn).GetCart(ctx, &pb.GetCartRequest{UserId: userID})\n\treturn resp.GetItems(), err\n}\n\nfunc (fe *frontendServer) emptyCart(ctx context.Context, userID string) error {\n\t_, err := pb.NewCartServiceClient(fe.cartSvcConn).EmptyCart(ctx, &pb.EmptyCartRequest{UserId: userID})\n\treturn err\n}\n\nfunc (fe *frontendServer) insertCart(ctx context.Context, userID, productID string, quantity int32) error {\n\t_, err := pb.NewCartServiceClient(fe.cartSvcConn).AddItem(ctx, &pb.AddItemRequest{\n\t\tUserId: userID,\n\t\tItem: &pb.CartItem{\n\t\t\tProductId: productID,\n\t\t\tQuantity:  quantity},\n\t})\n\treturn err\n}\n\nfunc (fe *frontendServer) convertCurrency(ctx context.Context, money *pb.Money, currency string) (*pb.Money, error) {\n\tif avoidNoopCurrencyConversionRPC && money.GetCurrencyCode() == currency {\n\t\treturn money, nil\n\t}\n\treturn pb.NewCurrencyServiceClient(fe.currencySvcConn).\n\t\tConvert(ctx, &pb.CurrencyConversionRequest{\n\t\t\tFrom:   money,\n\t\t\tToCode: currency})\n}\n\nfunc (fe *frontendServer) getShippingQuote(ctx context.Context, items []*pb.CartItem, currency string) (*pb.Money, error) {\n\tquote, err := pb.NewShippingServiceClient(fe.shippingSvcConn).GetQuote(ctx,\n\t\t&pb.GetQuoteRequest{\n\t\t\tAddress: nil,\n\t\t\tItems:   items})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tlocalized, err := fe.convertCurrency(ctx, quote.GetCostUsd(), currency)\n\treturn localized, errors.Wrap(err, \"failed to convert currency for shipping cost\")\n}\n\nfunc (fe *frontendServer) getRecommendations(ctx context.Context, userID string, productIDs []string) ([]*pb.Product, error) {\n\tresp, err := pb.NewRecommendationServiceClient(fe.recommendationSvcConn).ListRecommendations(ctx,\n\t\t&pb.ListRecommendationsRequest{UserId: userID, ProductIds: productIDs})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tout := make([]*pb.Product, len(resp.GetProductIds()))\n\tfor i, v := range resp.GetProductIds() {\n\t\tp, err := fe.getProduct(ctx, v)\n\t\tif err != nil {\n\t\t\treturn nil, errors.Wrapf(err, \"failed to get recommended product info (#%s)\", v)\n\t\t}\n\t\tout[i] = p\n\t}\n\tif len(out) > 4 {\n\t\tout = out[:4] // take only first four to fit the UI\n\t}\n\treturn out, err\n}\n\nfunc (fe *frontendServer) getAd(ctx context.Context, ctxKeys []string) ([]*pb.Ad, error) {\n\tctx, cancel := context.WithTimeout(ctx, time.Millisecond*100)\n\tdefer cancel()\n\n\tresp, err := pb.NewAdServiceClient(fe.adSvcConn).GetAds(ctx, &pb.AdRequest{\n\t\tContextKeys: ctxKeys,\n\t})\n\treturn resp.GetAds(), errors.Wrap(err, \"failed to get ads\")\n}\n"
  },
  {
    "path": "src/frontend/static/images/credits.txt",
    "content": "folded-clothes-on-white-chair.jpg,,https://unsplash.com/photos/fr0J5-GIVyg\nfolded-clothes-on-white-chair-wide.jpg,,https://unsplash.com/photos/fr0J5-GIVyg\n"
  },
  {
    "path": "src/frontend/static/styles/bot.css",
    "content": ".chat-modal {\n  width: 100%;\n  height: 85vh;\n  margin-top: 50px;\n  background-color: #EEE;\n  border-radius: 16px;\n  box-shadow: 0px 4px 4px rgba(0, 0, 0, 0.25);\n  overflow: auto;\n  display: block;\n}\n\n@keyframes scale-in {\n  0% {\n    scale: 0;\n  }\n  100% {\n    scale: 1;\n  }\n}\n\n\n.bot-messages {\n  overflow: auto;\n  height: calc(100% - 100px);\n  scroll-snap-align: end;\n}\n\n.bot-message {\n  position: relative;\n  margin: 16px;\n  padding: 16px;\n  margin-right: 20%;\n  border-radius: 16px;\n  background-color: white;\n  min-height: 55px;\n}\n\n.bot-message-loading {\n  -webkit-mask:linear-gradient(-60deg,#000 30%,#0005,#000 70%) right/300% 100%;\n  animation: shimmer 1.5s infinite;\n}\n\n.user-message {\n  position: relative;\n  margin: 16px;\n  margin-left: 20%;\n  padding: 16px;\n  border-radius: 16px;\n  background-color: var(--blue);\n}\n\n.bot-input {\n  display: flex;\n  position: absolute;\n  bottom: 0;\n  width: -webkit-fill-available;\n  margin: 16px;\n  margin-right: 32px;\n  padding: 16px;\n  border-radius: 16px;\n  background-color: white;\n}\n\n.bot-input-file-button {\n  padding-top: 5px;\n}\n\n.bot-input-text {\n  border: none;\n  border-bottom: 1px solid #9AA0A6;\n  padding: 0 0 8px 16px;\n  outline: none;\n  color: #1E2021;\n  width: -webkit-fill-available;\n}\n\n.user-message-text {\n  color: white;\n}\n\n.user-image-div {\n  position: relative;\n  width: 100%;\n  height: 150px;\n}\n\n.user-image {\n  position: absolute;\n  right: 0;\n  border-radius: 16px;\n  margin-right: 16px;\n  height: 150px;\n}\n\n.bot-input-button {\n  display: inline-block;\n  border: solid 1px var(--blue);\n  padding: 8px 16px;\n  outline: none;\n  font-size: 14px;\n  border-radius: 22px;\n  cursor: pointer;\n  background-color: var(--blue);\n  color: white;\n}\n\n.bot-input-button:disabled,\nbutton[disabled]{\n  border: 1px solid #999999;\n  background-color: #cccccc;\n  color: #666666;\n}\n\n.bot-products {\n  margin-left: 16px;\n  margin-right: 20%;\n}\n\n.bot-product {\n  height: 150px;\n  margin-bottom: 16px;\n  display: flex;\n  align-items: center;\n  border-radius: 16px;\n  background-color: white;\n  color: black;\n}\n\n.bot-product-img {\n  height: 150px;\n  border-top-left-radius: 16px;\n  border-bottom-left-radius: 16px;\n  margin-right: 16px;\n}\n\n.bot-product-description {\n  float: right;\n  width: calc(100% - 180px);\n}\n\n@keyframes typing {\n  0% {\n    opacity: 0;\n  }\n  50% {\n    opacity: 1;\n  }\n  100% {\n    opacity: 0;\n  }\n}\n\n.shimmer {\n  color: grey;\n  display:inline-block;\n  -webkit-mask:linear-gradient(-60deg,#000 30%,#0005,#000 70%) right/300% 100%;\n  background-repeat: no-repeat;\n  animation: shimmer 2.5s infinite;\n  font-size: 50px;\n  max-width:200px;\n  filter: invert(21%) sepia(100%) saturate(7414%) hue-rotate(210deg) brightness(50%) contrast(117%);\n}\n\n@keyframes shimmer {\n  100% {-webkit-mask-position:left}\n}\n"
  },
  {
    "path": "src/frontend/static/styles/cart.css",
    "content": "/**\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n.cart-sections {\n    padding-bottom: 120px;\n    padding-top: 56px;\n    background-color: #F9F9F9;\n}\n\n.cart-sections h3 {\n    font-size: 36px;\n    font-weight: normal;\n}\n\n.cart-sections a.cymbal-button-primary:hover {\n    text-decoration: none;\n    color: white;\n}\n\n/* Empty Cart Section */\n\n.empty-cart-section {\n    max-width: 458px;\n    margin: auto;\n    text-align: center;\n}\n\n.empty-cart-section a {\n    display: inline-block; /* So margin-top works. */\n    margin-top: 32px;\n}\n\n.empty-cart-section a:hover {\n    color: white;\n    text-decoration: none;\n}\n\n/*  Cart Summary Section */\n\n.cart-summary-empty-cart-button {\n    margin-right: 10px;\n}\n\n.cart-summary-item-row,\n.cart-summary-shipping-row,\n.cart-summary-total-row {\n    padding-bottom: 24px;\n    padding-top: 24px;\n    border-top: solid 1px rgba(154, 160, 166, 0.5);\n}\n\n.cart-summary-item-row img {\n    border-radius: 20% 0 20% 20%;\n}\n\n.cart-summary-item-row-item-id-row {\n    font-size: 12px;\n    color: #5C6063;\n}\n\n.cart-summary-item-row h4 {\n    font-size: 18px;\n    font-weight: normal;\n}\n\n/* Stick item quantity and cost to the bottom (for wider screens). */\n@media (min-width: 768px) {\n    .cart-summary-item-row .row:last-child {\n        position: absolute;\n        bottom: 0px;\n        width: 100%;\n    }\n}\n\n/* Item cost (price). */\n.cart-summary-item-row .row:last-child strong {\n    font-weight: 500;\n}\n\n.cart-summary-total-row {\n    font-size: 28px;\n}\n\n/* Cart Checkout Form */\n\n.cart-checkout-form h3 {\n    margin-bottom: 0;\n}\n\n.payment-method-heading {\n    margin-top: 36px;\n}\n\n/* \"Place Order\" button */\n.cart-checkout-form .cymbal-button-primary {\n    margin-top: 36px;\n}"
  },
  {
    "path": "src/frontend/static/styles/order.css",
    "content": "/**\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n.order {\n    background: #F9F9F9;\n}\n\n.order-complete-section {\n    max-width: 487px;\n    padding-top: 56px;\n    padding-bottom: 120px;\n}\n\n.order-complete-section h3 {\n    margin: 0;\n    font-size: 36px;\n    font-weight: normal;\n}\n\n.order-complete-section p {\n    margin-top: 8px;\n}\n\n.order-complete-section .padding-y-24 {\n    padding-bottom: 24px;\n    padding-top: 24px;\n}\n\n.order-complete-section .border-bottom-solid {\n    border-bottom: 1px solid rgba(154, 160, 166, 0.5);\n}\n\n.order-complete-section .cymbal-button-primary {\n    margin-top: 24px;\n}\n\n.order-complete-section a.cymbal-button-primary:hover {\n    text-decoration: none;\n    color: white;\n}\n"
  },
  {
    "path": "src/frontend/static/styles/styles.css",
    "content": "/**\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/* General */\n\nhtml, body {\n  height: 100%;\n}\n\nbody {\n  color: #111111;\n  font-family: 'DM Sans', sans-serif;\n  display: flex;\n  flex-direction: column;\n}\n\n/* Header */\n\nheader {\n  background-color: #853B5C;\n  color: white;\n}\n\n/*\nThis allows the sub-navbar (white strip containing logo)\nto be as wide as the browser window.\n*/\nheader > div:nth-child(2).navbar.sub-navbar {\n  padding-left: 0;\n  padding-right: 0;\n}\nheader > div:nth-child(2) > .container {\n  max-width: none;\n}\n\nheader .cart-link {\n  position: relative;\n  display: block;\n  margin-left: 25px;\n  display: flex;\n  flex-flow: column;\n  align-items: center;\n  justify-content: center;\n}\n\nheader .cart-size-circle {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  position: absolute;\n  top: 24px;\n  left: 11px;\n  width: 16px;\n  height: 16px;\n  font-size: 11px;\n  border-radius: 4px 4px 0 4px;\n  color: white;\n  background-color: #853B5C;\n}\n\nheader .navbar {\n  padding-top: 5px;\n  padding-bottom: 5px;\n}\n\nheader .h-free-shipping {\n  font-size: 14px;\n}\n\nheader .h-controls {\n  display: flex;\n  justify-content: flex-end;\n}\n\nheader .h-control {\n  display: flex;\n  align-items: center;\n  font-size: 12px;\n  position: relative;\n  margin-left: 40px;\n  color: #605f64;\n}\n\nheader .h-control:first-child {\n  margin-left: 0;\n}\n\nheader .h-control input {\n  border: none;\n  padding: 0 31px 0 31px;\n  width: 250px;\n  height: 24px;\n  flex-shrink: 0;\n  background-color: #f2f2f2;\n  display: flex;\n  align-items: center;\n}\n\nheader .h-control input:focus {\n  outline: 0;\n  border: 0;\n  box-shadow: 0;\n}\n\nheader .icon {\n  width: 20px;\n  height: 20px;\n}\n\nheader .icon.search-icon {\n  width: 12px;\n  height: 13px;\n  position: absolute;\n  left: 10px;\n}\n\n/* The currency drop-down. */\n\nheader img.currency-icon, header span.currency-icon {\n  position: relative;\n  left: 35px;\n  top: -1px;\n  width: 20px;\n  display: inline-block;\n  height: 20px;\n}\n\nheader span.currency-icon {\n  font-size: 16px;\n  text-align: center;\n}\n\nheader .h-control select {\n  display: flex;\n  align-items: center;\n  background: transparent;\n  border-radius: 0;\n  border: 1px solid #acacac;\n  width: 130px;\n  height: 40px;\n  flex-shrink: 0;\n  padding: 1px 0 0 45px;\n  font-size: 16px;\n  border-radius: 8px;\n}\n\nheader .icon.arrow {\n  position: absolute;\n  right: 25px;\n  width: 10px;\n  height: 5px;\n}\n\nheader .h-control::-webkit-input-placeholder {\n  /* Chrome/Opera/Safari */\n  font-size: 12px;\n  color: #605f64;\n}\n\nheader .h-control::-moz-placeholder {\n  /* Firefox 19+ */\n  font-size: 12px;\n  color: #605f64;\n}\n\nheader .h-control :-ms-input-placeholder {\n  /* IE 10+ */\n  font-size: 12px;\n  color: #605f64;\n}\n\nheader .h-control :-moz-placeholder {\n  /* Firefox 18- */\n  font-size: 12px;\n  color: #605f64;\n}\n\nheader .navbar.sub-navbar {\n  height: 60px;\n  background-color: white;\n  font-size: 15px;\n  color: #b4b2bb;\n  padding-top: 0;\n  padding-bottom: 0;\n  box-shadow: 0px 0px 4px rgba(0, 0, 0, 0.25);\n  z-index: 1; /* Need this to see the box-shadow on the home page. */\n}\n\nheader .navbar.sub-navbar > .container {\n  padding-left: 26px;\n  padding-right: 26px;\n}\n\nheader .top-left-logo {\n  height: 40px;\n}\n\nheader .top-left-logo-cymbal {\n  height: 30px;\n}\n\nheader .navbar.sub-navbar .navbar-brand {\n  padding: 0;\n}\n\nheader .navbar.sub-navbar a {\n  color: #b4b2bb;\n}\n\nheader .navbar.sub-navbar nav a {\n  margin: 0 10px;\n}\n\nheader .navbar.sub-navbar .controls {\n  display: flex;\n  height: 60px;\n}\n\nheader .navbar.sub-navbar .controls a img {\n  width: 20px;\n  height: 20px;\n  margin-bottom: 3px;\n}\n\n/* Footer */\n\nfooter.py-5 {\n  flex-shrink: 0;\n  padding: 0 !important;\n}\n\nfooter .footer-top {\n  padding: 60px 0px;\n  background-color: #570D2E;\n  color: white;\n}\n\nfooter .footer-top a {\n  color: white;\n  text-decoration: underline;\n}\n\n/* The <p> containing the session-id. */\nfooter .footer-top p:nth-child(3) {\n  margin-top: 56px;\n}\n\nfooter .footer-top .footer-social,\nfooter .footer-top .footer-app,\nfooter .footer-links,\nfooter .footer-top .social,\nfooter .footer-top .app {\n  display: block;\n  align-items: center;\n}\n\nfooter .footer-top .footer-social {\n  padding: 31px;\n}\n\nfooter .footer-top .footer-social h4 {\n  margin-bottom: 0;\n}\n\nfooter .footer-top .footer-social div {\n  width: 50%;\n}\n\n/* Home */\n\nmain {\n  flex: 1 0 auto;\n  background-color: #F9F9F9;\n}\n\n@media (min-width: 992px) {\n  .home .container-fluid {\n    height: calc(100vh - 91px); /* 91px is the height of the top/header bars. */\n  }\n  .home .container-fluid > .row > .col-4 {\n    height: calc(100vh - 91px);\n  }\n  .home .container-fluid > .row > .col-lg-8 {\n    height: calc(100vh - 91px);\n    overflow-y: scroll;\n  }\n\n  .px-10-percent {\n    padding-left: 10%;\n    padding-right: 10%;\n  }\n}\n\n.home-mobile-hero-banner {\n  height: 200px;\n  background: url(/static/images/folded-clothes-on-white-chair-wide.jpg) no-repeat top center;\n  background-size: cover;\n}\n\n.home-desktop-left-image {\n  background: url(/static/images/folded-clothes-on-white-chair.jpg) no-repeat center;\n  background-size: cover;\n}\n\n.hot-products-row h3 {\n  margin-bottom: 32px;\n  margin-top: 56px;\n  font-size: 36px;\n  font-weight: normal;\n}\n\n.hot-products-row {\n  padding-bottom: 70px;\n  padding-left: 10%;\n  padding-right: 10%;\n}\n\n.hot-product-card  {\n  margin-bottom: 52px;\n  padding-left: 16px;\n  padding-right: 16px;\n}\n\n.hot-product-card img {\n  width: 100%;\n  height: auto;\n  border-radius: 20% 0 20% 20%;\n}\n\n.hot-product-card-name {\n  margin-top: 8px;\n  font-size: 18px;\n}\n\n.hot-product-card-price {\n  font-size: 14px;\n}\n\n.hot-product-card > a:first-child {\n  position: relative;\n  display: block;\n}\n\n.hot-product-card-img-overlay {\n  position: absolute;\n  height: 100%;\n  width: 100%;\n  top: 0;\n  left: 0;\n  border-radius: 20% 0 20% 20%;\n  background-color: transparent;\n}\n\n.hot-product-card:hover .hot-product-card-img-overlay {\n  background-color: rgba(71, 0, 29, 0.2);\n}\n\n/*\nThis chunk ensures the left/right padding of the footer is\nsimilar to that of the hot-products-row.\n*/\n.home-desktop-footer-row {\n  padding-left: 9%;\n  padding-right: 9%;\n  background-color: #570D2E;\n  width: 100%;\n  margin: 0;\n}\n\n/* Ad */\n\n.ad {\n  position: relative;\n  background-color: #FF9A9B;\n  font-size: 24px;\n  text-align: center;\n}\n\n/* \"Ad\" text. */\n.ad strong {\n  position: absolute;\n  top: 6px;\n  left: 12px;\n  font-size: 14px;\n  font-weight: normal;\n}\n\n.ad a {\n  color: black;\n}\n\n/* Product */\n\n.h-product {\n  margin-top: 56px;\n  margin-bottom: 112px;\n  max-width: 1200px;\n  background-color: #F9F9F9;\n}\n\n.h-product > .row {\n  align-items: flex-end;\n}\n\n.h-product .product-image {\n  width: 100%;\n  border-radius: 20% 20% 0 20%;\n}\n\n.h-product .product-price {\n  font-size: 28px;\n}\n\n.h-product .product-info .product-wrapper {\n  margin-left: 15px;\n}\n\n.h-product .product-info h2 {\n  margin-bottom: 16px;\n  margin-top: 16px;\n  font-size: 56px;\n  line-height: 1.14;\n  font-weight: normal;\n  color: #111111;\n}\n\n.h-product .product-packaging {\n  margin: 0 0 15px 0;\n}\n\n.h-product .product-packaging h3 {\n  font-size: 20px;\n}\n\n.h-product .product-packaging span {\n  display: inline-block;\n  margin: 0 10px 0 0;\n}\n\n.h-product .input-group-text,\n.h-product .btn.btn-info {\n  font-size: 18px;\n  line-height: 1.89;\n  letter-spacing: 3.6px;\n  text-align: center;\n  color: #111111;\n  border-radius: 0;\n}\n\n.product-quantity-dropdown {\n  position: relative;\n  width: 100px;\n}\n\n.product-quantity-dropdown select {\n  width: 100%;\n  height: 45px;\n  border: 1px solid #acacac;\n  padding: 10px 16px;\n  border-radius: 8px;\n}\n\n.product-quantity-dropdown img {\n  position: absolute;\n  right: 25px;\n  top: 20px;\n  width: 10px;\n  height: 5px;\n}\n\n.h-product .cymbal-button-primary {\n  margin-top: 16px;\n}\n\n/* Platform Banner */\n\n.local,\n.aws-platform,\n.onprem-platform,\n.azure-platform,\n.alibaba-platform,\n.gcp-platform {\n  position: fixed;\n  top: 0;\n  left: 0;\n  width: 10px;\n  height: 100vh;\n  color: white;\n  font-size: 24px;\n  z-index: 999;\n}\n\n.aws-platform,\n.aws-platform .platform-flag {\n  background-color: #ff9900;\n}\n\n.onprem-platform,\n.onprem-platform .platform-flag {\n  background-color: #34A853;\n}\n\n.gcp-platform,\n.gcp-platform .platform-flag {\n  background-color: #4285f4;\n}\n\n\n.azure-platform,\n.azure-platform .platform-flag {\n  background-color: #f35426;\n}\n\n.alibaba-platform,\n.alibaba-platform .platform-flag {\n  background-color: #ffC300;\n}\n\n.local,\n.local .platform-flag {\n  background-color: #2c0678;\n}\n\n.platform-flag {\n  position: absolute;\n  top: 98px;\n  left: 0;\n  width: 190px;\n  height: 50px;\n  display: flex;\n  justify-content: center;\n  align-items: center;\n}\n\n/* Recommendation */\n\n.recommendations {\n  background: #F9F9F9;\n  padding-bottom: 55px;\n}\n\n.recommendations .container {\n  max-width: 1174px;\n}\n\n@media (max-width: 992px) {\n  .recommendations .container {\n    max-width: none;\n  }\n}\n\n.recommendations h2 {\n  border-top: solid 1px;\n  padding: 40px 0;\n  font-weight: normal;\n  text-align: center;\n}\n\n.recommendations h5 {\n  margin-top: 8px;\n  font-weight: normal;\n  font-size: 18px;\n}\n\n.recommendations img {\n  height: 100%;\n  width: 100%;\n  border-radius: 20% 0 20% 20%;\n}\n\nselect {\n  -webkit-appearance: none;\n  -webkit-border-radius: 0px;\n}\n\n/* Cymbal */\n\n/*\nIf we ever decide to create a separate Cymbal CSS library for Cymbal components,\nthe rules below could be extracted.\n*/\n\n.cymbal-button-primary, .cymbal-button-secondary {\n  display: inline-block;\n  border: solid 1px #CE0631;\n  padding: 8px 16px;\n  outline: none;\n  font-size: 14px;\n  border-radius: 22px;\n  cursor: pointer;\n}\n\n.cymbal-button-primary:focus, .cymbal-button-secondary:focus {\n  outline: none; /* To override browser (Chrome) default blue outline. */\n}\n\n.cymbal-button-primary {\n  background-color: #CE0631;\n  color: white;\n}\n\n.cymbal-button-primary:active,\n.cymbal-button-primary:focus,\n.cymbal-button-primary:hover {\n  border: solid 1px #7b031d;\n  background-color: #7b031d;\n  box-shadow: 0px 2px 2px 0px rgb(0 0 0 / 30%);\n}\n\n.cymbal-button-primary:active {\n  box-shadow: 0px 3px 6px 0px rgb(0 0 0 / 30%);\n}\n\n.cymbal-button-secondary {\n  background: none;\n  color: #CE0631;\n}\n\n.cymbal-button-secondary:active,\n.cymbal-button-secondary:focus,\n.cymbal-button-secondary:hover {\n  color: #7b031d;\n  border: solid 1px #7b031d;\n}\n\n.cymbal-button-secondary:active {\n  background-color: #f5ccd5;\n}\n\n.cymbal-form-field {\n  position: relative;\n  margin-top: 24px;\n}\n\n.cymbal-form-field label {\n  width: 100%;\n  margin: 0;\n  padding: 8px 16px 0 16px;\n  font-size: 12px;\n  line-height: 1.8em; /* Without this, there might be a 1px gap underneath. */\n  font-weight: normal;\n  border-radius: 4px 4px 0px 0px;\n  color: #5C6063;\n  background-color: white;\n}\n\n.cymbal-form-field input[type='email'],\n.cymbal-form-field input[type='password'],\n.cymbal-form-field select,\n.cymbal-form-field input[type='text'] {\n  width: 100%;\n  border: none;\n  border-bottom: 1px solid #9AA0A6;\n  padding: 0 16px 8px 16px;\n  outline: none;\n  color: #1E2021;\n}\n\n.cymbal-form-field .cymbal-dropdown-chevron {\n  position: absolute;\n  right: 25px;\n  width: 10px;\n  height: 5px;\n}\n"
  },
  {
    "path": "src/frontend/templates/ad.html",
    "content": "<!--\n Copyright 2020 Google LLC\n\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n      http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n-->\n\n{{ define \"text_ad\" }}\n<div class=\"container py-3 px-lg-5 py-lg-5\">\n    <div role=\"alert\">\n        <strong>Ad</strong>\n        <a href=\"{{$.baseUrl}}{{.ad.RedirectUrl}}\" rel=\"nofollow noopener noreferrer\" target=\"_blank\">\n            {{.ad.Text}}\n        </a>\n    </div>\n</div>\n{{ end }}\n"
  },
  {
    "path": "src/frontend/templates/assistant.html",
    "content": "<!--\n Copyright 2020 Google LLC\n\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n      http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n-->\n\n{{ define \"assistant\" }}\n\n{{ template \"header\" . }}\n<div {{ with $.platform_css }} class=\"{{.}}\" {{ end }}>\n  <span class=\"platform-flag\">\n    {{$.platform_name}}\n  </span>\n</div>\n\n<main role=\"main\">\n  <div class=\"container\">\n    <div class=\"row\">\n      <div class=\"col-md-12\">\n        <div id=\"chat-modal\" class=\"chat-modal\">\n          <div id=\"bot-messages\" class=\"bot-messages\">\n            <p class=\"bot-message\">\n              <span class=\"bot-message-text\">Hi, I'm the Cymbal Shops assistant. I can help you with your shopping experience.</span>\n            </p>\n            <p class=\"bot-message\">\n              <span class=\"bot-message-text\">What can I help you with?</span>\n            </p>\n          </div>\n          <div class=\"bot-input\">\n            <input id=\"bot-input-text\" type=\"text\" style=\"margin-right: 30px;\" class=\"bot-input-text\" placeholder=\"Recommend me items...\">\n            <input type=\"file\" class=\"bot-input-file-button\"  onchange=\"getBase64()\">\n            <button id=\"bot-input-button\" class=\"bot-input-button\">Send</button>\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n</main>\n\n<script>\n  var image;\n  function getBase64 ()  {\n    var file = document.querySelector('input[type=file]')['files'][0];\n    var reader = new FileReader();\n    var baseString;\n    reader.onloadend = function () {\n      baseString = reader.result;\n      console.log(baseString);\n      image = baseString;\n    };\n    reader.readAsDataURL(file);\n  }\n\n  function extractIdsFromString(message) {\n    const idPattern = /\\[([a-zA-Z0-9-]+)\\]/g;\n    const matches = message.matchAll(idPattern);\n    const ids = [];\n    for (const match of matches) {\n      ids.push(match[1]);\n    }\n\n    return ids;\n  }\n\n  const chatModal = document.getElementById(\"chat-modal\");\n  const botMessages = document.getElementById(\"bot-messages\");\n  const botbutton = document.getElementById(\"bot-input-button\");\n  const botinput = document.getElementById(\"bot-input-text\");\n\n  async function main() {\n    botbutton.addEventListener(\"click\", handleButtonClick);\n\n    botinput.addEventListener(\"keypress\", (event) => {\n      if (event.key === \"Enter\") {\n        botbutton.click();\n      }\n    });\n  }\n\n  async function handleButtonClick() {\n    if(!botinput.value || !botinput.value.trim){\n      return;\n    }\n\n    // Construct and render user message\n    console.log(\"bot button clicked\");\n    const message = botinput.value;\n    console.log(\"message: \" + message);\n    const usermessage = document.createElement(\"p\");\n    const userMessageSpan = document.createElement(\"span\");\n    const imageDivElement = document.createElement(\"div\");\n    imageDivElement.classList.add(\"user-image-div\")\n    const imageElement = document.createElement(\"img\");\n    imageElement.src = image;\n    imageElement.classList.add(\"user-image\");\n    imageElement.onerror = function() { this.style.display = 'none'; };\n    userMessageSpan.innerText = message;\n    userMessageSpan.classList.add(\"user-message-text\");\n    usermessage.classList.add(\"user-message\");\n    usermessage.appendChild(userMessageSpan);\n    botMessages.appendChild(usermessage);\n    imageDivElement.appendChild(imageElement);\n    botMessages.appendChild(imageDivElement);\n    botMessages.scrollTo(0, botMessages.scrollHeight);\n    botinput.value = \"\";\n\n    // Disable send button and input field\n    botbutton.disabled = true;\n    botinput.disabled = true;\n    console.log(\"bot is typing\");\n\n    // Construct and render placeholder bot message\n    const botMessage = document.createElement(\"p\");\n    botMessage.classList.add(\"bot-message-loading\");\n    const botMessageSpan = document.createElement(\"span\");\n    botMessageSpan.innerText = \"\"\n    botMessageSpan.classList.add(\"bot-message-text\");\n    botMessage.classList.add(\"bot-message\");\n    botMessage.appendChild(botMessageSpan);\n    botMessages.appendChild(botMessage);\n    botMessages.scrollTo(0, botMessages.scrollHeight);\n\n    // Request a response from the Shopping Assistant\n    const response = await fetch(\"{{ $.baseUrl }}/bot\", {\n      method: \"POST\",\n      headers: {\n        \"Content-Type\": \"application/json\",\n      },\n      body: JSON.stringify({\n        message: message,\n        image: image\n      }),\n    });\n    const responseJson = await response.json();\n    console.log(responseJson);\n\n    // Fetch the product IDs from the response\n    const extractedIds = extractIdsFromString(responseJson.message);\n    console.log(extractedIds);\n\n    // Replace the placeholder bot message text with the real response\n    // Making sure to remove any lists or product IDs from that message\n    botMessageSpan.innerText = responseJson.message.replace(/\\n+[-*\\d][\\S\\s]*/g, \"\");\n    botMessage.classList.remove(\"bot-message-loading\");\n\n    // If there are any product IDs...\n    if (extractedIds.length > 0) {\n      // Construct root products div\n      const botProductsDiv = document.createElement(\"div\");\n      botProductsDiv.classList.add(\"bot-products\");\n\n      // For each product...\n      for (const id of extractedIds) {\n        // Retrieve product metadata from the Product Catalog\n        const productResponse = await fetch(\"{{ $.baseUrl }}/product-meta/\" + id, {\n          method: \"GET\",\n          headers: {\n            \"Content-Type\": \"application/json\",\n          },\n        });\n        const product = await productResponse.json();\n\n        // Construct main product div\n        const botProductDiv = document.createElement(\"a\");\n        botProductDiv.classList.add(\"bot-product\");\n        botProductDiv.href = \"{{ $.baseUrl }}/product/\" + id;\n\n        // Construct product image\n        const botProductImg = document.createElement(\"img\");\n        botProductImg.src = product[\"picture\"];\n        botProductImg.classList.add(\"bot-product-img\");\n        botProductImg.onerror = function() { this.style.display = 'none'; };\n        botProductDiv.appendChild(botProductImg);\n\n        // Construct product description div\n        const botProductDescription = document.createElement(\"div\");\n        botProductDescription.classList.add(\"bot-product-description\");\n        let productDescription = product[\"description\"];\n        if (productDescription.length > 350) { // Shorten descriptions that are too long\n          productDescription = productDescription.substring(0, 330) + '...';\n        }\n        botProductDescription.innerHTML = \"<b>\" + product[\"name\"] + \"</b><br>\" + productDescription;\n        botProductDiv.appendChild(botProductDescription);\n\n        // Append main product div into the root products div\n        botProductsDiv.appendChild(botProductDiv);\n      }\n      // Render products\n      botMessages.appendChild(botProductsDiv)\n    }\n\n    botMessages.scrollTo(0, botMessages.scrollHeight);\n\n    // Re-enable button and input field\n    botbutton.disabled = false;\n    botinput.disabled = false;\n    botinput.focus();\n  }\n\n  main();\n</script>\n\n{{ end }}\n"
  },
  {
    "path": "src/frontend/templates/cart.html",
    "content": "<!--\n Copyright 2020 Google LLC\n\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n      http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n-->\n\n{{ define \"cart\" }}\n    {{ template \"header\" . }}\n\n    <div {{ with $.platform_css }} class=\"{{.}}\" {{ end }}>\n        <span class=\"platform-flag\">\n            {{$.platform_name}}\n        </span>\n    </div>\n\n    <main role=\"main\" class=\"cart-sections\">\n\n        {{ if eq (len $.items) 0 }}\n        <section class=\"empty-cart-section\">\n            <h3>Your shopping cart is empty!</h3>\n            <p>Items you add to your shopping cart will appear here.</p>\n            <a class=\"cymbal-button-primary\" href=\"{{ $.baseUrl }}/\" role=\"button\">Continue Shopping</a>\n        </section>\n        {{ else }}\n        <section class=\"container\">\n            <div class=\"row\">\n\n                <div class=\"col-lg-6 col-xl-5 offset-xl-1 cart-summary-section\">\n\n                    <div class=\"row mb-3 py-2\">\n                        <div class=\"col-4 pl-md-0\">\n                            <h3>Cart ({{ $.cart_size }})</h3>\n                        </div>\n                        <div class=\"col-8 pr-md-0 text-right\">\n                            <form method=\"POST\" action=\"{{ $.baseUrl }}/cart/empty\">\n                                <button class=\"cymbal-button-secondary cart-summary-empty-cart-button\" type=\"submit\">\n                                    Empty Cart\n                                </button>\n                                <a class=\"cymbal-button-primary\" href=\"{{ $.baseUrl }}/\" role=\"button\">\n                                    Continue Shopping\n                                </a>\n                            </form>\n                        </div>\n                    </div>\n\n                    {{ range $.items }}\n                    <div class=\"row cart-summary-item-row\">\n                        <div class=\"col-md-4 pl-md-0\">\n                            <a href=\"{{ $.baseUrl }}/product/{{.Item.Id}}\">\n                                <img class=\"img-fluid\" alt=\"\" src=\"{{ $.baseUrl }}{{.Item.Picture}}\" />\n                            </a>\n                        </div>\n                        <div class=\"col-md-8 pr-md-0\">\n                            <div class=\"row\">\n                                <div class=\"col\">\n                                    <h4>{{ .Item.Name }}</h4>\n                                </div>\n                            </div>\n                            <div class=\"row cart-summary-item-row-item-id-row\">\n                                <div class=\"col\">\n                                    SKU #{{ .Item.Id }}\n                                </div>\n                            </div>\n                            <div class=\"row\">\n                                <div class=\"col\">\n                                    Quantity: {{ .Quantity }}\n                                </div>\n                                <div class=\"col pr-md-0 text-right\">\n                                    <strong>\n                                        {{ renderMoney .Price }}\n                                    </strong>\n                                </div>\n                            </div>\n                        </div>\n                    </div>\n                    {{ end }}\n\n                    <div class=\"row cart-summary-shipping-row\">\n                        <div class=\"col pl-md-0\">Shipping</div>\n                        <div class=\"col pr-md-0 text-right\">{{ renderMoney .shipping_cost }}</div>\n                    </div>\n\n                    <div class=\"row cart-summary-total-row\">\n                        <div class=\"col pl-md-0\">Total</div>\n                        <div class=\"col pr-md-0 text-right\">{{ renderMoney .total_cost }}</div>\n                    </div>\n\n                </div>\n\n                <div class=\"col-lg-5 offset-lg-1 col-xl-4\">\n\n                    <form class=\"cart-checkout-form\" action=\"{{ $.baseUrl }}/cart/checkout\" method=\"POST\">\n\n                        <div class=\"row\">\n                            <div class=\"col\">\n                                <h3>Shipping Address</h3>\n                            </div>\n                        </div>\n\n                        <div class=\"form-row\">\n                            <div class=\"col cymbal-form-field\">\n                                <label for=\"email\">E-mail Address</label>\n                                <input type=\"email\" id=\"email\"\n                                    name=\"email\" value=\"someone@example.com\" required>\n                            </div>\n                        </div>\n\n                        <div class=\"form-row\">\n                            <div class=\"col cymbal-form-field\">\n                                <label for=\"street_address\">Street Address</label>\n                                <input type=\"text\" name=\"street_address\"\n                                    id=\"street_address\" value=\"1600 Amphitheatre Parkway\" required>\n                            </div>\n                        </div>\n\n                        <div class=\"form-row\">\n                            <div class=\"col cymbal-form-field\">\n                                <label for=\"zip_code\">Zip Code</label>\n                                <input type=\"text\"\n                                    name=\"zip_code\" id=\"zip_code\" value=\"94043\" required pattern=\"\\d{4,5}\">\n                            </div>\n                        </div>\n\n                        <div class=\"form-row\">\n                            <div class=\"col cymbal-form-field\">\n                                <label for=\"city\">City</label>\n                                <input type=\"text\" name=\"city\" id=\"city\"\n                                    value=\"Mountain View\" required>\n                                </div>\n                            </div>\n\n                        <div class=\"form-row\">\n                            <div class=\"col-md-5 cymbal-form-field\">\n                                <label for=\"state\">State</label>\n                                <input type=\"text\" name=\"state\" id=\"state\"\n                                    value=\"CA\" required>\n                            </div>\n                            <div class=\"col-md-7 cymbal-form-field\">\n                                <label for=\"country\">Country</label>\n                                <input type=\"text\" id=\"country\"\n                                    placeholder=\"Country Name\"\n                                    name=\"country\" value=\"United States\" required>\n                            </div>\n                        </div>\n\n                        <div class=\"row\">\n                            <div class=\"col\">\n                                <h3 class=\"payment-method-heading\">Payment Method</h3>\n                            </div>\n                        </div>\n\n                        <div class=\"form-row\">\n                            <div class=\"col cymbal-form-field\">\n                                <label for=\"credit_card_number\">Credit Card Number</label>\n                                <input type=\"text\" id=\"credit_card_number\"\n                                    name=\"credit_card_number\"\n                                    placeholder=\"0000000000000000\"\n                                    value=\"4432801561520454\"\n                                    required pattern=\"\\d{16}\">\n                            </div>\n                        </div>\n\n                        <div class=\"form-row\">\n                            <div class=\"col-md-5 cymbal-form-field\">\n                                <label for=\"credit_card_expiration_month\">Month</label>\n                                <select name=\"credit_card_expiration_month\" id=\"credit_card_expiration_month\">\n                                    <option value=\"1\">January</option>\n                                    <option value=\"2\">February</option>\n                                    <option value=\"3\">March</option>\n                                    <option value=\"4\">April</option>\n                                    <option value=\"5\">May</option>\n                                    <option value=\"6\">June</option>\n                                    <option value=\"7\">July</option>\n                                    <option value=\"8\">August</option>\n                                    <option value=\"9\">September</option>\n                                    <option value=\"10\">October</option>\n                                    <option value=\"11\">November</option>\n                                    <option value=\"12\">January</option>\n                                </select>\n                                <img src=\"{{ $.baseUrl }}/static/icons/Hipster_DownArrow.svg\" alt=\"\" class=\"cymbal-dropdown-chevron\">\n                            </div>\n                            <div class=\"col-md-4 cymbal-form-field\">\n                                    <label for=\"credit_card_expiration_year\">Year</label>\n                                    <select name=\"credit_card_expiration_year\" id=\"credit_card_expiration_year\">\n                                    {{ range $i, $y := $.expiration_years}}<option value=\"{{$y}}\"\n                                        {{if eq $i 1 -}}\n                                            selected=\"selected\"\n                                        {{- end}}\n                                    >{{$y}}</option>{{end}}\n                                    </select>\n                                    <img src=\"{{ $.baseUrl }}/static/icons/Hipster_DownArrow.svg\" alt=\"\" class=\"cymbal-dropdown-chevron\">\n                                </div>\n                            <div class=\"col-md-3 cymbal-form-field\">\n                                <label for=\"credit_card_cvv\">CVV</label>\n                                <input type=\"password\" id=\"credit_card_cvv\"\n                                    name=\"credit_card_cvv\" value=\"672\" required pattern=\"\\d{3}\">\n                            </div>\n                        </div>\n\n                        <div class=\"form-row justify-content-center\">\n                            <div class=\"col text-center\">\n                                <button class=\"cymbal-button-primary\" type=\"submit\">\n                                    Place Order\n                                </button>\n                            </div>\n                        </div>\n\n                    </form>\n\n                </div>\n\n            </div>\n        </section>\n        {{ end }} <!-- end if $.items -->\n\n    </main>\n\n    {{ if $.recommendations }}\n        {{ template \"recommendations\" $ }}\n    {{ end }}\n\n    {{ template \"footer\" . }}\n{{ end }}\n"
  },
  {
    "path": "src/frontend/templates/error.html",
    "content": "<!--\n Copyright 2020 Google LLC\n\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n      http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n-->\n\n{{ define \"error\" }}\n    {{ template \"header\" . }}\n    <div {{ with $.platform_css }} class=\"{{.}}\" {{ end }}>\n        <span class=\"platform-flag\">\n          {{$.platform_name}}\n        </span>\n      </div>\n    <main role=\"main\">\n        <div class=\"py-5\">\n            <div class=\"container bg-light py-3 px-lg-5 py-lg-5\">\n                <h1>Uh, oh!</h1>\n                <p>Something has failed. Below are some details for debugging.</p>\n\n                <p><strong>HTTP Status:</strong> {{.status_code}} {{.status}}</p>\n                <pre class=\"border border-danger p-3\"\n                    style=\"white-space: pre-wrap; word-break: keep-all;\">\n                    {{- .error -}}\n                </pre>\n            </div>\n        </div>\n    </main>\n\n    {{ template \"footer\" . }}\n    {{ end }}"
  },
  {
    "path": "src/frontend/templates/footer.html",
    "content": "<!--\n Copyright 2020 Google LLC\n\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n      http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n-->\n\n{{ define \"footer\" }}\n\n<footer class=\"py-5\">\n    <div class=\"footer-top\">\n        <div class=\"container footer-social\">\n            <p class=\"footer-text\">This website is hosted for demo purposes only. It is not an actual shop. This is not a Google product.</p>\n            <p class=\"footer-text\">© 2020-{{ .currentYear }} Google LLC (<a href=\"https://github.com/GoogleCloudPlatform/microservices-demo\">Source Code</a>)</p>\n            <p class=\"footer-text\">\n                <small>\n                    {{ if $.session_id }}session-id: {{ $.session_id }} — {{end}}\n                    {{ if $.request_id }}request-id: {{ $.request_id }}{{end}}\n                </small>\n                <br/>\n                <small>\n                    {{ if $.deploymentDetails }}\n                        {{ if index .deploymentDetails \"CLUSTERNAME\" }}\n                        <b>Cluster: </b>{{ index .deploymentDetails \"CLUSTERNAME\" }}<br/>\n                        {{ end }}\n                        {{ if index .deploymentDetails \"ZONE\" }}\n                        <b>Zone: </b>{{ index .deploymentDetails \"ZONE\" }}<br/>\n                        {{ end }}\n                        {{ if index .deploymentDetails \"HOSTNAME\" }}\n                        <b>Pod: </b>{{ index .deploymentDetails \"HOSTNAME\" }}\n                        {{ end }}\n                    {{ else }}\n                    Deployment details are still loading.\n                    Try refreshing this page.\n                    {{ end }}\n                </small>\n            </p>\n        </div>\n    </div>\n</footer>\n<script src=\"https://stackpath.bootstrapcdn.com/bootstrap/4.1.1/js/bootstrap.min.js\"\n    integrity=\"sha384-smHYKdLADwkXOn1EmN1qk/HfnUcbVRZyYmZ4qpPea6sjB/pTJ0euyQp0Mk8ck+5T\" crossorigin=\"anonymous\">\n</script>\n</body>\n\n</html>\n{{ end }}\n"
  },
  {
    "path": "src/frontend/templates/header.html",
    "content": "<!--\n Copyright 2020 Google LLC\n\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n      http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n-->\n\n{{ define \"header\" }}\n<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n    <meta charset=\"UTF-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, shrink-to-fit=no\">\n    <meta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\">\n    <title>\n        {{ if $.is_cymbal_brand }}\n        Cymbal Shops\n        {{ else }}\n        Online Boutique\n        {{ end }}\n    </title>\n    <link href=\"https://stackpath.bootstrapcdn.com/bootstrap/4.1.1/css/bootstrap.min.css\" rel=\"stylesheet\" integrity=\"sha384-WskhaSGFgHYWDcbwN70/dfYBj47jz9qbsMId/iRN3ewGhXQFZCSftd1LZCfmhktB\"\n        crossorigin=\"anonymous\">\n    <link rel=\"preconnect\" href=\"https://fonts.googleapis.com\">\n    <link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin>\n    <link href=\"https://fonts.googleapis.com/css2?family=DM+Sans:ital,wght@0,400;0,700;1,400;1,700&display=swap\" rel=\"stylesheet\">\n    <link href=\"https://fonts.googleapis.com/css2?family=Google+Symbols:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200\" rel=\"stylesheet\" />\n    <link rel=\"stylesheet\" type=\"text/css\" href=\"{{ $.baseUrl }}/static/styles/styles.css\">\n    <link rel=\"stylesheet\" type=\"text/css\" href=\"{{ $.baseUrl }}/static/styles/cart.css\">\n    <link rel=\"stylesheet\" type=\"text/css\" href=\"{{ $.baseUrl }}/static/styles/order.css\">\n    <link rel=\"stylesheet\" type=\"text/css\" href=\"{{ $.baseUrl }}/static/styles/bot.css\">\n    {{ if $.is_cymbal_brand }}\n    <link rel='shortcut icon' type='image/x-icon' href='{{ $.baseUrl }}/static/favicon-cymbal.ico' />\n    {{ else }}\n    <link rel='shortcut icon' type='image/x-icon' href='{{ $.baseUrl }}/static/favicon.ico' />\n    {{ end }}\n</head>\n\n<body>\n    <header>\n        {{ if $.frontendMessage }}\n        <div class=\"navbar\">\n            <div class=\"container d-flex justify-content-center\">\n                <div class=\"h-free-shipping\">{{ $.frontendMessage }}</div>\n            </div>\n        </div>\n        {{ end }}\n        <div class=\"navbar sub-navbar\">\n            <div class=\"container d-flex justify-content-between\">\n                <a href=\"{{ $.baseUrl }}/\" class=\"navbar-brand d-flex align-items-center\">\n                    {{ if $.is_cymbal_brand }}\n                    <img src=\"{{ $.baseUrl }}/static/icons/Cymbal_NavLogo.svg\" alt=\"\" class=\"top-left-logo-cymbal\" />\n                    {{ else }}\n                    <img src=\"{{ $.baseUrl }}/static/icons/Hipster_NavLogo.svg\" alt=\"\" class=\"top-left-logo\" />\n                    {{ end }}\n                </a>\n                <div class=\"controls\">\n\n                    {{ if $.show_currency }}\n                    <div class=\"h-controls\">\n                        <div class=\"h-control\">\n                            <span class=\"icon currency-icon\"> {{ renderCurrencyLogo $.user_currency}}</span>\n                            <form method=\"POST\" class=\"controls-form\" action=\"{{ $.baseUrl }}/setCurrency\" id=\"currency_form\" >\n                                <select name=\"currency_code\" onchange=\"document.getElementById('currency_form').submit();\">\n                                        {{range $.currencies}}\n                                    <option value=\"{{.}}\" {{if eq . $.user_currency}}selected=\"selected\"{{end}}>{{.}}</option>\n                                    {{end}}\n                                </select>\n                            </form>\n                            <img src=\"{{ $.baseUrl }}/static/icons/Hipster_DownArrow.svg\" alt=\"\" class=\"icon arrow\" />\n                        </div>\n                    </div>\n                    {{ end }}\n\n                    {{ if $.assistant_enabled }}\n                    <a href=\"{{ $.baseUrl }}/assistant\" class=\"cart-link\">\n                      <img src=\"{{ $.baseUrl }}/static/icons/Hipster_WandIcon.svg\" style=\"width: 22px; height: 22px;\" alt=\"Assistant icon\" class=\"logo\" title=\"Assistant\" />\n                    </a>\n                    {{ end }}\n\n                    <a href=\"{{ $.baseUrl }}/cart\" class=\"cart-link\">\n                        <img src=\"{{ $.baseUrl }}/static/icons/Hipster_CartIcon.svg\" alt=\"Cart icon\" class=\"logo\" title=\"Cart\" />\n                        {{ if $.cart_size }}\n                        <span class=\"cart-size-circle\">{{$.cart_size}}</span>\n                        {{ end }}\n                    </a>\n                </div>\n            </div>\n        </div>\n\n    </header>\n    {{end}}\n"
  },
  {
    "path": "src/frontend/templates/home.html",
    "content": "<!--\n Copyright 2020 Google LLC\n\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n      http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n-->\n\n{{ define \"home\" }}\n\n{{ template \"header\" . }}\n<div {{ with $.platform_css }} class=\"{{.}}\" {{ end }}>\n  <span class=\"platform-flag\">\n    {{$.platform_name}}\n  </span>\n</div>\n<main role=\"main\" class=\"home\">\n\n  <!-- The image at the top of the home page, displayed on smaller screens. -->\n  <div class=\"home-mobile-hero-banner d-lg-none\"></div>\n\n  <div class=\"container-fluid\">\n    <div class=\"row\">\n\n      <!-- The image on the left of the home page, displayed on larger screens. -->\n      <!--<div class=\"col-4 d-none d-lg-block home-desktop-left-image\"></div>-->\n      <!-- @TODO: removed temporarily. When uncommenting, also replace below div with this -->\n      <!--<div class=\"col-12 col-lg-8\">-->\n\n      <div class=\"col-12 col-lg-12 px-10-percent\">\n\n        <div class=\"row hot-products-row px-xl-6\">\n\n          <div class=\"col-12\">\n            <h3>Hot Products</h3>\n          </div>\n\n          {{ range $.products }}\n          <div class=\"col-md-4 hot-product-card\">\n            <a href=\"{{ $.baseUrl }}/product/{{.Item.Id}}\">\n              <img loading=\"lazy\" src=\"{{ $.baseUrl }}{{.Item.Picture}}\">\n              <div class=\"hot-product-card-img-overlay\"></div>\n            </a>\n            <div>\n              <div class=\"hot-product-card-name\">{{ .Item.Name }}</div>\n              <div class=\"hot-product-card-price\">{{ renderMoney .Price }}</div>\n            </div>\n          </div>\n          {{ end }}\n\n        </div>\n\n        <!-- Footer for larger screens. -->\n        <div class=\"row d-none d-lg-block home-desktop-footer-row\">\n          <div class=\"col-12 p-0\">\n            {{ template \"footer\" . }}\n          </div>\n        </div>\n\n      </div>\n\n    </div>\n  </div>\n\n</main>\n\n<!-- Footer for smaller screens. -->\n<div class=\"d-lg-none\">\n  {{ template \"footer\" . }}\n</div>\n\n{{ end }}\n"
  },
  {
    "path": "src/frontend/templates/order.html",
    "content": "<!--\n Copyright 2020 Google LLC\n\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n      http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n-->\n\n{{ define \"order\" }}\n\n    {{ template \"header\" . }}\n\n    <div {{ with $.platform_css }} class=\"{{.}}\" {{ end }}>\n        <span class=\"platform-flag\">\n            {{$.platform_name}}\n        </span>\n    </div>\n\n    <main role=\"main\" class=\"order\">\n\n        <section class=\"container order-complete-section\">\n            <div class=\"row\">\n                <div class=\"col-12 text-center\">\n                    <h3>\n                        Your order is complete!\n                    </h3>\n                </div>\n                <div class=\"col-12 text-center\">\n                    <p>We've sent you a confirmation email.</p>\n                </div>\n            </div>\n            <div class=\"row border-bottom-solid padding-y-24\">\n                <div class=\"col-6 pl-md-0\">\n                    Confirmation #\n                </div>\n                <div class=\"col-6 pr-md-0 text-right\">\n                    {{.order.OrderId}}\n                </div>\n            </div>\n            <div class=\"row border-bottom-solid padding-y-24\">\n                <div class=\"col-6 pl-md-0\">\n                    Tracking #\n                </div>\n                <div class=\"col-6 pr-md-0 text-right\">\n                    {{.order.ShippingTrackingId}}\n                </div>\n            </div>\n            <div class=\"row padding-y-24\">\n                <div class=\"col-6 pl-md-0\">\n                    Total Paid\n                </div>\n                <div class=\"col-6 pr-md-0 text-right\">\n                    {{renderMoney .total_paid}}\n                </div>\n            </div>\n            <div class=\"row\">\n                <div class=\"col-12 text-center\">\n                    <a class=\"cymbal-button-primary\" href=\"{{ $.baseUrl }}/\" role=\"button\">\n                        Continue Shopping\n                    </a>\n                </div>\n            </div>\n        </section>\n\n        {{ if $.recommendations }}\n            {{ template \"recommendations\" $ }}\n        {{ end }}\n\n    </main>\n\n    {{ template \"footer\" . }}\n    {{ end }}\n"
  },
  {
    "path": "src/frontend/templates/product.html",
    "content": "<!--\n Copyright 2020 Google LLC\n\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n      http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n-->\n\n{{ define \"product\" }}\n{{ template \"header\" . }}\n<div {{ with $.platform_css }} class=\"{{.}}\" {{ end }}>\n  <span class=\"platform-flag\">\n    {{$.platform_name}}\n  </span>\n</div>\n\n<main role=\"main\">\n  <div class=\"h-product container\">\n    <div class=\"row\">\n      <div class=\"col-md-6\">\n        <img class=\"product-image\" alt=\"\" src=\"{{ $.baseUrl }}{{$.product.Item.Picture}}\" />\n      </div>\n      <div class=\"product-info col-md-5\">\n        <div class=\"product-wrapper\">\n\n          <h2>{{ $.product.Item.Name }}</h2>\n          <p class=\"product-price\">{{ renderMoney $.product.Price }}</p>\n          <p>{{ $.product.Item.Description }}</p>\n\n          {{ if $.packagingInfo }}\n          <div class=\"product-packaging\">\n            <h3>Packaging</h3>\n            <span>\n              Weight: {{ if $.packagingInfo.Weight }}{{ $.packagingInfo.Weight }}lb{{ else }}n/a{{ end }}\n            </span>\n            <span>\n              Width: {{ if $.packagingInfo.Width }}{{ $.packagingInfo.Width }}cm{{ else }}n/a{{ end }}\n            </span>\n            <span>\n              Height: {{ if $.packagingInfo.Height }}{{ $.packagingInfo.Height }}cm{{ else }}n/a{{ end }}\n            </span>\n            <span>\n              Depth: {{ if $.packagingInfo.Depth }}{{ $.packagingInfo.Depth }}cm{{ else }}n/a{{ end }}\n            </span>\n          </div>\n          {{ end }}\n\n          <form method=\"POST\" action=\"{{ $.baseUrl }}/cart\">\n            <input type=\"hidden\" name=\"product_id\" value=\"{{$.product.Item.Id}}\" />\n            <div class=\"product-quantity-dropdown\">\n              <select name=\"quantity\" id=\"quantity\">\n                <option>1</option>\n                <option>2</option>\n                <option>3</option>\n                <option>4</option>\n                <option>5</option>\n                <option>10</option>\n              </select>\n              <img src=\"{{ $.baseUrl }}/static/icons/Hipster_DownArrow.svg\" alt=\"\">\n            </div>\n            <button type=\"submit\" class=\"cymbal-button-primary\">Add To Cart</button>\n          </form>\n        </div>\n      </div>\n    </div>\n  </div>\n  <div>\n    {{ if $.recommendations}}\n      {{ template \"recommendations\" $ }}\n    {{ end }}\n  </div>\n  <div class=\"ad\">\n   {{ if $.ad }}{{ template \"text_ad\" $ }}{{ end }}\n  </div>\n\n</main>\n{{ template \"footer\" . }}\n{{ end }}\n"
  },
  {
    "path": "src/frontend/templates/recommendations.html",
    "content": "<!--\n Copyright 2020 Google LLC\n\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n      http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n-->\n\n{{ define \"recommendations\" }}\n<section class=\"recommendations\">\n    <div class=\"container\">\n      <div class=\"row\">\n        <div class=\"col-xl-10 offset-xl-1\">\n          <h2>You May Also Like</h2>\n          <div class=\"row\">\n            {{ range .recommendations }}\n            <div class=\"col-md-3\">\n              <div>\n                <a href=\"{{ $.baseUrl }}/product/{{.Id}}\">\n                  <img alt=\"\" src=\"{{ $.baseUrl }}{{.Picture}}\">\n                </a>\n                <div>\n                  <h5>\n                    {{ .Name }}\n                  </h5>\n                </div>\n              </div>\n            </div>\n            {{ end }}\n          </div>\n        </div>\n      </div>\n    </div>\n</section>\n{{ end }}\n"
  },
  {
    "path": "src/frontend/validator/validator.go",
    "content": "// Copyright 2024 Google LLC\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage validator\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\n\t\"github.com/go-playground/validator/v10\"\n)\n\nvar validate *validator.Validate\n\n// init() is a special function that will run when this package is imported.\n// It instantiates a SINGLE instance of *validator.Validate with the added\n// benefit of caching struct info and validations.\nfunc init() {\n\tvalidate = validator.New(validator.WithRequiredStructEnabled())\n}\n\ntype Payload interface {\n\tValidate() error\n}\n\ntype AddToCartPayload struct {\n\tQuantity  uint64 `validate:\"required,gte=1,lte=10\"`\n\tProductID string `validate:\"required\"`\n}\n\ntype PlaceOrderPayload struct {\n\tEmail         string `validate:\"required,email\"`\n\tStreetAddress string `validate:\"required,max=512\"`\n\tZipCode       int64  `validate:\"required\"`\n\tCity          string `validate:\"required,max=128\"`\n\tState         string `validate:\"required,max=128\"`\n\tCountry       string `validate:\"required,max=128\"`\n\tCcNumber      string `validate:\"required,credit_card\"`\n\tCcMonth       int64  `validate:\"required,gte=1,lte=12\"`\n\tCcYear        int64  `validate:\"required\"`\n\tCcCVV         int64  `validate:\"required\"`\n}\n\ntype SetCurrencyPayload struct {\n\tCurrency string `validate:\"required,iso4217\"`\n}\n\n// Implementations of the 'Payload' interface.\nfunc (ad *AddToCartPayload) Validate() error {\n\treturn validate.Struct(ad)\n}\n\nfunc (po *PlaceOrderPayload) Validate() error {\n\treturn validate.Struct(po)\n}\n\nfunc (sc *SetCurrencyPayload) Validate() error {\n\treturn validate.Struct(sc)\n}\n\n// Reusable error response function.\nfunc ValidationErrorResponse(err error) error {\n\tvalidationErrs, ok := err.(validator.ValidationErrors)\n\tif !ok {\n\t\treturn errors.New(\"invalid validation error format\")\n\t}\n\tvar msg string\n\tfor _, err := range validationErrs {\n\t\tmsg += fmt.Sprintf(\"Field '%s' is invalid: %s\\n\", err.Field(), err.Tag())\n\t}\n\treturn fmt.Errorf(\"%s\", msg)\n}\n"
  },
  {
    "path": "src/frontend/validator/validator_test.go",
    "content": "// Copyright 2024 Google LLC\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage validator\n\nimport (\n\t\"strings\"\n\t\"testing\"\n)\n\nfunc TestPlaceOrderPassesValidation(t *testing.T) {\n\ttests := []struct {\n\t\tname          string\n\t\temail         string\n\t\tstreetAddress string\n\t\tzipCode       int64\n\t\tcity          string\n\t\tstate         string\n\t\tcountry       string\n\t\tccNumber      string\n\t\tccMonth       int64\n\t\tccYear        int64\n\t\tccCVV         int64\n\t}{\n\t\t{\"valid\", \"test@example.com\", \"12345 example street\", 10004, \"New York\", \"New York\", \"United States\", \"5272940000751666\", 4, 2024, 584},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tpayload := PlaceOrderPayload{\n\t\t\t\tEmail:         tt.email,\n\t\t\t\tStreetAddress: tt.streetAddress,\n\t\t\t\tZipCode:       tt.zipCode,\n\t\t\t\tCity:          tt.city,\n\t\t\t\tState:         tt.state,\n\t\t\t\tCountry:       tt.country,\n\t\t\t\tCcNumber:      tt.ccNumber,\n\t\t\t\tCcMonth:       tt.ccMonth,\n\t\t\t\tCcYear:        tt.ccYear,\n\t\t\t\tCcCVV:         tt.ccCVV,\n\t\t\t}\n\t\t\tif err := payload.Validate(); err != nil {\n\t\t\t\tt.Errorf(\"want validation on %v, got %v\", payload, err)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestPlaceOrderFailsValidation(t *testing.T) {\n\ttests := []struct {\n\t\tname          string\n\t\temail         string\n\t\tstreetAddress string\n\t\tzipCode       int64\n\t\tcity          string\n\t\tstate         string\n\t\tcountry       string\n\t\tccNumber      string\n\t\tccMonth       int64\n\t\tccYear        int64\n\t\tccCVV         int64\n\t}{\n\t\t{\"invalid email\", \"test@example\", \"12345 example street\", 10004, \"New York\", \"New York\", \"United States\", \"5272940000751666\", 4, 2024, 584},\n\t\t{\"invalid address (too long)\", \"test@example.com\", strings.Repeat(\"12345 example street\", 513), 10004, \"New York\", \"New York\", \"United States\", \"5272940000751666\", 4, 2024, 584},\n\t\t{\"invalid zip code\", \"test@example.com\", \"12345 example street\", 0, \"New York\", \"New York\", \"United States\", \"5272940000751666\", 4, 2024, 584},\n\t\t{\"invalid city\", \"test@example.com\", \"12345 example street\", 10004, \"\", \"New York\", \"United States\", \"5272940000751666\", 4, 2024, 584},\n\t\t{\"invalid state\", \"test@example.com\", \"12345 example street\", 10004, \"New York\", \"\", \"United States\", \"5272940000751666\", 4, 2024, 584},\n\t\t{\"invalid country\", \"test@example.com\", \"12345 example street\", 10004, \"New York\", \"New York\", \"\", \"5272940000751666\", 4, 2024, 584},\n\t\t{\"invalid ccNumber\", \"test@example.com\", \"12345 example street\", 10004, \"New York\", \"New York\", \"United States\", \"5272940000\", 4, 2024, 584},\n\t\t{\"invalid ccMonth (month < 1)\", \"test@example.com\", \"12345 example street\", 10004, \"New York\", \"New York\", \"United States\", \"5272940000751666\", 0, 2024, 584},\n\t\t{\"invalid ccMonth (month > 12)\", \"test@example.com\", \"12345 example street\", 10004, \"New York\", \"New York\", \"United States\", \"5272940000751666\", 13, 2024, 584},\n\t\t{\"invalid ccYear (not provided)\", \"test@example.com\", \"12345 example street\", 10004, \"New York\", \"New York\", \"United States\", \"5272940000751666\", 12, 0, 584},\n\t\t{\"invalid ccCVV (not provided)\", \"test@example.com\", \"12345 example street\", 10004, \"New York\", \"New York\", \"United States\", \"5272940000751666\", 12, 2024, 0},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tpayload := PlaceOrderPayload{\n\t\t\t\tEmail:         tt.email,\n\t\t\t\tStreetAddress: tt.streetAddress,\n\t\t\t\tZipCode:       tt.zipCode,\n\t\t\t\tCity:          tt.city,\n\t\t\t\tState:         tt.state,\n\t\t\t\tCountry:       tt.country,\n\t\t\t\tCcNumber:      tt.ccNumber,\n\t\t\t\tCcMonth:       tt.ccMonth,\n\t\t\t\tCcYear:        tt.ccYear,\n\t\t\t\tCcCVV:         tt.ccCVV,\n\t\t\t}\n\t\t\tif err := payload.Validate(); err == nil {\n\t\t\t\tt.Errorf(\"want validation on %v, got %v\", payload, err)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestAddToCartPassesValidation(t *testing.T) {\n\ttests := []struct {\n\t\tname      string\n\t\tquantity  uint64\n\t\tproductID string\n\t}{\n\t\t{\"valid min quantity and product id\", 1, \"OLJCESPC7Z\"},\n\t\t{\"valid max quantity and product id\", 10, \"OLJCESPC7Z\"},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tpayload := AddToCartPayload{Quantity: tt.quantity, ProductID: tt.productID}\n\t\t\tif err := payload.Validate(); err != nil {\n\t\t\t\tt.Errorf(\"want validation on %v, got %v\", payload, err)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestAddToCartFailsValidation(t *testing.T) {\n\ttests := []struct {\n\t\tname      string\n\t\tquantity  uint64\n\t\tproductID string\n\t}{\n\t\t{\"invalid min quantity\", 0, \"OLJCESPC7Z\"},\n\t\t{\"invalid max quantity\", 11, \"OLJCESPC7Z\"},\n\t\t{\"invalid product id\", 1, \"\"},\n\t\t{\"invalid quantity and product id\", 0, \"\"},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tpayload := AddToCartPayload{Quantity: tt.quantity, ProductID: tt.productID}\n\t\t\tif err := payload.Validate(); err == nil {\n\t\t\t\tt.Errorf(\"want validation on %v, got %v\", payload, err)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestSetCurrencyPassesValidation(t *testing.T) {\n\ttests := []struct {\n\t\tname     string\n\t\tcurrency string\n\t}{\n\t\t{\"valid currency (EUR)\", \"EUR\"},\n\t\t{\"valid currency (USD)\", \"USD\"},\n\t\t{\"valid currency (JPY)\", \"JPY\"},\n\t\t{\"valid currency (GBP)\", \"GBP\"},\n\t\t{\"valid currency (TRY)\", \"TRY\"},\n\t\t{\"valid currency (CAD)\", \"CAD\"},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tpayload := SetCurrencyPayload{Currency: tt.currency}\n\t\t\tif err := payload.Validate(); err != nil {\n\t\t\t\tt.Errorf(\"want validation on %v, got %v\", payload, err)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestSetCurrencyFailsValidation(t *testing.T) {\n\ttests := []struct {\n\t\tname     string\n\t\tcurrency string\n\t}{\n\t\t{\"invalid currency\", \"ABC\"},\n\t\t{\"invalid currency (symbol)\", \"$\"},\n\t\t{\"invalid (no currency)\", \"\"},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tpayload := SetCurrencyPayload{Currency: tt.currency}\n\t\t\tif err := payload.Validate(); err == nil {\n\t\t\t\tt.Errorf(\"want validation on %v, got %v\", payload, err)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "src/loadgenerator/Dockerfile",
    "content": "# Copyright 2020 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# Define a default value so it's not empty if the builder fails to provide it\nARG BUILDPLATFORM=linux/amd64\n\nFROM --platform=$BUILDPLATFORM python:3.14.3-alpine@sha256:faee120f7885a06fcc9677922331391fa690d911c020abb9e8025ff3d908e510 AS base\n\nFROM base AS builder\n\nENV PYTHONDONTWRITEBYTECODE=1\nENV PYTHONUNBUFFERED=1\n\nRUN apk update \\\n    && apk add --no-cache g++ linux-headers \\\n    && rm -rf /var/cache/apk/*\n\nCOPY requirements.txt .\n\nRUN pip install --prefix=\"/install\" -r requirements.txt\n\nFROM base\n\nENV PYTHONDONTWRITEBYTECODE=1\nENV PYTHONUNBUFFERED=1\n\nRUN apk update \\\n    && apk add --no-cache libstdc++ \\\n    && rm -rf /var/cache/apk/*\n\nWORKDIR /loadgen\n\nCOPY --from=builder /install /usr/local\n\n# Add application code.\nCOPY locustfile.py .\n\n# enable gevent support in debugger\nENV GEVENT_SUPPORT=True\n\nENTRYPOINT locust --host=\"http://${FRONTEND_ADDR}\" --headless -u \"${USERS:-10}\" -r \"${RATE:-1}\" 2>&1\n"
  },
  {
    "path": "src/loadgenerator/locustfile.py",
    "content": "#!/usr/bin/python\n#\n# Copyright 2018 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport random\nfrom locust import FastHttpUser, TaskSet, between\nfrom faker import Faker\nimport datetime\nfake = Faker()\n\nproducts = [\n    '0PUK6V6EV0',\n    '1YMWWN1N4O',\n    '2ZYFJ3GM2N',\n    '66VCHSJNUP',\n    '6E92ZMYYFZ',\n    '9SIQT8TOJO',\n    'L9ECAV7KIM',\n    'LS4PSXUNUM',\n    'OLJCESPC7Z']\n\ndef index(l):\n    l.client.get(\"/\")\n\ndef setCurrency(l):\n    currencies = ['EUR', 'USD', 'JPY', 'CAD', 'GBP', 'TRY']\n    l.client.post(\"/setCurrency\",\n        {'currency_code': random.choice(currencies)})\n\ndef browseProduct(l):\n    l.client.get(\"/product/\" + random.choice(products))\n\ndef viewCart(l):\n    l.client.get(\"/cart\")\n\ndef addToCart(l):\n    product = random.choice(products)\n    l.client.get(\"/product/\" + product)\n    l.client.post(\"/cart\", {\n        'product_id': product,\n        'quantity': random.randint(1,10)})\n    \ndef empty_cart(l):\n    l.client.post('/cart/empty')\n\ndef checkout(l):\n    addToCart(l)\n    current_year = datetime.datetime.now().year+1\n    l.client.post(\"/cart/checkout\", {\n        'email': fake.email(),\n        'street_address': fake.street_address(),\n        'zip_code': fake.zipcode(),\n        'city': fake.city(),\n        'state': fake.state_abbr(),\n        'country': fake.country(),\n        'credit_card_number': fake.credit_card_number(card_type=\"visa\"),\n        'credit_card_expiration_month': random.randint(1, 12),\n        'credit_card_expiration_year': random.randint(current_year, current_year + 70),\n        'credit_card_cvv': f\"{random.randint(100, 999)}\",\n    })\n    \ndef logout(l):\n    l.client.get('/logout')  \n\n\nclass UserBehavior(TaskSet):\n\n    def on_start(self):\n        index(self)\n\n    tasks = {index: 1,\n        setCurrency: 2,\n        browseProduct: 10,\n        addToCart: 2,\n        viewCart: 3,\n        checkout: 1}\n\nclass WebsiteUser(FastHttpUser):\n    tasks = [UserBehavior]\n    wait_time = between(1, 10)\n"
  },
  {
    "path": "src/loadgenerator/requirements.in",
    "content": "locust==2.43.0\nfaker==40.1.0\n"
  },
  {
    "path": "src/loadgenerator/requirements.txt",
    "content": "# This file was autogenerated by uv via the following command:\n#    uv pip compile requirements.in -o requirements.txt\nbidict==0.23.1\n    # via python-socketio\nblinker==1.9.0\n    # via flask\nbrotli==1.2.0\n    # via geventhttpclient\ncertifi==2025.8.3\n    # via\n    #   geventhttpclient\n    #   requests\ncharset-normalizer==3.4.3\n    # via requests\nclick==8.3.0\n    # via flask\nconfigargparse==1.7.1\n    # via locust\nfaker==40.1.0\n    # via -r requirements.in\nflask==3.1.3\n    # via\n    #   flask-cors\n    #   flask-login\n    #   locust\nflask-cors==6.0.1\n    # via locust\nflask-login==0.6.3\n    # via locust\ngevent==25.9.1\n    # via\n    #   geventhttpclient\n    #   locust\ngeventhttpclient==2.3.4\n    # via locust\ngreenlet==3.2.4\n    # via gevent\nh11==0.16.0\n    # via wsproto\nidna==3.10\n    # via requests\niniconfig==2.1.0\n    # via pytest\nitsdangerous==2.2.0\n    # via flask\njinja2==3.1.6\n    # via flask\nlocust==2.43.0\n    # via -r requirements.in\nmarkupsafe==3.0.2\n    # via\n    #   flask\n    #   jinja2\n    #   werkzeug\nmsgpack==1.1.1\n    # via locust\npackaging==25.0\n    # via pytest\npluggy==1.6.0\n    # via pytest\npsutil==7.1.0\n    # via locust\npygments==2.19.2\n    # via pytest\npytest==8.4.2\n    # via locust\npython-engineio==4.12.2\n    # via\n    #   locust\n    #   python-socketio\npython-socketio[client]==5.13.0\n    # via locust\npyzmq==27.1.0\n    # via locust\nrequests==2.32.5\n    # via\n    #   locust\n    #   python-socketio\nsimple-websocket==1.1.0\n    # via python-engineio\ntzdata==2025.2\n    # via faker\nurllib3==2.6.3\n    # via\n    #   geventhttpclient\n    #   requests\nwebsocket-client==1.8.0\n    # via python-socketio\nwerkzeug==3.1.6\n    # via\n    #   flask\n    #   flask-cors\n    #   flask-login\n    #   locust\nwsproto==1.2.0\n    # via simple-websocket\nzope-event==6.0\n    # via gevent\nzope-interface==8.0\n    # via gevent\n"
  },
  {
    "path": "src/paymentservice/.dockerignore",
    "content": "node_modules\n"
  },
  {
    "path": "src/paymentservice/Dockerfile",
    "content": "# Copyright 2020 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# Define a default value so it's not empty if the builder fails to provide it\nARG BUILDPLATFORM=linux/amd64\n\nFROM --platform=$BUILDPLATFORM node:20.20.1-alpine@sha256:b88333c42c23fbd91596ebd7fd10de239cedab9617de04142dde7315e3bc0afa AS builder\n\n# Some packages (e.g. @google-cloud/profiler) require additional\n# deps for post-install scripts\nRUN apk add --update --no-cache \\\n    python3 \\\n    make \\\n    g++\n\nWORKDIR /usr/src/app\n\nCOPY package*.json ./\n\nRUN npm install --only=production\n\nFROM alpine:3.23.3@sha256:25109184c71bdad752c8312a8623239686a9a2071e8825f20acb8f2198c3f659\n\nRUN apk add --no-cache nodejs\n\nWORKDIR /usr/src/app\n\nCOPY --from=builder /usr/src/app/node_modules ./node_modules\n\nCOPY . .\n\nEXPOSE 50051\n\nENTRYPOINT [ \"node\", \"index.js\" ]\n"
  },
  {
    "path": "src/paymentservice/charge.js",
    "content": "// Copyright 2018 Google LLC\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\nconst cardValidator = require('simple-card-validator');\nconst { v4: uuidv4 } = require('uuid');\nconst pino = require('pino');\n\nconst logger = pino({\n  name: 'paymentservice-charge',\n  messageKey: 'message',\n  formatters: {\n    level (logLevelString, logLevelNum) {\n      return { severity: logLevelString }\n    }\n  }\n});\n\n\nclass CreditCardError extends Error {\n  constructor (message) {\n    super(message);\n    this.code = 400; // Invalid argument error\n  }\n}\n\nclass InvalidCreditCard extends CreditCardError {\n  constructor (cardType) {\n    super(`Credit card info is invalid`);\n  }\n}\n\nclass UnacceptedCreditCard extends CreditCardError {\n  constructor (cardType) {\n    super(`Sorry, we cannot process ${cardType} credit cards. Only VISA or MasterCard is accepted.`);\n  }\n}\n\nclass ExpiredCreditCard extends CreditCardError {\n  constructor (number, month, year) {\n    super(`Your credit card (ending ${number.substr(-4)}) expired on ${month}/${year}`);\n  }\n}\n\n/**\n * Verifies the credit card number and (pretend) charges the card.\n *\n * @param {*} request\n * @return transaction_id - a random uuid.\n */\nmodule.exports = function charge (request) {\n  const { amount, credit_card: creditCard } = request;\n  const cardNumber = creditCard.credit_card_number;\n  const cardInfo = cardValidator(cardNumber);\n  const {\n    card_type: cardType,\n    valid\n  } = cardInfo.getCardDetails();\n\n  if (!valid) { throw new InvalidCreditCard(); }\n\n  // Only VISA and mastercard is accepted, other card types (AMEX, dinersclub) will\n  // throw UnacceptedCreditCard error.\n  if (!(cardType === 'visa' || cardType === 'mastercard')) { throw new UnacceptedCreditCard(cardType); }\n\n  // Also validate expiration is > today.\n  const currentMonth = new Date().getMonth() + 1;\n  const currentYear = new Date().getFullYear();\n  const { credit_card_expiration_year: year, credit_card_expiration_month: month } = creditCard;\n  if ((currentYear * 12 + currentMonth) > (year * 12 + month)) { throw new ExpiredCreditCard(cardNumber.replace('-', ''), month, year); }\n\n  logger.info(`Transaction processed: ${cardType} ending ${cardNumber.substr(-4)} \\\n    Amount: ${amount.currency_code}${amount.units}.${amount.nanos}`);\n\n  return { transaction_id: uuidv4() };\n};\n"
  },
  {
    "path": "src/paymentservice/genproto.sh",
    "content": "#!/bin/bash -eu\n#\n# Copyright 2018 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# [START gke_paymentservice_genproto]\n\n# protos are loaded dynamically for node, simply copies over the proto.\nmkdir -p proto\ncp -r ../../protos/* ./proto\n\n# [END gke_paymentservice_genproto]"
  },
  {
    "path": "src/paymentservice/index.js",
    "content": "/*\n * Copyright 2018 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n'use strict';\n\nconst logger = require('./logger')\n\nif (process.env.DISABLE_PROFILER) {\n  logger.info(\"Profiler disabled.\")\n} else {\n  logger.info(\"Profiler enabled.\")\n  require('@google-cloud/profiler').start({\n    serviceContext: {\n      service: 'paymentservice',\n      version: '1.0.0'\n    }\n  });\n}\n\n\nif (process.env.ENABLE_TRACING == \"1\") {\n  logger.info(\"Tracing enabled.\")\n\n  const { resourceFromAttributes } = require('@opentelemetry/resources');\n\n  const { ATTR_SERVICE_NAME }= require('@opentelemetry/semantic-conventions');\n\n  const { GrpcInstrumentation } = require('@opentelemetry/instrumentation-grpc');\n  const { registerInstrumentations } = require('@opentelemetry/instrumentation');\n  const opentelemetry = require('@opentelemetry/sdk-node');\n\n  const { OTLPTraceExporter } = require('@opentelemetry/exporter-otlp-grpc');\n\n  const collectorUrl = process.env.COLLECTOR_SERVICE_ADDR;\n  const traceExporter = new OTLPTraceExporter({url: collectorUrl});\n\n  const sdk = new opentelemetry.NodeSDK({\n    resource: resourceFromAttributes({\n      [ATTR_SERVICE_NAME]: process.env.OTEL_SERVICE_NAME || 'paymentservice',\n    }),\n    traceExporter: traceExporter,\n  });\n\n  registerInstrumentations({\n    instrumentations: [new GrpcInstrumentation()]\n  });\n\n  sdk.start()\n} else {\n  logger.info(\"Tracing disabled.\")\n}\n\n\nconst path = require('path');\nconst HipsterShopServer = require('./server');\n\nconst PORT = process.env['PORT'];\nconst PROTO_PATH = path.join(__dirname, '/proto/');\n\nconst server = new HipsterShopServer(PROTO_PATH, PORT);\n\nserver.listen();\n"
  },
  {
    "path": "src/paymentservice/logger.js",
    "content": "/*\r\n * Copyright 2023 Google LLC\r\n *\r\n * Licensed under the Apache License, Version 2.0 (the \"License\");\r\n * you may not use this file except in compliance with the License.\r\n * You may obtain a copy of the License at\r\n *\r\n *     https://www.apache.org/licenses/LICENSE-2.0\r\n *\r\n * Unless required by applicable law or agreed to in writing, software\r\n * distributed under the License is distributed on an \"AS IS\" BASIS,\r\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n * See the License for the specific language governing permissions and\r\n * limitations under the License.\r\n */\r\n\r\nconst pino = require('pino');\r\n\r\nmodule.exports = pino({\r\n  name: 'paymentservice-server',\r\n  messageKey: 'message',\r\n  formatters: {\r\n    level (logLevelString, logLevelNum) {\r\n      return { severity: logLevelString }\r\n    }\r\n  }\r\n});"
  },
  {
    "path": "src/paymentservice/package.json",
    "content": "{\n  \"name\": \"paymentservice\",\n  \"version\": \"0.0.1\",\n  \"description\": \"Payment Microservice demo\",\n  \"repository\": \"https://github.com/GoogleCloudPlatform/microservices-demo\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"test\": \"echo \\\"Error: no test specified\\\" && exit 1\"\n  },\n  \"author\": \"Jonathan Lui\",\n  \"license\": \"ISC\",\n  \"dependencies\": {\n    \"@google-cloud/profiler\": \"6.0.4\",\n    \"@grpc/grpc-js\": \"1.14.3\",\n    \"@grpc/proto-loader\": \"0.8.0\",\n    \"@opentelemetry/api\": \"1.9.0\",\n    \"@opentelemetry/exporter-otlp-grpc\": \"0.26.0\",\n    \"@opentelemetry/instrumentation-grpc\": \"0.213.0\",\n    \"@opentelemetry/sdk-trace-base\": \"2.6.0\",\n    \"@opentelemetry/sdk-node\": \"0.213.0\",\n    \"@opentelemetry/resources\": \"2.6.0\",\n    \"@opentelemetry/semantic-conventions\": \"1.40.0\",\n    \"pino\": \"10.3.1\",\n    \"simple-card-validator\": \"^1.1.0\",\n    \"uuid\": \"^13.0.0\"\n  }\n}\n"
  },
  {
    "path": "src/paymentservice/proto/demo.proto",
    "content": "// Copyright 2020 Google LLC\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\nsyntax = \"proto3\";\n\npackage hipstershop;\n\n// -----------------Cart service-----------------\n\nservice CartService {\n    rpc AddItem(AddItemRequest) returns (Empty) {}\n    rpc GetCart(GetCartRequest) returns (Cart) {}\n    rpc EmptyCart(EmptyCartRequest) returns (Empty) {}\n}\n\nmessage CartItem {\n    string product_id = 1;\n    int32  quantity = 2;\n}\n\nmessage AddItemRequest {\n    string user_id = 1;\n    CartItem item = 2;\n}\n\nmessage EmptyCartRequest {\n    string user_id = 1;\n}\n\nmessage GetCartRequest {\n    string user_id = 1;\n}\n\nmessage Cart {\n    string user_id = 1;\n    repeated CartItem items = 2;\n}\n\nmessage Empty {}\n\n// ---------------Recommendation service----------\n\nservice RecommendationService {\n  rpc ListRecommendations(ListRecommendationsRequest) returns (ListRecommendationsResponse){}\n}\n\nmessage ListRecommendationsRequest {\n    string user_id = 1;\n    repeated string product_ids = 2;\n}\n\nmessage ListRecommendationsResponse {\n    repeated string product_ids = 1;\n}\n\n// ---------------Product Catalog----------------\n\nservice ProductCatalogService {\n    rpc ListProducts(Empty) returns (ListProductsResponse) {}\n    rpc GetProduct(GetProductRequest) returns (Product) {}\n    rpc SearchProducts(SearchProductsRequest) returns (SearchProductsResponse) {}\n}\n\nmessage Product {\n    string id = 1;\n    string name = 2;\n    string description = 3;\n    string picture = 4;\n    Money price_usd = 5;\n\n    // Categories such as \"clothing\" or \"kitchen\" that can be used to look up\n    // other related products.\n    repeated string categories = 6;\n}\n\nmessage ListProductsResponse {\n    repeated Product products = 1;\n}\n\nmessage GetProductRequest {\n    string id = 1;\n}\n\nmessage SearchProductsRequest {\n    string query = 1;\n}\n\nmessage SearchProductsResponse {\n    repeated Product results = 1;\n}\n\n// ---------------Shipping Service----------\n\nservice ShippingService {\n    rpc GetQuote(GetQuoteRequest) returns (GetQuoteResponse) {}\n    rpc ShipOrder(ShipOrderRequest) returns (ShipOrderResponse) {}\n}\n\nmessage GetQuoteRequest {\n    Address address = 1;\n    repeated CartItem items = 2;\n}\n\nmessage GetQuoteResponse {\n    Money cost_usd = 1;\n}\n\nmessage ShipOrderRequest {\n    Address address = 1;\n    repeated CartItem items = 2;\n}\n\nmessage ShipOrderResponse {\n    string tracking_id = 1;\n}\n\nmessage Address {\n    string street_address = 1;\n    string city = 2;\n    string state = 3;\n    string country = 4;\n    int32 zip_code = 5;\n}\n\n// -----------------Currency service-----------------\n\nservice CurrencyService {\n    rpc GetSupportedCurrencies(Empty) returns (GetSupportedCurrenciesResponse) {}\n    rpc Convert(CurrencyConversionRequest) returns (Money) {}\n}\n\n// Represents an amount of money with its currency type.\nmessage Money {\n    // The 3-letter currency code defined in ISO 4217.\n    string currency_code = 1;\n\n    // The whole units of the amount.\n    // For example if `currencyCode` is `\"USD\"`, then 1 unit is one US dollar.\n    int64 units = 2;\n\n    // Number of nano (10^-9) units of the amount.\n    // The value must be between -999,999,999 and +999,999,999 inclusive.\n    // If `units` is positive, `nanos` must be positive or zero.\n    // If `units` is zero, `nanos` can be positive, zero, or negative.\n    // If `units` is negative, `nanos` must be negative or zero.\n    // For example $-1.75 is represented as `units`=-1 and `nanos`=-750,000,000.\n    int32 nanos = 3;\n}\n\nmessage GetSupportedCurrenciesResponse {\n    // The 3-letter currency code defined in ISO 4217.\n    repeated string currency_codes = 1;\n}\n\nmessage CurrencyConversionRequest {\n    Money from = 1;\n\n    // The 3-letter currency code defined in ISO 4217.\n    string to_code = 2;\n}\n\n// -------------Payment service-----------------\n\nservice PaymentService {\n    rpc Charge(ChargeRequest) returns (ChargeResponse) {}\n}\n\nmessage CreditCardInfo {\n    string credit_card_number = 1;\n    int32 credit_card_cvv = 2;\n    int32 credit_card_expiration_year = 3;\n    int32 credit_card_expiration_month = 4;\n}\n\nmessage ChargeRequest {\n    Money amount = 1;\n    CreditCardInfo credit_card = 2;\n}\n\nmessage ChargeResponse {\n    string transaction_id = 1;\n}\n\n// -------------Email service-----------------\n\nservice EmailService {\n    rpc SendOrderConfirmation(SendOrderConfirmationRequest) returns (Empty) {}\n}\n\nmessage OrderItem {\n    CartItem item = 1;\n    Money cost = 2;\n}\n\nmessage OrderResult {\n    string   order_id = 1;\n    string   shipping_tracking_id = 2;\n    Money shipping_cost = 3;\n    Address  shipping_address = 4;\n    repeated OrderItem items = 5;\n}\n\nmessage SendOrderConfirmationRequest {\n    string email = 1;\n    OrderResult order = 2;\n}\n\n\n// -------------Checkout service-----------------\n\nservice CheckoutService {\n    rpc PlaceOrder(PlaceOrderRequest) returns (PlaceOrderResponse) {}\n}\n\nmessage PlaceOrderRequest {\n    string user_id = 1;\n    string user_currency = 2;\n\n    Address address = 3;\n    string email = 5;\n    CreditCardInfo credit_card = 6;\n}\n\nmessage PlaceOrderResponse {\n    OrderResult order = 1;\n}\n\n// ------------Ad service------------------\n\nservice AdService {\n    rpc GetAds(AdRequest) returns (AdResponse) {}\n}\n\nmessage AdRequest {\n    // List of important key words from the current page describing the context.\n    repeated string context_keys = 1;\n}\n\nmessage AdResponse {\n    repeated Ad ads = 1;\n}\n\nmessage Ad {\n    // url to redirect to when an ad is clicked.\n    string redirect_url = 1;\n\n    // short advertisement text to display.\n    string text = 2;\n}\n"
  },
  {
    "path": "src/paymentservice/proto/grpc/health/v1/health.proto",
    "content": "// Copyright 2015 The gRPC Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// The canonical version of this proto can be found at\n// https://github.com/grpc/grpc-proto/blob/master/grpc/health/v1/health.proto\n\nsyntax = \"proto3\";\n\npackage grpc.health.v1;\n\noption csharp_namespace = \"Grpc.Health.V1\";\noption go_package = \"google.golang.org/grpc/health/grpc_health_v1\";\noption java_multiple_files = true;\noption java_outer_classname = \"HealthProto\";\noption java_package = \"io.grpc.health.v1\";\n\nmessage HealthCheckRequest {\n  string service = 1;\n}\n\nmessage HealthCheckResponse {\n  enum ServingStatus {\n    UNKNOWN = 0;\n    SERVING = 1;\n    NOT_SERVING = 2;\n  }\n  ServingStatus status = 1;\n}\n\nservice Health {\n  rpc Check(HealthCheckRequest) returns (HealthCheckResponse);\n}\n"
  },
  {
    "path": "src/paymentservice/server.js",
    "content": "// Copyright 2018 Google LLC\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\nconst path = require('path');\nconst grpc = require('@grpc/grpc-js');\nconst protoLoader = require('@grpc/proto-loader');\n\nconst charge = require('./charge');\n\nconst logger = require('./logger')\n\nclass HipsterShopServer {\n  constructor(protoRoot, port = HipsterShopServer.PORT) {\n    this.port = port;\n\n    this.packages = {\n      hipsterShop: this.loadProto(path.join(protoRoot, 'demo.proto')),\n      health: this.loadProto(path.join(protoRoot, 'grpc/health/v1/health.proto'))\n    };\n\n    this.server = new grpc.Server();\n    this.loadAllProtos(protoRoot);\n  }\n\n  /**\n   * Handler for PaymentService.Charge.\n   * @param {*} call  { ChargeRequest }\n   * @param {*} callback  fn(err, ChargeResponse)\n   */\n  static ChargeServiceHandler(call, callback) {\n    try {\n      logger.info(`PaymentService#Charge invoked with request ${JSON.stringify(call.request)}`);\n      const response = charge(call.request);\n      callback(null, response);\n    } catch (err) {\n      console.warn(err);\n      callback(err);\n    }\n  }\n\n  static CheckHandler(call, callback) {\n    callback(null, { status: 'SERVING' });\n  }\n\n\n  listen() {\n    const server = this.server \n    const port = this.port\n    server.bindAsync(\n      `[::]:${port}`,\n      grpc.ServerCredentials.createInsecure(),\n      function () {\n        logger.info(`PaymentService gRPC server started on port ${port}`);\n        server.start();\n      }\n    );\n  }\n\n  loadProto(path) {\n    const packageDefinition = protoLoader.loadSync(\n      path,\n      {\n        keepCase: true,\n        longs: String,\n        enums: String,\n        defaults: true,\n        oneofs: true\n      }\n    );\n    return grpc.loadPackageDefinition(packageDefinition);\n  }\n\n  loadAllProtos(protoRoot) {\n    const hipsterShopPackage = this.packages.hipsterShop.hipstershop;\n    const healthPackage = this.packages.health.grpc.health.v1;\n\n    this.server.addService(\n      hipsterShopPackage.PaymentService.service,\n      {\n        charge: HipsterShopServer.ChargeServiceHandler.bind(this)\n      }\n    );\n\n    this.server.addService(\n      healthPackage.Health.service,\n      {\n        check: HipsterShopServer.CheckHandler.bind(this)\n      }\n    );\n  }\n}\n\nHipsterShopServer.PORT = process.env.PORT;\n\nmodule.exports = HipsterShopServer;\n"
  },
  {
    "path": "src/productcatalogservice/.dockerignore",
    "content": "vendor/\n"
  },
  {
    "path": "src/productcatalogservice/Dockerfile",
    "content": "# Copyright 2020 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# Define a default value so it's not empty if the builder fails to provide it\nARG BUILDPLATFORM=linux/amd64\n\nFROM --platform=$BUILDPLATFORM golang:1.26.1-alpine@sha256:2389ebfa5b7f43eeafbd6be0c3700cc46690ef842ad962f6c5bd6be49ed82039 AS builder\nARG TARGETOS=linux\nARG TARGETARCH=amd64\n\nWORKDIR /src\n# restore dependencies\nCOPY go.mod go.sum ./\nRUN go mod download\nCOPY . .\n\n# Skaffold passes in debug-oriented compiler flags\nARG SKAFFOLD_GO_GCFLAGS\nRUN GOOS=${TARGETOS} GOARCH=${TARGETARCH} CGO_ENABLED=0 go build -ldflags=\"-s -w\" -gcflags=\"${SKAFFOLD_GO_GCFLAGS}\" -o /productcatalogservice .\n\nFROM gcr.io/distroless/static\n\nWORKDIR /src\nCOPY --from=builder /productcatalogservice ./server\nCOPY products.json .\n\n# Definition of this variable is used by 'skaffold debug' to identify a golang binary.\n# Default behavior - a failure prints a stack trace for the current goroutine.\n# See https://golang.org/pkg/runtime/\nENV GOTRACEBACK=single\n\nEXPOSE 3550\nENTRYPOINT [\"/src/server\"]\n"
  },
  {
    "path": "src/productcatalogservice/README.md",
    "content": "# productcatalogservice\n\nRun the following command to restore dependencies to `vendor/` directory:\n\n    go mod vendor\n\n## Dynamic catalog reloading / artificial delay\n\nThis service has a \"dynamic catalog reloading\" feature that is purposefully\nnot well implemented. The goal of this feature is to allow you to modify the\n`products.json` file and have the changes be picked up without having to\nrestart the service.\n\nHowever, this feature is bugged: the catalog is actually reloaded on each\nrequest, introducing a noticeable delay in the frontend. This delay will also\nshow up in profiling tools: the `parseCatalog` function will take more than 80%\nof the CPU time.\n\nYou can trigger this feature (and the delay) by sending a `USR1` signal and\nremove it (if needed) by sending a `USR2` signal:\n\n```\n# Trigger bug\nkubectl exec \\\n    $(kubectl get pods -l app=productcatalogservice -o jsonpath='{.items[0].metadata.name}') \\\n    -c server -- kill -USR1 1\n# Remove bug\nkubectl exec \\\n    $(kubectl get pods -l app=productcatalogservice -o jsonpath='{.items[0].metadata.name}') \\\n    -c server -- kill -USR2 1\n```\n\n## Latency injection\n\nThis 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\nto the server.\n\nFor example, use `EXTRA_LATENCY=\"5.5s\"` to sleep for 5.5 seconds on every request.\n"
  },
  {
    "path": "src/productcatalogservice/catalog_loader.go",
    "content": "// Copyright 2024 Google LLC\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage main\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"fmt\"\n\t\"net\"\n\t\"os\"\n\t\"strings\"\n\n\t\"cloud.google.com/go/alloydbconn\"\n\tsecretmanager \"cloud.google.com/go/secretmanager/apiv1\"\n\t\"cloud.google.com/go/secretmanager/apiv1/secretmanagerpb\"\n\tpb \"github.com/GoogleCloudPlatform/microservices-demo/src/productcatalogservice/genproto\"\n\t\"github.com/golang/protobuf/jsonpb\"\n\t\"github.com/jackc/pgx/v5/pgxpool\"\n)\n\nfunc loadCatalog(catalog *pb.ListProductsResponse) error {\n\tcatalogMutex.Lock()\n\tdefer catalogMutex.Unlock()\n\n\tif os.Getenv(\"ALLOYDB_CLUSTER_NAME\") != \"\" {\n\t\treturn loadCatalogFromAlloyDB(catalog)\n\t}\n\n\treturn loadCatalogFromLocalFile(catalog)\n}\n\nfunc loadCatalogFromLocalFile(catalog *pb.ListProductsResponse) error {\n\tlog.Info(\"loading catalog from local products.json file...\")\n\n\tcatalogJSON, err := os.ReadFile(\"products.json\")\n\tif err != nil {\n\t\tlog.Warnf(\"failed to open product catalog json file: %v\", err)\n\t\treturn err\n\t}\n\n\tif err := jsonpb.Unmarshal(bytes.NewReader(catalogJSON), catalog); err != nil {\n\t\tlog.Warnf(\"failed to parse the catalog JSON: %v\", err)\n\t\treturn err\n\t}\n\n\tlog.Info(\"successfully parsed product catalog json\")\n\treturn nil\n}\n\nfunc getSecretPayload(project, secret, version string) (string, error) {\n\tctx := context.Background()\n\tclient, err := secretmanager.NewClient(ctx)\n\tif err != nil {\n\t\tlog.Warnf(\"failed to create SecretManager client: %v\", err)\n\t\treturn \"\", err\n\t}\n\tdefer client.Close()\n\n\treq := &secretmanagerpb.AccessSecretVersionRequest{\n\t\tName: fmt.Sprintf(\"projects/%s/secrets/%s/versions/%s\", project, secret, version),\n\t}\n\n\t// Call the API.\n\tresult, err := client.AccessSecretVersion(ctx, req)\n\tif err != nil {\n\t\tlog.Warnf(\"failed to access SecretVersion: %v\", err)\n\t\treturn \"\", err\n\t}\n\n\treturn string(result.Payload.Data), nil\n}\n\nfunc loadCatalogFromAlloyDB(catalog *pb.ListProductsResponse) error {\n\tlog.Info(\"loading catalog from AlloyDB...\")\n\n\tprojectID := os.Getenv(\"PROJECT_ID\")\n\tregion := os.Getenv(\"REGION\")\n\tpgClusterName := os.Getenv(\"ALLOYDB_CLUSTER_NAME\")\n\tpgInstanceName := os.Getenv(\"ALLOYDB_INSTANCE_NAME\")\n\tpgDatabaseName := os.Getenv(\"ALLOYDB_DATABASE_NAME\")\n\tpgTableName := os.Getenv(\"ALLOYDB_TABLE_NAME\")\n\tpgSecretName := os.Getenv(\"ALLOYDB_SECRET_NAME\")\n\n\tpgPassword, err := getSecretPayload(projectID, pgSecretName, \"latest\")\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tdialer, err := alloydbconn.NewDialer(context.Background())\n\tif err != nil {\n\t\tlog.Warnf(\"failed to set-up dialer connection: %v\", err)\n\t\treturn err\n\t}\n\tcleanup := func() error { return dialer.Close() }\n\tdefer cleanup()\n\n\tdsn := fmt.Sprintf(\n\t\t\"user=%s password=%s dbname=%s sslmode=disable\",\n\t\t\"postgres\", pgPassword, pgDatabaseName,\n\t)\n\n\tconfig, err := pgxpool.ParseConfig(dsn)\n\tif err != nil {\n\t\tlog.Warnf(\"failed to parse DSN config: %v\", err)\n\t\treturn err\n\t}\n\n\tpgInstanceURI := fmt.Sprintf(\"projects/%s/locations/%s/clusters/%s/instances/%s\", projectID, region, pgClusterName, pgInstanceName)\n\tconfig.ConnConfig.DialFunc = func(ctx context.Context, _ string, _ string) (net.Conn, error) {\n\t\treturn dialer.Dial(ctx, pgInstanceURI)\n\t}\n\n\tpool, err := pgxpool.NewWithConfig(context.Background(), config)\n\tif err != nil {\n\t\tlog.Warnf(\"failed to set-up pgx pool: %v\", err)\n\t\treturn err\n\t}\n\tdefer pool.Close()\n\n\tquery := \"SELECT id, name, description, picture, price_usd_currency_code, price_usd_units, price_usd_nanos, categories FROM \" + pgTableName\n\trows, err := pool.Query(context.Background(), query)\n\tif err != nil {\n\t\tlog.Warnf(\"failed to query database: %v\", err)\n\t\treturn err\n\t}\n\tdefer rows.Close()\n\n\tcatalog.Products = catalog.Products[:0]\n\tfor rows.Next() {\n\t\tproduct := &pb.Product{}\n\t\tproduct.PriceUsd = &pb.Money{}\n\n\t\tvar categories string\n\t\terr = rows.Scan(&product.Id, &product.Name, &product.Description,\n\t\t\t&product.Picture, &product.PriceUsd.CurrencyCode, &product.PriceUsd.Units,\n\t\t\t&product.PriceUsd.Nanos, &categories)\n\t\tif err != nil {\n\t\t\tlog.Warnf(\"failed to scan query result row: %v\", err)\n\t\t\treturn err\n\t\t}\n\t\tcategories = strings.ToLower(categories)\n\t\tproduct.Categories = strings.Split(categories, \",\")\n\n\t\tcatalog.Products = append(catalog.Products, product)\n\t}\n\n\tlog.Info(\"successfully parsed product catalog from AlloyDB\")\n\treturn nil\n}\n"
  },
  {
    "path": "src/productcatalogservice/genproto/demo.pb.go",
    "content": "// Copyright 2020 Google LLC\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.34.2\n// \tprotoc        v3.6.1\n// source: demo.proto\n\npackage hipstershop\n\nimport (\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\treflect \"reflect\"\n\tsync \"sync\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\ntype CartItem struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tProductId string `protobuf:\"bytes,1,opt,name=product_id,json=productId,proto3\" json:\"product_id,omitempty\"`\n\tQuantity  int32  `protobuf:\"varint,2,opt,name=quantity,proto3\" json:\"quantity,omitempty\"`\n}\n\nfunc (x *CartItem) Reset() {\n\t*x = CartItem{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_demo_proto_msgTypes[0]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *CartItem) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*CartItem) ProtoMessage() {}\n\nfunc (x *CartItem) ProtoReflect() protoreflect.Message {\n\tmi := &file_demo_proto_msgTypes[0]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use CartItem.ProtoReflect.Descriptor instead.\nfunc (*CartItem) Descriptor() ([]byte, []int) {\n\treturn file_demo_proto_rawDescGZIP(), []int{0}\n}\n\nfunc (x *CartItem) GetProductId() string {\n\tif x != nil {\n\t\treturn x.ProductId\n\t}\n\treturn \"\"\n}\n\nfunc (x *CartItem) GetQuantity() int32 {\n\tif x != nil {\n\t\treturn x.Quantity\n\t}\n\treturn 0\n}\n\ntype AddItemRequest struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tUserId string    `protobuf:\"bytes,1,opt,name=user_id,json=userId,proto3\" json:\"user_id,omitempty\"`\n\tItem   *CartItem `protobuf:\"bytes,2,opt,name=item,proto3\" json:\"item,omitempty\"`\n}\n\nfunc (x *AddItemRequest) Reset() {\n\t*x = AddItemRequest{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_demo_proto_msgTypes[1]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *AddItemRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*AddItemRequest) ProtoMessage() {}\n\nfunc (x *AddItemRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_demo_proto_msgTypes[1]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use AddItemRequest.ProtoReflect.Descriptor instead.\nfunc (*AddItemRequest) Descriptor() ([]byte, []int) {\n\treturn file_demo_proto_rawDescGZIP(), []int{1}\n}\n\nfunc (x *AddItemRequest) GetUserId() string {\n\tif x != nil {\n\t\treturn x.UserId\n\t}\n\treturn \"\"\n}\n\nfunc (x *AddItemRequest) GetItem() *CartItem {\n\tif x != nil {\n\t\treturn x.Item\n\t}\n\treturn nil\n}\n\ntype EmptyCartRequest struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tUserId string `protobuf:\"bytes,1,opt,name=user_id,json=userId,proto3\" json:\"user_id,omitempty\"`\n}\n\nfunc (x *EmptyCartRequest) Reset() {\n\t*x = EmptyCartRequest{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_demo_proto_msgTypes[2]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *EmptyCartRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*EmptyCartRequest) ProtoMessage() {}\n\nfunc (x *EmptyCartRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_demo_proto_msgTypes[2]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use EmptyCartRequest.ProtoReflect.Descriptor instead.\nfunc (*EmptyCartRequest) Descriptor() ([]byte, []int) {\n\treturn file_demo_proto_rawDescGZIP(), []int{2}\n}\n\nfunc (x *EmptyCartRequest) GetUserId() string {\n\tif x != nil {\n\t\treturn x.UserId\n\t}\n\treturn \"\"\n}\n\ntype GetCartRequest struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tUserId string `protobuf:\"bytes,1,opt,name=user_id,json=userId,proto3\" json:\"user_id,omitempty\"`\n}\n\nfunc (x *GetCartRequest) Reset() {\n\t*x = GetCartRequest{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_demo_proto_msgTypes[3]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *GetCartRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*GetCartRequest) ProtoMessage() {}\n\nfunc (x *GetCartRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_demo_proto_msgTypes[3]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use GetCartRequest.ProtoReflect.Descriptor instead.\nfunc (*GetCartRequest) Descriptor() ([]byte, []int) {\n\treturn file_demo_proto_rawDescGZIP(), []int{3}\n}\n\nfunc (x *GetCartRequest) GetUserId() string {\n\tif x != nil {\n\t\treturn x.UserId\n\t}\n\treturn \"\"\n}\n\ntype Cart struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tUserId string      `protobuf:\"bytes,1,opt,name=user_id,json=userId,proto3\" json:\"user_id,omitempty\"`\n\tItems  []*CartItem `protobuf:\"bytes,2,rep,name=items,proto3\" json:\"items,omitempty\"`\n}\n\nfunc (x *Cart) Reset() {\n\t*x = Cart{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_demo_proto_msgTypes[4]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *Cart) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Cart) ProtoMessage() {}\n\nfunc (x *Cart) ProtoReflect() protoreflect.Message {\n\tmi := &file_demo_proto_msgTypes[4]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Cart.ProtoReflect.Descriptor instead.\nfunc (*Cart) Descriptor() ([]byte, []int) {\n\treturn file_demo_proto_rawDescGZIP(), []int{4}\n}\n\nfunc (x *Cart) GetUserId() string {\n\tif x != nil {\n\t\treturn x.UserId\n\t}\n\treturn \"\"\n}\n\nfunc (x *Cart) GetItems() []*CartItem {\n\tif x != nil {\n\t\treturn x.Items\n\t}\n\treturn nil\n}\n\ntype Empty struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n}\n\nfunc (x *Empty) Reset() {\n\t*x = Empty{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_demo_proto_msgTypes[5]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *Empty) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Empty) ProtoMessage() {}\n\nfunc (x *Empty) ProtoReflect() protoreflect.Message {\n\tmi := &file_demo_proto_msgTypes[5]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Empty.ProtoReflect.Descriptor instead.\nfunc (*Empty) Descriptor() ([]byte, []int) {\n\treturn file_demo_proto_rawDescGZIP(), []int{5}\n}\n\ntype ListRecommendationsRequest struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tUserId     string   `protobuf:\"bytes,1,opt,name=user_id,json=userId,proto3\" json:\"user_id,omitempty\"`\n\tProductIds []string `protobuf:\"bytes,2,rep,name=product_ids,json=productIds,proto3\" json:\"product_ids,omitempty\"`\n}\n\nfunc (x *ListRecommendationsRequest) Reset() {\n\t*x = ListRecommendationsRequest{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_demo_proto_msgTypes[6]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *ListRecommendationsRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ListRecommendationsRequest) ProtoMessage() {}\n\nfunc (x *ListRecommendationsRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_demo_proto_msgTypes[6]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use ListRecommendationsRequest.ProtoReflect.Descriptor instead.\nfunc (*ListRecommendationsRequest) Descriptor() ([]byte, []int) {\n\treturn file_demo_proto_rawDescGZIP(), []int{6}\n}\n\nfunc (x *ListRecommendationsRequest) GetUserId() string {\n\tif x != nil {\n\t\treturn x.UserId\n\t}\n\treturn \"\"\n}\n\nfunc (x *ListRecommendationsRequest) GetProductIds() []string {\n\tif x != nil {\n\t\treturn x.ProductIds\n\t}\n\treturn nil\n}\n\ntype ListRecommendationsResponse struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tProductIds []string `protobuf:\"bytes,1,rep,name=product_ids,json=productIds,proto3\" json:\"product_ids,omitempty\"`\n}\n\nfunc (x *ListRecommendationsResponse) Reset() {\n\t*x = ListRecommendationsResponse{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_demo_proto_msgTypes[7]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *ListRecommendationsResponse) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ListRecommendationsResponse) ProtoMessage() {}\n\nfunc (x *ListRecommendationsResponse) ProtoReflect() protoreflect.Message {\n\tmi := &file_demo_proto_msgTypes[7]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use ListRecommendationsResponse.ProtoReflect.Descriptor instead.\nfunc (*ListRecommendationsResponse) Descriptor() ([]byte, []int) {\n\treturn file_demo_proto_rawDescGZIP(), []int{7}\n}\n\nfunc (x *ListRecommendationsResponse) GetProductIds() []string {\n\tif x != nil {\n\t\treturn x.ProductIds\n\t}\n\treturn nil\n}\n\ntype Product struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tId          string `protobuf:\"bytes,1,opt,name=id,proto3\" json:\"id,omitempty\"`\n\tName        string `protobuf:\"bytes,2,opt,name=name,proto3\" json:\"name,omitempty\"`\n\tDescription string `protobuf:\"bytes,3,opt,name=description,proto3\" json:\"description,omitempty\"`\n\tPicture     string `protobuf:\"bytes,4,opt,name=picture,proto3\" json:\"picture,omitempty\"`\n\tPriceUsd    *Money `protobuf:\"bytes,5,opt,name=price_usd,json=priceUsd,proto3\" json:\"price_usd,omitempty\"`\n\t// Categories such as \"clothing\" or \"kitchen\" that can be used to look up\n\t// other related products.\n\tCategories []string `protobuf:\"bytes,6,rep,name=categories,proto3\" json:\"categories,omitempty\"`\n}\n\nfunc (x *Product) Reset() {\n\t*x = Product{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_demo_proto_msgTypes[8]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *Product) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Product) ProtoMessage() {}\n\nfunc (x *Product) ProtoReflect() protoreflect.Message {\n\tmi := &file_demo_proto_msgTypes[8]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Product.ProtoReflect.Descriptor instead.\nfunc (*Product) Descriptor() ([]byte, []int) {\n\treturn file_demo_proto_rawDescGZIP(), []int{8}\n}\n\nfunc (x *Product) GetId() string {\n\tif x != nil {\n\t\treturn x.Id\n\t}\n\treturn \"\"\n}\n\nfunc (x *Product) GetName() string {\n\tif x != nil {\n\t\treturn x.Name\n\t}\n\treturn \"\"\n}\n\nfunc (x *Product) GetDescription() string {\n\tif x != nil {\n\t\treturn x.Description\n\t}\n\treturn \"\"\n}\n\nfunc (x *Product) GetPicture() string {\n\tif x != nil {\n\t\treturn x.Picture\n\t}\n\treturn \"\"\n}\n\nfunc (x *Product) GetPriceUsd() *Money {\n\tif x != nil {\n\t\treturn x.PriceUsd\n\t}\n\treturn nil\n}\n\nfunc (x *Product) GetCategories() []string {\n\tif x != nil {\n\t\treturn x.Categories\n\t}\n\treturn nil\n}\n\ntype ListProductsResponse struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tProducts []*Product `protobuf:\"bytes,1,rep,name=products,proto3\" json:\"products,omitempty\"`\n}\n\nfunc (x *ListProductsResponse) Reset() {\n\t*x = ListProductsResponse{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_demo_proto_msgTypes[9]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *ListProductsResponse) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ListProductsResponse) ProtoMessage() {}\n\nfunc (x *ListProductsResponse) ProtoReflect() protoreflect.Message {\n\tmi := &file_demo_proto_msgTypes[9]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use ListProductsResponse.ProtoReflect.Descriptor instead.\nfunc (*ListProductsResponse) Descriptor() ([]byte, []int) {\n\treturn file_demo_proto_rawDescGZIP(), []int{9}\n}\n\nfunc (x *ListProductsResponse) GetProducts() []*Product {\n\tif x != nil {\n\t\treturn x.Products\n\t}\n\treturn nil\n}\n\ntype GetProductRequest struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tId string `protobuf:\"bytes,1,opt,name=id,proto3\" json:\"id,omitempty\"`\n}\n\nfunc (x *GetProductRequest) Reset() {\n\t*x = GetProductRequest{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_demo_proto_msgTypes[10]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *GetProductRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*GetProductRequest) ProtoMessage() {}\n\nfunc (x *GetProductRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_demo_proto_msgTypes[10]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use GetProductRequest.ProtoReflect.Descriptor instead.\nfunc (*GetProductRequest) Descriptor() ([]byte, []int) {\n\treturn file_demo_proto_rawDescGZIP(), []int{10}\n}\n\nfunc (x *GetProductRequest) GetId() string {\n\tif x != nil {\n\t\treturn x.Id\n\t}\n\treturn \"\"\n}\n\ntype SearchProductsRequest struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tQuery string `protobuf:\"bytes,1,opt,name=query,proto3\" json:\"query,omitempty\"`\n}\n\nfunc (x *SearchProductsRequest) Reset() {\n\t*x = SearchProductsRequest{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_demo_proto_msgTypes[11]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *SearchProductsRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*SearchProductsRequest) ProtoMessage() {}\n\nfunc (x *SearchProductsRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_demo_proto_msgTypes[11]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use SearchProductsRequest.ProtoReflect.Descriptor instead.\nfunc (*SearchProductsRequest) Descriptor() ([]byte, []int) {\n\treturn file_demo_proto_rawDescGZIP(), []int{11}\n}\n\nfunc (x *SearchProductsRequest) GetQuery() string {\n\tif x != nil {\n\t\treturn x.Query\n\t}\n\treturn \"\"\n}\n\ntype SearchProductsResponse struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tResults []*Product `protobuf:\"bytes,1,rep,name=results,proto3\" json:\"results,omitempty\"`\n}\n\nfunc (x *SearchProductsResponse) Reset() {\n\t*x = SearchProductsResponse{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_demo_proto_msgTypes[12]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *SearchProductsResponse) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*SearchProductsResponse) ProtoMessage() {}\n\nfunc (x *SearchProductsResponse) ProtoReflect() protoreflect.Message {\n\tmi := &file_demo_proto_msgTypes[12]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use SearchProductsResponse.ProtoReflect.Descriptor instead.\nfunc (*SearchProductsResponse) Descriptor() ([]byte, []int) {\n\treturn file_demo_proto_rawDescGZIP(), []int{12}\n}\n\nfunc (x *SearchProductsResponse) GetResults() []*Product {\n\tif x != nil {\n\t\treturn x.Results\n\t}\n\treturn nil\n}\n\ntype GetQuoteRequest struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tAddress *Address    `protobuf:\"bytes,1,opt,name=address,proto3\" json:\"address,omitempty\"`\n\tItems   []*CartItem `protobuf:\"bytes,2,rep,name=items,proto3\" json:\"items,omitempty\"`\n}\n\nfunc (x *GetQuoteRequest) Reset() {\n\t*x = GetQuoteRequest{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_demo_proto_msgTypes[13]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *GetQuoteRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*GetQuoteRequest) ProtoMessage() {}\n\nfunc (x *GetQuoteRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_demo_proto_msgTypes[13]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use GetQuoteRequest.ProtoReflect.Descriptor instead.\nfunc (*GetQuoteRequest) Descriptor() ([]byte, []int) {\n\treturn file_demo_proto_rawDescGZIP(), []int{13}\n}\n\nfunc (x *GetQuoteRequest) GetAddress() *Address {\n\tif x != nil {\n\t\treturn x.Address\n\t}\n\treturn nil\n}\n\nfunc (x *GetQuoteRequest) GetItems() []*CartItem {\n\tif x != nil {\n\t\treturn x.Items\n\t}\n\treturn nil\n}\n\ntype GetQuoteResponse struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tCostUsd *Money `protobuf:\"bytes,1,opt,name=cost_usd,json=costUsd,proto3\" json:\"cost_usd,omitempty\"`\n}\n\nfunc (x *GetQuoteResponse) Reset() {\n\t*x = GetQuoteResponse{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_demo_proto_msgTypes[14]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *GetQuoteResponse) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*GetQuoteResponse) ProtoMessage() {}\n\nfunc (x *GetQuoteResponse) ProtoReflect() protoreflect.Message {\n\tmi := &file_demo_proto_msgTypes[14]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use GetQuoteResponse.ProtoReflect.Descriptor instead.\nfunc (*GetQuoteResponse) Descriptor() ([]byte, []int) {\n\treturn file_demo_proto_rawDescGZIP(), []int{14}\n}\n\nfunc (x *GetQuoteResponse) GetCostUsd() *Money {\n\tif x != nil {\n\t\treturn x.CostUsd\n\t}\n\treturn nil\n}\n\ntype ShipOrderRequest struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tAddress *Address    `protobuf:\"bytes,1,opt,name=address,proto3\" json:\"address,omitempty\"`\n\tItems   []*CartItem `protobuf:\"bytes,2,rep,name=items,proto3\" json:\"items,omitempty\"`\n}\n\nfunc (x *ShipOrderRequest) Reset() {\n\t*x = ShipOrderRequest{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_demo_proto_msgTypes[15]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *ShipOrderRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ShipOrderRequest) ProtoMessage() {}\n\nfunc (x *ShipOrderRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_demo_proto_msgTypes[15]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use ShipOrderRequest.ProtoReflect.Descriptor instead.\nfunc (*ShipOrderRequest) Descriptor() ([]byte, []int) {\n\treturn file_demo_proto_rawDescGZIP(), []int{15}\n}\n\nfunc (x *ShipOrderRequest) GetAddress() *Address {\n\tif x != nil {\n\t\treturn x.Address\n\t}\n\treturn nil\n}\n\nfunc (x *ShipOrderRequest) GetItems() []*CartItem {\n\tif x != nil {\n\t\treturn x.Items\n\t}\n\treturn nil\n}\n\ntype ShipOrderResponse struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tTrackingId string `protobuf:\"bytes,1,opt,name=tracking_id,json=trackingId,proto3\" json:\"tracking_id,omitempty\"`\n}\n\nfunc (x *ShipOrderResponse) Reset() {\n\t*x = ShipOrderResponse{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_demo_proto_msgTypes[16]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *ShipOrderResponse) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ShipOrderResponse) ProtoMessage() {}\n\nfunc (x *ShipOrderResponse) ProtoReflect() protoreflect.Message {\n\tmi := &file_demo_proto_msgTypes[16]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use ShipOrderResponse.ProtoReflect.Descriptor instead.\nfunc (*ShipOrderResponse) Descriptor() ([]byte, []int) {\n\treturn file_demo_proto_rawDescGZIP(), []int{16}\n}\n\nfunc (x *ShipOrderResponse) GetTrackingId() string {\n\tif x != nil {\n\t\treturn x.TrackingId\n\t}\n\treturn \"\"\n}\n\ntype Address struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tStreetAddress string `protobuf:\"bytes,1,opt,name=street_address,json=streetAddress,proto3\" json:\"street_address,omitempty\"`\n\tCity          string `protobuf:\"bytes,2,opt,name=city,proto3\" json:\"city,omitempty\"`\n\tState         string `protobuf:\"bytes,3,opt,name=state,proto3\" json:\"state,omitempty\"`\n\tCountry       string `protobuf:\"bytes,4,opt,name=country,proto3\" json:\"country,omitempty\"`\n\tZipCode       int32  `protobuf:\"varint,5,opt,name=zip_code,json=zipCode,proto3\" json:\"zip_code,omitempty\"`\n}\n\nfunc (x *Address) Reset() {\n\t*x = Address{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_demo_proto_msgTypes[17]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *Address) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Address) ProtoMessage() {}\n\nfunc (x *Address) ProtoReflect() protoreflect.Message {\n\tmi := &file_demo_proto_msgTypes[17]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Address.ProtoReflect.Descriptor instead.\nfunc (*Address) Descriptor() ([]byte, []int) {\n\treturn file_demo_proto_rawDescGZIP(), []int{17}\n}\n\nfunc (x *Address) GetStreetAddress() string {\n\tif x != nil {\n\t\treturn x.StreetAddress\n\t}\n\treturn \"\"\n}\n\nfunc (x *Address) GetCity() string {\n\tif x != nil {\n\t\treturn x.City\n\t}\n\treturn \"\"\n}\n\nfunc (x *Address) GetState() string {\n\tif x != nil {\n\t\treturn x.State\n\t}\n\treturn \"\"\n}\n\nfunc (x *Address) GetCountry() string {\n\tif x != nil {\n\t\treturn x.Country\n\t}\n\treturn \"\"\n}\n\nfunc (x *Address) GetZipCode() int32 {\n\tif x != nil {\n\t\treturn x.ZipCode\n\t}\n\treturn 0\n}\n\n// Represents an amount of money with its currency type.\ntype Money struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// The 3-letter currency code defined in ISO 4217.\n\tCurrencyCode string `protobuf:\"bytes,1,opt,name=currency_code,json=currencyCode,proto3\" json:\"currency_code,omitempty\"`\n\t// The whole units of the amount.\n\t// For example if `currencyCode` is `\"USD\"`, then 1 unit is one US dollar.\n\tUnits int64 `protobuf:\"varint,2,opt,name=units,proto3\" json:\"units,omitempty\"`\n\t// Number of nano (10^-9) units of the amount.\n\t// The value must be between -999,999,999 and +999,999,999 inclusive.\n\t// If `units` is positive, `nanos` must be positive or zero.\n\t// If `units` is zero, `nanos` can be positive, zero, or negative.\n\t// If `units` is negative, `nanos` must be negative or zero.\n\t// For example $-1.75 is represented as `units`=-1 and `nanos`=-750,000,000.\n\tNanos int32 `protobuf:\"varint,3,opt,name=nanos,proto3\" json:\"nanos,omitempty\"`\n}\n\nfunc (x *Money) Reset() {\n\t*x = Money{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_demo_proto_msgTypes[18]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *Money) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Money) ProtoMessage() {}\n\nfunc (x *Money) ProtoReflect() protoreflect.Message {\n\tmi := &file_demo_proto_msgTypes[18]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Money.ProtoReflect.Descriptor instead.\nfunc (*Money) Descriptor() ([]byte, []int) {\n\treturn file_demo_proto_rawDescGZIP(), []int{18}\n}\n\nfunc (x *Money) GetCurrencyCode() string {\n\tif x != nil {\n\t\treturn x.CurrencyCode\n\t}\n\treturn \"\"\n}\n\nfunc (x *Money) GetUnits() int64 {\n\tif x != nil {\n\t\treturn x.Units\n\t}\n\treturn 0\n}\n\nfunc (x *Money) GetNanos() int32 {\n\tif x != nil {\n\t\treturn x.Nanos\n\t}\n\treturn 0\n}\n\ntype GetSupportedCurrenciesResponse struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// The 3-letter currency code defined in ISO 4217.\n\tCurrencyCodes []string `protobuf:\"bytes,1,rep,name=currency_codes,json=currencyCodes,proto3\" json:\"currency_codes,omitempty\"`\n}\n\nfunc (x *GetSupportedCurrenciesResponse) Reset() {\n\t*x = GetSupportedCurrenciesResponse{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_demo_proto_msgTypes[19]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *GetSupportedCurrenciesResponse) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*GetSupportedCurrenciesResponse) ProtoMessage() {}\n\nfunc (x *GetSupportedCurrenciesResponse) ProtoReflect() protoreflect.Message {\n\tmi := &file_demo_proto_msgTypes[19]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use GetSupportedCurrenciesResponse.ProtoReflect.Descriptor instead.\nfunc (*GetSupportedCurrenciesResponse) Descriptor() ([]byte, []int) {\n\treturn file_demo_proto_rawDescGZIP(), []int{19}\n}\n\nfunc (x *GetSupportedCurrenciesResponse) GetCurrencyCodes() []string {\n\tif x != nil {\n\t\treturn x.CurrencyCodes\n\t}\n\treturn nil\n}\n\ntype CurrencyConversionRequest struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tFrom *Money `protobuf:\"bytes,1,opt,name=from,proto3\" json:\"from,omitempty\"`\n\t// The 3-letter currency code defined in ISO 4217.\n\tToCode string `protobuf:\"bytes,2,opt,name=to_code,json=toCode,proto3\" json:\"to_code,omitempty\"`\n}\n\nfunc (x *CurrencyConversionRequest) Reset() {\n\t*x = CurrencyConversionRequest{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_demo_proto_msgTypes[20]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *CurrencyConversionRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*CurrencyConversionRequest) ProtoMessage() {}\n\nfunc (x *CurrencyConversionRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_demo_proto_msgTypes[20]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use CurrencyConversionRequest.ProtoReflect.Descriptor instead.\nfunc (*CurrencyConversionRequest) Descriptor() ([]byte, []int) {\n\treturn file_demo_proto_rawDescGZIP(), []int{20}\n}\n\nfunc (x *CurrencyConversionRequest) GetFrom() *Money {\n\tif x != nil {\n\t\treturn x.From\n\t}\n\treturn nil\n}\n\nfunc (x *CurrencyConversionRequest) GetToCode() string {\n\tif x != nil {\n\t\treturn x.ToCode\n\t}\n\treturn \"\"\n}\n\ntype CreditCardInfo struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tCreditCardNumber          string `protobuf:\"bytes,1,opt,name=credit_card_number,json=creditCardNumber,proto3\" json:\"credit_card_number,omitempty\"`\n\tCreditCardCvv             int32  `protobuf:\"varint,2,opt,name=credit_card_cvv,json=creditCardCvv,proto3\" json:\"credit_card_cvv,omitempty\"`\n\tCreditCardExpirationYear  int32  `protobuf:\"varint,3,opt,name=credit_card_expiration_year,json=creditCardExpirationYear,proto3\" json:\"credit_card_expiration_year,omitempty\"`\n\tCreditCardExpirationMonth int32  `protobuf:\"varint,4,opt,name=credit_card_expiration_month,json=creditCardExpirationMonth,proto3\" json:\"credit_card_expiration_month,omitempty\"`\n}\n\nfunc (x *CreditCardInfo) Reset() {\n\t*x = CreditCardInfo{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_demo_proto_msgTypes[21]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *CreditCardInfo) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*CreditCardInfo) ProtoMessage() {}\n\nfunc (x *CreditCardInfo) ProtoReflect() protoreflect.Message {\n\tmi := &file_demo_proto_msgTypes[21]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use CreditCardInfo.ProtoReflect.Descriptor instead.\nfunc (*CreditCardInfo) Descriptor() ([]byte, []int) {\n\treturn file_demo_proto_rawDescGZIP(), []int{21}\n}\n\nfunc (x *CreditCardInfo) GetCreditCardNumber() string {\n\tif x != nil {\n\t\treturn x.CreditCardNumber\n\t}\n\treturn \"\"\n}\n\nfunc (x *CreditCardInfo) GetCreditCardCvv() int32 {\n\tif x != nil {\n\t\treturn x.CreditCardCvv\n\t}\n\treturn 0\n}\n\nfunc (x *CreditCardInfo) GetCreditCardExpirationYear() int32 {\n\tif x != nil {\n\t\treturn x.CreditCardExpirationYear\n\t}\n\treturn 0\n}\n\nfunc (x *CreditCardInfo) GetCreditCardExpirationMonth() int32 {\n\tif x != nil {\n\t\treturn x.CreditCardExpirationMonth\n\t}\n\treturn 0\n}\n\ntype ChargeRequest struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tAmount     *Money          `protobuf:\"bytes,1,opt,name=amount,proto3\" json:\"amount,omitempty\"`\n\tCreditCard *CreditCardInfo `protobuf:\"bytes,2,opt,name=credit_card,json=creditCard,proto3\" json:\"credit_card,omitempty\"`\n}\n\nfunc (x *ChargeRequest) Reset() {\n\t*x = ChargeRequest{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_demo_proto_msgTypes[22]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *ChargeRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ChargeRequest) ProtoMessage() {}\n\nfunc (x *ChargeRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_demo_proto_msgTypes[22]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use ChargeRequest.ProtoReflect.Descriptor instead.\nfunc (*ChargeRequest) Descriptor() ([]byte, []int) {\n\treturn file_demo_proto_rawDescGZIP(), []int{22}\n}\n\nfunc (x *ChargeRequest) GetAmount() *Money {\n\tif x != nil {\n\t\treturn x.Amount\n\t}\n\treturn nil\n}\n\nfunc (x *ChargeRequest) GetCreditCard() *CreditCardInfo {\n\tif x != nil {\n\t\treturn x.CreditCard\n\t}\n\treturn nil\n}\n\ntype ChargeResponse struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tTransactionId string `protobuf:\"bytes,1,opt,name=transaction_id,json=transactionId,proto3\" json:\"transaction_id,omitempty\"`\n}\n\nfunc (x *ChargeResponse) Reset() {\n\t*x = ChargeResponse{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_demo_proto_msgTypes[23]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *ChargeResponse) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ChargeResponse) ProtoMessage() {}\n\nfunc (x *ChargeResponse) ProtoReflect() protoreflect.Message {\n\tmi := &file_demo_proto_msgTypes[23]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use ChargeResponse.ProtoReflect.Descriptor instead.\nfunc (*ChargeResponse) Descriptor() ([]byte, []int) {\n\treturn file_demo_proto_rawDescGZIP(), []int{23}\n}\n\nfunc (x *ChargeResponse) GetTransactionId() string {\n\tif x != nil {\n\t\treturn x.TransactionId\n\t}\n\treturn \"\"\n}\n\ntype OrderItem struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tItem *CartItem `protobuf:\"bytes,1,opt,name=item,proto3\" json:\"item,omitempty\"`\n\tCost *Money    `protobuf:\"bytes,2,opt,name=cost,proto3\" json:\"cost,omitempty\"`\n}\n\nfunc (x *OrderItem) Reset() {\n\t*x = OrderItem{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_demo_proto_msgTypes[24]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *OrderItem) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*OrderItem) ProtoMessage() {}\n\nfunc (x *OrderItem) ProtoReflect() protoreflect.Message {\n\tmi := &file_demo_proto_msgTypes[24]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use OrderItem.ProtoReflect.Descriptor instead.\nfunc (*OrderItem) Descriptor() ([]byte, []int) {\n\treturn file_demo_proto_rawDescGZIP(), []int{24}\n}\n\nfunc (x *OrderItem) GetItem() *CartItem {\n\tif x != nil {\n\t\treturn x.Item\n\t}\n\treturn nil\n}\n\nfunc (x *OrderItem) GetCost() *Money {\n\tif x != nil {\n\t\treturn x.Cost\n\t}\n\treturn nil\n}\n\ntype OrderResult struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tOrderId            string       `protobuf:\"bytes,1,opt,name=order_id,json=orderId,proto3\" json:\"order_id,omitempty\"`\n\tShippingTrackingId string       `protobuf:\"bytes,2,opt,name=shipping_tracking_id,json=shippingTrackingId,proto3\" json:\"shipping_tracking_id,omitempty\"`\n\tShippingCost       *Money       `protobuf:\"bytes,3,opt,name=shipping_cost,json=shippingCost,proto3\" json:\"shipping_cost,omitempty\"`\n\tShippingAddress    *Address     `protobuf:\"bytes,4,opt,name=shipping_address,json=shippingAddress,proto3\" json:\"shipping_address,omitempty\"`\n\tItems              []*OrderItem `protobuf:\"bytes,5,rep,name=items,proto3\" json:\"items,omitempty\"`\n}\n\nfunc (x *OrderResult) Reset() {\n\t*x = OrderResult{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_demo_proto_msgTypes[25]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *OrderResult) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*OrderResult) ProtoMessage() {}\n\nfunc (x *OrderResult) ProtoReflect() protoreflect.Message {\n\tmi := &file_demo_proto_msgTypes[25]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use OrderResult.ProtoReflect.Descriptor instead.\nfunc (*OrderResult) Descriptor() ([]byte, []int) {\n\treturn file_demo_proto_rawDescGZIP(), []int{25}\n}\n\nfunc (x *OrderResult) GetOrderId() string {\n\tif x != nil {\n\t\treturn x.OrderId\n\t}\n\treturn \"\"\n}\n\nfunc (x *OrderResult) GetShippingTrackingId() string {\n\tif x != nil {\n\t\treturn x.ShippingTrackingId\n\t}\n\treturn \"\"\n}\n\nfunc (x *OrderResult) GetShippingCost() *Money {\n\tif x != nil {\n\t\treturn x.ShippingCost\n\t}\n\treturn nil\n}\n\nfunc (x *OrderResult) GetShippingAddress() *Address {\n\tif x != nil {\n\t\treturn x.ShippingAddress\n\t}\n\treturn nil\n}\n\nfunc (x *OrderResult) GetItems() []*OrderItem {\n\tif x != nil {\n\t\treturn x.Items\n\t}\n\treturn nil\n}\n\ntype SendOrderConfirmationRequest struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tEmail string       `protobuf:\"bytes,1,opt,name=email,proto3\" json:\"email,omitempty\"`\n\tOrder *OrderResult `protobuf:\"bytes,2,opt,name=order,proto3\" json:\"order,omitempty\"`\n}\n\nfunc (x *SendOrderConfirmationRequest) Reset() {\n\t*x = SendOrderConfirmationRequest{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_demo_proto_msgTypes[26]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *SendOrderConfirmationRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*SendOrderConfirmationRequest) ProtoMessage() {}\n\nfunc (x *SendOrderConfirmationRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_demo_proto_msgTypes[26]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use SendOrderConfirmationRequest.ProtoReflect.Descriptor instead.\nfunc (*SendOrderConfirmationRequest) Descriptor() ([]byte, []int) {\n\treturn file_demo_proto_rawDescGZIP(), []int{26}\n}\n\nfunc (x *SendOrderConfirmationRequest) GetEmail() string {\n\tif x != nil {\n\t\treturn x.Email\n\t}\n\treturn \"\"\n}\n\nfunc (x *SendOrderConfirmationRequest) GetOrder() *OrderResult {\n\tif x != nil {\n\t\treturn x.Order\n\t}\n\treturn nil\n}\n\ntype PlaceOrderRequest struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tUserId       string          `protobuf:\"bytes,1,opt,name=user_id,json=userId,proto3\" json:\"user_id,omitempty\"`\n\tUserCurrency string          `protobuf:\"bytes,2,opt,name=user_currency,json=userCurrency,proto3\" json:\"user_currency,omitempty\"`\n\tAddress      *Address        `protobuf:\"bytes,3,opt,name=address,proto3\" json:\"address,omitempty\"`\n\tEmail        string          `protobuf:\"bytes,5,opt,name=email,proto3\" json:\"email,omitempty\"`\n\tCreditCard   *CreditCardInfo `protobuf:\"bytes,6,opt,name=credit_card,json=creditCard,proto3\" json:\"credit_card,omitempty\"`\n}\n\nfunc (x *PlaceOrderRequest) Reset() {\n\t*x = PlaceOrderRequest{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_demo_proto_msgTypes[27]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *PlaceOrderRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*PlaceOrderRequest) ProtoMessage() {}\n\nfunc (x *PlaceOrderRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_demo_proto_msgTypes[27]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use PlaceOrderRequest.ProtoReflect.Descriptor instead.\nfunc (*PlaceOrderRequest) Descriptor() ([]byte, []int) {\n\treturn file_demo_proto_rawDescGZIP(), []int{27}\n}\n\nfunc (x *PlaceOrderRequest) GetUserId() string {\n\tif x != nil {\n\t\treturn x.UserId\n\t}\n\treturn \"\"\n}\n\nfunc (x *PlaceOrderRequest) GetUserCurrency() string {\n\tif x != nil {\n\t\treturn x.UserCurrency\n\t}\n\treturn \"\"\n}\n\nfunc (x *PlaceOrderRequest) GetAddress() *Address {\n\tif x != nil {\n\t\treturn x.Address\n\t}\n\treturn nil\n}\n\nfunc (x *PlaceOrderRequest) GetEmail() string {\n\tif x != nil {\n\t\treturn x.Email\n\t}\n\treturn \"\"\n}\n\nfunc (x *PlaceOrderRequest) GetCreditCard() *CreditCardInfo {\n\tif x != nil {\n\t\treturn x.CreditCard\n\t}\n\treturn nil\n}\n\ntype PlaceOrderResponse struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tOrder *OrderResult `protobuf:\"bytes,1,opt,name=order,proto3\" json:\"order,omitempty\"`\n}\n\nfunc (x *PlaceOrderResponse) Reset() {\n\t*x = PlaceOrderResponse{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_demo_proto_msgTypes[28]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *PlaceOrderResponse) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*PlaceOrderResponse) ProtoMessage() {}\n\nfunc (x *PlaceOrderResponse) ProtoReflect() protoreflect.Message {\n\tmi := &file_demo_proto_msgTypes[28]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use PlaceOrderResponse.ProtoReflect.Descriptor instead.\nfunc (*PlaceOrderResponse) Descriptor() ([]byte, []int) {\n\treturn file_demo_proto_rawDescGZIP(), []int{28}\n}\n\nfunc (x *PlaceOrderResponse) GetOrder() *OrderResult {\n\tif x != nil {\n\t\treturn x.Order\n\t}\n\treturn nil\n}\n\ntype AdRequest struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// List of important key words from the current page describing the context.\n\tContextKeys []string `protobuf:\"bytes,1,rep,name=context_keys,json=contextKeys,proto3\" json:\"context_keys,omitempty\"`\n}\n\nfunc (x *AdRequest) Reset() {\n\t*x = AdRequest{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_demo_proto_msgTypes[29]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *AdRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*AdRequest) ProtoMessage() {}\n\nfunc (x *AdRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_demo_proto_msgTypes[29]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use AdRequest.ProtoReflect.Descriptor instead.\nfunc (*AdRequest) Descriptor() ([]byte, []int) {\n\treturn file_demo_proto_rawDescGZIP(), []int{29}\n}\n\nfunc (x *AdRequest) GetContextKeys() []string {\n\tif x != nil {\n\t\treturn x.ContextKeys\n\t}\n\treturn nil\n}\n\ntype AdResponse struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tAds []*Ad `protobuf:\"bytes,1,rep,name=ads,proto3\" json:\"ads,omitempty\"`\n}\n\nfunc (x *AdResponse) Reset() {\n\t*x = AdResponse{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_demo_proto_msgTypes[30]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *AdResponse) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*AdResponse) ProtoMessage() {}\n\nfunc (x *AdResponse) ProtoReflect() protoreflect.Message {\n\tmi := &file_demo_proto_msgTypes[30]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use AdResponse.ProtoReflect.Descriptor instead.\nfunc (*AdResponse) Descriptor() ([]byte, []int) {\n\treturn file_demo_proto_rawDescGZIP(), []int{30}\n}\n\nfunc (x *AdResponse) GetAds() []*Ad {\n\tif x != nil {\n\t\treturn x.Ads\n\t}\n\treturn nil\n}\n\ntype Ad struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// url to redirect to when an ad is clicked.\n\tRedirectUrl string `protobuf:\"bytes,1,opt,name=redirect_url,json=redirectUrl,proto3\" json:\"redirect_url,omitempty\"`\n\t// short advertisement text to display.\n\tText string `protobuf:\"bytes,2,opt,name=text,proto3\" json:\"text,omitempty\"`\n}\n\nfunc (x *Ad) Reset() {\n\t*x = Ad{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_demo_proto_msgTypes[31]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *Ad) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Ad) ProtoMessage() {}\n\nfunc (x *Ad) ProtoReflect() protoreflect.Message {\n\tmi := &file_demo_proto_msgTypes[31]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Ad.ProtoReflect.Descriptor instead.\nfunc (*Ad) Descriptor() ([]byte, []int) {\n\treturn file_demo_proto_rawDescGZIP(), []int{31}\n}\n\nfunc (x *Ad) GetRedirectUrl() string {\n\tif x != nil {\n\t\treturn x.RedirectUrl\n\t}\n\treturn \"\"\n}\n\nfunc (x *Ad) GetText() string {\n\tif x != nil {\n\t\treturn x.Text\n\t}\n\treturn \"\"\n}\n\nvar File_demo_proto protoreflect.FileDescriptor\n\nvar file_demo_proto_rawDesc = []byte{\n\t0x0a, 0x0a, 0x64, 0x65, 0x6d, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0b, 0x68, 0x69,\n\t0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x22, 0x45, 0x0a, 0x08, 0x43, 0x61, 0x72,\n\t0x74, 0x49, 0x74, 0x65, 0x6d, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74,\n\t0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x70, 0x72, 0x6f, 0x64, 0x75,\n\t0x63, 0x74, 0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x71, 0x75, 0x61, 0x6e, 0x74, 0x69, 0x74, 0x79,\n\t0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x71, 0x75, 0x61, 0x6e, 0x74, 0x69, 0x74, 0x79,\n\t0x22, 0x54, 0x0a, 0x0e, 0x41, 0x64, 0x64, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65,\n\t0x73, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20,\n\t0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x29, 0x0a, 0x04, 0x69,\n\t0x74, 0x65, 0x6d, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x68, 0x69, 0x70, 0x73,\n\t0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x43, 0x61, 0x72, 0x74, 0x49, 0x74, 0x65, 0x6d,\n\t0x52, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x22, 0x2b, 0x0a, 0x10, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x43,\n\t0x61, 0x72, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73,\n\t0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65,\n\t0x72, 0x49, 0x64, 0x22, 0x29, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x43, 0x61, 0x72, 0x74, 0x52, 0x65,\n\t0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64,\n\t0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0x4c,\n\t0x0a, 0x04, 0x43, 0x61, 0x72, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69,\n\t0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12,\n\t0x2b, 0x0a, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15,\n\t0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x43, 0x61, 0x72,\n\t0x74, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x22, 0x07, 0x0a, 0x05,\n\t0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x56, 0x0a, 0x1a, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x63,\n\t0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75,\n\t0x65, 0x73, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01,\n\t0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b,\n\t0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28,\n\t0x09, 0x52, 0x0a, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x49, 0x64, 0x73, 0x22, 0x3e, 0x0a,\n\t0x1b, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x64, 0x61, 0x74,\n\t0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1f, 0x0a, 0x0b,\n\t0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28,\n\t0x09, 0x52, 0x0a, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x49, 0x64, 0x73, 0x22, 0xba, 0x01,\n\t0x0a, 0x07, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18,\n\t0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d,\n\t0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x20, 0x0a,\n\t0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01,\n\t0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12,\n\t0x18, 0x0a, 0x07, 0x70, 0x69, 0x63, 0x74, 0x75, 0x72, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09,\n\t0x52, 0x07, 0x70, 0x69, 0x63, 0x74, 0x75, 0x72, 0x65, 0x12, 0x2f, 0x0a, 0x09, 0x70, 0x72, 0x69,\n\t0x63, 0x65, 0x5f, 0x75, 0x73, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x68,\n\t0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x4d, 0x6f, 0x6e, 0x65, 0x79,\n\t0x52, 0x08, 0x70, 0x72, 0x69, 0x63, 0x65, 0x55, 0x73, 0x64, 0x12, 0x1e, 0x0a, 0x0a, 0x63, 0x61,\n\t0x74, 0x65, 0x67, 0x6f, 0x72, 0x69, 0x65, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a,\n\t0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x69, 0x65, 0x73, 0x22, 0x48, 0x0a, 0x14, 0x4c, 0x69,\n\t0x73, 0x74, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,\n\t0x73, 0x65, 0x12, 0x30, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x73, 0x18, 0x01,\n\t0x20, 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68,\n\t0x6f, 0x70, 0x2e, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x64,\n\t0x75, 0x63, 0x74, 0x73, 0x22, 0x23, 0x0a, 0x11, 0x47, 0x65, 0x74, 0x50, 0x72, 0x6f, 0x64, 0x75,\n\t0x63, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18,\n\t0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x22, 0x2d, 0x0a, 0x15, 0x53, 0x65, 0x61,\n\t0x72, 0x63, 0x68, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65,\n\t0x73, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28,\n\t0x09, 0x52, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x22, 0x48, 0x0a, 0x16, 0x53, 0x65, 0x61, 0x72,\n\t0x63, 0x68, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,\n\t0x73, 0x65, 0x12, 0x2e, 0x0a, 0x07, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x18, 0x01, 0x20,\n\t0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f,\n\t0x70, 0x2e, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x52, 0x07, 0x72, 0x65, 0x73, 0x75, 0x6c,\n\t0x74, 0x73, 0x22, 0x6e, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x51, 0x75, 0x6f, 0x74, 0x65, 0x52, 0x65,\n\t0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73,\n\t0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72,\n\t0x73, 0x68, 0x6f, 0x70, 0x2e, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, 0x07, 0x61, 0x64,\n\t0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x2b, 0x0a, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x18, 0x02,\n\t0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68,\n\t0x6f, 0x70, 0x2e, 0x43, 0x61, 0x72, 0x74, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x05, 0x69, 0x74, 0x65,\n\t0x6d, 0x73, 0x22, 0x41, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x51, 0x75, 0x6f, 0x74, 0x65, 0x52, 0x65,\n\t0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2d, 0x0a, 0x08, 0x63, 0x6f, 0x73, 0x74, 0x5f, 0x75,\n\t0x73, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74,\n\t0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x4d, 0x6f, 0x6e, 0x65, 0x79, 0x52, 0x07, 0x63, 0x6f,\n\t0x73, 0x74, 0x55, 0x73, 0x64, 0x22, 0x6f, 0x0a, 0x10, 0x53, 0x68, 0x69, 0x70, 0x4f, 0x72, 0x64,\n\t0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x07, 0x61, 0x64, 0x64,\n\t0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x68, 0x69, 0x70,\n\t0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73,\n\t0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x2b, 0x0a, 0x05, 0x69, 0x74, 0x65,\n\t0x6d, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74,\n\t0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x43, 0x61, 0x72, 0x74, 0x49, 0x74, 0x65, 0x6d, 0x52,\n\t0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x22, 0x34, 0x0a, 0x11, 0x53, 0x68, 0x69, 0x70, 0x4f, 0x72,\n\t0x64, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x74,\n\t0x72, 0x61, 0x63, 0x6b, 0x69, 0x6e, 0x67, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,\n\t0x52, 0x0a, 0x74, 0x72, 0x61, 0x63, 0x6b, 0x69, 0x6e, 0x67, 0x49, 0x64, 0x22, 0x8f, 0x01, 0x0a,\n\t0x07, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x25, 0x0a, 0x0e, 0x73, 0x74, 0x72, 0x65,\n\t0x65, 0x74, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,\n\t0x52, 0x0d, 0x73, 0x74, 0x72, 0x65, 0x65, 0x74, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12,\n\t0x12, 0x0a, 0x04, 0x63, 0x69, 0x74, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x63,\n\t0x69, 0x74, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01,\n\t0x28, 0x09, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x75,\n\t0x6e, 0x74, 0x72, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x6f, 0x75, 0x6e,\n\t0x74, 0x72, 0x79, 0x12, 0x19, 0x0a, 0x08, 0x7a, 0x69, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18,\n\t0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x07, 0x7a, 0x69, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x22, 0x58,\n\t0x0a, 0x05, 0x4d, 0x6f, 0x6e, 0x65, 0x79, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x75, 0x72, 0x72, 0x65,\n\t0x6e, 0x63, 0x79, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c,\n\t0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x14, 0x0a, 0x05,\n\t0x75, 0x6e, 0x69, 0x74, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x75, 0x6e, 0x69,\n\t0x74, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x6e, 0x61, 0x6e, 0x6f, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28,\n\t0x05, 0x52, 0x05, 0x6e, 0x61, 0x6e, 0x6f, 0x73, 0x22, 0x47, 0x0a, 0x1e, 0x47, 0x65, 0x74, 0x53,\n\t0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x69,\n\t0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x63, 0x75,\n\t0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03,\n\t0x28, 0x09, 0x52, 0x0d, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x43, 0x6f, 0x64, 0x65,\n\t0x73, 0x22, 0x5c, 0x0a, 0x19, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x43, 0x6f, 0x6e,\n\t0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x26,\n\t0x0a, 0x04, 0x66, 0x72, 0x6f, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x68,\n\t0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x4d, 0x6f, 0x6e, 0x65, 0x79,\n\t0x52, 0x04, 0x66, 0x72, 0x6f, 0x6d, 0x12, 0x17, 0x0a, 0x07, 0x74, 0x6f, 0x5f, 0x63, 0x6f, 0x64,\n\t0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x74, 0x6f, 0x43, 0x6f, 0x64, 0x65, 0x22,\n\t0xe6, 0x01, 0x0a, 0x0e, 0x43, 0x72, 0x65, 0x64, 0x69, 0x74, 0x43, 0x61, 0x72, 0x64, 0x49, 0x6e,\n\t0x66, 0x6f, 0x12, 0x2c, 0x0a, 0x12, 0x63, 0x72, 0x65, 0x64, 0x69, 0x74, 0x5f, 0x63, 0x61, 0x72,\n\t0x64, 0x5f, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10,\n\t0x63, 0x72, 0x65, 0x64, 0x69, 0x74, 0x43, 0x61, 0x72, 0x64, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72,\n\t0x12, 0x26, 0x0a, 0x0f, 0x63, 0x72, 0x65, 0x64, 0x69, 0x74, 0x5f, 0x63, 0x61, 0x72, 0x64, 0x5f,\n\t0x63, 0x76, 0x76, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0d, 0x63, 0x72, 0x65, 0x64, 0x69,\n\t0x74, 0x43, 0x61, 0x72, 0x64, 0x43, 0x76, 0x76, 0x12, 0x3d, 0x0a, 0x1b, 0x63, 0x72, 0x65, 0x64,\n\t0x69, 0x74, 0x5f, 0x63, 0x61, 0x72, 0x64, 0x5f, 0x65, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69,\n\t0x6f, 0x6e, 0x5f, 0x79, 0x65, 0x61, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x18, 0x63,\n\t0x72, 0x65, 0x64, 0x69, 0x74, 0x43, 0x61, 0x72, 0x64, 0x45, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74,\n\t0x69, 0x6f, 0x6e, 0x59, 0x65, 0x61, 0x72, 0x12, 0x3f, 0x0a, 0x1c, 0x63, 0x72, 0x65, 0x64, 0x69,\n\t0x74, 0x5f, 0x63, 0x61, 0x72, 0x64, 0x5f, 0x65, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f,\n\t0x6e, 0x5f, 0x6d, 0x6f, 0x6e, 0x74, 0x68, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x19, 0x63,\n\t0x72, 0x65, 0x64, 0x69, 0x74, 0x43, 0x61, 0x72, 0x64, 0x45, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74,\n\t0x69, 0x6f, 0x6e, 0x4d, 0x6f, 0x6e, 0x74, 0x68, 0x22, 0x79, 0x0a, 0x0d, 0x43, 0x68, 0x61, 0x72,\n\t0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2a, 0x0a, 0x06, 0x61, 0x6d, 0x6f,\n\t0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x68, 0x69, 0x70, 0x73,\n\t0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x4d, 0x6f, 0x6e, 0x65, 0x79, 0x52, 0x06, 0x61,\n\t0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x3c, 0x0a, 0x0b, 0x63, 0x72, 0x65, 0x64, 0x69, 0x74, 0x5f,\n\t0x63, 0x61, 0x72, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x68, 0x69, 0x70,\n\t0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x43, 0x72, 0x65, 0x64, 0x69, 0x74, 0x43,\n\t0x61, 0x72, 0x64, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x0a, 0x63, 0x72, 0x65, 0x64, 0x69, 0x74, 0x43,\n\t0x61, 0x72, 0x64, 0x22, 0x37, 0x0a, 0x0e, 0x43, 0x68, 0x61, 0x72, 0x67, 0x65, 0x52, 0x65, 0x73,\n\t0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63,\n\t0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x74,\n\t0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x22, 0x5e, 0x0a, 0x09,\n\t0x4f, 0x72, 0x64, 0x65, 0x72, 0x49, 0x74, 0x65, 0x6d, 0x12, 0x29, 0x0a, 0x04, 0x69, 0x74, 0x65,\n\t0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65,\n\t0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x43, 0x61, 0x72, 0x74, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x04,\n\t0x69, 0x74, 0x65, 0x6d, 0x12, 0x26, 0x0a, 0x04, 0x63, 0x6f, 0x73, 0x74, 0x18, 0x02, 0x20, 0x01,\n\t0x28, 0x0b, 0x32, 0x12, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70,\n\t0x2e, 0x4d, 0x6f, 0x6e, 0x65, 0x79, 0x52, 0x04, 0x63, 0x6f, 0x73, 0x74, 0x22, 0x82, 0x02, 0x0a,\n\t0x0b, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x19, 0x0a, 0x08,\n\t0x6f, 0x72, 0x64, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07,\n\t0x6f, 0x72, 0x64, 0x65, 0x72, 0x49, 0x64, 0x12, 0x30, 0x0a, 0x14, 0x73, 0x68, 0x69, 0x70, 0x70,\n\t0x69, 0x6e, 0x67, 0x5f, 0x74, 0x72, 0x61, 0x63, 0x6b, 0x69, 0x6e, 0x67, 0x5f, 0x69, 0x64, 0x18,\n\t0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x12, 0x73, 0x68, 0x69, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x54,\n\t0x72, 0x61, 0x63, 0x6b, 0x69, 0x6e, 0x67, 0x49, 0x64, 0x12, 0x37, 0x0a, 0x0d, 0x73, 0x68, 0x69,\n\t0x70, 0x70, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x6f, 0x73, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b,\n\t0x32, 0x12, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x4d,\n\t0x6f, 0x6e, 0x65, 0x79, 0x52, 0x0c, 0x73, 0x68, 0x69, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x43, 0x6f,\n\t0x73, 0x74, 0x12, 0x3f, 0x0a, 0x10, 0x73, 0x68, 0x69, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x5f, 0x61,\n\t0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x68,\n\t0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x41, 0x64, 0x64, 0x72, 0x65,\n\t0x73, 0x73, 0x52, 0x0f, 0x73, 0x68, 0x69, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x41, 0x64, 0x64, 0x72,\n\t0x65, 0x73, 0x73, 0x12, 0x2c, 0x0a, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x18, 0x05, 0x20, 0x03,\n\t0x28, 0x0b, 0x32, 0x16, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70,\n\t0x2e, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d,\n\t0x73, 0x22, 0x64, 0x0a, 0x1c, 0x53, 0x65, 0x6e, 0x64, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x43, 0x6f,\n\t0x6e, 0x66, 0x69, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,\n\t0x74, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,\n\t0x52, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x12, 0x2e, 0x0a, 0x05, 0x6f, 0x72, 0x64, 0x65, 0x72,\n\t0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72,\n\t0x73, 0x68, 0x6f, 0x70, 0x2e, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74,\n\t0x52, 0x05, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x22, 0xd5, 0x01, 0x0a, 0x11, 0x50, 0x6c, 0x61, 0x63,\n\t0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x17, 0x0a,\n\t0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06,\n\t0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x63,\n\t0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x75,\n\t0x73, 0x65, 0x72, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x12, 0x2e, 0x0a, 0x07, 0x61,\n\t0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x68,\n\t0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x41, 0x64, 0x64, 0x72, 0x65,\n\t0x73, 0x73, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x65,\n\t0x6d, 0x61, 0x69, 0x6c, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x6d, 0x61, 0x69,\n\t0x6c, 0x12, 0x3c, 0x0a, 0x0b, 0x63, 0x72, 0x65, 0x64, 0x69, 0x74, 0x5f, 0x63, 0x61, 0x72, 0x64,\n\t0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72,\n\t0x73, 0x68, 0x6f, 0x70, 0x2e, 0x43, 0x72, 0x65, 0x64, 0x69, 0x74, 0x43, 0x61, 0x72, 0x64, 0x49,\n\t0x6e, 0x66, 0x6f, 0x52, 0x0a, 0x63, 0x72, 0x65, 0x64, 0x69, 0x74, 0x43, 0x61, 0x72, 0x64, 0x22,\n\t0x44, 0x0a, 0x12, 0x50, 0x6c, 0x61, 0x63, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x73,\n\t0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2e, 0x0a, 0x05, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x18, 0x01,\n\t0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68,\n\t0x6f, 0x70, 0x2e, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x05,\n\t0x6f, 0x72, 0x64, 0x65, 0x72, 0x22, 0x2e, 0x0a, 0x09, 0x41, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65,\n\t0x73, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x5f, 0x6b, 0x65,\n\t0x79, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78,\n\t0x74, 0x4b, 0x65, 0x79, 0x73, 0x22, 0x2f, 0x0a, 0x0a, 0x41, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f,\n\t0x6e, 0x73, 0x65, 0x12, 0x21, 0x0a, 0x03, 0x61, 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b,\n\t0x32, 0x0f, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x41,\n\t0x64, 0x52, 0x03, 0x61, 0x64, 0x73, 0x22, 0x3b, 0x0a, 0x02, 0x41, 0x64, 0x12, 0x21, 0x0a, 0x0c,\n\t0x72, 0x65, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01,\n\t0x28, 0x09, 0x52, 0x0b, 0x72, 0x65, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x55, 0x72, 0x6c, 0x12,\n\t0x12, 0x0a, 0x04, 0x74, 0x65, 0x78, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74,\n\t0x65, 0x78, 0x74, 0x32, 0xca, 0x01, 0x0a, 0x0b, 0x43, 0x61, 0x72, 0x74, 0x53, 0x65, 0x72, 0x76,\n\t0x69, 0x63, 0x65, 0x12, 0x3c, 0x0a, 0x07, 0x41, 0x64, 0x64, 0x49, 0x74, 0x65, 0x6d, 0x12, 0x1b,\n\t0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x41, 0x64, 0x64,\n\t0x49, 0x74, 0x65, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x68, 0x69,\n\t0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22,\n\t0x00, 0x12, 0x3b, 0x0a, 0x07, 0x47, 0x65, 0x74, 0x43, 0x61, 0x72, 0x74, 0x12, 0x1b, 0x2e, 0x68,\n\t0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x61,\n\t0x72, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x11, 0x2e, 0x68, 0x69, 0x70, 0x73,\n\t0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x43, 0x61, 0x72, 0x74, 0x22, 0x00, 0x12, 0x40,\n\t0x0a, 0x09, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x43, 0x61, 0x72, 0x74, 0x12, 0x1d, 0x2e, 0x68, 0x69,\n\t0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x43,\n\t0x61, 0x72, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x68, 0x69, 0x70,\n\t0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00,\n\t0x32, 0x83, 0x01, 0x0a, 0x15, 0x52, 0x65, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x64, 0x61, 0x74,\n\t0x69, 0x6f, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x6a, 0x0a, 0x13, 0x4c, 0x69,\n\t0x73, 0x74, 0x52, 0x65, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e,\n\t0x73, 0x12, 0x27, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e,\n\t0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x64, 0x61, 0x74, 0x69,\n\t0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, 0x68, 0x69, 0x70,\n\t0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x63,\n\t0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70,\n\t0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x32, 0x83, 0x02, 0x0a, 0x15, 0x50, 0x72, 0x6f, 0x64, 0x75,\n\t0x63, 0x74, 0x43, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65,\n\t0x12, 0x47, 0x0a, 0x0c, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x73,\n\t0x12, 0x12, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x45,\n\t0x6d, 0x70, 0x74, 0x79, 0x1a, 0x21, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68,\n\t0x6f, 0x70, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x73, 0x52,\n\t0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x44, 0x0a, 0x0a, 0x47, 0x65, 0x74,\n\t0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x12, 0x1e, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65,\n\t0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x47, 0x65, 0x74, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74,\n\t0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65,\n\t0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x22, 0x00, 0x12,\n\t0x5b, 0x0a, 0x0e, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74,\n\t0x73, 0x12, 0x22, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e,\n\t0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x73, 0x52, 0x65,\n\t0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73,\n\t0x68, 0x6f, 0x70, 0x2e, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63,\n\t0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x32, 0xaa, 0x01, 0x0a,\n\t0x0f, 0x53, 0x68, 0x69, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65,\n\t0x12, 0x49, 0x0a, 0x08, 0x47, 0x65, 0x74, 0x51, 0x75, 0x6f, 0x74, 0x65, 0x12, 0x1c, 0x2e, 0x68,\n\t0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x47, 0x65, 0x74, 0x51, 0x75,\n\t0x6f, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x68, 0x69, 0x70,\n\t0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x47, 0x65, 0x74, 0x51, 0x75, 0x6f, 0x74,\n\t0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x4c, 0x0a, 0x09, 0x53,\n\t0x68, 0x69, 0x70, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x1d, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74,\n\t0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x53, 0x68, 0x69, 0x70, 0x4f, 0x72, 0x64, 0x65, 0x72,\n\t0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65,\n\t0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x53, 0x68, 0x69, 0x70, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52,\n\t0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x32, 0xb7, 0x01, 0x0a, 0x0f, 0x43, 0x75,\n\t0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x5b, 0x0a,\n\t0x16, 0x47, 0x65, 0x74, 0x53, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x43, 0x75, 0x72,\n\t0x72, 0x65, 0x6e, 0x63, 0x69, 0x65, 0x73, 0x12, 0x12, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65,\n\t0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x2b, 0x2e, 0x68, 0x69,\n\t0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x75, 0x70,\n\t0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x69, 0x65, 0x73,\n\t0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x47, 0x0a, 0x07, 0x43, 0x6f,\n\t0x6e, 0x76, 0x65, 0x72, 0x74, 0x12, 0x26, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73,\n\t0x68, 0x6f, 0x70, 0x2e, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x43, 0x6f, 0x6e, 0x76,\n\t0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e,\n\t0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x4d, 0x6f, 0x6e, 0x65,\n\t0x79, 0x22, 0x00, 0x32, 0x55, 0x0a, 0x0e, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x65,\n\t0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x43, 0x0a, 0x06, 0x43, 0x68, 0x61, 0x72, 0x67, 0x65, 0x12,\n\t0x1a, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x43, 0x68,\n\t0x61, 0x72, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x68, 0x69,\n\t0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x43, 0x68, 0x61, 0x72, 0x67, 0x65,\n\t0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x32, 0x68, 0x0a, 0x0c, 0x45, 0x6d,\n\t0x61, 0x69, 0x6c, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x58, 0x0a, 0x15, 0x53, 0x65,\n\t0x6e, 0x64, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x61, 0x74,\n\t0x69, 0x6f, 0x6e, 0x12, 0x29, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f,\n\t0x70, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69,\n\t0x72, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12,\n\t0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x45, 0x6d, 0x70,\n\t0x74, 0x79, 0x22, 0x00, 0x32, 0x62, 0x0a, 0x0f, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x6f, 0x75, 0x74,\n\t0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x4f, 0x0a, 0x0a, 0x50, 0x6c, 0x61, 0x63, 0x65,\n\t0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x1e, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73,\n\t0x68, 0x6f, 0x70, 0x2e, 0x50, 0x6c, 0x61, 0x63, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65,\n\t0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73,\n\t0x68, 0x6f, 0x70, 0x2e, 0x50, 0x6c, 0x61, 0x63, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65,\n\t0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x32, 0x48, 0x0a, 0x09, 0x41, 0x64, 0x53, 0x65,\n\t0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x3b, 0x0a, 0x06, 0x47, 0x65, 0x74, 0x41, 0x64, 0x73, 0x12,\n\t0x16, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x41, 0x64,\n\t0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65,\n\t0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x41, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,\n\t0x22, 0x00, 0x42, 0x40, 0x5a, 0x3e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d,\n\t0x2f, 0x47, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x43, 0x6c, 0x6f, 0x75, 0x64, 0x50, 0x6c, 0x61, 0x74,\n\t0x66, 0x6f, 0x72, 0x6d, 0x2f, 0x6d, 0x69, 0x73, 0x63, 0x72, 0x6f, 0x73, 0x65, 0x72, 0x76, 0x69,\n\t0x63, 0x65, 0x73, 0x2d, 0x64, 0x65, 0x6d, 0x6f, 0x2f, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72,\n\t0x73, 0x68, 0x6f, 0x70, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,\n}\n\nvar (\n\tfile_demo_proto_rawDescOnce sync.Once\n\tfile_demo_proto_rawDescData = file_demo_proto_rawDesc\n)\n\nfunc file_demo_proto_rawDescGZIP() []byte {\n\tfile_demo_proto_rawDescOnce.Do(func() {\n\t\tfile_demo_proto_rawDescData = protoimpl.X.CompressGZIP(file_demo_proto_rawDescData)\n\t})\n\treturn file_demo_proto_rawDescData\n}\n\nvar file_demo_proto_msgTypes = make([]protoimpl.MessageInfo, 32)\nvar file_demo_proto_goTypes = []any{\n\t(*CartItem)(nil),                       // 0: hipstershop.CartItem\n\t(*AddItemRequest)(nil),                 // 1: hipstershop.AddItemRequest\n\t(*EmptyCartRequest)(nil),               // 2: hipstershop.EmptyCartRequest\n\t(*GetCartRequest)(nil),                 // 3: hipstershop.GetCartRequest\n\t(*Cart)(nil),                           // 4: hipstershop.Cart\n\t(*Empty)(nil),                          // 5: hipstershop.Empty\n\t(*ListRecommendationsRequest)(nil),     // 6: hipstershop.ListRecommendationsRequest\n\t(*ListRecommendationsResponse)(nil),    // 7: hipstershop.ListRecommendationsResponse\n\t(*Product)(nil),                        // 8: hipstershop.Product\n\t(*ListProductsResponse)(nil),           // 9: hipstershop.ListProductsResponse\n\t(*GetProductRequest)(nil),              // 10: hipstershop.GetProductRequest\n\t(*SearchProductsRequest)(nil),          // 11: hipstershop.SearchProductsRequest\n\t(*SearchProductsResponse)(nil),         // 12: hipstershop.SearchProductsResponse\n\t(*GetQuoteRequest)(nil),                // 13: hipstershop.GetQuoteRequest\n\t(*GetQuoteResponse)(nil),               // 14: hipstershop.GetQuoteResponse\n\t(*ShipOrderRequest)(nil),               // 15: hipstershop.ShipOrderRequest\n\t(*ShipOrderResponse)(nil),              // 16: hipstershop.ShipOrderResponse\n\t(*Address)(nil),                        // 17: hipstershop.Address\n\t(*Money)(nil),                          // 18: hipstershop.Money\n\t(*GetSupportedCurrenciesResponse)(nil), // 19: hipstershop.GetSupportedCurrenciesResponse\n\t(*CurrencyConversionRequest)(nil),      // 20: hipstershop.CurrencyConversionRequest\n\t(*CreditCardInfo)(nil),                 // 21: hipstershop.CreditCardInfo\n\t(*ChargeRequest)(nil),                  // 22: hipstershop.ChargeRequest\n\t(*ChargeResponse)(nil),                 // 23: hipstershop.ChargeResponse\n\t(*OrderItem)(nil),                      // 24: hipstershop.OrderItem\n\t(*OrderResult)(nil),                    // 25: hipstershop.OrderResult\n\t(*SendOrderConfirmationRequest)(nil),   // 26: hipstershop.SendOrderConfirmationRequest\n\t(*PlaceOrderRequest)(nil),              // 27: hipstershop.PlaceOrderRequest\n\t(*PlaceOrderResponse)(nil),             // 28: hipstershop.PlaceOrderResponse\n\t(*AdRequest)(nil),                      // 29: hipstershop.AdRequest\n\t(*AdResponse)(nil),                     // 30: hipstershop.AdResponse\n\t(*Ad)(nil),                             // 31: hipstershop.Ad\n}\nvar file_demo_proto_depIdxs = []int32{\n\t0,  // 0: hipstershop.AddItemRequest.item:type_name -> hipstershop.CartItem\n\t0,  // 1: hipstershop.Cart.items:type_name -> hipstershop.CartItem\n\t18, // 2: hipstershop.Product.price_usd:type_name -> hipstershop.Money\n\t8,  // 3: hipstershop.ListProductsResponse.products:type_name -> hipstershop.Product\n\t8,  // 4: hipstershop.SearchProductsResponse.results:type_name -> hipstershop.Product\n\t17, // 5: hipstershop.GetQuoteRequest.address:type_name -> hipstershop.Address\n\t0,  // 6: hipstershop.GetQuoteRequest.items:type_name -> hipstershop.CartItem\n\t18, // 7: hipstershop.GetQuoteResponse.cost_usd:type_name -> hipstershop.Money\n\t17, // 8: hipstershop.ShipOrderRequest.address:type_name -> hipstershop.Address\n\t0,  // 9: hipstershop.ShipOrderRequest.items:type_name -> hipstershop.CartItem\n\t18, // 10: hipstershop.CurrencyConversionRequest.from:type_name -> hipstershop.Money\n\t18, // 11: hipstershop.ChargeRequest.amount:type_name -> hipstershop.Money\n\t21, // 12: hipstershop.ChargeRequest.credit_card:type_name -> hipstershop.CreditCardInfo\n\t0,  // 13: hipstershop.OrderItem.item:type_name -> hipstershop.CartItem\n\t18, // 14: hipstershop.OrderItem.cost:type_name -> hipstershop.Money\n\t18, // 15: hipstershop.OrderResult.shipping_cost:type_name -> hipstershop.Money\n\t17, // 16: hipstershop.OrderResult.shipping_address:type_name -> hipstershop.Address\n\t24, // 17: hipstershop.OrderResult.items:type_name -> hipstershop.OrderItem\n\t25, // 18: hipstershop.SendOrderConfirmationRequest.order:type_name -> hipstershop.OrderResult\n\t17, // 19: hipstershop.PlaceOrderRequest.address:type_name -> hipstershop.Address\n\t21, // 20: hipstershop.PlaceOrderRequest.credit_card:type_name -> hipstershop.CreditCardInfo\n\t25, // 21: hipstershop.PlaceOrderResponse.order:type_name -> hipstershop.OrderResult\n\t31, // 22: hipstershop.AdResponse.ads:type_name -> hipstershop.Ad\n\t1,  // 23: hipstershop.CartService.AddItem:input_type -> hipstershop.AddItemRequest\n\t3,  // 24: hipstershop.CartService.GetCart:input_type -> hipstershop.GetCartRequest\n\t2,  // 25: hipstershop.CartService.EmptyCart:input_type -> hipstershop.EmptyCartRequest\n\t6,  // 26: hipstershop.RecommendationService.ListRecommendations:input_type -> hipstershop.ListRecommendationsRequest\n\t5,  // 27: hipstershop.ProductCatalogService.ListProducts:input_type -> hipstershop.Empty\n\t10, // 28: hipstershop.ProductCatalogService.GetProduct:input_type -> hipstershop.GetProductRequest\n\t11, // 29: hipstershop.ProductCatalogService.SearchProducts:input_type -> hipstershop.SearchProductsRequest\n\t13, // 30: hipstershop.ShippingService.GetQuote:input_type -> hipstershop.GetQuoteRequest\n\t15, // 31: hipstershop.ShippingService.ShipOrder:input_type -> hipstershop.ShipOrderRequest\n\t5,  // 32: hipstershop.CurrencyService.GetSupportedCurrencies:input_type -> hipstershop.Empty\n\t20, // 33: hipstershop.CurrencyService.Convert:input_type -> hipstershop.CurrencyConversionRequest\n\t22, // 34: hipstershop.PaymentService.Charge:input_type -> hipstershop.ChargeRequest\n\t26, // 35: hipstershop.EmailService.SendOrderConfirmation:input_type -> hipstershop.SendOrderConfirmationRequest\n\t27, // 36: hipstershop.CheckoutService.PlaceOrder:input_type -> hipstershop.PlaceOrderRequest\n\t29, // 37: hipstershop.AdService.GetAds:input_type -> hipstershop.AdRequest\n\t5,  // 38: hipstershop.CartService.AddItem:output_type -> hipstershop.Empty\n\t4,  // 39: hipstershop.CartService.GetCart:output_type -> hipstershop.Cart\n\t5,  // 40: hipstershop.CartService.EmptyCart:output_type -> hipstershop.Empty\n\t7,  // 41: hipstershop.RecommendationService.ListRecommendations:output_type -> hipstershop.ListRecommendationsResponse\n\t9,  // 42: hipstershop.ProductCatalogService.ListProducts:output_type -> hipstershop.ListProductsResponse\n\t8,  // 43: hipstershop.ProductCatalogService.GetProduct:output_type -> hipstershop.Product\n\t12, // 44: hipstershop.ProductCatalogService.SearchProducts:output_type -> hipstershop.SearchProductsResponse\n\t14, // 45: hipstershop.ShippingService.GetQuote:output_type -> hipstershop.GetQuoteResponse\n\t16, // 46: hipstershop.ShippingService.ShipOrder:output_type -> hipstershop.ShipOrderResponse\n\t19, // 47: hipstershop.CurrencyService.GetSupportedCurrencies:output_type -> hipstershop.GetSupportedCurrenciesResponse\n\t18, // 48: hipstershop.CurrencyService.Convert:output_type -> hipstershop.Money\n\t23, // 49: hipstershop.PaymentService.Charge:output_type -> hipstershop.ChargeResponse\n\t5,  // 50: hipstershop.EmailService.SendOrderConfirmation:output_type -> hipstershop.Empty\n\t28, // 51: hipstershop.CheckoutService.PlaceOrder:output_type -> hipstershop.PlaceOrderResponse\n\t30, // 52: hipstershop.AdService.GetAds:output_type -> hipstershop.AdResponse\n\t38, // [38:53] is the sub-list for method output_type\n\t23, // [23:38] is the sub-list for method input_type\n\t23, // [23:23] is the sub-list for extension type_name\n\t23, // [23:23] is the sub-list for extension extendee\n\t0,  // [0:23] is the sub-list for field type_name\n}\n\nfunc init() { file_demo_proto_init() }\nfunc file_demo_proto_init() {\n\tif File_demo_proto != nil {\n\t\treturn\n\t}\n\tif !protoimpl.UnsafeEnabled {\n\t\tfile_demo_proto_msgTypes[0].Exporter = func(v any, i int) any {\n\t\t\tswitch v := v.(*CartItem); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_demo_proto_msgTypes[1].Exporter = func(v any, i int) any {\n\t\t\tswitch v := v.(*AddItemRequest); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_demo_proto_msgTypes[2].Exporter = func(v any, i int) any {\n\t\t\tswitch v := v.(*EmptyCartRequest); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_demo_proto_msgTypes[3].Exporter = func(v any, i int) any {\n\t\t\tswitch v := v.(*GetCartRequest); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_demo_proto_msgTypes[4].Exporter = func(v any, i int) any {\n\t\t\tswitch v := v.(*Cart); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_demo_proto_msgTypes[5].Exporter = func(v any, i int) any {\n\t\t\tswitch v := v.(*Empty); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_demo_proto_msgTypes[6].Exporter = func(v any, i int) any {\n\t\t\tswitch v := v.(*ListRecommendationsRequest); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_demo_proto_msgTypes[7].Exporter = func(v any, i int) any {\n\t\t\tswitch v := v.(*ListRecommendationsResponse); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_demo_proto_msgTypes[8].Exporter = func(v any, i int) any {\n\t\t\tswitch v := v.(*Product); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_demo_proto_msgTypes[9].Exporter = func(v any, i int) any {\n\t\t\tswitch v := v.(*ListProductsResponse); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_demo_proto_msgTypes[10].Exporter = func(v any, i int) any {\n\t\t\tswitch v := v.(*GetProductRequest); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_demo_proto_msgTypes[11].Exporter = func(v any, i int) any {\n\t\t\tswitch v := v.(*SearchProductsRequest); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_demo_proto_msgTypes[12].Exporter = func(v any, i int) any {\n\t\t\tswitch v := v.(*SearchProductsResponse); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_demo_proto_msgTypes[13].Exporter = func(v any, i int) any {\n\t\t\tswitch v := v.(*GetQuoteRequest); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_demo_proto_msgTypes[14].Exporter = func(v any, i int) any {\n\t\t\tswitch v := v.(*GetQuoteResponse); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_demo_proto_msgTypes[15].Exporter = func(v any, i int) any {\n\t\t\tswitch v := v.(*ShipOrderRequest); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_demo_proto_msgTypes[16].Exporter = func(v any, i int) any {\n\t\t\tswitch v := v.(*ShipOrderResponse); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_demo_proto_msgTypes[17].Exporter = func(v any, i int) any {\n\t\t\tswitch v := v.(*Address); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_demo_proto_msgTypes[18].Exporter = func(v any, i int) any {\n\t\t\tswitch v := v.(*Money); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_demo_proto_msgTypes[19].Exporter = func(v any, i int) any {\n\t\t\tswitch v := v.(*GetSupportedCurrenciesResponse); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_demo_proto_msgTypes[20].Exporter = func(v any, i int) any {\n\t\t\tswitch v := v.(*CurrencyConversionRequest); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_demo_proto_msgTypes[21].Exporter = func(v any, i int) any {\n\t\t\tswitch v := v.(*CreditCardInfo); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_demo_proto_msgTypes[22].Exporter = func(v any, i int) any {\n\t\t\tswitch v := v.(*ChargeRequest); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_demo_proto_msgTypes[23].Exporter = func(v any, i int) any {\n\t\t\tswitch v := v.(*ChargeResponse); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_demo_proto_msgTypes[24].Exporter = func(v any, i int) any {\n\t\t\tswitch v := v.(*OrderItem); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_demo_proto_msgTypes[25].Exporter = func(v any, i int) any {\n\t\t\tswitch v := v.(*OrderResult); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_demo_proto_msgTypes[26].Exporter = func(v any, i int) any {\n\t\t\tswitch v := v.(*SendOrderConfirmationRequest); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_demo_proto_msgTypes[27].Exporter = func(v any, i int) any {\n\t\t\tswitch v := v.(*PlaceOrderRequest); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_demo_proto_msgTypes[28].Exporter = func(v any, i int) any {\n\t\t\tswitch v := v.(*PlaceOrderResponse); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_demo_proto_msgTypes[29].Exporter = func(v any, i int) any {\n\t\t\tswitch v := v.(*AdRequest); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_demo_proto_msgTypes[30].Exporter = func(v any, i int) any {\n\t\t\tswitch v := v.(*AdResponse); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_demo_proto_msgTypes[31].Exporter = func(v any, i int) any {\n\t\t\tswitch v := v.(*Ad); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: file_demo_proto_rawDesc,\n\t\t\tNumEnums:      0,\n\t\t\tNumMessages:   32,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   9,\n\t\t},\n\t\tGoTypes:           file_demo_proto_goTypes,\n\t\tDependencyIndexes: file_demo_proto_depIdxs,\n\t\tMessageInfos:      file_demo_proto_msgTypes,\n\t}.Build()\n\tFile_demo_proto = out.File\n\tfile_demo_proto_rawDesc = nil\n\tfile_demo_proto_goTypes = nil\n\tfile_demo_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "src/productcatalogservice/genproto/demo_grpc.pb.go",
    "content": "// Copyright 2020 Google LLC\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// Code generated by protoc-gen-go-grpc. DO NOT EDIT.\n// versions:\n// - protoc-gen-go-grpc v1.5.1\n// - protoc             v3.6.1\n// source: demo.proto\n\npackage hipstershop\n\nimport (\n\tcontext \"context\"\n\tgrpc \"google.golang.org/grpc\"\n\tcodes \"google.golang.org/grpc/codes\"\n\tstatus \"google.golang.org/grpc/status\"\n)\n\n// This is a compile-time assertion to ensure that this generated file\n// is compatible with the grpc package it is being compiled against.\n// Requires gRPC-Go v1.64.0 or later.\nconst _ = grpc.SupportPackageIsVersion9\n\nconst (\n\tCartService_AddItem_FullMethodName   = \"/hipstershop.CartService/AddItem\"\n\tCartService_GetCart_FullMethodName   = \"/hipstershop.CartService/GetCart\"\n\tCartService_EmptyCart_FullMethodName = \"/hipstershop.CartService/EmptyCart\"\n)\n\n// CartServiceClient is the client API for CartService service.\n//\n// 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.\ntype CartServiceClient interface {\n\tAddItem(ctx context.Context, in *AddItemRequest, opts ...grpc.CallOption) (*Empty, error)\n\tGetCart(ctx context.Context, in *GetCartRequest, opts ...grpc.CallOption) (*Cart, error)\n\tEmptyCart(ctx context.Context, in *EmptyCartRequest, opts ...grpc.CallOption) (*Empty, error)\n}\n\ntype cartServiceClient struct {\n\tcc grpc.ClientConnInterface\n}\n\nfunc NewCartServiceClient(cc grpc.ClientConnInterface) CartServiceClient {\n\treturn &cartServiceClient{cc}\n}\n\nfunc (c *cartServiceClient) AddItem(ctx context.Context, in *AddItemRequest, opts ...grpc.CallOption) (*Empty, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(Empty)\n\terr := c.cc.Invoke(ctx, CartService_AddItem_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *cartServiceClient) GetCart(ctx context.Context, in *GetCartRequest, opts ...grpc.CallOption) (*Cart, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(Cart)\n\terr := c.cc.Invoke(ctx, CartService_GetCart_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *cartServiceClient) EmptyCart(ctx context.Context, in *EmptyCartRequest, opts ...grpc.CallOption) (*Empty, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(Empty)\n\terr := c.cc.Invoke(ctx, CartService_EmptyCart_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\n// CartServiceServer is the server API for CartService service.\n// All implementations must embed UnimplementedCartServiceServer\n// for forward compatibility.\ntype CartServiceServer interface {\n\tAddItem(context.Context, *AddItemRequest) (*Empty, error)\n\tGetCart(context.Context, *GetCartRequest) (*Cart, error)\n\tEmptyCart(context.Context, *EmptyCartRequest) (*Empty, error)\n\tmustEmbedUnimplementedCartServiceServer()\n}\n\n// UnimplementedCartServiceServer must be embedded to have\n// forward compatible implementations.\n//\n// NOTE: this should be embedded by value instead of pointer to avoid a nil\n// pointer dereference when methods are called.\ntype UnimplementedCartServiceServer struct{}\n\nfunc (UnimplementedCartServiceServer) AddItem(context.Context, *AddItemRequest) (*Empty, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method AddItem not implemented\")\n}\nfunc (UnimplementedCartServiceServer) GetCart(context.Context, *GetCartRequest) (*Cart, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method GetCart not implemented\")\n}\nfunc (UnimplementedCartServiceServer) EmptyCart(context.Context, *EmptyCartRequest) (*Empty, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method EmptyCart not implemented\")\n}\nfunc (UnimplementedCartServiceServer) mustEmbedUnimplementedCartServiceServer() {}\nfunc (UnimplementedCartServiceServer) testEmbeddedByValue()                     {}\n\n// UnsafeCartServiceServer may be embedded to opt out of forward compatibility for this service.\n// Use of this interface is not recommended, as added methods to CartServiceServer will\n// result in compilation errors.\ntype UnsafeCartServiceServer interface {\n\tmustEmbedUnimplementedCartServiceServer()\n}\n\nfunc RegisterCartServiceServer(s grpc.ServiceRegistrar, srv CartServiceServer) {\n\t// If the following call pancis, it indicates UnimplementedCartServiceServer was\n\t// embedded by pointer and is nil.  This will cause panics if an\n\t// unimplemented method is ever invoked, so we test this at initialization\n\t// time to prevent it from happening at runtime later due to I/O.\n\tif t, ok := srv.(interface{ testEmbeddedByValue() }); ok {\n\t\tt.testEmbeddedByValue()\n\t}\n\ts.RegisterService(&CartService_ServiceDesc, srv)\n}\n\nfunc _CartService_AddItem_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(AddItemRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(CartServiceServer).AddItem(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: CartService_AddItem_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(CartServiceServer).AddItem(ctx, req.(*AddItemRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _CartService_GetCart_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(GetCartRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(CartServiceServer).GetCart(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: CartService_GetCart_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(CartServiceServer).GetCart(ctx, req.(*GetCartRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _CartService_EmptyCart_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(EmptyCartRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(CartServiceServer).EmptyCart(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: CartService_EmptyCart_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(CartServiceServer).EmptyCart(ctx, req.(*EmptyCartRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\n// CartService_ServiceDesc is the grpc.ServiceDesc for CartService service.\n// It's only intended for direct use with grpc.RegisterService,\n// and not to be introspected or modified (even as a copy)\nvar CartService_ServiceDesc = grpc.ServiceDesc{\n\tServiceName: \"hipstershop.CartService\",\n\tHandlerType: (*CartServiceServer)(nil),\n\tMethods: []grpc.MethodDesc{\n\t\t{\n\t\t\tMethodName: \"AddItem\",\n\t\t\tHandler:    _CartService_AddItem_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"GetCart\",\n\t\t\tHandler:    _CartService_GetCart_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"EmptyCart\",\n\t\t\tHandler:    _CartService_EmptyCart_Handler,\n\t\t},\n\t},\n\tStreams:  []grpc.StreamDesc{},\n\tMetadata: \"demo.proto\",\n}\n\nconst (\n\tRecommendationService_ListRecommendations_FullMethodName = \"/hipstershop.RecommendationService/ListRecommendations\"\n)\n\n// RecommendationServiceClient is the client API for RecommendationService service.\n//\n// 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.\ntype RecommendationServiceClient interface {\n\tListRecommendations(ctx context.Context, in *ListRecommendationsRequest, opts ...grpc.CallOption) (*ListRecommendationsResponse, error)\n}\n\ntype recommendationServiceClient struct {\n\tcc grpc.ClientConnInterface\n}\n\nfunc NewRecommendationServiceClient(cc grpc.ClientConnInterface) RecommendationServiceClient {\n\treturn &recommendationServiceClient{cc}\n}\n\nfunc (c *recommendationServiceClient) ListRecommendations(ctx context.Context, in *ListRecommendationsRequest, opts ...grpc.CallOption) (*ListRecommendationsResponse, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(ListRecommendationsResponse)\n\terr := c.cc.Invoke(ctx, RecommendationService_ListRecommendations_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\n// RecommendationServiceServer is the server API for RecommendationService service.\n// All implementations must embed UnimplementedRecommendationServiceServer\n// for forward compatibility.\ntype RecommendationServiceServer interface {\n\tListRecommendations(context.Context, *ListRecommendationsRequest) (*ListRecommendationsResponse, error)\n\tmustEmbedUnimplementedRecommendationServiceServer()\n}\n\n// UnimplementedRecommendationServiceServer must be embedded to have\n// forward compatible implementations.\n//\n// NOTE: this should be embedded by value instead of pointer to avoid a nil\n// pointer dereference when methods are called.\ntype UnimplementedRecommendationServiceServer struct{}\n\nfunc (UnimplementedRecommendationServiceServer) ListRecommendations(context.Context, *ListRecommendationsRequest) (*ListRecommendationsResponse, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method ListRecommendations not implemented\")\n}\nfunc (UnimplementedRecommendationServiceServer) mustEmbedUnimplementedRecommendationServiceServer() {}\nfunc (UnimplementedRecommendationServiceServer) testEmbeddedByValue()                               {}\n\n// UnsafeRecommendationServiceServer may be embedded to opt out of forward compatibility for this service.\n// Use of this interface is not recommended, as added methods to RecommendationServiceServer will\n// result in compilation errors.\ntype UnsafeRecommendationServiceServer interface {\n\tmustEmbedUnimplementedRecommendationServiceServer()\n}\n\nfunc RegisterRecommendationServiceServer(s grpc.ServiceRegistrar, srv RecommendationServiceServer) {\n\t// If the following call pancis, it indicates UnimplementedRecommendationServiceServer was\n\t// embedded by pointer and is nil.  This will cause panics if an\n\t// unimplemented method is ever invoked, so we test this at initialization\n\t// time to prevent it from happening at runtime later due to I/O.\n\tif t, ok := srv.(interface{ testEmbeddedByValue() }); ok {\n\t\tt.testEmbeddedByValue()\n\t}\n\ts.RegisterService(&RecommendationService_ServiceDesc, srv)\n}\n\nfunc _RecommendationService_ListRecommendations_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(ListRecommendationsRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(RecommendationServiceServer).ListRecommendations(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: RecommendationService_ListRecommendations_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(RecommendationServiceServer).ListRecommendations(ctx, req.(*ListRecommendationsRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\n// RecommendationService_ServiceDesc is the grpc.ServiceDesc for RecommendationService service.\n// It's only intended for direct use with grpc.RegisterService,\n// and not to be introspected or modified (even as a copy)\nvar RecommendationService_ServiceDesc = grpc.ServiceDesc{\n\tServiceName: \"hipstershop.RecommendationService\",\n\tHandlerType: (*RecommendationServiceServer)(nil),\n\tMethods: []grpc.MethodDesc{\n\t\t{\n\t\t\tMethodName: \"ListRecommendations\",\n\t\t\tHandler:    _RecommendationService_ListRecommendations_Handler,\n\t\t},\n\t},\n\tStreams:  []grpc.StreamDesc{},\n\tMetadata: \"demo.proto\",\n}\n\nconst (\n\tProductCatalogService_ListProducts_FullMethodName   = \"/hipstershop.ProductCatalogService/ListProducts\"\n\tProductCatalogService_GetProduct_FullMethodName     = \"/hipstershop.ProductCatalogService/GetProduct\"\n\tProductCatalogService_SearchProducts_FullMethodName = \"/hipstershop.ProductCatalogService/SearchProducts\"\n)\n\n// ProductCatalogServiceClient is the client API for ProductCatalogService service.\n//\n// 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.\ntype ProductCatalogServiceClient interface {\n\tListProducts(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*ListProductsResponse, error)\n\tGetProduct(ctx context.Context, in *GetProductRequest, opts ...grpc.CallOption) (*Product, error)\n\tSearchProducts(ctx context.Context, in *SearchProductsRequest, opts ...grpc.CallOption) (*SearchProductsResponse, error)\n}\n\ntype productCatalogServiceClient struct {\n\tcc grpc.ClientConnInterface\n}\n\nfunc NewProductCatalogServiceClient(cc grpc.ClientConnInterface) ProductCatalogServiceClient {\n\treturn &productCatalogServiceClient{cc}\n}\n\nfunc (c *productCatalogServiceClient) ListProducts(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*ListProductsResponse, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(ListProductsResponse)\n\terr := c.cc.Invoke(ctx, ProductCatalogService_ListProducts_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *productCatalogServiceClient) GetProduct(ctx context.Context, in *GetProductRequest, opts ...grpc.CallOption) (*Product, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(Product)\n\terr := c.cc.Invoke(ctx, ProductCatalogService_GetProduct_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *productCatalogServiceClient) SearchProducts(ctx context.Context, in *SearchProductsRequest, opts ...grpc.CallOption) (*SearchProductsResponse, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(SearchProductsResponse)\n\terr := c.cc.Invoke(ctx, ProductCatalogService_SearchProducts_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\n// ProductCatalogServiceServer is the server API for ProductCatalogService service.\n// All implementations must embed UnimplementedProductCatalogServiceServer\n// for forward compatibility.\ntype ProductCatalogServiceServer interface {\n\tListProducts(context.Context, *Empty) (*ListProductsResponse, error)\n\tGetProduct(context.Context, *GetProductRequest) (*Product, error)\n\tSearchProducts(context.Context, *SearchProductsRequest) (*SearchProductsResponse, error)\n\tmustEmbedUnimplementedProductCatalogServiceServer()\n}\n\n// UnimplementedProductCatalogServiceServer must be embedded to have\n// forward compatible implementations.\n//\n// NOTE: this should be embedded by value instead of pointer to avoid a nil\n// pointer dereference when methods are called.\ntype UnimplementedProductCatalogServiceServer struct{}\n\nfunc (UnimplementedProductCatalogServiceServer) ListProducts(context.Context, *Empty) (*ListProductsResponse, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method ListProducts not implemented\")\n}\nfunc (UnimplementedProductCatalogServiceServer) GetProduct(context.Context, *GetProductRequest) (*Product, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method GetProduct not implemented\")\n}\nfunc (UnimplementedProductCatalogServiceServer) SearchProducts(context.Context, *SearchProductsRequest) (*SearchProductsResponse, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method SearchProducts not implemented\")\n}\nfunc (UnimplementedProductCatalogServiceServer) mustEmbedUnimplementedProductCatalogServiceServer() {}\nfunc (UnimplementedProductCatalogServiceServer) testEmbeddedByValue()                               {}\n\n// UnsafeProductCatalogServiceServer may be embedded to opt out of forward compatibility for this service.\n// Use of this interface is not recommended, as added methods to ProductCatalogServiceServer will\n// result in compilation errors.\ntype UnsafeProductCatalogServiceServer interface {\n\tmustEmbedUnimplementedProductCatalogServiceServer()\n}\n\nfunc RegisterProductCatalogServiceServer(s grpc.ServiceRegistrar, srv ProductCatalogServiceServer) {\n\t// If the following call pancis, it indicates UnimplementedProductCatalogServiceServer was\n\t// embedded by pointer and is nil.  This will cause panics if an\n\t// unimplemented method is ever invoked, so we test this at initialization\n\t// time to prevent it from happening at runtime later due to I/O.\n\tif t, ok := srv.(interface{ testEmbeddedByValue() }); ok {\n\t\tt.testEmbeddedByValue()\n\t}\n\ts.RegisterService(&ProductCatalogService_ServiceDesc, srv)\n}\n\nfunc _ProductCatalogService_ListProducts_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(Empty)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ProductCatalogServiceServer).ListProducts(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: ProductCatalogService_ListProducts_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ProductCatalogServiceServer).ListProducts(ctx, req.(*Empty))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _ProductCatalogService_GetProduct_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(GetProductRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ProductCatalogServiceServer).GetProduct(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: ProductCatalogService_GetProduct_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ProductCatalogServiceServer).GetProduct(ctx, req.(*GetProductRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _ProductCatalogService_SearchProducts_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(SearchProductsRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ProductCatalogServiceServer).SearchProducts(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: ProductCatalogService_SearchProducts_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ProductCatalogServiceServer).SearchProducts(ctx, req.(*SearchProductsRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\n// ProductCatalogService_ServiceDesc is the grpc.ServiceDesc for ProductCatalogService service.\n// It's only intended for direct use with grpc.RegisterService,\n// and not to be introspected or modified (even as a copy)\nvar ProductCatalogService_ServiceDesc = grpc.ServiceDesc{\n\tServiceName: \"hipstershop.ProductCatalogService\",\n\tHandlerType: (*ProductCatalogServiceServer)(nil),\n\tMethods: []grpc.MethodDesc{\n\t\t{\n\t\t\tMethodName: \"ListProducts\",\n\t\t\tHandler:    _ProductCatalogService_ListProducts_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"GetProduct\",\n\t\t\tHandler:    _ProductCatalogService_GetProduct_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"SearchProducts\",\n\t\t\tHandler:    _ProductCatalogService_SearchProducts_Handler,\n\t\t},\n\t},\n\tStreams:  []grpc.StreamDesc{},\n\tMetadata: \"demo.proto\",\n}\n\nconst (\n\tShippingService_GetQuote_FullMethodName  = \"/hipstershop.ShippingService/GetQuote\"\n\tShippingService_ShipOrder_FullMethodName = \"/hipstershop.ShippingService/ShipOrder\"\n)\n\n// ShippingServiceClient is the client API for ShippingService service.\n//\n// 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.\ntype ShippingServiceClient interface {\n\tGetQuote(ctx context.Context, in *GetQuoteRequest, opts ...grpc.CallOption) (*GetQuoteResponse, error)\n\tShipOrder(ctx context.Context, in *ShipOrderRequest, opts ...grpc.CallOption) (*ShipOrderResponse, error)\n}\n\ntype shippingServiceClient struct {\n\tcc grpc.ClientConnInterface\n}\n\nfunc NewShippingServiceClient(cc grpc.ClientConnInterface) ShippingServiceClient {\n\treturn &shippingServiceClient{cc}\n}\n\nfunc (c *shippingServiceClient) GetQuote(ctx context.Context, in *GetQuoteRequest, opts ...grpc.CallOption) (*GetQuoteResponse, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(GetQuoteResponse)\n\terr := c.cc.Invoke(ctx, ShippingService_GetQuote_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *shippingServiceClient) ShipOrder(ctx context.Context, in *ShipOrderRequest, opts ...grpc.CallOption) (*ShipOrderResponse, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(ShipOrderResponse)\n\terr := c.cc.Invoke(ctx, ShippingService_ShipOrder_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\n// ShippingServiceServer is the server API for ShippingService service.\n// All implementations must embed UnimplementedShippingServiceServer\n// for forward compatibility.\ntype ShippingServiceServer interface {\n\tGetQuote(context.Context, *GetQuoteRequest) (*GetQuoteResponse, error)\n\tShipOrder(context.Context, *ShipOrderRequest) (*ShipOrderResponse, error)\n\tmustEmbedUnimplementedShippingServiceServer()\n}\n\n// UnimplementedShippingServiceServer must be embedded to have\n// forward compatible implementations.\n//\n// NOTE: this should be embedded by value instead of pointer to avoid a nil\n// pointer dereference when methods are called.\ntype UnimplementedShippingServiceServer struct{}\n\nfunc (UnimplementedShippingServiceServer) GetQuote(context.Context, *GetQuoteRequest) (*GetQuoteResponse, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method GetQuote not implemented\")\n}\nfunc (UnimplementedShippingServiceServer) ShipOrder(context.Context, *ShipOrderRequest) (*ShipOrderResponse, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method ShipOrder not implemented\")\n}\nfunc (UnimplementedShippingServiceServer) mustEmbedUnimplementedShippingServiceServer() {}\nfunc (UnimplementedShippingServiceServer) testEmbeddedByValue()                         {}\n\n// UnsafeShippingServiceServer may be embedded to opt out of forward compatibility for this service.\n// Use of this interface is not recommended, as added methods to ShippingServiceServer will\n// result in compilation errors.\ntype UnsafeShippingServiceServer interface {\n\tmustEmbedUnimplementedShippingServiceServer()\n}\n\nfunc RegisterShippingServiceServer(s grpc.ServiceRegistrar, srv ShippingServiceServer) {\n\t// If the following call pancis, it indicates UnimplementedShippingServiceServer was\n\t// embedded by pointer and is nil.  This will cause panics if an\n\t// unimplemented method is ever invoked, so we test this at initialization\n\t// time to prevent it from happening at runtime later due to I/O.\n\tif t, ok := srv.(interface{ testEmbeddedByValue() }); ok {\n\t\tt.testEmbeddedByValue()\n\t}\n\ts.RegisterService(&ShippingService_ServiceDesc, srv)\n}\n\nfunc _ShippingService_GetQuote_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(GetQuoteRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ShippingServiceServer).GetQuote(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: ShippingService_GetQuote_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ShippingServiceServer).GetQuote(ctx, req.(*GetQuoteRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _ShippingService_ShipOrder_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(ShipOrderRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ShippingServiceServer).ShipOrder(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: ShippingService_ShipOrder_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ShippingServiceServer).ShipOrder(ctx, req.(*ShipOrderRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\n// ShippingService_ServiceDesc is the grpc.ServiceDesc for ShippingService service.\n// It's only intended for direct use with grpc.RegisterService,\n// and not to be introspected or modified (even as a copy)\nvar ShippingService_ServiceDesc = grpc.ServiceDesc{\n\tServiceName: \"hipstershop.ShippingService\",\n\tHandlerType: (*ShippingServiceServer)(nil),\n\tMethods: []grpc.MethodDesc{\n\t\t{\n\t\t\tMethodName: \"GetQuote\",\n\t\t\tHandler:    _ShippingService_GetQuote_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"ShipOrder\",\n\t\t\tHandler:    _ShippingService_ShipOrder_Handler,\n\t\t},\n\t},\n\tStreams:  []grpc.StreamDesc{},\n\tMetadata: \"demo.proto\",\n}\n\nconst (\n\tCurrencyService_GetSupportedCurrencies_FullMethodName = \"/hipstershop.CurrencyService/GetSupportedCurrencies\"\n\tCurrencyService_Convert_FullMethodName                = \"/hipstershop.CurrencyService/Convert\"\n)\n\n// CurrencyServiceClient is the client API for CurrencyService service.\n//\n// 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.\ntype CurrencyServiceClient interface {\n\tGetSupportedCurrencies(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*GetSupportedCurrenciesResponse, error)\n\tConvert(ctx context.Context, in *CurrencyConversionRequest, opts ...grpc.CallOption) (*Money, error)\n}\n\ntype currencyServiceClient struct {\n\tcc grpc.ClientConnInterface\n}\n\nfunc NewCurrencyServiceClient(cc grpc.ClientConnInterface) CurrencyServiceClient {\n\treturn &currencyServiceClient{cc}\n}\n\nfunc (c *currencyServiceClient) GetSupportedCurrencies(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*GetSupportedCurrenciesResponse, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(GetSupportedCurrenciesResponse)\n\terr := c.cc.Invoke(ctx, CurrencyService_GetSupportedCurrencies_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *currencyServiceClient) Convert(ctx context.Context, in *CurrencyConversionRequest, opts ...grpc.CallOption) (*Money, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(Money)\n\terr := c.cc.Invoke(ctx, CurrencyService_Convert_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\n// CurrencyServiceServer is the server API for CurrencyService service.\n// All implementations must embed UnimplementedCurrencyServiceServer\n// for forward compatibility.\ntype CurrencyServiceServer interface {\n\tGetSupportedCurrencies(context.Context, *Empty) (*GetSupportedCurrenciesResponse, error)\n\tConvert(context.Context, *CurrencyConversionRequest) (*Money, error)\n\tmustEmbedUnimplementedCurrencyServiceServer()\n}\n\n// UnimplementedCurrencyServiceServer must be embedded to have\n// forward compatible implementations.\n//\n// NOTE: this should be embedded by value instead of pointer to avoid a nil\n// pointer dereference when methods are called.\ntype UnimplementedCurrencyServiceServer struct{}\n\nfunc (UnimplementedCurrencyServiceServer) GetSupportedCurrencies(context.Context, *Empty) (*GetSupportedCurrenciesResponse, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method GetSupportedCurrencies not implemented\")\n}\nfunc (UnimplementedCurrencyServiceServer) Convert(context.Context, *CurrencyConversionRequest) (*Money, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method Convert not implemented\")\n}\nfunc (UnimplementedCurrencyServiceServer) mustEmbedUnimplementedCurrencyServiceServer() {}\nfunc (UnimplementedCurrencyServiceServer) testEmbeddedByValue()                         {}\n\n// UnsafeCurrencyServiceServer may be embedded to opt out of forward compatibility for this service.\n// Use of this interface is not recommended, as added methods to CurrencyServiceServer will\n// result in compilation errors.\ntype UnsafeCurrencyServiceServer interface {\n\tmustEmbedUnimplementedCurrencyServiceServer()\n}\n\nfunc RegisterCurrencyServiceServer(s grpc.ServiceRegistrar, srv CurrencyServiceServer) {\n\t// If the following call pancis, it indicates UnimplementedCurrencyServiceServer was\n\t// embedded by pointer and is nil.  This will cause panics if an\n\t// unimplemented method is ever invoked, so we test this at initialization\n\t// time to prevent it from happening at runtime later due to I/O.\n\tif t, ok := srv.(interface{ testEmbeddedByValue() }); ok {\n\t\tt.testEmbeddedByValue()\n\t}\n\ts.RegisterService(&CurrencyService_ServiceDesc, srv)\n}\n\nfunc _CurrencyService_GetSupportedCurrencies_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(Empty)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(CurrencyServiceServer).GetSupportedCurrencies(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: CurrencyService_GetSupportedCurrencies_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(CurrencyServiceServer).GetSupportedCurrencies(ctx, req.(*Empty))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _CurrencyService_Convert_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(CurrencyConversionRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(CurrencyServiceServer).Convert(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: CurrencyService_Convert_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(CurrencyServiceServer).Convert(ctx, req.(*CurrencyConversionRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\n// CurrencyService_ServiceDesc is the grpc.ServiceDesc for CurrencyService service.\n// It's only intended for direct use with grpc.RegisterService,\n// and not to be introspected or modified (even as a copy)\nvar CurrencyService_ServiceDesc = grpc.ServiceDesc{\n\tServiceName: \"hipstershop.CurrencyService\",\n\tHandlerType: (*CurrencyServiceServer)(nil),\n\tMethods: []grpc.MethodDesc{\n\t\t{\n\t\t\tMethodName: \"GetSupportedCurrencies\",\n\t\t\tHandler:    _CurrencyService_GetSupportedCurrencies_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"Convert\",\n\t\t\tHandler:    _CurrencyService_Convert_Handler,\n\t\t},\n\t},\n\tStreams:  []grpc.StreamDesc{},\n\tMetadata: \"demo.proto\",\n}\n\nconst (\n\tPaymentService_Charge_FullMethodName = \"/hipstershop.PaymentService/Charge\"\n)\n\n// PaymentServiceClient is the client API for PaymentService service.\n//\n// 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.\ntype PaymentServiceClient interface {\n\tCharge(ctx context.Context, in *ChargeRequest, opts ...grpc.CallOption) (*ChargeResponse, error)\n}\n\ntype paymentServiceClient struct {\n\tcc grpc.ClientConnInterface\n}\n\nfunc NewPaymentServiceClient(cc grpc.ClientConnInterface) PaymentServiceClient {\n\treturn &paymentServiceClient{cc}\n}\n\nfunc (c *paymentServiceClient) Charge(ctx context.Context, in *ChargeRequest, opts ...grpc.CallOption) (*ChargeResponse, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(ChargeResponse)\n\terr := c.cc.Invoke(ctx, PaymentService_Charge_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\n// PaymentServiceServer is the server API for PaymentService service.\n// All implementations must embed UnimplementedPaymentServiceServer\n// for forward compatibility.\ntype PaymentServiceServer interface {\n\tCharge(context.Context, *ChargeRequest) (*ChargeResponse, error)\n\tmustEmbedUnimplementedPaymentServiceServer()\n}\n\n// UnimplementedPaymentServiceServer must be embedded to have\n// forward compatible implementations.\n//\n// NOTE: this should be embedded by value instead of pointer to avoid a nil\n// pointer dereference when methods are called.\ntype UnimplementedPaymentServiceServer struct{}\n\nfunc (UnimplementedPaymentServiceServer) Charge(context.Context, *ChargeRequest) (*ChargeResponse, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method Charge not implemented\")\n}\nfunc (UnimplementedPaymentServiceServer) mustEmbedUnimplementedPaymentServiceServer() {}\nfunc (UnimplementedPaymentServiceServer) testEmbeddedByValue()                        {}\n\n// UnsafePaymentServiceServer may be embedded to opt out of forward compatibility for this service.\n// Use of this interface is not recommended, as added methods to PaymentServiceServer will\n// result in compilation errors.\ntype UnsafePaymentServiceServer interface {\n\tmustEmbedUnimplementedPaymentServiceServer()\n}\n\nfunc RegisterPaymentServiceServer(s grpc.ServiceRegistrar, srv PaymentServiceServer) {\n\t// If the following call pancis, it indicates UnimplementedPaymentServiceServer was\n\t// embedded by pointer and is nil.  This will cause panics if an\n\t// unimplemented method is ever invoked, so we test this at initialization\n\t// time to prevent it from happening at runtime later due to I/O.\n\tif t, ok := srv.(interface{ testEmbeddedByValue() }); ok {\n\t\tt.testEmbeddedByValue()\n\t}\n\ts.RegisterService(&PaymentService_ServiceDesc, srv)\n}\n\nfunc _PaymentService_Charge_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(ChargeRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(PaymentServiceServer).Charge(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: PaymentService_Charge_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(PaymentServiceServer).Charge(ctx, req.(*ChargeRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\n// PaymentService_ServiceDesc is the grpc.ServiceDesc for PaymentService service.\n// It's only intended for direct use with grpc.RegisterService,\n// and not to be introspected or modified (even as a copy)\nvar PaymentService_ServiceDesc = grpc.ServiceDesc{\n\tServiceName: \"hipstershop.PaymentService\",\n\tHandlerType: (*PaymentServiceServer)(nil),\n\tMethods: []grpc.MethodDesc{\n\t\t{\n\t\t\tMethodName: \"Charge\",\n\t\t\tHandler:    _PaymentService_Charge_Handler,\n\t\t},\n\t},\n\tStreams:  []grpc.StreamDesc{},\n\tMetadata: \"demo.proto\",\n}\n\nconst (\n\tEmailService_SendOrderConfirmation_FullMethodName = \"/hipstershop.EmailService/SendOrderConfirmation\"\n)\n\n// EmailServiceClient is the client API for EmailService service.\n//\n// 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.\ntype EmailServiceClient interface {\n\tSendOrderConfirmation(ctx context.Context, in *SendOrderConfirmationRequest, opts ...grpc.CallOption) (*Empty, error)\n}\n\ntype emailServiceClient struct {\n\tcc grpc.ClientConnInterface\n}\n\nfunc NewEmailServiceClient(cc grpc.ClientConnInterface) EmailServiceClient {\n\treturn &emailServiceClient{cc}\n}\n\nfunc (c *emailServiceClient) SendOrderConfirmation(ctx context.Context, in *SendOrderConfirmationRequest, opts ...grpc.CallOption) (*Empty, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(Empty)\n\terr := c.cc.Invoke(ctx, EmailService_SendOrderConfirmation_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\n// EmailServiceServer is the server API for EmailService service.\n// All implementations must embed UnimplementedEmailServiceServer\n// for forward compatibility.\ntype EmailServiceServer interface {\n\tSendOrderConfirmation(context.Context, *SendOrderConfirmationRequest) (*Empty, error)\n\tmustEmbedUnimplementedEmailServiceServer()\n}\n\n// UnimplementedEmailServiceServer must be embedded to have\n// forward compatible implementations.\n//\n// NOTE: this should be embedded by value instead of pointer to avoid a nil\n// pointer dereference when methods are called.\ntype UnimplementedEmailServiceServer struct{}\n\nfunc (UnimplementedEmailServiceServer) SendOrderConfirmation(context.Context, *SendOrderConfirmationRequest) (*Empty, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method SendOrderConfirmation not implemented\")\n}\nfunc (UnimplementedEmailServiceServer) mustEmbedUnimplementedEmailServiceServer() {}\nfunc (UnimplementedEmailServiceServer) testEmbeddedByValue()                      {}\n\n// UnsafeEmailServiceServer may be embedded to opt out of forward compatibility for this service.\n// Use of this interface is not recommended, as added methods to EmailServiceServer will\n// result in compilation errors.\ntype UnsafeEmailServiceServer interface {\n\tmustEmbedUnimplementedEmailServiceServer()\n}\n\nfunc RegisterEmailServiceServer(s grpc.ServiceRegistrar, srv EmailServiceServer) {\n\t// If the following call pancis, it indicates UnimplementedEmailServiceServer was\n\t// embedded by pointer and is nil.  This will cause panics if an\n\t// unimplemented method is ever invoked, so we test this at initialization\n\t// time to prevent it from happening at runtime later due to I/O.\n\tif t, ok := srv.(interface{ testEmbeddedByValue() }); ok {\n\t\tt.testEmbeddedByValue()\n\t}\n\ts.RegisterService(&EmailService_ServiceDesc, srv)\n}\n\nfunc _EmailService_SendOrderConfirmation_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(SendOrderConfirmationRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(EmailServiceServer).SendOrderConfirmation(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: EmailService_SendOrderConfirmation_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(EmailServiceServer).SendOrderConfirmation(ctx, req.(*SendOrderConfirmationRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\n// EmailService_ServiceDesc is the grpc.ServiceDesc for EmailService service.\n// It's only intended for direct use with grpc.RegisterService,\n// and not to be introspected or modified (even as a copy)\nvar EmailService_ServiceDesc = grpc.ServiceDesc{\n\tServiceName: \"hipstershop.EmailService\",\n\tHandlerType: (*EmailServiceServer)(nil),\n\tMethods: []grpc.MethodDesc{\n\t\t{\n\t\t\tMethodName: \"SendOrderConfirmation\",\n\t\t\tHandler:    _EmailService_SendOrderConfirmation_Handler,\n\t\t},\n\t},\n\tStreams:  []grpc.StreamDesc{},\n\tMetadata: \"demo.proto\",\n}\n\nconst (\n\tCheckoutService_PlaceOrder_FullMethodName = \"/hipstershop.CheckoutService/PlaceOrder\"\n)\n\n// CheckoutServiceClient is the client API for CheckoutService service.\n//\n// 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.\ntype CheckoutServiceClient interface {\n\tPlaceOrder(ctx context.Context, in *PlaceOrderRequest, opts ...grpc.CallOption) (*PlaceOrderResponse, error)\n}\n\ntype checkoutServiceClient struct {\n\tcc grpc.ClientConnInterface\n}\n\nfunc NewCheckoutServiceClient(cc grpc.ClientConnInterface) CheckoutServiceClient {\n\treturn &checkoutServiceClient{cc}\n}\n\nfunc (c *checkoutServiceClient) PlaceOrder(ctx context.Context, in *PlaceOrderRequest, opts ...grpc.CallOption) (*PlaceOrderResponse, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(PlaceOrderResponse)\n\terr := c.cc.Invoke(ctx, CheckoutService_PlaceOrder_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\n// CheckoutServiceServer is the server API for CheckoutService service.\n// All implementations must embed UnimplementedCheckoutServiceServer\n// for forward compatibility.\ntype CheckoutServiceServer interface {\n\tPlaceOrder(context.Context, *PlaceOrderRequest) (*PlaceOrderResponse, error)\n\tmustEmbedUnimplementedCheckoutServiceServer()\n}\n\n// UnimplementedCheckoutServiceServer must be embedded to have\n// forward compatible implementations.\n//\n// NOTE: this should be embedded by value instead of pointer to avoid a nil\n// pointer dereference when methods are called.\ntype UnimplementedCheckoutServiceServer struct{}\n\nfunc (UnimplementedCheckoutServiceServer) PlaceOrder(context.Context, *PlaceOrderRequest) (*PlaceOrderResponse, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method PlaceOrder not implemented\")\n}\nfunc (UnimplementedCheckoutServiceServer) mustEmbedUnimplementedCheckoutServiceServer() {}\nfunc (UnimplementedCheckoutServiceServer) testEmbeddedByValue()                         {}\n\n// UnsafeCheckoutServiceServer may be embedded to opt out of forward compatibility for this service.\n// Use of this interface is not recommended, as added methods to CheckoutServiceServer will\n// result in compilation errors.\ntype UnsafeCheckoutServiceServer interface {\n\tmustEmbedUnimplementedCheckoutServiceServer()\n}\n\nfunc RegisterCheckoutServiceServer(s grpc.ServiceRegistrar, srv CheckoutServiceServer) {\n\t// If the following call pancis, it indicates UnimplementedCheckoutServiceServer was\n\t// embedded by pointer and is nil.  This will cause panics if an\n\t// unimplemented method is ever invoked, so we test this at initialization\n\t// time to prevent it from happening at runtime later due to I/O.\n\tif t, ok := srv.(interface{ testEmbeddedByValue() }); ok {\n\t\tt.testEmbeddedByValue()\n\t}\n\ts.RegisterService(&CheckoutService_ServiceDesc, srv)\n}\n\nfunc _CheckoutService_PlaceOrder_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(PlaceOrderRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(CheckoutServiceServer).PlaceOrder(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: CheckoutService_PlaceOrder_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(CheckoutServiceServer).PlaceOrder(ctx, req.(*PlaceOrderRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\n// CheckoutService_ServiceDesc is the grpc.ServiceDesc for CheckoutService service.\n// It's only intended for direct use with grpc.RegisterService,\n// and not to be introspected or modified (even as a copy)\nvar CheckoutService_ServiceDesc = grpc.ServiceDesc{\n\tServiceName: \"hipstershop.CheckoutService\",\n\tHandlerType: (*CheckoutServiceServer)(nil),\n\tMethods: []grpc.MethodDesc{\n\t\t{\n\t\t\tMethodName: \"PlaceOrder\",\n\t\t\tHandler:    _CheckoutService_PlaceOrder_Handler,\n\t\t},\n\t},\n\tStreams:  []grpc.StreamDesc{},\n\tMetadata: \"demo.proto\",\n}\n\nconst (\n\tAdService_GetAds_FullMethodName = \"/hipstershop.AdService/GetAds\"\n)\n\n// AdServiceClient is the client API for AdService service.\n//\n// 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.\ntype AdServiceClient interface {\n\tGetAds(ctx context.Context, in *AdRequest, opts ...grpc.CallOption) (*AdResponse, error)\n}\n\ntype adServiceClient struct {\n\tcc grpc.ClientConnInterface\n}\n\nfunc NewAdServiceClient(cc grpc.ClientConnInterface) AdServiceClient {\n\treturn &adServiceClient{cc}\n}\n\nfunc (c *adServiceClient) GetAds(ctx context.Context, in *AdRequest, opts ...grpc.CallOption) (*AdResponse, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(AdResponse)\n\terr := c.cc.Invoke(ctx, AdService_GetAds_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\n// AdServiceServer is the server API for AdService service.\n// All implementations must embed UnimplementedAdServiceServer\n// for forward compatibility.\ntype AdServiceServer interface {\n\tGetAds(context.Context, *AdRequest) (*AdResponse, error)\n\tmustEmbedUnimplementedAdServiceServer()\n}\n\n// UnimplementedAdServiceServer must be embedded to have\n// forward compatible implementations.\n//\n// NOTE: this should be embedded by value instead of pointer to avoid a nil\n// pointer dereference when methods are called.\ntype UnimplementedAdServiceServer struct{}\n\nfunc (UnimplementedAdServiceServer) GetAds(context.Context, *AdRequest) (*AdResponse, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method GetAds not implemented\")\n}\nfunc (UnimplementedAdServiceServer) mustEmbedUnimplementedAdServiceServer() {}\nfunc (UnimplementedAdServiceServer) testEmbeddedByValue()                   {}\n\n// UnsafeAdServiceServer may be embedded to opt out of forward compatibility for this service.\n// Use of this interface is not recommended, as added methods to AdServiceServer will\n// result in compilation errors.\ntype UnsafeAdServiceServer interface {\n\tmustEmbedUnimplementedAdServiceServer()\n}\n\nfunc RegisterAdServiceServer(s grpc.ServiceRegistrar, srv AdServiceServer) {\n\t// If the following call pancis, it indicates UnimplementedAdServiceServer was\n\t// embedded by pointer and is nil.  This will cause panics if an\n\t// unimplemented method is ever invoked, so we test this at initialization\n\t// time to prevent it from happening at runtime later due to I/O.\n\tif t, ok := srv.(interface{ testEmbeddedByValue() }); ok {\n\t\tt.testEmbeddedByValue()\n\t}\n\ts.RegisterService(&AdService_ServiceDesc, srv)\n}\n\nfunc _AdService_GetAds_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(AdRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(AdServiceServer).GetAds(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: AdService_GetAds_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(AdServiceServer).GetAds(ctx, req.(*AdRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\n// AdService_ServiceDesc is the grpc.ServiceDesc for AdService service.\n// It's only intended for direct use with grpc.RegisterService,\n// and not to be introspected or modified (even as a copy)\nvar AdService_ServiceDesc = grpc.ServiceDesc{\n\tServiceName: \"hipstershop.AdService\",\n\tHandlerType: (*AdServiceServer)(nil),\n\tMethods: []grpc.MethodDesc{\n\t\t{\n\t\t\tMethodName: \"GetAds\",\n\t\t\tHandler:    _AdService_GetAds_Handler,\n\t\t},\n\t},\n\tStreams:  []grpc.StreamDesc{},\n\tMetadata: \"demo.proto\",\n}\n"
  },
  {
    "path": "src/productcatalogservice/genproto.sh",
    "content": "#!/bin/bash -eu\n#\n# Copyright 2018 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# [START gke_productcatalogservice_genproto]\n\nPATH=$PATH:$(go env GOPATH)/bin\nprotodir=../../protos\noutdir=./genproto\n\nprotoc --proto_path=$protodir --go_out=./$outdir --go_opt=paths=source_relative --go-grpc_out=./$outdir --go-grpc_opt=paths=source_relative $protodir/demo.proto\n\n# [END gke_productcatalogservice_genproto]"
  },
  {
    "path": "src/productcatalogservice/go.mod",
    "content": "module github.com/GoogleCloudPlatform/microservices-demo/src/productcatalogservice\n\ngo 1.25.0\n\ntoolchain go1.26.1\n\nrequire (\n\tcloud.google.com/go/alloydbconn v1.17.3\n\tcloud.google.com/go/profiler v0.4.3\n\tcloud.google.com/go/secretmanager v1.16.0\n\tgithub.com/golang/protobuf v1.5.4\n\tgithub.com/jackc/pgx/v5 v5.8.0\n\tgithub.com/pkg/errors v0.9.1\n\tgithub.com/sirupsen/logrus v1.9.4\n\tgo.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0\n\tgo.opentelemetry.io/otel v1.42.0\n\tgo.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.42.0\n\tgo.opentelemetry.io/otel/sdk v1.42.0\n\tgoogle.golang.org/grpc v1.79.2\n\tgoogle.golang.org/protobuf v1.36.11\n)\n\nrequire (\n\tcloud.google.com/go v0.123.0 // indirect\n\tcloud.google.com/go/alloydb v1.20.0 // indirect\n\tcloud.google.com/go/auth v0.18.1 // indirect\n\tcloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect\n\tcloud.google.com/go/compute/metadata v0.9.0 // indirect\n\tcloud.google.com/go/iam v1.5.3 // indirect\n\tcloud.google.com/go/longrunning v0.7.0 // indirect\n\tcloud.google.com/go/monitoring v1.24.3 // indirect\n\tgithub.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.55.0 // indirect\n\tgithub.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.55.0 // indirect\n\tgithub.com/cenkalti/backoff/v5 v5.0.3 // indirect\n\tgithub.com/cespare/xxhash/v2 v2.3.0 // indirect\n\tgithub.com/felixge/httpsnoop v1.0.4 // indirect\n\tgithub.com/go-logr/logr v1.4.3 // indirect\n\tgithub.com/go-logr/stdr v1.2.2 // indirect\n\tgithub.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect\n\tgithub.com/google/pprof v0.0.0-20251114195745-4902fdda35c8 // indirect\n\tgithub.com/google/s2a-go v0.1.9 // indirect\n\tgithub.com/google/uuid v1.6.0 // indirect\n\tgithub.com/googleapis/enterprise-certificate-proxy v0.3.11 // indirect\n\tgithub.com/googleapis/gax-go/v2 v2.16.0 // indirect\n\tgithub.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 // indirect\n\tgithub.com/jackc/pgpassfile v1.0.0 // indirect\n\tgithub.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect\n\tgithub.com/jackc/puddle/v2 v2.2.2 // indirect\n\tgo.opencensus.io v0.24.0 // indirect\n\tgo.opentelemetry.io/auto/sdk v1.2.1 // indirect\n\tgo.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 // indirect\n\tgo.opentelemetry.io/otel/exporters/otlp/otlptrace v1.42.0 // indirect\n\tgo.opentelemetry.io/otel/metric v1.42.0 // indirect\n\tgo.opentelemetry.io/otel/sdk/metric v1.42.0 // indirect\n\tgo.opentelemetry.io/otel/trace v1.42.0 // indirect\n\tgo.opentelemetry.io/proto/otlp v1.9.0 // indirect\n\tgolang.org/x/crypto v0.48.0 // indirect\n\tgolang.org/x/net v0.51.0 // indirect\n\tgolang.org/x/oauth2 v0.35.0 // indirect\n\tgolang.org/x/sync v0.19.0 // indirect\n\tgolang.org/x/sys v0.41.0 // indirect\n\tgolang.org/x/text v0.34.0 // indirect\n\tgolang.org/x/time v0.14.0 // indirect\n\tgoogle.golang.org/api v0.265.0 // indirect\n\tgoogle.golang.org/genproto v0.0.0-20251202230838-ff82c1b0f217 // indirect\n\tgoogle.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57 // indirect\n\tgoogle.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 // indirect\n)\n"
  },
  {
    "path": "src/productcatalogservice/go.sum",
    "content": "cel.dev/expr v0.24.0 h1:56OvJKSH3hDGL0ml5uSxZmz3/3Pq4tJ+fb1unVLAFcY=\ncel.dev/expr v0.24.0/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw=\ncloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=\ncloud.google.com/go v0.123.0 h1:2NAUJwPR47q+E35uaJeYoNhuNEM9kM8SjgRgdeOJUSE=\ncloud.google.com/go v0.123.0/go.mod h1:xBoMV08QcqUGuPW65Qfm1o9Y4zKZBpGS+7bImXLTAZU=\ncloud.google.com/go/alloydb v1.19.0 h1:JVcIM43+ndcjdXXvOFWkLKjhF6kREQKAEtRxpsaKIQo=\ncloud.google.com/go/alloydb v1.19.0/go.mod h1:WQYERzf1+LwX5zCbmeyza4DNalaBiCN8P04POkZ/vds=\ncloud.google.com/go/alloydb v1.20.0 h1:p9SbcJhdi6s39SAIpz4lpJJTkfboSQUCwDd7go0bJ6o=\ncloud.google.com/go/alloydb v1.20.0/go.mod h1:ZwDNJOn6Z2uzzwDIlS6ctpM/EgMD1LqPzI+/ynJNaIU=\ncloud.google.com/go/alloydbconn v1.17.0 h1:3tmudS+MI4g2jkdVyJ6EeaGHEHvBtJ+iHMsu3LJX91s=\ncloud.google.com/go/alloydbconn v1.17.0/go.mod h1:XUHWgUfH5fhzZ50vIK31KBOecerzpH6NBAwEoG7fic4=\ncloud.google.com/go/alloydbconn v1.17.1 h1:S1wj+0KN6x89+VnRLMZjvCiVNBH6WSj5dP/7PGZ/dzE=\ncloud.google.com/go/alloydbconn v1.17.1/go.mod h1:vzlHhKPy1epUXIJSPPOq94NK1yFSXZ0t1uQlv8mC/ZA=\ncloud.google.com/go/alloydbconn v1.17.2 h1:VvJ+hxcs0EHI9VnQh65D8VwpjV3hFS6GT6YMo/5oC9s=\ncloud.google.com/go/alloydbconn v1.17.2/go.mod h1:VpdGJOWemdCfuNgQM7DKCAcr2H0sl+eU6V/IuEcjeuU=\ncloud.google.com/go/alloydbconn v1.17.3 h1:fz4JB72A4Q0fVfv52bDppRJtwiyoLp85xvVGibk/zB4=\ncloud.google.com/go/alloydbconn v1.17.3/go.mod h1:YOq1U+7SeiXaQUKTlZi2QR6XadKis9x6yDRiEQIrbeo=\ncloud.google.com/go/auth v0.17.0 h1:74yCm7hCj2rUyyAocqnFzsAYXgJhrG26XCFimrc/Kz4=\ncloud.google.com/go/auth v0.17.0/go.mod h1:6wv/t5/6rOPAX4fJiRjKkJCvswLwdet7G8+UGXt7nCQ=\ncloud.google.com/go/auth v0.18.0 h1:wnqy5hrv7p3k7cShwAU/Br3nzod7fxoqG+k0VZ+/Pk0=\ncloud.google.com/go/auth v0.18.0/go.mod h1:wwkPM1AgE1f2u6dG443MiWoD8C3BtOywNsUMcUTVDRo=\ncloud.google.com/go/auth v0.18.1 h1:IwTEx92GFUo2pJ6Qea0EU3zYvKnTAeRCODxfA/G5UWs=\ncloud.google.com/go/auth v0.18.1/go.mod h1:GfTYoS9G3CWpRA3Va9doKN9mjPGRS+v41jmZAhBzbrA=\ncloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc=\ncloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c=\ncloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs=\ncloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10=\ncloud.google.com/go/iam v1.5.3 h1:+vMINPiDF2ognBJ97ABAYYwRgsaqxPbQDlMnbHMjolc=\ncloud.google.com/go/iam v1.5.3/go.mod h1:MR3v9oLkZCTlaqljW6Eb2d3HGDGK5/bDv93jhfISFvU=\ncloud.google.com/go/logging v1.13.1 h1:O7LvmO0kGLaHY/gq8cV7T0dyp6zJhYAOtZPX4TF3QtY=\ncloud.google.com/go/logging v1.13.1/go.mod h1:XAQkfkMBxQRjQek96WLPNze7vsOmay9H5PqfsNYDqvw=\ncloud.google.com/go/longrunning v0.7.0 h1:FV0+SYF1RIj59gyoWDRi45GiYUMM3K1qO51qoboQT1E=\ncloud.google.com/go/longrunning v0.7.0/go.mod h1:ySn2yXmjbK9Ba0zsQqunhDkYi0+9rlXIwnoAf+h+TPY=\ncloud.google.com/go/monitoring v1.24.3 h1:dde+gMNc0UhPZD1Azu6at2e79bfdztVDS5lvhOdsgaE=\ncloud.google.com/go/monitoring v1.24.3/go.mod h1:nYP6W0tm3N9H/bOw8am7t62YTzZY+zUeQ+Bi6+2eonI=\ncloud.google.com/go/profiler v0.4.3 h1:IY3QNKlr8VbXwGWHcZbJQsMA/83ZTH6uAHf8jYyj7OI=\ncloud.google.com/go/profiler v0.4.3/go.mod h1:3xFodugWfPIQZWFcXdUmfa+yTiiyQ8fWrdT+d2Sg4J0=\ncloud.google.com/go/secretmanager v1.16.0 h1:19QT7ZsLJ8FSP1k+4esQvuCD7npMJml6hYzilxVyT+k=\ncloud.google.com/go/secretmanager v1.16.0/go.mod h1://C/e4I8D26SDTz1f3TQcddhcmiC3rMEl0S1Cakvs3Q=\ncloud.google.com/go/storage v1.56.0 h1:iixmq2Fse2tqxMbWhLWC9HfBj1qdxqAmiK8/eqtsLxI=\ncloud.google.com/go/storage v1.56.0/go.mod h1:Tpuj6t4NweCLzlNbw9Z9iwxEkrSem20AetIeH/shgVU=\ncloud.google.com/go/trace v1.11.7 h1:kDNDX8JkaAG3R2nq1lIdkb7FCSi1rCmsEtKVsty7p+U=\ncloud.google.com/go/trace v1.11.7/go.mod h1:TNn9d5V3fQVf6s4SCveVMIBS2LJUqo73GACmq/Tky0s=\ngithub.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=\ngithub.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0 h1:sBEjpZlNHzK1voKq9695PJSX2o5NEXl7/OL3coiIY0c=\ngithub.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0/go.mod h1:P4WPRUkOhJC13W//jWpyfJNDAIpvRbAUIYLX/4jtlE0=\ngithub.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.54.0 h1:lhhYARPUu3LmHysQ/igznQphfzynnqI3D75oUyw1HXk=\ngithub.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.54.0/go.mod h1:l9rva3ApbBpEJxSNYnwT9N4CDLrWgtq3u8736C5hyJw=\ngithub.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.55.0 h1:UnDZ/zFfG1JhH/DqxIZYU/1CUAlTUScoXD/LcM2Ykk8=\ngithub.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.55.0/go.mod h1:IA1C1U7jO/ENqm/vhi7V9YYpBsp+IMyqNrEN94N7tVc=\ngithub.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.54.0 h1:xfK3bbi6F2RDtaZFtUdKO3osOBIhNb+xTs8lFW6yx9o=\ngithub.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.54.0/go.mod h1:vB2GH9GAYYJTO3mEn8oYwzEdhlayZIdQz6zdzgUIRvA=\ngithub.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.54.0 h1:s0WlVbf9qpvkh1c/uDAPElam0WrL7fHRIidgZJ7UqZI=\ngithub.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.54.0/go.mod h1:Mf6O40IAyB9zR/1J8nGDDPirZQQPbYJni8Yisy7NTMc=\ngithub.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.55.0 h1:0s6TxfCu2KHkkZPnBfsQ2y5qia0jl3MMrmBhu3nCOYk=\ngithub.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.55.0/go.mod h1:Mf6O40IAyB9zR/1J8nGDDPirZQQPbYJni8Yisy7NTMc=\ngithub.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM=\ngithub.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=\ngithub.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=\ngithub.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=\ngithub.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=\ngithub.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=\ngithub.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=\ngithub.com/cncf/xds/go v0.0.0-20251022180443-0feb69152e9f h1:Y8xYupdHxryycyPlc9Y+bSQAYZnetRJ70VMVKm5CKI0=\ngithub.com/cncf/xds/go v0.0.0-20251022180443-0feb69152e9f/go.mod h1:HlzOvOjVBOfTGSRXRyY0OiCS/3J1akRGQQpRO/7zyF4=\ngithub.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=\ngithub.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=\ngithub.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=\ngithub.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=\ngithub.com/envoyproxy/go-control-plane v0.13.5-0.20251024222203-75eaa193e329 h1:K+fnvUM0VZ7ZFJf0n4L/BRlnsb9pL/GuDG6FqaH+PwM=\ngithub.com/envoyproxy/go-control-plane/envoy v1.35.0 h1:ixjkELDE+ru6idPxcHLj8LBVc2bFP7iBytj353BoHUo=\ngithub.com/envoyproxy/go-control-plane/envoy v1.35.0/go.mod h1:09qwbGVuSWWAyN5t/b3iyVfz5+z8QWGrzkoqm/8SbEs=\ngithub.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=\ngithub.com/envoyproxy/protoc-gen-validate v1.2.1 h1:DEo3O99U8j4hBFwbJfrz9VtgcDfUKS7KJ7spH3d86P8=\ngithub.com/envoyproxy/protoc-gen-validate v1.2.1/go.mod h1:d/C80l/jxXLdfEIhX1W2TmLfsJ31lvEjwamM4DxlWXU=\ngithub.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=\ngithub.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=\ngithub.com/go-jose/go-jose/v4 v4.1.3 h1:CVLmWDhDVRa6Mi/IgCgaopNosCaHz7zrMeF9MlZRkrs=\ngithub.com/go-jose/go-jose/v4 v4.1.3/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=\ngithub.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=\ngithub.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=\ngithub.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=\ngithub.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=\ngithub.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=\ngithub.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=\ngithub.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=\ngithub.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ=\ngithub.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw=\ngithub.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=\ngithub.com/golang/mock v1.7.0-rc.1 h1:YojYx61/OLFsiv6Rw1Z96LpldJIy31o+UHmwAUMJ6/U=\ngithub.com/golang/mock v1.7.0-rc.1/go.mod h1:s42URUywIqd+OcERslBJvOjepvNymP31m3q8d/GkuRs=\ngithub.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=\ngithub.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=\ngithub.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=\ngithub.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=\ngithub.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=\ngithub.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=\ngithub.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=\ngithub.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=\ngithub.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=\ngithub.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=\ngithub.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=\ngithub.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=\ngithub.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=\ngithub.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=\ngithub.com/google/pprof v0.0.0-20251114195745-4902fdda35c8 h1:3DsUAV+VNEQa2CUVLxCY3f87278uWfIDhJnbdvDjvmE=\ngithub.com/google/pprof v0.0.0-20251114195745-4902fdda35c8/go.mod h1:I6V7YzU0XDpsHqbsyrghnFZLO1gwK6NPTNvmetQIk9U=\ngithub.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0=\ngithub.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM=\ngithub.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=\ngithub.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=\ngithub.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=\ngithub.com/googleapis/enterprise-certificate-proxy v0.3.7 h1:zrn2Ee/nWmHulBx5sAVrGgAa0f2/R35S4DJwfFaUPFQ=\ngithub.com/googleapis/enterprise-certificate-proxy v0.3.7/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA=\ngithub.com/googleapis/enterprise-certificate-proxy v0.3.11 h1:vAe81Msw+8tKUxi2Dqh/NZMz7475yUvmRIkXr4oN2ao=\ngithub.com/googleapis/enterprise-certificate-proxy v0.3.11/go.mod h1:RFV7MUdlb7AgEq2v7FmMCfeSMCllAzWxFgRdusoGks8=\ngithub.com/googleapis/gax-go/v2 v2.15.0 h1:SyjDc1mGgZU5LncH8gimWo9lW1DtIfPibOG81vgd/bo=\ngithub.com/googleapis/gax-go/v2 v2.15.0/go.mod h1:zVVkkxAQHa1RQpg9z2AUCMnKhi0Qld9rcmyfL1OZhoc=\ngithub.com/googleapis/gax-go/v2 v2.16.0 h1:iHbQmKLLZrexmb0OSsNGTeSTS0HO4YvFOG8g5E4Zd0Y=\ngithub.com/googleapis/gax-go/v2 v2.16.0/go.mod h1:o1vfQjjNZn4+dPnRdl/4ZD7S9414Y4xA+a/6Icj6l14=\ngithub.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 h1:NmZ1PKzSTQbuGHw9DGPFomqkkLWMC+vZCkfs+FHv1Vg=\ngithub.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3/go.mod h1:zQrxl1YP88HQlA6i9c63DSVPFklWpGX4OWAc9bFuaH4=\ngithub.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 h1:HWRh5R2+9EifMyIHV7ZV+MIZqgz+PMpZ14Jynv3O2Zs=\ngithub.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0/go.mod h1:JfhWUomR1baixubs02l85lZYYOm7LV6om4ceouMv45c=\ngithub.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8=\ngithub.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=\ngithub.com/jackc/pgconn v1.14.3 h1:bVoTr12EGANZz66nZPkMInAV/KHD2TxH9npjXXgiB3w=\ngithub.com/jackc/pgconn v1.14.3/go.mod h1:RZbme4uasqzybK2RK5c65VsHxoyaml09lx3tXOcO/VM=\ngithub.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE=\ngithub.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8=\ngithub.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=\ngithub.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=\ngithub.com/jackc/pgproto3/v2 v2.3.3 h1:1HLSx5H+tXR9pW3in3zaztoEwQYRC9SQaYUHjTSUOag=\ngithub.com/jackc/pgproto3/v2 v2.3.3/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=\ngithub.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=\ngithub.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=\ngithub.com/jackc/pgtype v1.14.0 h1:y+xUdabmyMkJLyApYuPj38mW+aAIqCe5uuBB51rH3Vw=\ngithub.com/jackc/pgtype v1.14.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4=\ngithub.com/jackc/pgx/v4 v4.18.3 h1:dE2/TrEsGX3RBprb3qryqSV9Y60iZN1C6i8IrmW9/BA=\ngithub.com/jackc/pgx/v4 v4.18.3/go.mod h1:Ey4Oru5tH5sB6tV7hDmfWFahwF15Eb7DNXlRKx2CkVw=\ngithub.com/jackc/pgx/v5 v5.7.6 h1:rWQc5FwZSPX58r1OQmkuaNicxdmExaEz5A2DO2hUuTk=\ngithub.com/jackc/pgx/v5 v5.7.6/go.mod h1:aruU7o91Tc2q2cFp5h4uP3f6ztExVpyVv88Xl/8Vl8M=\ngithub.com/jackc/pgx/v5 v5.8.0 h1:TYPDoleBBme0xGSAX3/+NujXXtpZn9HBONkQC7IEZSo=\ngithub.com/jackc/pgx/v5 v5.8.0/go.mod h1:QVeDInX2m9VyzvNeiCJVjCkNFqzsNb43204HshNSZKw=\ngithub.com/jackc/puddle v1.3.0 h1:eHK/5clGOatcjX3oWGBO/MpxpbHzSwud5EWTSCI+MX0=\ngithub.com/jackc/puddle v1.3.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=\ngithub.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=\ngithub.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=\ngithub.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=\ngithub.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\ngithub.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo=\ngithub.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8=\ngithub.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=\ngithub.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=\ngithub.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=\ngithub.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=\ngithub.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w=\ngithub.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g=\ngithub.com/spiffe/go-spiffe/v2 v2.6.0 h1:l+DolpxNWYgruGQVV0xsfeya3CsC7m8iBzDnMpsbLuo=\ngithub.com/spiffe/go-spiffe/v2 v2.6.0/go.mod h1:gm2SeUoMZEtpnzPNs2Csc0D/gX33k1xIx7lEzqblHEs=\ngithub.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=\ngithub.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=\ngithub.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=\ngithub.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngithub.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngithub.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=\ngithub.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=\ngithub.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=\ngithub.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=\ngo.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=\ngo.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=\ngo.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=\ngo.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=\ngo.opentelemetry.io/contrib/detectors/gcp v1.38.0 h1:ZoYbqX7OaA/TAikspPl3ozPI6iY6LiIY9I8cUfm+pJs=\ngo.opentelemetry.io/contrib/detectors/gcp v1.38.0/go.mod h1:SU+iU7nu5ud4oCb3LQOhIZ3nRLj6FNVrKgtflbaf2ts=\ngo.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0 h1:YH4g8lQroajqUwWbq/tr2QX1JFmEXaDLgG+ew9bLMWo=\ngo.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0/go.mod h1:fvPi2qXDqFs8M4B4fmJhE92TyQs9Ydjlg3RvfUp+NbQ=\ngo.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.64.0 h1:RN3ifU8y4prNWeEnQp2kRRHz8UwonAEYZl8tUzHEXAk=\ngo.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.64.0/go.mod h1:habDz3tEWiFANTo6oUE99EmaFUrCNYAAg3wiVmusm70=\ngo.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0 h1:yI1/OhfEPy7J9eoa6Sj051C7n5dvpj0QX8g4sRchg04=\ngo.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0/go.mod h1:NoUCKYWK+3ecatC4HjkRktREheMeEtrXoQxrqYFeHSc=\ngo.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 h1:RbKq8BG0FI8OiXhBfcRtqqHcZcka+gU3cskNuf05R18=\ngo.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0/go.mod h1:h06DGIukJOevXaj/xrNjhi/2098RZzcLTbc0jDAUbsg=\ngo.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8=\ngo.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM=\ngo.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48=\ngo.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8=\ngo.opentelemetry.io/otel v1.42.0 h1:lSQGzTgVR3+sgJDAU/7/ZMjN9Z+vUip7leaqBKy4sho=\ngo.opentelemetry.io/otel v1.42.0/go.mod h1:lJNsdRMxCUIWuMlVJWzecSMuNjE7dOYyWlqOXWkdqCc=\ngo.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 h1:GqRJVj7UmLjCVyVJ3ZFLdPRmhDUp2zFmQe3RHIOsw24=\ngo.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0/go.mod h1:ri3aaHSmCTVYu2AWv44YMauwAQc0aqI9gHKIcSbI1pU=\ngo.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0 h1:f0cb2XPmrqn4XMy9PNliTgRKJgS5WcL/u0/WRYGz4t0=\ngo.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0/go.mod h1:vnakAaFckOMiMtOIhFI2MNH4FYrZzXCYxmb1LlhoGz8=\ngo.opentelemetry.io/otel/exporters/otlp/otlptrace v1.42.0 h1:THuZiwpQZuHPul65w4WcwEnkX2QIuMT+UFoOrygtoJw=\ngo.opentelemetry.io/otel/exporters/otlp/otlptrace v1.42.0/go.mod h1:J2pvYM5NGHofZ2/Ru6zw/TNWnEQp5crgyDeSrYpXkAw=\ngo.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 h1:lwI4Dc5leUqENgGuQImwLo4WnuXFPetmPpkLi2IrX54=\ngo.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0/go.mod h1:Kz/oCE7z5wuyhPxsXDuaPteSWqjSBD5YaSdbxZYGbGk=\ngo.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0 h1:in9O8ESIOlwJAEGTkkf34DesGRAc/Pn8qJ7k3r/42LM=\ngo.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0/go.mod h1:Rp0EXBm5tfnv0WL+ARyO/PHBEaEAT8UUHQ6AGJcSq6c=\ngo.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.42.0 h1:zWWrB1U6nqhS/k6zYB74CjRpuiitRtLLi68VcgmOEto=\ngo.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.42.0/go.mod h1:2qXPNBX1OVRC0IwOnfo1ljoid+RD0QK3443EaqVlsOU=\ngo.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA=\ngo.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI=\ngo.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0=\ngo.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs=\ngo.opentelemetry.io/otel/metric v1.42.0 h1:2jXG+3oZLNXEPfNmnpxKDeZsFI5o4J+nz6xUlaFdF/4=\ngo.opentelemetry.io/otel/metric v1.42.0/go.mod h1:RlUN/7vTU7Ao/diDkEpQpnz3/92J9ko05BIwxYa2SSI=\ngo.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E=\ngo.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg=\ngo.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18=\ngo.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE=\ngo.opentelemetry.io/otel/sdk v1.42.0 h1:LyC8+jqk6UJwdrI/8VydAq/hvkFKNHZVIWuslJXYsDo=\ngo.opentelemetry.io/otel/sdk v1.42.0/go.mod h1:rGHCAxd9DAph0joO4W6OPwxjNTYWghRWmkHuGbayMts=\ngo.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM=\ngo.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA=\ngo.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8=\ngo.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew=\ngo.opentelemetry.io/otel/sdk/metric v1.42.0 h1:D/1QR46Clz6ajyZ3G8SgNlTJKBdGp84q9RKCAZ3YGuA=\ngo.opentelemetry.io/otel/sdk/metric v1.42.0/go.mod h1:Ua6AAlDKdZ7tdvaQKfSmnFTdHx37+J4ba8MwVCYM5hc=\ngo.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE=\ngo.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs=\ngo.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI=\ngo.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA=\ngo.opentelemetry.io/otel/trace v1.42.0 h1:OUCgIPt+mzOnaUTpOQcBiM/PLQ/Op7oq6g4LenLmOYY=\ngo.opentelemetry.io/otel/trace v1.42.0/go.mod h1:f3K9S+IFqnumBkKhRJMeaZeNk9epyhnCmQh/EysQCdc=\ngo.opentelemetry.io/proto/otlp v1.9.0 h1:l706jCMITVouPOqEnii2fIAuO3IVGBRPV5ICjceRb/A=\ngo.opentelemetry.io/proto/otlp v1.9.0/go.mod h1:xE+Cx5E/eEHw+ISFkwPLwCZefwVjY+pqKg1qcK03+/4=\ngo.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=\ngo.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=\ngolang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=\ngolang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=\ngolang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=\ngolang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=\ngolang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8=\ngolang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A=\ngolang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=\ngolang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=\ngolang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=\ngolang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=\ngolang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=\ngolang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=\ngolang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=\ngolang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=\ngolang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=\ngolang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o=\ngolang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8=\ngolang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=\ngolang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=\ngolang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=\ngolang.org/x/oauth2 v0.33.0 h1:4Q+qn+E5z8gPRJfmRy7C2gGG3T4jIprK6aSYgTXGRpo=\ngolang.org/x/oauth2 v0.33.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=\ngolang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw=\ngolang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=\ngolang.org/x/oauth2 v0.35.0 h1:Mv2mzuHuZuY2+bkyWXIHMfhNdJAdwW3FuWeCPYN5GVQ=\ngolang.org/x/oauth2 v0.35.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=\ngolang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=\ngolang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=\ngolang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=\ngolang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=\ngolang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=\ngolang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=\ngolang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=\ngolang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=\ngolang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=\ngolang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=\ngolang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=\ngolang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=\ngolang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=\ngolang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=\ngolang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=\ngolang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8=\ngolang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=\ngolang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=\ngolang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=\ngolang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=\ngolang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=\ngolang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=\ngolang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=\ngolang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=\ngonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=\ngoogle.golang.org/api v0.256.0 h1:u6Khm8+F9sxbCTYNoBHg6/Hwv0N/i+V94MvkOSor6oI=\ngoogle.golang.org/api v0.256.0/go.mod h1:KIgPhksXADEKJlnEoRa9qAII4rXcy40vfI8HRqcU964=\ngoogle.golang.org/api v0.257.0 h1:8Y0lzvHlZps53PEaw+G29SsQIkuKrumGWs9puiexNAA=\ngoogle.golang.org/api v0.257.0/go.mod h1:4eJrr+vbVaZSqs7vovFd1Jb/A6ml6iw2e6FBYf3GAO4=\ngoogle.golang.org/api v0.259.0 h1:90TaGVIxScrh1Vn/XI2426kRpBqHwWIzVBzJsVZ5XrQ=\ngoogle.golang.org/api v0.259.0/go.mod h1:LC2ISWGWbRoyQVpxGntWwLWN/vLNxxKBK9KuJRI8Te4=\ngoogle.golang.org/api v0.265.0 h1:FZvfUdI8nfmuNrE34aOWFPmLC+qRBEiNm3JdivTvAAU=\ngoogle.golang.org/api v0.265.0/go.mod h1:uAvfEl3SLUj/7n6k+lJutcswVojHPp2Sp08jWCu8hLY=\ngoogle.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=\ngoogle.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=\ngoogle.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=\ngoogle.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=\ngoogle.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=\ngoogle.golang.org/genproto v0.0.0-20251124214823-79d6a2a48846 h1:dDbsTLIK7EzwUq36kCSAsk0slouq/S0tWHeeGi97cD8=\ngoogle.golang.org/genproto v0.0.0-20251124214823-79d6a2a48846/go.mod h1:PP0g88Dz3C7hRAfbQCQggeWAXjuqGsNPLE4s7jh0RGU=\ngoogle.golang.org/genproto v0.0.0-20251202230838-ff82c1b0f217 h1:GvESR9BIyHUahIb0NcTum6itIWtdoglGX+rnGxm2934=\ngoogle.golang.org/genproto v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:yJ2HH4EHEDTd3JiLmhds6NkJ17ITVYOdV3m3VKOnws0=\ngoogle.golang.org/genproto/googleapis/api v0.0.0-20251124214823-79d6a2a48846 h1:ZdyUkS9po3H7G0tuh955QVyyotWvOD4W0aEapeGeUYk=\ngoogle.golang.org/genproto/googleapis/api v0.0.0-20251124214823-79d6a2a48846/go.mod h1:Fk4kyraUvqD7i5H6S43sj2W98fbZa75lpZz/eUyhfO0=\ngoogle.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 h1:fCvbg86sFXwdrl5LgVcTEvNC+2txB5mgROGmRL5mrls=\ngoogle.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:+rXWjjaukWZun3mLfjmVnQi18E1AsFbDN9QdJ5YXLto=\ngoogle.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57 h1:JLQynH/LBHfCTSbDWl+py8C+Rg/k1OVH3xfcaiANuF0=\ngoogle.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57/go.mod h1:kSJwQxqmFXeo79zOmbrALdflXQeAYcUbgS7PbpMknCY=\ngoogle.golang.org/genproto/googleapis/rpc v0.0.0-20251124214823-79d6a2a48846 h1:Wgl1rcDNThT+Zn47YyCXOXyX/COgMTIdhJ717F0l4xk=\ngoogle.golang.org/genproto/googleapis/rpc v0.0.0-20251124214823-79d6a2a48846/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=\ngoogle.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 h1:gRkg/vSppuSQoDjxyiGfN4Upv/h/DQmIR10ZU8dh4Ww=\ngoogle.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=\ngoogle.golang.org/genproto/googleapis/rpc v0.0.0-20260112192933-99fd39fd28a9 h1:IY6/YYRrFUk0JPp0xOVctvFIVuRnjccihY5kxf5g0TE=\ngoogle.golang.org/genproto/googleapis/rpc v0.0.0-20260112192933-99fd39fd28a9/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=\ngoogle.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 h1:ggcbiqK8WWh6l1dnltU4BgWGIGo+EVYxCaAPih/zQXQ=\ngoogle.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=\ngoogle.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=\ngoogle.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=\ngoogle.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=\ngoogle.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=\ngoogle.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=\ngoogle.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM=\ngoogle.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig=\ngoogle.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc=\ngoogle.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U=\ngoogle.golang.org/grpc v1.79.2 h1:fRMD94s2tITpyJGtBBn7MkMseNpOZU8ZxgC3MMBaXRU=\ngoogle.golang.org/grpc v1.79.2/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=\ngoogle.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=\ngoogle.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=\ngoogle.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=\ngoogle.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=\ngoogle.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=\ngoogle.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=\ngoogle.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=\ngoogle.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=\ngoogle.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=\ngoogle.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=\ngopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\nhonnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\n"
  },
  {
    "path": "src/productcatalogservice/product_catalog.go",
    "content": "// Copyright 2023 Google LLC\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage main\n\nimport (\n\t\"context\"\n\t\"strings\"\n\t\"time\"\n\n\tpb \"github.com/GoogleCloudPlatform/microservices-demo/src/productcatalogservice/genproto\"\n\t\"google.golang.org/grpc/codes\"\n\thealthpb \"google.golang.org/grpc/health/grpc_health_v1\"\n\t\"google.golang.org/grpc/status\"\n)\n\ntype productCatalog struct {\n\tpb.UnimplementedProductCatalogServiceServer\n\tcatalog pb.ListProductsResponse\n}\n\nfunc (p *productCatalog) Check(ctx context.Context, req *healthpb.HealthCheckRequest) (*healthpb.HealthCheckResponse, error) {\n\treturn &healthpb.HealthCheckResponse{Status: healthpb.HealthCheckResponse_SERVING}, nil\n}\n\nfunc (p *productCatalog) Watch(req *healthpb.HealthCheckRequest, ws healthpb.Health_WatchServer) error {\n\treturn status.Errorf(codes.Unimplemented, \"health check via Watch not implemented\")\n}\n\nfunc (p *productCatalog) ListProducts(context.Context, *pb.Empty) (*pb.ListProductsResponse, error) {\n\ttime.Sleep(extraLatency)\n\n\treturn &pb.ListProductsResponse{Products: p.parseCatalog()}, nil\n}\n\nfunc (p *productCatalog) GetProduct(ctx context.Context, req *pb.GetProductRequest) (*pb.Product, error) {\n\ttime.Sleep(extraLatency)\n\n\tvar found *pb.Product\n\tfor i := 0; i < len(p.parseCatalog()); i++ {\n\t\tif req.Id == p.parseCatalog()[i].Id {\n\t\t\tfound = p.parseCatalog()[i]\n\t\t}\n\t}\n\n\tif found == nil {\n\t\treturn nil, status.Errorf(codes.NotFound, \"no product with ID %s\", req.Id)\n\t}\n\treturn found, nil\n}\n\nfunc (p *productCatalog) SearchProducts(ctx context.Context, req *pb.SearchProductsRequest) (*pb.SearchProductsResponse, error) {\n\ttime.Sleep(extraLatency)\n\n\tvar ps []*pb.Product\n\tfor _, product := range p.parseCatalog() {\n\t\tif strings.Contains(strings.ToLower(product.Name), strings.ToLower(req.Query)) ||\n\t\t\tstrings.Contains(strings.ToLower(product.Description), strings.ToLower(req.Query)) {\n\t\t\tps = append(ps, product)\n\t\t}\n\t}\n\n\treturn &pb.SearchProductsResponse{Results: ps}, nil\n}\n\nfunc (p *productCatalog) parseCatalog() []*pb.Product {\n\tif reloadCatalog || len(p.catalog.Products) == 0 {\n\t\terr := loadCatalog(&p.catalog)\n\t\tif err != nil {\n\t\t\treturn []*pb.Product{}\n\t\t}\n\t}\n\n\treturn p.catalog.Products\n}\n"
  },
  {
    "path": "src/productcatalogservice/product_catalog_test.go",
    "content": "// Copyright 2023 Google LLC\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage main\n\nimport (\n\t\"context\"\n\t\"os\"\n\t\"testing\"\n\n\tpb \"github.com/GoogleCloudPlatform/microservices-demo/src/productcatalogservice/genproto\"\n\t\"google.golang.org/grpc/codes\"\n\t\"google.golang.org/grpc/status\"\n)\n\nvar (\n\tmockProductCatalog *productCatalog\n)\n\nfunc TestMain(m *testing.M) {\n\tmockProductCatalog = &productCatalog{\n\t\tcatalog: pb.ListProductsResponse{\n\t\t\tProducts: []*pb.Product{},\n\t\t},\n\t}\n\n\tmockProductCatalog.catalog.Products = append(mockProductCatalog.catalog.Products, &pb.Product{\n\t\tId:   \"abc001\",\n\t\tName: \"Product Alpha One\",\n\t})\n\tmockProductCatalog.catalog.Products = append(mockProductCatalog.catalog.Products, &pb.Product{\n\t\tId:   \"abc002\",\n\t\tName: \"Product Delta\",\n\t})\n\tmockProductCatalog.catalog.Products = append(mockProductCatalog.catalog.Products, &pb.Product{\n\t\tId:   \"abc003\",\n\t\tName: \"Product Alpha Two\",\n\t})\n\tmockProductCatalog.catalog.Products = append(mockProductCatalog.catalog.Products, &pb.Product{\n\t\tId:   \"abc004\",\n\t\tName: \"Product Gamma\",\n\t})\n\n\tos.Exit(m.Run())\n}\n\nfunc TestGetProductExists(t *testing.T) {\n\tproduct, err := mockProductCatalog.GetProduct(context.Background(),\n\t\t&pb.GetProductRequest{Id: \"abc003\"},\n\t)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif got, want := product.Name, \"Product Alpha Two\"; got != want {\n\t\tt.Errorf(\"got %s, want %s\", got, want)\n\t}\n}\n\nfunc TestGetProductNotFound(t *testing.T) {\n\t_, err := mockProductCatalog.GetProduct(context.Background(),\n\t\t&pb.GetProductRequest{Id: \"abc005\"},\n\t)\n\tif got, want := status.Code(err), codes.NotFound; got != want {\n\t\tt.Errorf(\"got %s, want %s\", got, want)\n\t}\n}\n\nfunc TestListProducts(t *testing.T) {\n\tproducts, err := mockProductCatalog.ListProducts(context.Background(),\n\t\t&pb.Empty{},\n\t)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif got, want := len(products.Products), 4; got != want {\n\t\tt.Errorf(\"got %d, want %d\", got, want)\n\t}\n}\n\nfunc TestSearchProducts(t *testing.T) {\n\tproducts, err := mockProductCatalog.SearchProducts(context.Background(),\n\t\t&pb.SearchProductsRequest{Query: \"alpha\"},\n\t)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif got, want := len(products.Results), 2; got != want {\n\t\tt.Errorf(\"got %d, want %d\", got, want)\n\t}\n}\n"
  },
  {
    "path": "src/productcatalogservice/products.json",
    "content": "{\n    \"products\": [\n        {\n            \"id\": \"OLJCESPC7Z\",\n            \"name\": \"Sunglasses\",\n            \"description\": \"Add a modern touch to your outfits with these sleek aviator sunglasses.\",\n            \"picture\": \"/static/img/products/sunglasses.jpg\",\n            \"priceUsd\": {\n                \"currencyCode\": \"USD\",\n                \"units\": 19,\n                \"nanos\": 990000000\n            },\n            \"categories\": [\"accessories\"]\n        },\n        {\n            \"id\": \"66VCHSJNUP\",\n            \"name\": \"Tank Top\",\n            \"description\": \"Perfectly cropped cotton tank, with a scooped neckline.\",\n            \"picture\": \"/static/img/products/tank-top.jpg\",\n            \"priceUsd\": {\n                \"currencyCode\": \"USD\",\n                \"units\": 18,\n                \"nanos\": 990000000\n            },\n            \"categories\": [\"clothing\", \"tops\"]\n        },\n        {\n            \"id\": \"1YMWWN1N4O\",\n            \"name\": \"Watch\",\n            \"description\": \"This gold-tone stainless steel watch will work with most of your outfits.\",\n            \"picture\": \"/static/img/products/watch.jpg\",\n            \"priceUsd\": {\n                \"currencyCode\": \"USD\",\n                \"units\": 109,\n                \"nanos\": 990000000\n            },\n            \"categories\": [\"accessories\"]\n        },\n        {\n            \"id\": \"L9ECAV7KIM\",\n            \"name\": \"Loafers\",\n            \"description\": \"A neat addition to your summer wardrobe.\",\n            \"picture\": \"/static/img/products/loafers.jpg\",\n            \"priceUsd\": {\n                \"currencyCode\": \"USD\",\n                \"units\": 89,\n                \"nanos\": 990000000\n            },\n            \"categories\": [\"footwear\"]\n        },\n        {\n            \"id\": \"2ZYFJ3GM2N\",\n            \"name\": \"Hairdryer\",\n            \"description\": \"This lightweight hairdryer has 3 heat and speed settings. It's perfect for travel.\",\n            \"picture\": \"/static/img/products/hairdryer.jpg\",\n            \"priceUsd\": {\n                \"currencyCode\": \"USD\",\n                \"units\": 24,\n                \"nanos\": 990000000\n            },\n            \"categories\": [\"hair\", \"beauty\"]\n        },\n        {\n            \"id\": \"0PUK6V6EV0\",\n            \"name\": \"Candle Holder\",\n            \"description\": \"This small but intricate candle holder is an excellent gift.\",\n            \"picture\": \"/static/img/products/candle-holder.jpg\",\n            \"priceUsd\": {\n                \"currencyCode\": \"USD\",\n                \"units\": 18,\n                \"nanos\": 990000000\n            },\n            \"categories\": [\"decor\", \"home\"]\n        },\n        {\n            \"id\": \"LS4PSXUNUM\",\n            \"name\": \"Salt & Pepper Shakers\",\n            \"description\": \"Add some flavor to your kitchen.\",\n            \"picture\": \"/static/img/products/salt-and-pepper-shakers.jpg\",\n            \"priceUsd\": {\n                \"currencyCode\": \"USD\",\n                \"units\": 18,\n                \"nanos\": 490000000\n            },\n            \"categories\": [\"kitchen\"]\n        },\n        {\n            \"id\": \"9SIQT8TOJO\",\n            \"name\": \"Bamboo Glass Jar\",\n            \"description\": \"This bamboo glass jar can hold 57 oz (1.7 l) and is perfect for any kitchen.\",\n            \"picture\": \"/static/img/products/bamboo-glass-jar.jpg\",\n            \"priceUsd\": {\n                \"currencyCode\": \"USD\",\n                \"units\": 5,\n                \"nanos\": 490000000\n            },\n            \"categories\": [\"kitchen\"]\n        },\n        {\n            \"id\": \"6E92ZMYYFZ\",\n            \"name\": \"Mug\",\n            \"description\": \"A simple mug with a mustard interior.\",\n            \"picture\": \"/static/img/products/mug.jpg\",\n            \"priceUsd\": {\n                \"currencyCode\": \"USD\",\n                \"units\": 8,\n                \"nanos\": 990000000\n            },\n            \"categories\": [\"kitchen\"]\n        }\n    ]\n}\n"
  },
  {
    "path": "src/productcatalogservice/server.go",
    "content": "// Copyright 2018 Google LLC\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage main\n\nimport (\n\t\"context\"\n\t\"flag\"\n\t\"fmt\"\n\t\"net\"\n\t\"os\"\n\t\"os/signal\"\n\t\"sync\"\n\t\"syscall\"\n\t\"time\"\n\n\tpb \"github.com/GoogleCloudPlatform/microservices-demo/src/productcatalogservice/genproto\"\n\t\"google.golang.org/grpc/credentials/insecure\"\n\t\"google.golang.org/grpc/health\"\n\thealthpb \"google.golang.org/grpc/health/grpc_health_v1\"\n\n\t\"cloud.google.com/go/profiler\"\n\t\"github.com/pkg/errors\"\n\t\"github.com/sirupsen/logrus\"\n\t\"go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc\"\n\t\"go.opentelemetry.io/otel\"\n\t\"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc\"\n\t\"go.opentelemetry.io/otel/propagation\"\n\tsdktrace \"go.opentelemetry.io/otel/sdk/trace\"\n\t\"google.golang.org/grpc\"\n)\n\nvar (\n\tcatalogMutex *sync.Mutex\n\tlog          *logrus.Logger\n\textraLatency time.Duration\n\n\tport = \"3550\"\n\n\treloadCatalog bool\n)\n\nfunc init() {\n\tlog = logrus.New()\n\tlog.Formatter = &logrus.JSONFormatter{\n\t\tFieldMap: logrus.FieldMap{\n\t\t\tlogrus.FieldKeyTime:  \"timestamp\",\n\t\t\tlogrus.FieldKeyLevel: \"severity\",\n\t\t\tlogrus.FieldKeyMsg:   \"message\",\n\t\t},\n\t\tTimestampFormat: time.RFC3339Nano,\n\t}\n\tlog.Out = os.Stdout\n\tcatalogMutex = &sync.Mutex{}\n}\n\nfunc main() {\n\tif os.Getenv(\"ENABLE_TRACING\") == \"1\" {\n\t\terr := initTracing()\n\t\tif err != nil {\n\t\t\tlog.Warnf(\"warn: failed to start tracer: %+v\", err)\n\t\t}\n\t} else {\n\t\tlog.Info(\"Tracing disabled.\")\n\t}\n\n\tif os.Getenv(\"DISABLE_PROFILER\") == \"\" {\n\t\tlog.Info(\"Profiling enabled.\")\n\t\tgo initProfiling(\"productcatalogservice\", \"1.0.0\")\n\t} else {\n\t\tlog.Info(\"Profiling disabled.\")\n\t}\n\n\tflag.Parse()\n\n\t// set injected latency\n\tif s := os.Getenv(\"EXTRA_LATENCY\"); s != \"\" {\n\t\tv, err := time.ParseDuration(s)\n\t\tif err != nil {\n\t\t\tlog.Fatalf(\"failed to parse EXTRA_LATENCY (%s) as time.Duration: %+v\", v, err)\n\t\t}\n\t\textraLatency = v\n\t\tlog.Infof(\"extra latency enabled (duration: %v)\", extraLatency)\n\t} else {\n\t\textraLatency = time.Duration(0)\n\t}\n\n\tsigs := make(chan os.Signal, 1)\n\tsignal.Notify(sigs, syscall.SIGUSR1, syscall.SIGUSR2)\n\tgo func() {\n\t\tfor {\n\t\t\tsig := <-sigs\n\t\t\tlog.Printf(\"Received signal: %s\", sig)\n\t\t\tif sig == syscall.SIGUSR1 {\n\t\t\t\treloadCatalog = true\n\t\t\t\tlog.Infof(\"Enable catalog reloading\")\n\t\t\t} else {\n\t\t\t\treloadCatalog = false\n\t\t\t\tlog.Infof(\"Disable catalog reloading\")\n\t\t\t}\n\t\t}\n\t}()\n\n\tif os.Getenv(\"PORT\") != \"\" {\n\t\tport = os.Getenv(\"PORT\")\n\t}\n\tlog.Infof(\"starting grpc server at :%s\", port)\n\trun(port)\n\tselect {}\n}\n\nfunc run(port string) string {\n\tlistener, err := net.Listen(\"tcp\", fmt.Sprintf(\":%s\", port))\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\t// Propagate trace context\n\totel.SetTextMapPropagator(\n\t\tpropagation.NewCompositeTextMapPropagator(\n\t\t\tpropagation.TraceContext{}, propagation.Baggage{}))\n\tvar srv *grpc.Server\n\tsrv = grpc.NewServer(\n\t\tgrpc.StatsHandler(otelgrpc.NewServerHandler()))\n\n\tsvc := &productCatalog{}\n\terr = loadCatalog(&svc.catalog)\n\tif err != nil {\n\t\tlog.Fatalf(\"could not parse product catalog: %v\", err)\n\t}\n\n\tpb.RegisterProductCatalogServiceServer(srv, svc)\n\thealthcheck := health.NewServer()\n\thealthpb.RegisterHealthServer(srv, healthcheck)\n\tgo srv.Serve(listener)\n\n\treturn listener.Addr().String()\n}\n\nfunc initStats() {\n\t// TODO(drewbr) Implement OpenTelemetry stats\n}\n\nfunc initTracing() error {\n\tvar (\n\t\tcollectorAddr string\n\t\tcollectorConn *grpc.ClientConn\n\t)\n\n\tctx := context.Background()\n\n\tmustMapEnv(&collectorAddr, \"COLLECTOR_SERVICE_ADDR\")\n\tmustConnGRPC(ctx, &collectorConn, collectorAddr)\n\n\texporter, err := otlptracegrpc.New(\n\t\tctx,\n\t\totlptracegrpc.WithGRPCConn(collectorConn))\n\tif err != nil {\n\t\tlog.Warnf(\"warn: Failed to create trace exporter: %v\", err)\n\t}\n\ttp := sdktrace.NewTracerProvider(\n\t\tsdktrace.WithBatcher(exporter),\n\t\tsdktrace.WithSampler(sdktrace.AlwaysSample()))\n\totel.SetTracerProvider(tp)\n\treturn err\n}\n\nfunc initProfiling(service, version string) {\n\tfor i := 1; i <= 3; i++ {\n\t\tif err := profiler.Start(profiler.Config{\n\t\t\tService:        service,\n\t\t\tServiceVersion: version,\n\t\t\t// ProjectID must be set if not running on GCP.\n\t\t\t// ProjectID: \"my-project\",\n\t\t}); err != nil {\n\t\t\tlog.Warnf(\"failed to start profiler: %+v\", err)\n\t\t} else {\n\t\t\tlog.Info(\"started Stackdriver profiler\")\n\t\t\treturn\n\t\t}\n\t\td := time.Second * 10 * time.Duration(i)\n\t\tlog.Infof(\"sleeping %v to retry initializing Stackdriver profiler\", d)\n\t\ttime.Sleep(d)\n\t}\n\tlog.Warn(\"could not initialize Stackdriver profiler after retrying, giving up\")\n}\n\nfunc mustMapEnv(target *string, envKey string) {\n\tv := os.Getenv(envKey)\n\tif v == \"\" {\n\t\tpanic(fmt.Sprintf(\"environment variable %q not set\", envKey))\n\t}\n\t*target = v\n}\n\nfunc mustConnGRPC(ctx context.Context, conn **grpc.ClientConn, addr string) {\n\tvar err error\n\t_, cancel := context.WithTimeout(ctx, time.Second*3)\n\tdefer cancel()\n\t*conn, err = grpc.NewClient(addr,\n\t\tgrpc.WithTransportCredentials(insecure.NewCredentials()),\n\t\tgrpc.WithStatsHandler(otelgrpc.NewClientHandler()))\n\tif err != nil {\n\t\tpanic(errors.Wrapf(err, \"grpc: failed to connect %s\", addr))\n\t}\n}\n"
  },
  {
    "path": "src/recommendationservice/Dockerfile",
    "content": "# Copyright 2020 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# Define a default value so it's not empty if the builder fails to provide it\nARG BUILDPLATFORM=linux/amd64\n\nFROM --platform=$BUILDPLATFORM python:3.14.3-alpine@sha256:faee120f7885a06fcc9677922331391fa690d911c020abb9e8025ff3d908e510 AS base\n\nFROM base AS builder\n\nENV PYTHONDONTWRITEBYTECODE=1\nENV PYTHONUNBUFFERED=1\n\nRUN apk update \\\n    && apk add --no-cache g++ linux-headers \\\n    && rm -rf /var/cache/apk/*\n\n# get packages\nCOPY requirements.txt .\nRUN pip install -r requirements.txt\n\nFROM base\n\nENV PYTHONDONTWRITEBYTECODE=1\nENV PYTHONUNBUFFERED=1\n\nRUN apk update \\\n    && apk add --no-cache libstdc++ \\\n    && rm -rf /var/cache/apk/*\n\n# get packages\nWORKDIR /recommendationservice\n\n# Grab packages from builder\nCOPY --from=builder /usr/local/lib/python3.14/ /usr/local/lib/python3.14/\n\n# Add the application\nCOPY . .\n\n# set listen port\nENV PORT=\"8080\"\nEXPOSE 8080\n\nENTRYPOINT [\"python\", \"recommendation_server.py\"]\n"
  },
  {
    "path": "src/recommendationservice/client.py",
    "content": "#!/usr/bin/python\n#\n# Copyright 2018 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport sys\nimport grpc\nimport demo_pb2\nimport demo_pb2_grpc\n\nfrom logger import getJSONLogger\nlogger = getJSONLogger('recommendationservice-server')\n\nif __name__ == \"__main__\":\n    # get port\n    if len(sys.argv) > 1:\n        port = sys.argv[1]\n    else:\n        port = \"8080\"\n\n    # set up server stub\n    channel = grpc.insecure_channel('localhost:'+port)\n    stub = demo_pb2_grpc.RecommendationServiceStub(channel)\n    # form request\n    request = demo_pb2.ListRecommendationsRequest(user_id=\"test\", product_ids=[\"test\"])\n    # make call to server\n    response = stub.ListRecommendations(request)\n    logger.info(response)\n"
  },
  {
    "path": "src/recommendationservice/demo_pb2.py",
    "content": "#!/usr/bin/python\n#\n# Copyright 2018 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      https://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# -*- coding: utf-8 -*-\n# Generated by the protocol buffer compiler.  DO NOT EDIT!\n# source: demo.proto\n\"\"\"Generated protocol buffer code.\"\"\"\nfrom google.protobuf.internal import builder as _builder\nfrom google.protobuf import descriptor as _descriptor\nfrom google.protobuf import descriptor_pool as _descriptor_pool\nfrom google.protobuf import symbol_database as _symbol_database\n# @@protoc_insertion_point(imports)\n\n_sym_db = _symbol_database.Default()\n\n\n\n\nDESCRIPTOR = _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')\n\n_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals())\n_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'demo_pb2', globals())\nif _descriptor._USE_C_DESCRIPTORS == False:\n\n  DESCRIPTOR._options = None\n  _CARTITEM._serialized_start=27\n  _CARTITEM._serialized_end=75\n  _ADDITEMREQUEST._serialized_start=77\n  _ADDITEMREQUEST._serialized_end=147\n  _EMPTYCARTREQUEST._serialized_start=149\n  _EMPTYCARTREQUEST._serialized_end=184\n  _GETCARTREQUEST._serialized_start=186\n  _GETCARTREQUEST._serialized_end=219\n  _CART._serialized_start=221\n  _CART._serialized_end=282\n  _EMPTY._serialized_start=284\n  _EMPTY._serialized_end=291\n  _LISTRECOMMENDATIONSREQUEST._serialized_start=293\n  _LISTRECOMMENDATIONSREQUEST._serialized_end=359\n  _LISTRECOMMENDATIONSRESPONSE._serialized_start=361\n  _LISTRECOMMENDATIONSRESPONSE._serialized_end=411\n  _PRODUCT._serialized_start=414\n  _PRODUCT._serialized_end=546\n  _LISTPRODUCTSRESPONSE._serialized_start=548\n  _LISTPRODUCTSRESPONSE._serialized_end=610\n  _GETPRODUCTREQUEST._serialized_start=612\n  _GETPRODUCTREQUEST._serialized_end=643\n  _SEARCHPRODUCTSREQUEST._serialized_start=645\n  _SEARCHPRODUCTSREQUEST._serialized_end=683\n  _SEARCHPRODUCTSRESPONSE._serialized_start=685\n  _SEARCHPRODUCTSRESPONSE._serialized_end=748\n  _GETQUOTEREQUEST._serialized_start=750\n  _GETQUOTEREQUEST._serialized_end=844\n  _GETQUOTERESPONSE._serialized_start=846\n  _GETQUOTERESPONSE._serialized_end=902\n  _SHIPORDERREQUEST._serialized_start=904\n  _SHIPORDERREQUEST._serialized_end=999\n  _SHIPORDERRESPONSE._serialized_start=1001\n  _SHIPORDERRESPONSE._serialized_end=1041\n  _ADDRESS._serialized_start=1043\n  _ADDRESS._serialized_end=1140\n  _MONEY._serialized_start=1142\n  _MONEY._serialized_end=1202\n  _GETSUPPORTEDCURRENCIESRESPONSE._serialized_start=1204\n  _GETSUPPORTEDCURRENCIESRESPONSE._serialized_end=1260\n  _CURRENCYCONVERSIONREQUEST._serialized_start=1262\n  _CURRENCYCONVERSIONREQUEST._serialized_end=1340\n  _CREDITCARDINFO._serialized_start=1343\n  _CREDITCARDINFO._serialized_end=1487\n  _CHARGEREQUEST._serialized_start=1489\n  _CHARGEREQUEST._serialized_end=1590\n  _CHARGERESPONSE._serialized_start=1592\n  _CHARGERESPONSE._serialized_end=1632\n  _ORDERITEM._serialized_start=1634\n  _ORDERITEM._serialized_end=1716\n  _ORDERRESULT._serialized_start=1719\n  _ORDERRESULT._serialized_end=1910\n  _SENDORDERCONFIRMATIONREQUEST._serialized_start=1912\n  _SENDORDERCONFIRMATIONREQUEST._serialized_end=1998\n  _PLACEORDERREQUEST._serialized_start=2001\n  _PLACEORDERREQUEST._serialized_end=2164\n  _PLACEORDERRESPONSE._serialized_start=2166\n  _PLACEORDERRESPONSE._serialized_end=2227\n  _ADREQUEST._serialized_start=2229\n  _ADREQUEST._serialized_end=2262\n  _ADRESPONSE._serialized_start=2264\n  _ADRESPONSE._serialized_end=2306\n  _AD._serialized_start=2308\n  _AD._serialized_end=2348\n  _CARTSERVICE._serialized_start=2351\n  _CARTSERVICE._serialized_end=2553\n  _RECOMMENDATIONSERVICE._serialized_start=2556\n  _RECOMMENDATIONSERVICE._serialized_end=2687\n  _PRODUCTCATALOGSERVICE._serialized_start=2690\n  _PRODUCTCATALOGSERVICE._serialized_end=2949\n  _SHIPPINGSERVICE._serialized_start=2952\n  _SHIPPINGSERVICE._serialized_end=3122\n  _CURRENCYSERVICE._serialized_start=3125\n  _CURRENCYSERVICE._serialized_end=3308\n  _PAYMENTSERVICE._serialized_start=3310\n  _PAYMENTSERVICE._serialized_end=3395\n  _EMAILSERVICE._serialized_start=3397\n  _EMAILSERVICE._serialized_end=3501\n  _CHECKOUTSERVICE._serialized_start=3503\n  _CHECKOUTSERVICE._serialized_end=3601\n  _ADSERVICE._serialized_start=3603\n  _ADSERVICE._serialized_end=3675\n# @@protoc_insertion_point(module_scope)\n"
  },
  {
    "path": "src/recommendationservice/demo_pb2_grpc.py",
    "content": "#!/usr/bin/python\n#\n# Copyright 2018 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      https://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT!\n\"\"\"Client and server classes corresponding to protobuf-defined services.\"\"\"\nimport grpc\n\nimport demo_pb2 as demo__pb2\n\n\nclass CartServiceStub(object):\n    \"\"\"-----------------Cart service-----------------\n\n    \"\"\"\n\n    def __init__(self, channel):\n        \"\"\"Constructor.\n\n        Args:\n            channel: A grpc.Channel.\n        \"\"\"\n        self.AddItem = channel.unary_unary(\n                '/hipstershop.CartService/AddItem',\n                request_serializer=demo__pb2.AddItemRequest.SerializeToString,\n                response_deserializer=demo__pb2.Empty.FromString,\n                )\n        self.GetCart = channel.unary_unary(\n                '/hipstershop.CartService/GetCart',\n                request_serializer=demo__pb2.GetCartRequest.SerializeToString,\n                response_deserializer=demo__pb2.Cart.FromString,\n                )\n        self.EmptyCart = channel.unary_unary(\n                '/hipstershop.CartService/EmptyCart',\n                request_serializer=demo__pb2.EmptyCartRequest.SerializeToString,\n                response_deserializer=demo__pb2.Empty.FromString,\n                )\n\n\nclass CartServiceServicer(object):\n    \"\"\"-----------------Cart service-----------------\n\n    \"\"\"\n\n    def AddItem(self, request, context):\n        \"\"\"Missing associated documentation comment in .proto file.\"\"\"\n        context.set_code(grpc.StatusCode.UNIMPLEMENTED)\n        context.set_details('Method not implemented!')\n        raise NotImplementedError('Method not implemented!')\n\n    def GetCart(self, request, context):\n        \"\"\"Missing associated documentation comment in .proto file.\"\"\"\n        context.set_code(grpc.StatusCode.UNIMPLEMENTED)\n        context.set_details('Method not implemented!')\n        raise NotImplementedError('Method not implemented!')\n\n    def EmptyCart(self, request, context):\n        \"\"\"Missing associated documentation comment in .proto file.\"\"\"\n        context.set_code(grpc.StatusCode.UNIMPLEMENTED)\n        context.set_details('Method not implemented!')\n        raise NotImplementedError('Method not implemented!')\n\n\ndef add_CartServiceServicer_to_server(servicer, server):\n    rpc_method_handlers = {\n            'AddItem': grpc.unary_unary_rpc_method_handler(\n                    servicer.AddItem,\n                    request_deserializer=demo__pb2.AddItemRequest.FromString,\n                    response_serializer=demo__pb2.Empty.SerializeToString,\n            ),\n            'GetCart': grpc.unary_unary_rpc_method_handler(\n                    servicer.GetCart,\n                    request_deserializer=demo__pb2.GetCartRequest.FromString,\n                    response_serializer=demo__pb2.Cart.SerializeToString,\n            ),\n            'EmptyCart': grpc.unary_unary_rpc_method_handler(\n                    servicer.EmptyCart,\n                    request_deserializer=demo__pb2.EmptyCartRequest.FromString,\n                    response_serializer=demo__pb2.Empty.SerializeToString,\n            ),\n    }\n    generic_handler = grpc.method_handlers_generic_handler(\n            'hipstershop.CartService', rpc_method_handlers)\n    server.add_generic_rpc_handlers((generic_handler,))\n\n\n # This class is part of an EXPERIMENTAL API.\nclass CartService(object):\n    \"\"\"-----------------Cart service-----------------\n\n    \"\"\"\n\n    @staticmethod\n    def AddItem(request,\n            target,\n            options=(),\n            channel_credentials=None,\n            call_credentials=None,\n            insecure=False,\n            compression=None,\n            wait_for_ready=None,\n            timeout=None,\n            metadata=None):\n        return grpc.experimental.unary_unary(request, target, '/hipstershop.CartService/AddItem',\n            demo__pb2.AddItemRequest.SerializeToString,\n            demo__pb2.Empty.FromString,\n            options, channel_credentials,\n            insecure, call_credentials, compression, wait_for_ready, timeout, metadata)\n\n    @staticmethod\n    def GetCart(request,\n            target,\n            options=(),\n            channel_credentials=None,\n            call_credentials=None,\n            insecure=False,\n            compression=None,\n            wait_for_ready=None,\n            timeout=None,\n            metadata=None):\n        return grpc.experimental.unary_unary(request, target, '/hipstershop.CartService/GetCart',\n            demo__pb2.GetCartRequest.SerializeToString,\n            demo__pb2.Cart.FromString,\n            options, channel_credentials,\n            insecure, call_credentials, compression, wait_for_ready, timeout, metadata)\n\n    @staticmethod\n    def EmptyCart(request,\n            target,\n            options=(),\n            channel_credentials=None,\n            call_credentials=None,\n            insecure=False,\n            compression=None,\n            wait_for_ready=None,\n            timeout=None,\n            metadata=None):\n        return grpc.experimental.unary_unary(request, target, '/hipstershop.CartService/EmptyCart',\n            demo__pb2.EmptyCartRequest.SerializeToString,\n            demo__pb2.Empty.FromString,\n            options, channel_credentials,\n            insecure, call_credentials, compression, wait_for_ready, timeout, metadata)\n\n\nclass RecommendationServiceStub(object):\n    \"\"\"---------------Recommendation service----------\n\n    \"\"\"\n\n    def __init__(self, channel):\n        \"\"\"Constructor.\n\n        Args:\n            channel: A grpc.Channel.\n        \"\"\"\n        self.ListRecommendations = channel.unary_unary(\n                '/hipstershop.RecommendationService/ListRecommendations',\n                request_serializer=demo__pb2.ListRecommendationsRequest.SerializeToString,\n                response_deserializer=demo__pb2.ListRecommendationsResponse.FromString,\n                )\n\n\nclass RecommendationServiceServicer(object):\n    \"\"\"---------------Recommendation service----------\n\n    \"\"\"\n\n    def ListRecommendations(self, request, context):\n        \"\"\"Missing associated documentation comment in .proto file.\"\"\"\n        context.set_code(grpc.StatusCode.UNIMPLEMENTED)\n        context.set_details('Method not implemented!')\n        raise NotImplementedError('Method not implemented!')\n\n\ndef add_RecommendationServiceServicer_to_server(servicer, server):\n    rpc_method_handlers = {\n            'ListRecommendations': grpc.unary_unary_rpc_method_handler(\n                    servicer.ListRecommendations,\n                    request_deserializer=demo__pb2.ListRecommendationsRequest.FromString,\n                    response_serializer=demo__pb2.ListRecommendationsResponse.SerializeToString,\n            ),\n    }\n    generic_handler = grpc.method_handlers_generic_handler(\n            'hipstershop.RecommendationService', rpc_method_handlers)\n    server.add_generic_rpc_handlers((generic_handler,))\n\n\n # This class is part of an EXPERIMENTAL API.\nclass RecommendationService(object):\n    \"\"\"---------------Recommendation service----------\n\n    \"\"\"\n\n    @staticmethod\n    def ListRecommendations(request,\n            target,\n            options=(),\n            channel_credentials=None,\n            call_credentials=None,\n            insecure=False,\n            compression=None,\n            wait_for_ready=None,\n            timeout=None,\n            metadata=None):\n        return grpc.experimental.unary_unary(request, target, '/hipstershop.RecommendationService/ListRecommendations',\n            demo__pb2.ListRecommendationsRequest.SerializeToString,\n            demo__pb2.ListRecommendationsResponse.FromString,\n            options, channel_credentials,\n            insecure, call_credentials, compression, wait_for_ready, timeout, metadata)\n\n\nclass ProductCatalogServiceStub(object):\n    \"\"\"---------------Product Catalog----------------\n\n    \"\"\"\n\n    def __init__(self, channel):\n        \"\"\"Constructor.\n\n        Args:\n            channel: A grpc.Channel.\n        \"\"\"\n        self.ListProducts = channel.unary_unary(\n                '/hipstershop.ProductCatalogService/ListProducts',\n                request_serializer=demo__pb2.Empty.SerializeToString,\n                response_deserializer=demo__pb2.ListProductsResponse.FromString,\n                )\n        self.GetProduct = channel.unary_unary(\n                '/hipstershop.ProductCatalogService/GetProduct',\n                request_serializer=demo__pb2.GetProductRequest.SerializeToString,\n                response_deserializer=demo__pb2.Product.FromString,\n                )\n        self.SearchProducts = channel.unary_unary(\n                '/hipstershop.ProductCatalogService/SearchProducts',\n                request_serializer=demo__pb2.SearchProductsRequest.SerializeToString,\n                response_deserializer=demo__pb2.SearchProductsResponse.FromString,\n                )\n\n\nclass ProductCatalogServiceServicer(object):\n    \"\"\"---------------Product Catalog----------------\n\n    \"\"\"\n\n    def ListProducts(self, request, context):\n        \"\"\"Missing associated documentation comment in .proto file.\"\"\"\n        context.set_code(grpc.StatusCode.UNIMPLEMENTED)\n        context.set_details('Method not implemented!')\n        raise NotImplementedError('Method not implemented!')\n\n    def GetProduct(self, request, context):\n        \"\"\"Missing associated documentation comment in .proto file.\"\"\"\n        context.set_code(grpc.StatusCode.UNIMPLEMENTED)\n        context.set_details('Method not implemented!')\n        raise NotImplementedError('Method not implemented!')\n\n    def SearchProducts(self, request, context):\n        \"\"\"Missing associated documentation comment in .proto file.\"\"\"\n        context.set_code(grpc.StatusCode.UNIMPLEMENTED)\n        context.set_details('Method not implemented!')\n        raise NotImplementedError('Method not implemented!')\n\n\ndef add_ProductCatalogServiceServicer_to_server(servicer, server):\n    rpc_method_handlers = {\n            'ListProducts': grpc.unary_unary_rpc_method_handler(\n                    servicer.ListProducts,\n                    request_deserializer=demo__pb2.Empty.FromString,\n                    response_serializer=demo__pb2.ListProductsResponse.SerializeToString,\n            ),\n            'GetProduct': grpc.unary_unary_rpc_method_handler(\n                    servicer.GetProduct,\n                    request_deserializer=demo__pb2.GetProductRequest.FromString,\n                    response_serializer=demo__pb2.Product.SerializeToString,\n            ),\n            'SearchProducts': grpc.unary_unary_rpc_method_handler(\n                    servicer.SearchProducts,\n                    request_deserializer=demo__pb2.SearchProductsRequest.FromString,\n                    response_serializer=demo__pb2.SearchProductsResponse.SerializeToString,\n            ),\n    }\n    generic_handler = grpc.method_handlers_generic_handler(\n            'hipstershop.ProductCatalogService', rpc_method_handlers)\n    server.add_generic_rpc_handlers((generic_handler,))\n\n\n # This class is part of an EXPERIMENTAL API.\nclass ProductCatalogService(object):\n    \"\"\"---------------Product Catalog----------------\n\n    \"\"\"\n\n    @staticmethod\n    def ListProducts(request,\n            target,\n            options=(),\n            channel_credentials=None,\n            call_credentials=None,\n            insecure=False,\n            compression=None,\n            wait_for_ready=None,\n            timeout=None,\n            metadata=None):\n        return grpc.experimental.unary_unary(request, target, '/hipstershop.ProductCatalogService/ListProducts',\n            demo__pb2.Empty.SerializeToString,\n            demo__pb2.ListProductsResponse.FromString,\n            options, channel_credentials,\n            insecure, call_credentials, compression, wait_for_ready, timeout, metadata)\n\n    @staticmethod\n    def GetProduct(request,\n            target,\n            options=(),\n            channel_credentials=None,\n            call_credentials=None,\n            insecure=False,\n            compression=None,\n            wait_for_ready=None,\n            timeout=None,\n            metadata=None):\n        return grpc.experimental.unary_unary(request, target, '/hipstershop.ProductCatalogService/GetProduct',\n            demo__pb2.GetProductRequest.SerializeToString,\n            demo__pb2.Product.FromString,\n            options, channel_credentials,\n            insecure, call_credentials, compression, wait_for_ready, timeout, metadata)\n\n    @staticmethod\n    def SearchProducts(request,\n            target,\n            options=(),\n            channel_credentials=None,\n            call_credentials=None,\n            insecure=False,\n            compression=None,\n            wait_for_ready=None,\n            timeout=None,\n            metadata=None):\n        return grpc.experimental.unary_unary(request, target, '/hipstershop.ProductCatalogService/SearchProducts',\n            demo__pb2.SearchProductsRequest.SerializeToString,\n            demo__pb2.SearchProductsResponse.FromString,\n            options, channel_credentials,\n            insecure, call_credentials, compression, wait_for_ready, timeout, metadata)\n\n\nclass ShippingServiceStub(object):\n    \"\"\"---------------Shipping Service----------\n\n    \"\"\"\n\n    def __init__(self, channel):\n        \"\"\"Constructor.\n\n        Args:\n            channel: A grpc.Channel.\n        \"\"\"\n        self.GetQuote = channel.unary_unary(\n                '/hipstershop.ShippingService/GetQuote',\n                request_serializer=demo__pb2.GetQuoteRequest.SerializeToString,\n                response_deserializer=demo__pb2.GetQuoteResponse.FromString,\n                )\n        self.ShipOrder = channel.unary_unary(\n                '/hipstershop.ShippingService/ShipOrder',\n                request_serializer=demo__pb2.ShipOrderRequest.SerializeToString,\n                response_deserializer=demo__pb2.ShipOrderResponse.FromString,\n                )\n\n\nclass ShippingServiceServicer(object):\n    \"\"\"---------------Shipping Service----------\n\n    \"\"\"\n\n    def GetQuote(self, request, context):\n        \"\"\"Missing associated documentation comment in .proto file.\"\"\"\n        context.set_code(grpc.StatusCode.UNIMPLEMENTED)\n        context.set_details('Method not implemented!')\n        raise NotImplementedError('Method not implemented!')\n\n    def ShipOrder(self, request, context):\n        \"\"\"Missing associated documentation comment in .proto file.\"\"\"\n        context.set_code(grpc.StatusCode.UNIMPLEMENTED)\n        context.set_details('Method not implemented!')\n        raise NotImplementedError('Method not implemented!')\n\n\ndef add_ShippingServiceServicer_to_server(servicer, server):\n    rpc_method_handlers = {\n            'GetQuote': grpc.unary_unary_rpc_method_handler(\n                    servicer.GetQuote,\n                    request_deserializer=demo__pb2.GetQuoteRequest.FromString,\n                    response_serializer=demo__pb2.GetQuoteResponse.SerializeToString,\n            ),\n            'ShipOrder': grpc.unary_unary_rpc_method_handler(\n                    servicer.ShipOrder,\n                    request_deserializer=demo__pb2.ShipOrderRequest.FromString,\n                    response_serializer=demo__pb2.ShipOrderResponse.SerializeToString,\n            ),\n    }\n    generic_handler = grpc.method_handlers_generic_handler(\n            'hipstershop.ShippingService', rpc_method_handlers)\n    server.add_generic_rpc_handlers((generic_handler,))\n\n\n # This class is part of an EXPERIMENTAL API.\nclass ShippingService(object):\n    \"\"\"---------------Shipping Service----------\n\n    \"\"\"\n\n    @staticmethod\n    def GetQuote(request,\n            target,\n            options=(),\n            channel_credentials=None,\n            call_credentials=None,\n            insecure=False,\n            compression=None,\n            wait_for_ready=None,\n            timeout=None,\n            metadata=None):\n        return grpc.experimental.unary_unary(request, target, '/hipstershop.ShippingService/GetQuote',\n            demo__pb2.GetQuoteRequest.SerializeToString,\n            demo__pb2.GetQuoteResponse.FromString,\n            options, channel_credentials,\n            insecure, call_credentials, compression, wait_for_ready, timeout, metadata)\n\n    @staticmethod\n    def ShipOrder(request,\n            target,\n            options=(),\n            channel_credentials=None,\n            call_credentials=None,\n            insecure=False,\n            compression=None,\n            wait_for_ready=None,\n            timeout=None,\n            metadata=None):\n        return grpc.experimental.unary_unary(request, target, '/hipstershop.ShippingService/ShipOrder',\n            demo__pb2.ShipOrderRequest.SerializeToString,\n            demo__pb2.ShipOrderResponse.FromString,\n            options, channel_credentials,\n            insecure, call_credentials, compression, wait_for_ready, timeout, metadata)\n\n\nclass CurrencyServiceStub(object):\n    \"\"\"-----------------Currency service-----------------\n\n    \"\"\"\n\n    def __init__(self, channel):\n        \"\"\"Constructor.\n\n        Args:\n            channel: A grpc.Channel.\n        \"\"\"\n        self.GetSupportedCurrencies = channel.unary_unary(\n                '/hipstershop.CurrencyService/GetSupportedCurrencies',\n                request_serializer=demo__pb2.Empty.SerializeToString,\n                response_deserializer=demo__pb2.GetSupportedCurrenciesResponse.FromString,\n                )\n        self.Convert = channel.unary_unary(\n                '/hipstershop.CurrencyService/Convert',\n                request_serializer=demo__pb2.CurrencyConversionRequest.SerializeToString,\n                response_deserializer=demo__pb2.Money.FromString,\n                )\n\n\nclass CurrencyServiceServicer(object):\n    \"\"\"-----------------Currency service-----------------\n\n    \"\"\"\n\n    def GetSupportedCurrencies(self, request, context):\n        \"\"\"Missing associated documentation comment in .proto file.\"\"\"\n        context.set_code(grpc.StatusCode.UNIMPLEMENTED)\n        context.set_details('Method not implemented!')\n        raise NotImplementedError('Method not implemented!')\n\n    def Convert(self, request, context):\n        \"\"\"Missing associated documentation comment in .proto file.\"\"\"\n        context.set_code(grpc.StatusCode.UNIMPLEMENTED)\n        context.set_details('Method not implemented!')\n        raise NotImplementedError('Method not implemented!')\n\n\ndef add_CurrencyServiceServicer_to_server(servicer, server):\n    rpc_method_handlers = {\n            'GetSupportedCurrencies': grpc.unary_unary_rpc_method_handler(\n                    servicer.GetSupportedCurrencies,\n                    request_deserializer=demo__pb2.Empty.FromString,\n                    response_serializer=demo__pb2.GetSupportedCurrenciesResponse.SerializeToString,\n            ),\n            'Convert': grpc.unary_unary_rpc_method_handler(\n                    servicer.Convert,\n                    request_deserializer=demo__pb2.CurrencyConversionRequest.FromString,\n                    response_serializer=demo__pb2.Money.SerializeToString,\n            ),\n    }\n    generic_handler = grpc.method_handlers_generic_handler(\n            'hipstershop.CurrencyService', rpc_method_handlers)\n    server.add_generic_rpc_handlers((generic_handler,))\n\n\n # This class is part of an EXPERIMENTAL API.\nclass CurrencyService(object):\n    \"\"\"-----------------Currency service-----------------\n\n    \"\"\"\n\n    @staticmethod\n    def GetSupportedCurrencies(request,\n            target,\n            options=(),\n            channel_credentials=None,\n            call_credentials=None,\n            insecure=False,\n            compression=None,\n            wait_for_ready=None,\n            timeout=None,\n            metadata=None):\n        return grpc.experimental.unary_unary(request, target, '/hipstershop.CurrencyService/GetSupportedCurrencies',\n            demo__pb2.Empty.SerializeToString,\n            demo__pb2.GetSupportedCurrenciesResponse.FromString,\n            options, channel_credentials,\n            insecure, call_credentials, compression, wait_for_ready, timeout, metadata)\n\n    @staticmethod\n    def Convert(request,\n            target,\n            options=(),\n            channel_credentials=None,\n            call_credentials=None,\n            insecure=False,\n            compression=None,\n            wait_for_ready=None,\n            timeout=None,\n            metadata=None):\n        return grpc.experimental.unary_unary(request, target, '/hipstershop.CurrencyService/Convert',\n            demo__pb2.CurrencyConversionRequest.SerializeToString,\n            demo__pb2.Money.FromString,\n            options, channel_credentials,\n            insecure, call_credentials, compression, wait_for_ready, timeout, metadata)\n\n\nclass PaymentServiceStub(object):\n    \"\"\"-------------Payment service-----------------\n\n    \"\"\"\n\n    def __init__(self, channel):\n        \"\"\"Constructor.\n\n        Args:\n            channel: A grpc.Channel.\n        \"\"\"\n        self.Charge = channel.unary_unary(\n                '/hipstershop.PaymentService/Charge',\n                request_serializer=demo__pb2.ChargeRequest.SerializeToString,\n                response_deserializer=demo__pb2.ChargeResponse.FromString,\n                )\n\n\nclass PaymentServiceServicer(object):\n    \"\"\"-------------Payment service-----------------\n\n    \"\"\"\n\n    def Charge(self, request, context):\n        \"\"\"Missing associated documentation comment in .proto file.\"\"\"\n        context.set_code(grpc.StatusCode.UNIMPLEMENTED)\n        context.set_details('Method not implemented!')\n        raise NotImplementedError('Method not implemented!')\n\n\ndef add_PaymentServiceServicer_to_server(servicer, server):\n    rpc_method_handlers = {\n            'Charge': grpc.unary_unary_rpc_method_handler(\n                    servicer.Charge,\n                    request_deserializer=demo__pb2.ChargeRequest.FromString,\n                    response_serializer=demo__pb2.ChargeResponse.SerializeToString,\n            ),\n    }\n    generic_handler = grpc.method_handlers_generic_handler(\n            'hipstershop.PaymentService', rpc_method_handlers)\n    server.add_generic_rpc_handlers((generic_handler,))\n\n\n # This class is part of an EXPERIMENTAL API.\nclass PaymentService(object):\n    \"\"\"-------------Payment service-----------------\n\n    \"\"\"\n\n    @staticmethod\n    def Charge(request,\n            target,\n            options=(),\n            channel_credentials=None,\n            call_credentials=None,\n            insecure=False,\n            compression=None,\n            wait_for_ready=None,\n            timeout=None,\n            metadata=None):\n        return grpc.experimental.unary_unary(request, target, '/hipstershop.PaymentService/Charge',\n            demo__pb2.ChargeRequest.SerializeToString,\n            demo__pb2.ChargeResponse.FromString,\n            options, channel_credentials,\n            insecure, call_credentials, compression, wait_for_ready, timeout, metadata)\n\n\nclass EmailServiceStub(object):\n    \"\"\"-------------Email service-----------------\n\n    \"\"\"\n\n    def __init__(self, channel):\n        \"\"\"Constructor.\n\n        Args:\n            channel: A grpc.Channel.\n        \"\"\"\n        self.SendOrderConfirmation = channel.unary_unary(\n                '/hipstershop.EmailService/SendOrderConfirmation',\n                request_serializer=demo__pb2.SendOrderConfirmationRequest.SerializeToString,\n                response_deserializer=demo__pb2.Empty.FromString,\n                )\n\n\nclass EmailServiceServicer(object):\n    \"\"\"-------------Email service-----------------\n\n    \"\"\"\n\n    def SendOrderConfirmation(self, request, context):\n        \"\"\"Missing associated documentation comment in .proto file.\"\"\"\n        context.set_code(grpc.StatusCode.UNIMPLEMENTED)\n        context.set_details('Method not implemented!')\n        raise NotImplementedError('Method not implemented!')\n\n\ndef add_EmailServiceServicer_to_server(servicer, server):\n    rpc_method_handlers = {\n            'SendOrderConfirmation': grpc.unary_unary_rpc_method_handler(\n                    servicer.SendOrderConfirmation,\n                    request_deserializer=demo__pb2.SendOrderConfirmationRequest.FromString,\n                    response_serializer=demo__pb2.Empty.SerializeToString,\n            ),\n    }\n    generic_handler = grpc.method_handlers_generic_handler(\n            'hipstershop.EmailService', rpc_method_handlers)\n    server.add_generic_rpc_handlers((generic_handler,))\n\n\n # This class is part of an EXPERIMENTAL API.\nclass EmailService(object):\n    \"\"\"-------------Email service-----------------\n\n    \"\"\"\n\n    @staticmethod\n    def SendOrderConfirmation(request,\n            target,\n            options=(),\n            channel_credentials=None,\n            call_credentials=None,\n            insecure=False,\n            compression=None,\n            wait_for_ready=None,\n            timeout=None,\n            metadata=None):\n        return grpc.experimental.unary_unary(request, target, '/hipstershop.EmailService/SendOrderConfirmation',\n            demo__pb2.SendOrderConfirmationRequest.SerializeToString,\n            demo__pb2.Empty.FromString,\n            options, channel_credentials,\n            insecure, call_credentials, compression, wait_for_ready, timeout, metadata)\n\n\nclass CheckoutServiceStub(object):\n    \"\"\"-------------Checkout service-----------------\n\n    \"\"\"\n\n    def __init__(self, channel):\n        \"\"\"Constructor.\n\n        Args:\n            channel: A grpc.Channel.\n        \"\"\"\n        self.PlaceOrder = channel.unary_unary(\n                '/hipstershop.CheckoutService/PlaceOrder',\n                request_serializer=demo__pb2.PlaceOrderRequest.SerializeToString,\n                response_deserializer=demo__pb2.PlaceOrderResponse.FromString,\n                )\n\n\nclass CheckoutServiceServicer(object):\n    \"\"\"-------------Checkout service-----------------\n\n    \"\"\"\n\n    def PlaceOrder(self, request, context):\n        \"\"\"Missing associated documentation comment in .proto file.\"\"\"\n        context.set_code(grpc.StatusCode.UNIMPLEMENTED)\n        context.set_details('Method not implemented!')\n        raise NotImplementedError('Method not implemented!')\n\n\ndef add_CheckoutServiceServicer_to_server(servicer, server):\n    rpc_method_handlers = {\n            'PlaceOrder': grpc.unary_unary_rpc_method_handler(\n                    servicer.PlaceOrder,\n                    request_deserializer=demo__pb2.PlaceOrderRequest.FromString,\n                    response_serializer=demo__pb2.PlaceOrderResponse.SerializeToString,\n            ),\n    }\n    generic_handler = grpc.method_handlers_generic_handler(\n            'hipstershop.CheckoutService', rpc_method_handlers)\n    server.add_generic_rpc_handlers((generic_handler,))\n\n\n # This class is part of an EXPERIMENTAL API.\nclass CheckoutService(object):\n    \"\"\"-------------Checkout service-----------------\n\n    \"\"\"\n\n    @staticmethod\n    def PlaceOrder(request,\n            target,\n            options=(),\n            channel_credentials=None,\n            call_credentials=None,\n            insecure=False,\n            compression=None,\n            wait_for_ready=None,\n            timeout=None,\n            metadata=None):\n        return grpc.experimental.unary_unary(request, target, '/hipstershop.CheckoutService/PlaceOrder',\n            demo__pb2.PlaceOrderRequest.SerializeToString,\n            demo__pb2.PlaceOrderResponse.FromString,\n            options, channel_credentials,\n            insecure, call_credentials, compression, wait_for_ready, timeout, metadata)\n\n\nclass AdServiceStub(object):\n    \"\"\"------------Ad service------------------\n\n    \"\"\"\n\n    def __init__(self, channel):\n        \"\"\"Constructor.\n\n        Args:\n            channel: A grpc.Channel.\n        \"\"\"\n        self.GetAds = channel.unary_unary(\n                '/hipstershop.AdService/GetAds',\n                request_serializer=demo__pb2.AdRequest.SerializeToString,\n                response_deserializer=demo__pb2.AdResponse.FromString,\n                )\n\n\nclass AdServiceServicer(object):\n    \"\"\"------------Ad service------------------\n\n    \"\"\"\n\n    def GetAds(self, request, context):\n        \"\"\"Missing associated documentation comment in .proto file.\"\"\"\n        context.set_code(grpc.StatusCode.UNIMPLEMENTED)\n        context.set_details('Method not implemented!')\n        raise NotImplementedError('Method not implemented!')\n\n\ndef add_AdServiceServicer_to_server(servicer, server):\n    rpc_method_handlers = {\n            'GetAds': grpc.unary_unary_rpc_method_handler(\n                    servicer.GetAds,\n                    request_deserializer=demo__pb2.AdRequest.FromString,\n                    response_serializer=demo__pb2.AdResponse.SerializeToString,\n            ),\n    }\n    generic_handler = grpc.method_handlers_generic_handler(\n            'hipstershop.AdService', rpc_method_handlers)\n    server.add_generic_rpc_handlers((generic_handler,))\n\n\n # This class is part of an EXPERIMENTAL API.\nclass AdService(object):\n    \"\"\"------------Ad service------------------\n\n    \"\"\"\n\n    @staticmethod\n    def GetAds(request,\n            target,\n            options=(),\n            channel_credentials=None,\n            call_credentials=None,\n            insecure=False,\n            compression=None,\n            wait_for_ready=None,\n            timeout=None,\n            metadata=None):\n        return grpc.experimental.unary_unary(request, target, '/hipstershop.AdService/GetAds',\n            demo__pb2.AdRequest.SerializeToString,\n            demo__pb2.AdResponse.FromString,\n            options, channel_credentials,\n            insecure, call_credentials, compression, wait_for_ready, timeout, metadata)\n"
  },
  {
    "path": "src/recommendationservice/genproto.sh",
    "content": "#!/bin/bash -eu\n#\n# Copyright 2018 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# [START gke_recommendationservice_genproto]\n\n# script to compile python protos\n#\n# requires gRPC tools:\n#   pip install -r requirements.txt\n\npython -m grpc_tools.protoc -I../../protos --python_out=. --grpc_python_out=. ../../protos/demo.proto\n\n# [END gke_recommendationservice_genproto]"
  },
  {
    "path": "src/recommendationservice/logger.py",
    "content": "#!/usr/bin/python\n#\n# Copyright 2018 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport logging\nimport sys\nfrom pythonjsonlogger import jsonlogger\n\n# TODO(yoshifumi) this class is duplicated since other Python services are\n# not sharing the modules for logging.\nclass CustomJsonFormatter(jsonlogger.JsonFormatter):\n  def add_fields(self, log_record, record, message_dict):\n    super(CustomJsonFormatter, self).add_fields(log_record, record, message_dict)\n    if not log_record.get('timestamp'):\n      log_record['timestamp'] = record.created\n    if log_record.get('severity'):\n      log_record['severity'] = log_record['severity'].upper()\n    else:\n      log_record['severity'] = record.levelname\n\ndef getJSONLogger(name):\n  logger = logging.getLogger(name)\n  handler = logging.StreamHandler(sys.stdout)\n  formatter = CustomJsonFormatter('%(timestamp)s %(severity)s %(name)s %(message)s')\n  handler.setFormatter(formatter)\n  logger.addHandler(handler)\n  logger.setLevel(logging.INFO)\n  logger.propagate = False\n  return logger\n"
  },
  {
    "path": "src/recommendationservice/recommendation_server.py",
    "content": "#!/usr/bin/python\n#\n# Copyright 2018 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport os\nimport random\nimport time\nimport traceback\nfrom concurrent import futures\n\n# @TODO: Temporarily removed in https://github.com/GoogleCloudPlatform/microservices-demo/pull/3196\n# import googlecloudprofiler\n\nfrom google.auth.exceptions import DefaultCredentialsError\nimport grpc\n\nimport demo_pb2\nimport demo_pb2_grpc\nfrom grpc_health.v1 import health_pb2\nfrom grpc_health.v1 import health_pb2_grpc\n\nfrom opentelemetry import trace\nfrom opentelemetry.instrumentation.grpc import GrpcInstrumentorClient, GrpcInstrumentorServer\nfrom opentelemetry.sdk.trace import TracerProvider\nfrom opentelemetry.sdk.trace.export import BatchSpanProcessor\nfrom opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter\n\nfrom logger import getJSONLogger\nlogger = getJSONLogger('recommendationservice-server')\n\ndef initStackdriverProfiling():\n  project_id = None\n  try:\n    project_id = os.environ[\"GCP_PROJECT_ID\"]\n  except KeyError:\n    # Environment variable not set\n    pass\n\n  # @TODO: Temporarily removed in https://github.com/GoogleCloudPlatform/microservices-demo/pull/3196\n  # for retry in range(1,4):\n  #   try:\n  #     if project_id:\n  #       googlecloudprofiler.start(service='recommendation_server', service_version='1.0.0', verbose=0, project_id=project_id)\n  #     else:\n  #       googlecloudprofiler.start(service='recommendation_server', service_version='1.0.0', verbose=0)\n  #     logger.info(\"Successfully started Stackdriver Profiler.\")\n  #     return\n  #   except (BaseException) as exc:\n  #     logger.info(\"Unable to start Stackdriver Profiler Python agent. \" + str(exc))\n  #     if (retry < 4):\n  #       logger.info(\"Sleeping %d seconds to retry Stackdriver Profiler agent initialization\"%(retry*10))\n  #       time.sleep (1)\n  #     else:\n  #       logger.warning(\"Could not initialize Stackdriver Profiler after retrying, giving up\")\n  return\n\nclass RecommendationService(demo_pb2_grpc.RecommendationServiceServicer):\n    def ListRecommendations(self, request, context):\n        max_responses = 5\n        # fetch list of products from product catalog stub\n        cat_response = product_catalog_stub.ListProducts(demo_pb2.Empty())\n        product_ids = [x.id for x in cat_response.products]\n        filtered_products = list(set(product_ids)-set(request.product_ids))\n        num_products = len(filtered_products)\n        num_return = min(max_responses, num_products)\n        # sample list of indicies to return\n        indices = random.sample(range(num_products), num_return)\n        # fetch product ids from indices\n        prod_list = [filtered_products[i] for i in indices]\n        logger.info(\"[Recv ListRecommendations] product_ids={}\".format(prod_list))\n        # build and return response\n        response = demo_pb2.ListRecommendationsResponse()\n        response.product_ids.extend(prod_list)\n        return response\n\n    def Check(self, request, context):\n        return health_pb2.HealthCheckResponse(\n            status=health_pb2.HealthCheckResponse.SERVING)\n\n    def Watch(self, request, context):\n        return health_pb2.HealthCheckResponse(\n            status=health_pb2.HealthCheckResponse.UNIMPLEMENTED)\n\n\nif __name__ == \"__main__\":\n    logger.info(\"initializing recommendationservice\")\n\n    try:\n      if \"DISABLE_PROFILER\" in os.environ:\n        raise KeyError()\n      else:\n        logger.info(\"Profiler enabled.\")\n        initStackdriverProfiling()\n    except KeyError:\n        logger.info(\"Profiler disabled.\")\n\n    try:\n      grpc_client_instrumentor = GrpcInstrumentorClient()\n      grpc_client_instrumentor.instrument()\n      grpc_server_instrumentor = GrpcInstrumentorServer()\n      grpc_server_instrumentor.instrument()\n      if os.environ[\"ENABLE_TRACING\"] == \"1\":\n        trace.set_tracer_provider(TracerProvider())\n        otel_endpoint = os.getenv(\"COLLECTOR_SERVICE_ADDR\", \"localhost:4317\")\n        trace.get_tracer_provider().add_span_processor(\n          BatchSpanProcessor(\n              OTLPSpanExporter(\n              endpoint = otel_endpoint,\n              insecure = True\n            )\n          )\n        )\n    except (KeyError, DefaultCredentialsError):\n        logger.info(\"Tracing disabled.\")\n    except Exception as e:\n        logger.warn(f\"Exception on Cloud Trace setup: {traceback.format_exc()}, tracing disabled.\") \n\n    port = os.environ.get('PORT', \"8080\")\n    catalog_addr = os.environ.get('PRODUCT_CATALOG_SERVICE_ADDR', '')\n    if catalog_addr == \"\":\n        raise Exception('PRODUCT_CATALOG_SERVICE_ADDR environment variable not set')\n    logger.info(\"product catalog address: \" + catalog_addr)\n    channel = grpc.insecure_channel(catalog_addr)\n    product_catalog_stub = demo_pb2_grpc.ProductCatalogServiceStub(channel)\n\n    # create gRPC server\n    server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))\n\n    # add class to gRPC server\n    service = RecommendationService()\n    demo_pb2_grpc.add_RecommendationServiceServicer_to_server(service, server)\n    health_pb2_grpc.add_HealthServicer_to_server(service, server)\n\n    # start server\n    logger.info(\"listening on port: \" + port)\n    server.add_insecure_port('[::]:'+port)\n    server.start()\n\n    # keep alive\n    try:\n         while True:\n            time.sleep(10000)\n    except KeyboardInterrupt:\n            server.stop(0)\n"
  },
  {
    "path": "src/recommendationservice/requirements.in",
    "content": "google-api-core==2.28.1\ngrpcio-health-checking==1.76.0\npython-json-logger==4.0.0\nrequests==2.32.5\nrsa==4.9.1\nopentelemetry-distro==0.60b1\nopentelemetry-instrumentation-grpc==0.60b1\nopentelemetry-exporter-otlp-proto-grpc==1.39.1\n"
  },
  {
    "path": "src/recommendationservice/requirements.txt",
    "content": "# This file was autogenerated by uv via the following command:\n#    uv pip compile requirements.in -o requirements.txt\ncachetools==5.3.2\n    # via google-auth\ncertifi==2024.7.4\n    # via requests\ncharset-normalizer==3.3.2\n    # via requests\ngoogle-api-core==2.28.1\n    # via -r requirements.in\ngoogle-auth==2.23.4\n    # via google-api-core\ngoogleapis-common-protos==1.72.0\n    # via\n    #   google-api-core\n    #   opentelemetry-exporter-otlp-proto-grpc\ngrpcio==1.76.0\n    # via\n    #   grpcio-health-checking\n    #   opentelemetry-exporter-otlp-proto-grpc\ngrpcio-health-checking==1.76.0\n    # via -r requirements.in\nidna==3.7\n    # via requests\nimportlib-metadata==6.8.0\n    # via opentelemetry-api\nopentelemetry-api==1.39.1\n    # via\n    #   opentelemetry-distro\n    #   opentelemetry-exporter-otlp-proto-grpc\n    #   opentelemetry-instrumentation\n    #   opentelemetry-instrumentation-grpc\n    #   opentelemetry-sdk\n    #   opentelemetry-semantic-conventions\nopentelemetry-distro==0.60b1\n    # via -r requirements.in\nopentelemetry-exporter-otlp-proto-common==1.39.1\n    # via opentelemetry-exporter-otlp-proto-grpc\nopentelemetry-exporter-otlp-proto-grpc==1.39.1\n    # via -r requirements.in\nopentelemetry-instrumentation==0.60b1\n    # via\n    #   opentelemetry-distro\n    #   opentelemetry-instrumentation-grpc\nopentelemetry-instrumentation-grpc==0.60b1\n    # via -r requirements.in\nopentelemetry-proto==1.39.1\n    # via\n    #   opentelemetry-exporter-otlp-proto-common\n    #   opentelemetry-exporter-otlp-proto-grpc\nopentelemetry-sdk==1.39.1\n    # via\n    #   opentelemetry-distro\n    #   opentelemetry-exporter-otlp-proto-grpc\nopentelemetry-semantic-conventions==0.60b1\n    # via\n    #   opentelemetry-instrumentation\n    #   opentelemetry-instrumentation-grpc\n    #   opentelemetry-sdk\npackaging==25.0\n    # via opentelemetry-instrumentation\nproto-plus==1.27.0\n    # via google-api-core\nprotobuf==6.33.5\n    # via\n    #   google-api-core\n    #   googleapis-common-protos\n    #   grpcio-health-checking\n    #   opentelemetry-proto\n    #   proto-plus\npyasn1==0.5.0\n    # via\n    #   pyasn1-modules\n    #   rsa\npyasn1-modules==0.3.0\n    # via google-auth\npython-json-logger==4.0.0\n    # via -r requirements.in\nrequests==2.32.5\n    # via\n    #   -r requirements.in\n    #   google-api-core\nrsa==4.9.1\n    # via\n    #   -r requirements.in\n    #   google-auth\ntyping-extensions==4.15.0\n    # via\n    #   grpcio\n    #   opentelemetry-api\n    #   opentelemetry-exporter-otlp-proto-grpc\n    #   opentelemetry-sdk\n    #   opentelemetry-semantic-conventions\nurllib3==2.6.3\n    # via requests\nwrapt==1.16.0\n    # via\n    #   opentelemetry-instrumentation\n    #   opentelemetry-instrumentation-grpc\nzipp==3.19.1\n    # via importlib-metadata\n"
  },
  {
    "path": "src/shippingservice/.dockerignore",
    "content": "vendor/\n"
  },
  {
    "path": "src/shippingservice/Dockerfile",
    "content": "# Copyright 2020 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# Define a default value so it's not empty if the builder fails to provide it\nARG BUILDPLATFORM=linux/amd64\n\nFROM --platform=$BUILDPLATFORM golang:1.26.1-alpine@sha256:2389ebfa5b7f43eeafbd6be0c3700cc46690ef842ad962f6c5bd6be49ed82039 AS builder\nARG TARGETOS=linux\nARG TARGETARCH=amd64\nWORKDIR /src\n\n# restore dependencies\nCOPY go.mod go.sum ./\nRUN go mod download\nCOPY . .\n\n# Skaffold passes in debug-oriented compiler flags\nARG SKAFFOLD_GO_GCFLAGS\nRUN GOOS=${TARGETOS} GOARCH=${TARGETARCH} CGO_ENABLED=0 go build -ldflags=\"-s -w\" -gcflags=\"${SKAFFOLD_GO_GCFLAGS}\" -o /go/bin/shippingservice .\n\nFROM gcr.io/distroless/static\n\nWORKDIR /src\nCOPY --from=builder /go/bin/shippingservice /src/shippingservice\nENV APP_PORT=50051\n\n# Definition of this variable is used by 'skaffold debug' to identify a golang binary.\n# Default behavior - a failure prints a stack trace for the current goroutine.\n# See https://golang.org/pkg/runtime/\nENV GOTRACEBACK=single\n\nEXPOSE 50051\nENTRYPOINT [\"/src/shippingservice\"]\n"
  },
  {
    "path": "src/shippingservice/README.md",
    "content": "# Shipping Service\n\nThe Shipping service provides price quote, tracking IDs, and the impression of order fulfillment & shipping processes.\n\n## Local\n\nRun the following command to restore dependencies to `vendor/` directory:\n\n    dep ensure --vendor-only\n\n## Build\n\nFrom `src/shippingservice`, run:\n\n```\ndocker build ./\n```\n\n## Test\n\n```\ngo test .\n```\n"
  },
  {
    "path": "src/shippingservice/genproto/demo.pb.go",
    "content": "// Copyright 2020 Google LLC\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.34.2\n// \tprotoc        v3.6.1\n// source: demo.proto\n\npackage hipstershop\n\nimport (\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\treflect \"reflect\"\n\tsync \"sync\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\ntype CartItem struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tProductId string `protobuf:\"bytes,1,opt,name=product_id,json=productId,proto3\" json:\"product_id,omitempty\"`\n\tQuantity  int32  `protobuf:\"varint,2,opt,name=quantity,proto3\" json:\"quantity,omitempty\"`\n}\n\nfunc (x *CartItem) Reset() {\n\t*x = CartItem{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_demo_proto_msgTypes[0]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *CartItem) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*CartItem) ProtoMessage() {}\n\nfunc (x *CartItem) ProtoReflect() protoreflect.Message {\n\tmi := &file_demo_proto_msgTypes[0]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use CartItem.ProtoReflect.Descriptor instead.\nfunc (*CartItem) Descriptor() ([]byte, []int) {\n\treturn file_demo_proto_rawDescGZIP(), []int{0}\n}\n\nfunc (x *CartItem) GetProductId() string {\n\tif x != nil {\n\t\treturn x.ProductId\n\t}\n\treturn \"\"\n}\n\nfunc (x *CartItem) GetQuantity() int32 {\n\tif x != nil {\n\t\treturn x.Quantity\n\t}\n\treturn 0\n}\n\ntype AddItemRequest struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tUserId string    `protobuf:\"bytes,1,opt,name=user_id,json=userId,proto3\" json:\"user_id,omitempty\"`\n\tItem   *CartItem `protobuf:\"bytes,2,opt,name=item,proto3\" json:\"item,omitempty\"`\n}\n\nfunc (x *AddItemRequest) Reset() {\n\t*x = AddItemRequest{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_demo_proto_msgTypes[1]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *AddItemRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*AddItemRequest) ProtoMessage() {}\n\nfunc (x *AddItemRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_demo_proto_msgTypes[1]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use AddItemRequest.ProtoReflect.Descriptor instead.\nfunc (*AddItemRequest) Descriptor() ([]byte, []int) {\n\treturn file_demo_proto_rawDescGZIP(), []int{1}\n}\n\nfunc (x *AddItemRequest) GetUserId() string {\n\tif x != nil {\n\t\treturn x.UserId\n\t}\n\treturn \"\"\n}\n\nfunc (x *AddItemRequest) GetItem() *CartItem {\n\tif x != nil {\n\t\treturn x.Item\n\t}\n\treturn nil\n}\n\ntype EmptyCartRequest struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tUserId string `protobuf:\"bytes,1,opt,name=user_id,json=userId,proto3\" json:\"user_id,omitempty\"`\n}\n\nfunc (x *EmptyCartRequest) Reset() {\n\t*x = EmptyCartRequest{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_demo_proto_msgTypes[2]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *EmptyCartRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*EmptyCartRequest) ProtoMessage() {}\n\nfunc (x *EmptyCartRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_demo_proto_msgTypes[2]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use EmptyCartRequest.ProtoReflect.Descriptor instead.\nfunc (*EmptyCartRequest) Descriptor() ([]byte, []int) {\n\treturn file_demo_proto_rawDescGZIP(), []int{2}\n}\n\nfunc (x *EmptyCartRequest) GetUserId() string {\n\tif x != nil {\n\t\treturn x.UserId\n\t}\n\treturn \"\"\n}\n\ntype GetCartRequest struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tUserId string `protobuf:\"bytes,1,opt,name=user_id,json=userId,proto3\" json:\"user_id,omitempty\"`\n}\n\nfunc (x *GetCartRequest) Reset() {\n\t*x = GetCartRequest{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_demo_proto_msgTypes[3]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *GetCartRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*GetCartRequest) ProtoMessage() {}\n\nfunc (x *GetCartRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_demo_proto_msgTypes[3]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use GetCartRequest.ProtoReflect.Descriptor instead.\nfunc (*GetCartRequest) Descriptor() ([]byte, []int) {\n\treturn file_demo_proto_rawDescGZIP(), []int{3}\n}\n\nfunc (x *GetCartRequest) GetUserId() string {\n\tif x != nil {\n\t\treturn x.UserId\n\t}\n\treturn \"\"\n}\n\ntype Cart struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tUserId string      `protobuf:\"bytes,1,opt,name=user_id,json=userId,proto3\" json:\"user_id,omitempty\"`\n\tItems  []*CartItem `protobuf:\"bytes,2,rep,name=items,proto3\" json:\"items,omitempty\"`\n}\n\nfunc (x *Cart) Reset() {\n\t*x = Cart{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_demo_proto_msgTypes[4]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *Cart) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Cart) ProtoMessage() {}\n\nfunc (x *Cart) ProtoReflect() protoreflect.Message {\n\tmi := &file_demo_proto_msgTypes[4]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Cart.ProtoReflect.Descriptor instead.\nfunc (*Cart) Descriptor() ([]byte, []int) {\n\treturn file_demo_proto_rawDescGZIP(), []int{4}\n}\n\nfunc (x *Cart) GetUserId() string {\n\tif x != nil {\n\t\treturn x.UserId\n\t}\n\treturn \"\"\n}\n\nfunc (x *Cart) GetItems() []*CartItem {\n\tif x != nil {\n\t\treturn x.Items\n\t}\n\treturn nil\n}\n\ntype Empty struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n}\n\nfunc (x *Empty) Reset() {\n\t*x = Empty{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_demo_proto_msgTypes[5]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *Empty) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Empty) ProtoMessage() {}\n\nfunc (x *Empty) ProtoReflect() protoreflect.Message {\n\tmi := &file_demo_proto_msgTypes[5]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Empty.ProtoReflect.Descriptor instead.\nfunc (*Empty) Descriptor() ([]byte, []int) {\n\treturn file_demo_proto_rawDescGZIP(), []int{5}\n}\n\ntype ListRecommendationsRequest struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tUserId     string   `protobuf:\"bytes,1,opt,name=user_id,json=userId,proto3\" json:\"user_id,omitempty\"`\n\tProductIds []string `protobuf:\"bytes,2,rep,name=product_ids,json=productIds,proto3\" json:\"product_ids,omitempty\"`\n}\n\nfunc (x *ListRecommendationsRequest) Reset() {\n\t*x = ListRecommendationsRequest{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_demo_proto_msgTypes[6]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *ListRecommendationsRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ListRecommendationsRequest) ProtoMessage() {}\n\nfunc (x *ListRecommendationsRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_demo_proto_msgTypes[6]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use ListRecommendationsRequest.ProtoReflect.Descriptor instead.\nfunc (*ListRecommendationsRequest) Descriptor() ([]byte, []int) {\n\treturn file_demo_proto_rawDescGZIP(), []int{6}\n}\n\nfunc (x *ListRecommendationsRequest) GetUserId() string {\n\tif x != nil {\n\t\treturn x.UserId\n\t}\n\treturn \"\"\n}\n\nfunc (x *ListRecommendationsRequest) GetProductIds() []string {\n\tif x != nil {\n\t\treturn x.ProductIds\n\t}\n\treturn nil\n}\n\ntype ListRecommendationsResponse struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tProductIds []string `protobuf:\"bytes,1,rep,name=product_ids,json=productIds,proto3\" json:\"product_ids,omitempty\"`\n}\n\nfunc (x *ListRecommendationsResponse) Reset() {\n\t*x = ListRecommendationsResponse{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_demo_proto_msgTypes[7]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *ListRecommendationsResponse) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ListRecommendationsResponse) ProtoMessage() {}\n\nfunc (x *ListRecommendationsResponse) ProtoReflect() protoreflect.Message {\n\tmi := &file_demo_proto_msgTypes[7]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use ListRecommendationsResponse.ProtoReflect.Descriptor instead.\nfunc (*ListRecommendationsResponse) Descriptor() ([]byte, []int) {\n\treturn file_demo_proto_rawDescGZIP(), []int{7}\n}\n\nfunc (x *ListRecommendationsResponse) GetProductIds() []string {\n\tif x != nil {\n\t\treturn x.ProductIds\n\t}\n\treturn nil\n}\n\ntype Product struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tId          string `protobuf:\"bytes,1,opt,name=id,proto3\" json:\"id,omitempty\"`\n\tName        string `protobuf:\"bytes,2,opt,name=name,proto3\" json:\"name,omitempty\"`\n\tDescription string `protobuf:\"bytes,3,opt,name=description,proto3\" json:\"description,omitempty\"`\n\tPicture     string `protobuf:\"bytes,4,opt,name=picture,proto3\" json:\"picture,omitempty\"`\n\tPriceUsd    *Money `protobuf:\"bytes,5,opt,name=price_usd,json=priceUsd,proto3\" json:\"price_usd,omitempty\"`\n\t// Categories such as \"clothing\" or \"kitchen\" that can be used to look up\n\t// other related products.\n\tCategories []string `protobuf:\"bytes,6,rep,name=categories,proto3\" json:\"categories,omitempty\"`\n}\n\nfunc (x *Product) Reset() {\n\t*x = Product{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_demo_proto_msgTypes[8]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *Product) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Product) ProtoMessage() {}\n\nfunc (x *Product) ProtoReflect() protoreflect.Message {\n\tmi := &file_demo_proto_msgTypes[8]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Product.ProtoReflect.Descriptor instead.\nfunc (*Product) Descriptor() ([]byte, []int) {\n\treturn file_demo_proto_rawDescGZIP(), []int{8}\n}\n\nfunc (x *Product) GetId() string {\n\tif x != nil {\n\t\treturn x.Id\n\t}\n\treturn \"\"\n}\n\nfunc (x *Product) GetName() string {\n\tif x != nil {\n\t\treturn x.Name\n\t}\n\treturn \"\"\n}\n\nfunc (x *Product) GetDescription() string {\n\tif x != nil {\n\t\treturn x.Description\n\t}\n\treturn \"\"\n}\n\nfunc (x *Product) GetPicture() string {\n\tif x != nil {\n\t\treturn x.Picture\n\t}\n\treturn \"\"\n}\n\nfunc (x *Product) GetPriceUsd() *Money {\n\tif x != nil {\n\t\treturn x.PriceUsd\n\t}\n\treturn nil\n}\n\nfunc (x *Product) GetCategories() []string {\n\tif x != nil {\n\t\treturn x.Categories\n\t}\n\treturn nil\n}\n\ntype ListProductsResponse struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tProducts []*Product `protobuf:\"bytes,1,rep,name=products,proto3\" json:\"products,omitempty\"`\n}\n\nfunc (x *ListProductsResponse) Reset() {\n\t*x = ListProductsResponse{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_demo_proto_msgTypes[9]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *ListProductsResponse) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ListProductsResponse) ProtoMessage() {}\n\nfunc (x *ListProductsResponse) ProtoReflect() protoreflect.Message {\n\tmi := &file_demo_proto_msgTypes[9]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use ListProductsResponse.ProtoReflect.Descriptor instead.\nfunc (*ListProductsResponse) Descriptor() ([]byte, []int) {\n\treturn file_demo_proto_rawDescGZIP(), []int{9}\n}\n\nfunc (x *ListProductsResponse) GetProducts() []*Product {\n\tif x != nil {\n\t\treturn x.Products\n\t}\n\treturn nil\n}\n\ntype GetProductRequest struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tId string `protobuf:\"bytes,1,opt,name=id,proto3\" json:\"id,omitempty\"`\n}\n\nfunc (x *GetProductRequest) Reset() {\n\t*x = GetProductRequest{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_demo_proto_msgTypes[10]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *GetProductRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*GetProductRequest) ProtoMessage() {}\n\nfunc (x *GetProductRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_demo_proto_msgTypes[10]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use GetProductRequest.ProtoReflect.Descriptor instead.\nfunc (*GetProductRequest) Descriptor() ([]byte, []int) {\n\treturn file_demo_proto_rawDescGZIP(), []int{10}\n}\n\nfunc (x *GetProductRequest) GetId() string {\n\tif x != nil {\n\t\treturn x.Id\n\t}\n\treturn \"\"\n}\n\ntype SearchProductsRequest struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tQuery string `protobuf:\"bytes,1,opt,name=query,proto3\" json:\"query,omitempty\"`\n}\n\nfunc (x *SearchProductsRequest) Reset() {\n\t*x = SearchProductsRequest{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_demo_proto_msgTypes[11]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *SearchProductsRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*SearchProductsRequest) ProtoMessage() {}\n\nfunc (x *SearchProductsRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_demo_proto_msgTypes[11]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use SearchProductsRequest.ProtoReflect.Descriptor instead.\nfunc (*SearchProductsRequest) Descriptor() ([]byte, []int) {\n\treturn file_demo_proto_rawDescGZIP(), []int{11}\n}\n\nfunc (x *SearchProductsRequest) GetQuery() string {\n\tif x != nil {\n\t\treturn x.Query\n\t}\n\treturn \"\"\n}\n\ntype SearchProductsResponse struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tResults []*Product `protobuf:\"bytes,1,rep,name=results,proto3\" json:\"results,omitempty\"`\n}\n\nfunc (x *SearchProductsResponse) Reset() {\n\t*x = SearchProductsResponse{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_demo_proto_msgTypes[12]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *SearchProductsResponse) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*SearchProductsResponse) ProtoMessage() {}\n\nfunc (x *SearchProductsResponse) ProtoReflect() protoreflect.Message {\n\tmi := &file_demo_proto_msgTypes[12]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use SearchProductsResponse.ProtoReflect.Descriptor instead.\nfunc (*SearchProductsResponse) Descriptor() ([]byte, []int) {\n\treturn file_demo_proto_rawDescGZIP(), []int{12}\n}\n\nfunc (x *SearchProductsResponse) GetResults() []*Product {\n\tif x != nil {\n\t\treturn x.Results\n\t}\n\treturn nil\n}\n\ntype GetQuoteRequest struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tAddress *Address    `protobuf:\"bytes,1,opt,name=address,proto3\" json:\"address,omitempty\"`\n\tItems   []*CartItem `protobuf:\"bytes,2,rep,name=items,proto3\" json:\"items,omitempty\"`\n}\n\nfunc (x *GetQuoteRequest) Reset() {\n\t*x = GetQuoteRequest{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_demo_proto_msgTypes[13]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *GetQuoteRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*GetQuoteRequest) ProtoMessage() {}\n\nfunc (x *GetQuoteRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_demo_proto_msgTypes[13]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use GetQuoteRequest.ProtoReflect.Descriptor instead.\nfunc (*GetQuoteRequest) Descriptor() ([]byte, []int) {\n\treturn file_demo_proto_rawDescGZIP(), []int{13}\n}\n\nfunc (x *GetQuoteRequest) GetAddress() *Address {\n\tif x != nil {\n\t\treturn x.Address\n\t}\n\treturn nil\n}\n\nfunc (x *GetQuoteRequest) GetItems() []*CartItem {\n\tif x != nil {\n\t\treturn x.Items\n\t}\n\treturn nil\n}\n\ntype GetQuoteResponse struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tCostUsd *Money `protobuf:\"bytes,1,opt,name=cost_usd,json=costUsd,proto3\" json:\"cost_usd,omitempty\"`\n}\n\nfunc (x *GetQuoteResponse) Reset() {\n\t*x = GetQuoteResponse{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_demo_proto_msgTypes[14]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *GetQuoteResponse) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*GetQuoteResponse) ProtoMessage() {}\n\nfunc (x *GetQuoteResponse) ProtoReflect() protoreflect.Message {\n\tmi := &file_demo_proto_msgTypes[14]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use GetQuoteResponse.ProtoReflect.Descriptor instead.\nfunc (*GetQuoteResponse) Descriptor() ([]byte, []int) {\n\treturn file_demo_proto_rawDescGZIP(), []int{14}\n}\n\nfunc (x *GetQuoteResponse) GetCostUsd() *Money {\n\tif x != nil {\n\t\treturn x.CostUsd\n\t}\n\treturn nil\n}\n\ntype ShipOrderRequest struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tAddress *Address    `protobuf:\"bytes,1,opt,name=address,proto3\" json:\"address,omitempty\"`\n\tItems   []*CartItem `protobuf:\"bytes,2,rep,name=items,proto3\" json:\"items,omitempty\"`\n}\n\nfunc (x *ShipOrderRequest) Reset() {\n\t*x = ShipOrderRequest{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_demo_proto_msgTypes[15]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *ShipOrderRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ShipOrderRequest) ProtoMessage() {}\n\nfunc (x *ShipOrderRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_demo_proto_msgTypes[15]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use ShipOrderRequest.ProtoReflect.Descriptor instead.\nfunc (*ShipOrderRequest) Descriptor() ([]byte, []int) {\n\treturn file_demo_proto_rawDescGZIP(), []int{15}\n}\n\nfunc (x *ShipOrderRequest) GetAddress() *Address {\n\tif x != nil {\n\t\treturn x.Address\n\t}\n\treturn nil\n}\n\nfunc (x *ShipOrderRequest) GetItems() []*CartItem {\n\tif x != nil {\n\t\treturn x.Items\n\t}\n\treturn nil\n}\n\ntype ShipOrderResponse struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tTrackingId string `protobuf:\"bytes,1,opt,name=tracking_id,json=trackingId,proto3\" json:\"tracking_id,omitempty\"`\n}\n\nfunc (x *ShipOrderResponse) Reset() {\n\t*x = ShipOrderResponse{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_demo_proto_msgTypes[16]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *ShipOrderResponse) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ShipOrderResponse) ProtoMessage() {}\n\nfunc (x *ShipOrderResponse) ProtoReflect() protoreflect.Message {\n\tmi := &file_demo_proto_msgTypes[16]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use ShipOrderResponse.ProtoReflect.Descriptor instead.\nfunc (*ShipOrderResponse) Descriptor() ([]byte, []int) {\n\treturn file_demo_proto_rawDescGZIP(), []int{16}\n}\n\nfunc (x *ShipOrderResponse) GetTrackingId() string {\n\tif x != nil {\n\t\treturn x.TrackingId\n\t}\n\treturn \"\"\n}\n\ntype Address struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tStreetAddress string `protobuf:\"bytes,1,opt,name=street_address,json=streetAddress,proto3\" json:\"street_address,omitempty\"`\n\tCity          string `protobuf:\"bytes,2,opt,name=city,proto3\" json:\"city,omitempty\"`\n\tState         string `protobuf:\"bytes,3,opt,name=state,proto3\" json:\"state,omitempty\"`\n\tCountry       string `protobuf:\"bytes,4,opt,name=country,proto3\" json:\"country,omitempty\"`\n\tZipCode       int32  `protobuf:\"varint,5,opt,name=zip_code,json=zipCode,proto3\" json:\"zip_code,omitempty\"`\n}\n\nfunc (x *Address) Reset() {\n\t*x = Address{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_demo_proto_msgTypes[17]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *Address) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Address) ProtoMessage() {}\n\nfunc (x *Address) ProtoReflect() protoreflect.Message {\n\tmi := &file_demo_proto_msgTypes[17]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Address.ProtoReflect.Descriptor instead.\nfunc (*Address) Descriptor() ([]byte, []int) {\n\treturn file_demo_proto_rawDescGZIP(), []int{17}\n}\n\nfunc (x *Address) GetStreetAddress() string {\n\tif x != nil {\n\t\treturn x.StreetAddress\n\t}\n\treturn \"\"\n}\n\nfunc (x *Address) GetCity() string {\n\tif x != nil {\n\t\treturn x.City\n\t}\n\treturn \"\"\n}\n\nfunc (x *Address) GetState() string {\n\tif x != nil {\n\t\treturn x.State\n\t}\n\treturn \"\"\n}\n\nfunc (x *Address) GetCountry() string {\n\tif x != nil {\n\t\treturn x.Country\n\t}\n\treturn \"\"\n}\n\nfunc (x *Address) GetZipCode() int32 {\n\tif x != nil {\n\t\treturn x.ZipCode\n\t}\n\treturn 0\n}\n\n// Represents an amount of money with its currency type.\ntype Money struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// The 3-letter currency code defined in ISO 4217.\n\tCurrencyCode string `protobuf:\"bytes,1,opt,name=currency_code,json=currencyCode,proto3\" json:\"currency_code,omitempty\"`\n\t// The whole units of the amount.\n\t// For example if `currencyCode` is `\"USD\"`, then 1 unit is one US dollar.\n\tUnits int64 `protobuf:\"varint,2,opt,name=units,proto3\" json:\"units,omitempty\"`\n\t// Number of nano (10^-9) units of the amount.\n\t// The value must be between -999,999,999 and +999,999,999 inclusive.\n\t// If `units` is positive, `nanos` must be positive or zero.\n\t// If `units` is zero, `nanos` can be positive, zero, or negative.\n\t// If `units` is negative, `nanos` must be negative or zero.\n\t// For example $-1.75 is represented as `units`=-1 and `nanos`=-750,000,000.\n\tNanos int32 `protobuf:\"varint,3,opt,name=nanos,proto3\" json:\"nanos,omitempty\"`\n}\n\nfunc (x *Money) Reset() {\n\t*x = Money{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_demo_proto_msgTypes[18]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *Money) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Money) ProtoMessage() {}\n\nfunc (x *Money) ProtoReflect() protoreflect.Message {\n\tmi := &file_demo_proto_msgTypes[18]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Money.ProtoReflect.Descriptor instead.\nfunc (*Money) Descriptor() ([]byte, []int) {\n\treturn file_demo_proto_rawDescGZIP(), []int{18}\n}\n\nfunc (x *Money) GetCurrencyCode() string {\n\tif x != nil {\n\t\treturn x.CurrencyCode\n\t}\n\treturn \"\"\n}\n\nfunc (x *Money) GetUnits() int64 {\n\tif x != nil {\n\t\treturn x.Units\n\t}\n\treturn 0\n}\n\nfunc (x *Money) GetNanos() int32 {\n\tif x != nil {\n\t\treturn x.Nanos\n\t}\n\treturn 0\n}\n\ntype GetSupportedCurrenciesResponse struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// The 3-letter currency code defined in ISO 4217.\n\tCurrencyCodes []string `protobuf:\"bytes,1,rep,name=currency_codes,json=currencyCodes,proto3\" json:\"currency_codes,omitempty\"`\n}\n\nfunc (x *GetSupportedCurrenciesResponse) Reset() {\n\t*x = GetSupportedCurrenciesResponse{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_demo_proto_msgTypes[19]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *GetSupportedCurrenciesResponse) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*GetSupportedCurrenciesResponse) ProtoMessage() {}\n\nfunc (x *GetSupportedCurrenciesResponse) ProtoReflect() protoreflect.Message {\n\tmi := &file_demo_proto_msgTypes[19]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use GetSupportedCurrenciesResponse.ProtoReflect.Descriptor instead.\nfunc (*GetSupportedCurrenciesResponse) Descriptor() ([]byte, []int) {\n\treturn file_demo_proto_rawDescGZIP(), []int{19}\n}\n\nfunc (x *GetSupportedCurrenciesResponse) GetCurrencyCodes() []string {\n\tif x != nil {\n\t\treturn x.CurrencyCodes\n\t}\n\treturn nil\n}\n\ntype CurrencyConversionRequest struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tFrom *Money `protobuf:\"bytes,1,opt,name=from,proto3\" json:\"from,omitempty\"`\n\t// The 3-letter currency code defined in ISO 4217.\n\tToCode string `protobuf:\"bytes,2,opt,name=to_code,json=toCode,proto3\" json:\"to_code,omitempty\"`\n}\n\nfunc (x *CurrencyConversionRequest) Reset() {\n\t*x = CurrencyConversionRequest{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_demo_proto_msgTypes[20]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *CurrencyConversionRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*CurrencyConversionRequest) ProtoMessage() {}\n\nfunc (x *CurrencyConversionRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_demo_proto_msgTypes[20]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use CurrencyConversionRequest.ProtoReflect.Descriptor instead.\nfunc (*CurrencyConversionRequest) Descriptor() ([]byte, []int) {\n\treturn file_demo_proto_rawDescGZIP(), []int{20}\n}\n\nfunc (x *CurrencyConversionRequest) GetFrom() *Money {\n\tif x != nil {\n\t\treturn x.From\n\t}\n\treturn nil\n}\n\nfunc (x *CurrencyConversionRequest) GetToCode() string {\n\tif x != nil {\n\t\treturn x.ToCode\n\t}\n\treturn \"\"\n}\n\ntype CreditCardInfo struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tCreditCardNumber          string `protobuf:\"bytes,1,opt,name=credit_card_number,json=creditCardNumber,proto3\" json:\"credit_card_number,omitempty\"`\n\tCreditCardCvv             int32  `protobuf:\"varint,2,opt,name=credit_card_cvv,json=creditCardCvv,proto3\" json:\"credit_card_cvv,omitempty\"`\n\tCreditCardExpirationYear  int32  `protobuf:\"varint,3,opt,name=credit_card_expiration_year,json=creditCardExpirationYear,proto3\" json:\"credit_card_expiration_year,omitempty\"`\n\tCreditCardExpirationMonth int32  `protobuf:\"varint,4,opt,name=credit_card_expiration_month,json=creditCardExpirationMonth,proto3\" json:\"credit_card_expiration_month,omitempty\"`\n}\n\nfunc (x *CreditCardInfo) Reset() {\n\t*x = CreditCardInfo{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_demo_proto_msgTypes[21]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *CreditCardInfo) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*CreditCardInfo) ProtoMessage() {}\n\nfunc (x *CreditCardInfo) ProtoReflect() protoreflect.Message {\n\tmi := &file_demo_proto_msgTypes[21]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use CreditCardInfo.ProtoReflect.Descriptor instead.\nfunc (*CreditCardInfo) Descriptor() ([]byte, []int) {\n\treturn file_demo_proto_rawDescGZIP(), []int{21}\n}\n\nfunc (x *CreditCardInfo) GetCreditCardNumber() string {\n\tif x != nil {\n\t\treturn x.CreditCardNumber\n\t}\n\treturn \"\"\n}\n\nfunc (x *CreditCardInfo) GetCreditCardCvv() int32 {\n\tif x != nil {\n\t\treturn x.CreditCardCvv\n\t}\n\treturn 0\n}\n\nfunc (x *CreditCardInfo) GetCreditCardExpirationYear() int32 {\n\tif x != nil {\n\t\treturn x.CreditCardExpirationYear\n\t}\n\treturn 0\n}\n\nfunc (x *CreditCardInfo) GetCreditCardExpirationMonth() int32 {\n\tif x != nil {\n\t\treturn x.CreditCardExpirationMonth\n\t}\n\treturn 0\n}\n\ntype ChargeRequest struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tAmount     *Money          `protobuf:\"bytes,1,opt,name=amount,proto3\" json:\"amount,omitempty\"`\n\tCreditCard *CreditCardInfo `protobuf:\"bytes,2,opt,name=credit_card,json=creditCard,proto3\" json:\"credit_card,omitempty\"`\n}\n\nfunc (x *ChargeRequest) Reset() {\n\t*x = ChargeRequest{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_demo_proto_msgTypes[22]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *ChargeRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ChargeRequest) ProtoMessage() {}\n\nfunc (x *ChargeRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_demo_proto_msgTypes[22]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use ChargeRequest.ProtoReflect.Descriptor instead.\nfunc (*ChargeRequest) Descriptor() ([]byte, []int) {\n\treturn file_demo_proto_rawDescGZIP(), []int{22}\n}\n\nfunc (x *ChargeRequest) GetAmount() *Money {\n\tif x != nil {\n\t\treturn x.Amount\n\t}\n\treturn nil\n}\n\nfunc (x *ChargeRequest) GetCreditCard() *CreditCardInfo {\n\tif x != nil {\n\t\treturn x.CreditCard\n\t}\n\treturn nil\n}\n\ntype ChargeResponse struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tTransactionId string `protobuf:\"bytes,1,opt,name=transaction_id,json=transactionId,proto3\" json:\"transaction_id,omitempty\"`\n}\n\nfunc (x *ChargeResponse) Reset() {\n\t*x = ChargeResponse{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_demo_proto_msgTypes[23]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *ChargeResponse) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ChargeResponse) ProtoMessage() {}\n\nfunc (x *ChargeResponse) ProtoReflect() protoreflect.Message {\n\tmi := &file_demo_proto_msgTypes[23]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use ChargeResponse.ProtoReflect.Descriptor instead.\nfunc (*ChargeResponse) Descriptor() ([]byte, []int) {\n\treturn file_demo_proto_rawDescGZIP(), []int{23}\n}\n\nfunc (x *ChargeResponse) GetTransactionId() string {\n\tif x != nil {\n\t\treturn x.TransactionId\n\t}\n\treturn \"\"\n}\n\ntype OrderItem struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tItem *CartItem `protobuf:\"bytes,1,opt,name=item,proto3\" json:\"item,omitempty\"`\n\tCost *Money    `protobuf:\"bytes,2,opt,name=cost,proto3\" json:\"cost,omitempty\"`\n}\n\nfunc (x *OrderItem) Reset() {\n\t*x = OrderItem{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_demo_proto_msgTypes[24]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *OrderItem) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*OrderItem) ProtoMessage() {}\n\nfunc (x *OrderItem) ProtoReflect() protoreflect.Message {\n\tmi := &file_demo_proto_msgTypes[24]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use OrderItem.ProtoReflect.Descriptor instead.\nfunc (*OrderItem) Descriptor() ([]byte, []int) {\n\treturn file_demo_proto_rawDescGZIP(), []int{24}\n}\n\nfunc (x *OrderItem) GetItem() *CartItem {\n\tif x != nil {\n\t\treturn x.Item\n\t}\n\treturn nil\n}\n\nfunc (x *OrderItem) GetCost() *Money {\n\tif x != nil {\n\t\treturn x.Cost\n\t}\n\treturn nil\n}\n\ntype OrderResult struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tOrderId            string       `protobuf:\"bytes,1,opt,name=order_id,json=orderId,proto3\" json:\"order_id,omitempty\"`\n\tShippingTrackingId string       `protobuf:\"bytes,2,opt,name=shipping_tracking_id,json=shippingTrackingId,proto3\" json:\"shipping_tracking_id,omitempty\"`\n\tShippingCost       *Money       `protobuf:\"bytes,3,opt,name=shipping_cost,json=shippingCost,proto3\" json:\"shipping_cost,omitempty\"`\n\tShippingAddress    *Address     `protobuf:\"bytes,4,opt,name=shipping_address,json=shippingAddress,proto3\" json:\"shipping_address,omitempty\"`\n\tItems              []*OrderItem `protobuf:\"bytes,5,rep,name=items,proto3\" json:\"items,omitempty\"`\n}\n\nfunc (x *OrderResult) Reset() {\n\t*x = OrderResult{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_demo_proto_msgTypes[25]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *OrderResult) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*OrderResult) ProtoMessage() {}\n\nfunc (x *OrderResult) ProtoReflect() protoreflect.Message {\n\tmi := &file_demo_proto_msgTypes[25]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use OrderResult.ProtoReflect.Descriptor instead.\nfunc (*OrderResult) Descriptor() ([]byte, []int) {\n\treturn file_demo_proto_rawDescGZIP(), []int{25}\n}\n\nfunc (x *OrderResult) GetOrderId() string {\n\tif x != nil {\n\t\treturn x.OrderId\n\t}\n\treturn \"\"\n}\n\nfunc (x *OrderResult) GetShippingTrackingId() string {\n\tif x != nil {\n\t\treturn x.ShippingTrackingId\n\t}\n\treturn \"\"\n}\n\nfunc (x *OrderResult) GetShippingCost() *Money {\n\tif x != nil {\n\t\treturn x.ShippingCost\n\t}\n\treturn nil\n}\n\nfunc (x *OrderResult) GetShippingAddress() *Address {\n\tif x != nil {\n\t\treturn x.ShippingAddress\n\t}\n\treturn nil\n}\n\nfunc (x *OrderResult) GetItems() []*OrderItem {\n\tif x != nil {\n\t\treturn x.Items\n\t}\n\treturn nil\n}\n\ntype SendOrderConfirmationRequest struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tEmail string       `protobuf:\"bytes,1,opt,name=email,proto3\" json:\"email,omitempty\"`\n\tOrder *OrderResult `protobuf:\"bytes,2,opt,name=order,proto3\" json:\"order,omitempty\"`\n}\n\nfunc (x *SendOrderConfirmationRequest) Reset() {\n\t*x = SendOrderConfirmationRequest{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_demo_proto_msgTypes[26]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *SendOrderConfirmationRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*SendOrderConfirmationRequest) ProtoMessage() {}\n\nfunc (x *SendOrderConfirmationRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_demo_proto_msgTypes[26]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use SendOrderConfirmationRequest.ProtoReflect.Descriptor instead.\nfunc (*SendOrderConfirmationRequest) Descriptor() ([]byte, []int) {\n\treturn file_demo_proto_rawDescGZIP(), []int{26}\n}\n\nfunc (x *SendOrderConfirmationRequest) GetEmail() string {\n\tif x != nil {\n\t\treturn x.Email\n\t}\n\treturn \"\"\n}\n\nfunc (x *SendOrderConfirmationRequest) GetOrder() *OrderResult {\n\tif x != nil {\n\t\treturn x.Order\n\t}\n\treturn nil\n}\n\ntype PlaceOrderRequest struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tUserId       string          `protobuf:\"bytes,1,opt,name=user_id,json=userId,proto3\" json:\"user_id,omitempty\"`\n\tUserCurrency string          `protobuf:\"bytes,2,opt,name=user_currency,json=userCurrency,proto3\" json:\"user_currency,omitempty\"`\n\tAddress      *Address        `protobuf:\"bytes,3,opt,name=address,proto3\" json:\"address,omitempty\"`\n\tEmail        string          `protobuf:\"bytes,5,opt,name=email,proto3\" json:\"email,omitempty\"`\n\tCreditCard   *CreditCardInfo `protobuf:\"bytes,6,opt,name=credit_card,json=creditCard,proto3\" json:\"credit_card,omitempty\"`\n}\n\nfunc (x *PlaceOrderRequest) Reset() {\n\t*x = PlaceOrderRequest{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_demo_proto_msgTypes[27]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *PlaceOrderRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*PlaceOrderRequest) ProtoMessage() {}\n\nfunc (x *PlaceOrderRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_demo_proto_msgTypes[27]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use PlaceOrderRequest.ProtoReflect.Descriptor instead.\nfunc (*PlaceOrderRequest) Descriptor() ([]byte, []int) {\n\treturn file_demo_proto_rawDescGZIP(), []int{27}\n}\n\nfunc (x *PlaceOrderRequest) GetUserId() string {\n\tif x != nil {\n\t\treturn x.UserId\n\t}\n\treturn \"\"\n}\n\nfunc (x *PlaceOrderRequest) GetUserCurrency() string {\n\tif x != nil {\n\t\treturn x.UserCurrency\n\t}\n\treturn \"\"\n}\n\nfunc (x *PlaceOrderRequest) GetAddress() *Address {\n\tif x != nil {\n\t\treturn x.Address\n\t}\n\treturn nil\n}\n\nfunc (x *PlaceOrderRequest) GetEmail() string {\n\tif x != nil {\n\t\treturn x.Email\n\t}\n\treturn \"\"\n}\n\nfunc (x *PlaceOrderRequest) GetCreditCard() *CreditCardInfo {\n\tif x != nil {\n\t\treturn x.CreditCard\n\t}\n\treturn nil\n}\n\ntype PlaceOrderResponse struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tOrder *OrderResult `protobuf:\"bytes,1,opt,name=order,proto3\" json:\"order,omitempty\"`\n}\n\nfunc (x *PlaceOrderResponse) Reset() {\n\t*x = PlaceOrderResponse{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_demo_proto_msgTypes[28]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *PlaceOrderResponse) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*PlaceOrderResponse) ProtoMessage() {}\n\nfunc (x *PlaceOrderResponse) ProtoReflect() protoreflect.Message {\n\tmi := &file_demo_proto_msgTypes[28]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use PlaceOrderResponse.ProtoReflect.Descriptor instead.\nfunc (*PlaceOrderResponse) Descriptor() ([]byte, []int) {\n\treturn file_demo_proto_rawDescGZIP(), []int{28}\n}\n\nfunc (x *PlaceOrderResponse) GetOrder() *OrderResult {\n\tif x != nil {\n\t\treturn x.Order\n\t}\n\treturn nil\n}\n\ntype AdRequest struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// List of important key words from the current page describing the context.\n\tContextKeys []string `protobuf:\"bytes,1,rep,name=context_keys,json=contextKeys,proto3\" json:\"context_keys,omitempty\"`\n}\n\nfunc (x *AdRequest) Reset() {\n\t*x = AdRequest{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_demo_proto_msgTypes[29]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *AdRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*AdRequest) ProtoMessage() {}\n\nfunc (x *AdRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_demo_proto_msgTypes[29]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use AdRequest.ProtoReflect.Descriptor instead.\nfunc (*AdRequest) Descriptor() ([]byte, []int) {\n\treturn file_demo_proto_rawDescGZIP(), []int{29}\n}\n\nfunc (x *AdRequest) GetContextKeys() []string {\n\tif x != nil {\n\t\treturn x.ContextKeys\n\t}\n\treturn nil\n}\n\ntype AdResponse struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tAds []*Ad `protobuf:\"bytes,1,rep,name=ads,proto3\" json:\"ads,omitempty\"`\n}\n\nfunc (x *AdResponse) Reset() {\n\t*x = AdResponse{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_demo_proto_msgTypes[30]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *AdResponse) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*AdResponse) ProtoMessage() {}\n\nfunc (x *AdResponse) ProtoReflect() protoreflect.Message {\n\tmi := &file_demo_proto_msgTypes[30]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use AdResponse.ProtoReflect.Descriptor instead.\nfunc (*AdResponse) Descriptor() ([]byte, []int) {\n\treturn file_demo_proto_rawDescGZIP(), []int{30}\n}\n\nfunc (x *AdResponse) GetAds() []*Ad {\n\tif x != nil {\n\t\treturn x.Ads\n\t}\n\treturn nil\n}\n\ntype Ad struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// url to redirect to when an ad is clicked.\n\tRedirectUrl string `protobuf:\"bytes,1,opt,name=redirect_url,json=redirectUrl,proto3\" json:\"redirect_url,omitempty\"`\n\t// short advertisement text to display.\n\tText string `protobuf:\"bytes,2,opt,name=text,proto3\" json:\"text,omitempty\"`\n}\n\nfunc (x *Ad) Reset() {\n\t*x = Ad{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_demo_proto_msgTypes[31]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *Ad) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Ad) ProtoMessage() {}\n\nfunc (x *Ad) ProtoReflect() protoreflect.Message {\n\tmi := &file_demo_proto_msgTypes[31]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Ad.ProtoReflect.Descriptor instead.\nfunc (*Ad) Descriptor() ([]byte, []int) {\n\treturn file_demo_proto_rawDescGZIP(), []int{31}\n}\n\nfunc (x *Ad) GetRedirectUrl() string {\n\tif x != nil {\n\t\treturn x.RedirectUrl\n\t}\n\treturn \"\"\n}\n\nfunc (x *Ad) GetText() string {\n\tif x != nil {\n\t\treturn x.Text\n\t}\n\treturn \"\"\n}\n\nvar File_demo_proto protoreflect.FileDescriptor\n\nvar file_demo_proto_rawDesc = []byte{\n\t0x0a, 0x0a, 0x64, 0x65, 0x6d, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0b, 0x68, 0x69,\n\t0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x22, 0x45, 0x0a, 0x08, 0x43, 0x61, 0x72,\n\t0x74, 0x49, 0x74, 0x65, 0x6d, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74,\n\t0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x70, 0x72, 0x6f, 0x64, 0x75,\n\t0x63, 0x74, 0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x71, 0x75, 0x61, 0x6e, 0x74, 0x69, 0x74, 0x79,\n\t0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x71, 0x75, 0x61, 0x6e, 0x74, 0x69, 0x74, 0x79,\n\t0x22, 0x54, 0x0a, 0x0e, 0x41, 0x64, 0x64, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65,\n\t0x73, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20,\n\t0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x29, 0x0a, 0x04, 0x69,\n\t0x74, 0x65, 0x6d, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x68, 0x69, 0x70, 0x73,\n\t0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x43, 0x61, 0x72, 0x74, 0x49, 0x74, 0x65, 0x6d,\n\t0x52, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x22, 0x2b, 0x0a, 0x10, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x43,\n\t0x61, 0x72, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73,\n\t0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65,\n\t0x72, 0x49, 0x64, 0x22, 0x29, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x43, 0x61, 0x72, 0x74, 0x52, 0x65,\n\t0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64,\n\t0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0x4c,\n\t0x0a, 0x04, 0x43, 0x61, 0x72, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69,\n\t0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12,\n\t0x2b, 0x0a, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15,\n\t0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x43, 0x61, 0x72,\n\t0x74, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x22, 0x07, 0x0a, 0x05,\n\t0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x56, 0x0a, 0x1a, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x63,\n\t0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75,\n\t0x65, 0x73, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01,\n\t0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b,\n\t0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28,\n\t0x09, 0x52, 0x0a, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x49, 0x64, 0x73, 0x22, 0x3e, 0x0a,\n\t0x1b, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x64, 0x61, 0x74,\n\t0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1f, 0x0a, 0x0b,\n\t0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28,\n\t0x09, 0x52, 0x0a, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x49, 0x64, 0x73, 0x22, 0xba, 0x01,\n\t0x0a, 0x07, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18,\n\t0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d,\n\t0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x20, 0x0a,\n\t0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01,\n\t0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12,\n\t0x18, 0x0a, 0x07, 0x70, 0x69, 0x63, 0x74, 0x75, 0x72, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09,\n\t0x52, 0x07, 0x70, 0x69, 0x63, 0x74, 0x75, 0x72, 0x65, 0x12, 0x2f, 0x0a, 0x09, 0x70, 0x72, 0x69,\n\t0x63, 0x65, 0x5f, 0x75, 0x73, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x68,\n\t0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x4d, 0x6f, 0x6e, 0x65, 0x79,\n\t0x52, 0x08, 0x70, 0x72, 0x69, 0x63, 0x65, 0x55, 0x73, 0x64, 0x12, 0x1e, 0x0a, 0x0a, 0x63, 0x61,\n\t0x74, 0x65, 0x67, 0x6f, 0x72, 0x69, 0x65, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a,\n\t0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x69, 0x65, 0x73, 0x22, 0x48, 0x0a, 0x14, 0x4c, 0x69,\n\t0x73, 0x74, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,\n\t0x73, 0x65, 0x12, 0x30, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x73, 0x18, 0x01,\n\t0x20, 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68,\n\t0x6f, 0x70, 0x2e, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x64,\n\t0x75, 0x63, 0x74, 0x73, 0x22, 0x23, 0x0a, 0x11, 0x47, 0x65, 0x74, 0x50, 0x72, 0x6f, 0x64, 0x75,\n\t0x63, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18,\n\t0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x22, 0x2d, 0x0a, 0x15, 0x53, 0x65, 0x61,\n\t0x72, 0x63, 0x68, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65,\n\t0x73, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28,\n\t0x09, 0x52, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x22, 0x48, 0x0a, 0x16, 0x53, 0x65, 0x61, 0x72,\n\t0x63, 0x68, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,\n\t0x73, 0x65, 0x12, 0x2e, 0x0a, 0x07, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x18, 0x01, 0x20,\n\t0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f,\n\t0x70, 0x2e, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x52, 0x07, 0x72, 0x65, 0x73, 0x75, 0x6c,\n\t0x74, 0x73, 0x22, 0x6e, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x51, 0x75, 0x6f, 0x74, 0x65, 0x52, 0x65,\n\t0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73,\n\t0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72,\n\t0x73, 0x68, 0x6f, 0x70, 0x2e, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, 0x07, 0x61, 0x64,\n\t0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x2b, 0x0a, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x18, 0x02,\n\t0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68,\n\t0x6f, 0x70, 0x2e, 0x43, 0x61, 0x72, 0x74, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x05, 0x69, 0x74, 0x65,\n\t0x6d, 0x73, 0x22, 0x41, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x51, 0x75, 0x6f, 0x74, 0x65, 0x52, 0x65,\n\t0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2d, 0x0a, 0x08, 0x63, 0x6f, 0x73, 0x74, 0x5f, 0x75,\n\t0x73, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74,\n\t0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x4d, 0x6f, 0x6e, 0x65, 0x79, 0x52, 0x07, 0x63, 0x6f,\n\t0x73, 0x74, 0x55, 0x73, 0x64, 0x22, 0x6f, 0x0a, 0x10, 0x53, 0x68, 0x69, 0x70, 0x4f, 0x72, 0x64,\n\t0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x07, 0x61, 0x64, 0x64,\n\t0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x68, 0x69, 0x70,\n\t0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73,\n\t0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x2b, 0x0a, 0x05, 0x69, 0x74, 0x65,\n\t0x6d, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74,\n\t0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x43, 0x61, 0x72, 0x74, 0x49, 0x74, 0x65, 0x6d, 0x52,\n\t0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x22, 0x34, 0x0a, 0x11, 0x53, 0x68, 0x69, 0x70, 0x4f, 0x72,\n\t0x64, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x74,\n\t0x72, 0x61, 0x63, 0x6b, 0x69, 0x6e, 0x67, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,\n\t0x52, 0x0a, 0x74, 0x72, 0x61, 0x63, 0x6b, 0x69, 0x6e, 0x67, 0x49, 0x64, 0x22, 0x8f, 0x01, 0x0a,\n\t0x07, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x25, 0x0a, 0x0e, 0x73, 0x74, 0x72, 0x65,\n\t0x65, 0x74, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,\n\t0x52, 0x0d, 0x73, 0x74, 0x72, 0x65, 0x65, 0x74, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12,\n\t0x12, 0x0a, 0x04, 0x63, 0x69, 0x74, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x63,\n\t0x69, 0x74, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01,\n\t0x28, 0x09, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x75,\n\t0x6e, 0x74, 0x72, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x6f, 0x75, 0x6e,\n\t0x74, 0x72, 0x79, 0x12, 0x19, 0x0a, 0x08, 0x7a, 0x69, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18,\n\t0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x07, 0x7a, 0x69, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x22, 0x58,\n\t0x0a, 0x05, 0x4d, 0x6f, 0x6e, 0x65, 0x79, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x75, 0x72, 0x72, 0x65,\n\t0x6e, 0x63, 0x79, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c,\n\t0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x14, 0x0a, 0x05,\n\t0x75, 0x6e, 0x69, 0x74, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x75, 0x6e, 0x69,\n\t0x74, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x6e, 0x61, 0x6e, 0x6f, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28,\n\t0x05, 0x52, 0x05, 0x6e, 0x61, 0x6e, 0x6f, 0x73, 0x22, 0x47, 0x0a, 0x1e, 0x47, 0x65, 0x74, 0x53,\n\t0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x69,\n\t0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x63, 0x75,\n\t0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03,\n\t0x28, 0x09, 0x52, 0x0d, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x43, 0x6f, 0x64, 0x65,\n\t0x73, 0x22, 0x5c, 0x0a, 0x19, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x43, 0x6f, 0x6e,\n\t0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x26,\n\t0x0a, 0x04, 0x66, 0x72, 0x6f, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x68,\n\t0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x4d, 0x6f, 0x6e, 0x65, 0x79,\n\t0x52, 0x04, 0x66, 0x72, 0x6f, 0x6d, 0x12, 0x17, 0x0a, 0x07, 0x74, 0x6f, 0x5f, 0x63, 0x6f, 0x64,\n\t0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x74, 0x6f, 0x43, 0x6f, 0x64, 0x65, 0x22,\n\t0xe6, 0x01, 0x0a, 0x0e, 0x43, 0x72, 0x65, 0x64, 0x69, 0x74, 0x43, 0x61, 0x72, 0x64, 0x49, 0x6e,\n\t0x66, 0x6f, 0x12, 0x2c, 0x0a, 0x12, 0x63, 0x72, 0x65, 0x64, 0x69, 0x74, 0x5f, 0x63, 0x61, 0x72,\n\t0x64, 0x5f, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10,\n\t0x63, 0x72, 0x65, 0x64, 0x69, 0x74, 0x43, 0x61, 0x72, 0x64, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72,\n\t0x12, 0x26, 0x0a, 0x0f, 0x63, 0x72, 0x65, 0x64, 0x69, 0x74, 0x5f, 0x63, 0x61, 0x72, 0x64, 0x5f,\n\t0x63, 0x76, 0x76, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0d, 0x63, 0x72, 0x65, 0x64, 0x69,\n\t0x74, 0x43, 0x61, 0x72, 0x64, 0x43, 0x76, 0x76, 0x12, 0x3d, 0x0a, 0x1b, 0x63, 0x72, 0x65, 0x64,\n\t0x69, 0x74, 0x5f, 0x63, 0x61, 0x72, 0x64, 0x5f, 0x65, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69,\n\t0x6f, 0x6e, 0x5f, 0x79, 0x65, 0x61, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x18, 0x63,\n\t0x72, 0x65, 0x64, 0x69, 0x74, 0x43, 0x61, 0x72, 0x64, 0x45, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74,\n\t0x69, 0x6f, 0x6e, 0x59, 0x65, 0x61, 0x72, 0x12, 0x3f, 0x0a, 0x1c, 0x63, 0x72, 0x65, 0x64, 0x69,\n\t0x74, 0x5f, 0x63, 0x61, 0x72, 0x64, 0x5f, 0x65, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f,\n\t0x6e, 0x5f, 0x6d, 0x6f, 0x6e, 0x74, 0x68, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x19, 0x63,\n\t0x72, 0x65, 0x64, 0x69, 0x74, 0x43, 0x61, 0x72, 0x64, 0x45, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74,\n\t0x69, 0x6f, 0x6e, 0x4d, 0x6f, 0x6e, 0x74, 0x68, 0x22, 0x79, 0x0a, 0x0d, 0x43, 0x68, 0x61, 0x72,\n\t0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2a, 0x0a, 0x06, 0x61, 0x6d, 0x6f,\n\t0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x68, 0x69, 0x70, 0x73,\n\t0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x4d, 0x6f, 0x6e, 0x65, 0x79, 0x52, 0x06, 0x61,\n\t0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x3c, 0x0a, 0x0b, 0x63, 0x72, 0x65, 0x64, 0x69, 0x74, 0x5f,\n\t0x63, 0x61, 0x72, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x68, 0x69, 0x70,\n\t0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x43, 0x72, 0x65, 0x64, 0x69, 0x74, 0x43,\n\t0x61, 0x72, 0x64, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x0a, 0x63, 0x72, 0x65, 0x64, 0x69, 0x74, 0x43,\n\t0x61, 0x72, 0x64, 0x22, 0x37, 0x0a, 0x0e, 0x43, 0x68, 0x61, 0x72, 0x67, 0x65, 0x52, 0x65, 0x73,\n\t0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63,\n\t0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x74,\n\t0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x22, 0x5e, 0x0a, 0x09,\n\t0x4f, 0x72, 0x64, 0x65, 0x72, 0x49, 0x74, 0x65, 0x6d, 0x12, 0x29, 0x0a, 0x04, 0x69, 0x74, 0x65,\n\t0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65,\n\t0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x43, 0x61, 0x72, 0x74, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x04,\n\t0x69, 0x74, 0x65, 0x6d, 0x12, 0x26, 0x0a, 0x04, 0x63, 0x6f, 0x73, 0x74, 0x18, 0x02, 0x20, 0x01,\n\t0x28, 0x0b, 0x32, 0x12, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70,\n\t0x2e, 0x4d, 0x6f, 0x6e, 0x65, 0x79, 0x52, 0x04, 0x63, 0x6f, 0x73, 0x74, 0x22, 0x82, 0x02, 0x0a,\n\t0x0b, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x19, 0x0a, 0x08,\n\t0x6f, 0x72, 0x64, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07,\n\t0x6f, 0x72, 0x64, 0x65, 0x72, 0x49, 0x64, 0x12, 0x30, 0x0a, 0x14, 0x73, 0x68, 0x69, 0x70, 0x70,\n\t0x69, 0x6e, 0x67, 0x5f, 0x74, 0x72, 0x61, 0x63, 0x6b, 0x69, 0x6e, 0x67, 0x5f, 0x69, 0x64, 0x18,\n\t0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x12, 0x73, 0x68, 0x69, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x54,\n\t0x72, 0x61, 0x63, 0x6b, 0x69, 0x6e, 0x67, 0x49, 0x64, 0x12, 0x37, 0x0a, 0x0d, 0x73, 0x68, 0x69,\n\t0x70, 0x70, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x6f, 0x73, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b,\n\t0x32, 0x12, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x4d,\n\t0x6f, 0x6e, 0x65, 0x79, 0x52, 0x0c, 0x73, 0x68, 0x69, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x43, 0x6f,\n\t0x73, 0x74, 0x12, 0x3f, 0x0a, 0x10, 0x73, 0x68, 0x69, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x5f, 0x61,\n\t0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x68,\n\t0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x41, 0x64, 0x64, 0x72, 0x65,\n\t0x73, 0x73, 0x52, 0x0f, 0x73, 0x68, 0x69, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x41, 0x64, 0x64, 0x72,\n\t0x65, 0x73, 0x73, 0x12, 0x2c, 0x0a, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x18, 0x05, 0x20, 0x03,\n\t0x28, 0x0b, 0x32, 0x16, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70,\n\t0x2e, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d,\n\t0x73, 0x22, 0x64, 0x0a, 0x1c, 0x53, 0x65, 0x6e, 0x64, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x43, 0x6f,\n\t0x6e, 0x66, 0x69, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,\n\t0x74, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,\n\t0x52, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x12, 0x2e, 0x0a, 0x05, 0x6f, 0x72, 0x64, 0x65, 0x72,\n\t0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72,\n\t0x73, 0x68, 0x6f, 0x70, 0x2e, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74,\n\t0x52, 0x05, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x22, 0xd5, 0x01, 0x0a, 0x11, 0x50, 0x6c, 0x61, 0x63,\n\t0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x17, 0x0a,\n\t0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06,\n\t0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x63,\n\t0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x75,\n\t0x73, 0x65, 0x72, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x12, 0x2e, 0x0a, 0x07, 0x61,\n\t0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x68,\n\t0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x41, 0x64, 0x64, 0x72, 0x65,\n\t0x73, 0x73, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x65,\n\t0x6d, 0x61, 0x69, 0x6c, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x6d, 0x61, 0x69,\n\t0x6c, 0x12, 0x3c, 0x0a, 0x0b, 0x63, 0x72, 0x65, 0x64, 0x69, 0x74, 0x5f, 0x63, 0x61, 0x72, 0x64,\n\t0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72,\n\t0x73, 0x68, 0x6f, 0x70, 0x2e, 0x43, 0x72, 0x65, 0x64, 0x69, 0x74, 0x43, 0x61, 0x72, 0x64, 0x49,\n\t0x6e, 0x66, 0x6f, 0x52, 0x0a, 0x63, 0x72, 0x65, 0x64, 0x69, 0x74, 0x43, 0x61, 0x72, 0x64, 0x22,\n\t0x44, 0x0a, 0x12, 0x50, 0x6c, 0x61, 0x63, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x73,\n\t0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2e, 0x0a, 0x05, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x18, 0x01,\n\t0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68,\n\t0x6f, 0x70, 0x2e, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x05,\n\t0x6f, 0x72, 0x64, 0x65, 0x72, 0x22, 0x2e, 0x0a, 0x09, 0x41, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65,\n\t0x73, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x5f, 0x6b, 0x65,\n\t0x79, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78,\n\t0x74, 0x4b, 0x65, 0x79, 0x73, 0x22, 0x2f, 0x0a, 0x0a, 0x41, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f,\n\t0x6e, 0x73, 0x65, 0x12, 0x21, 0x0a, 0x03, 0x61, 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b,\n\t0x32, 0x0f, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x41,\n\t0x64, 0x52, 0x03, 0x61, 0x64, 0x73, 0x22, 0x3b, 0x0a, 0x02, 0x41, 0x64, 0x12, 0x21, 0x0a, 0x0c,\n\t0x72, 0x65, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01,\n\t0x28, 0x09, 0x52, 0x0b, 0x72, 0x65, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x55, 0x72, 0x6c, 0x12,\n\t0x12, 0x0a, 0x04, 0x74, 0x65, 0x78, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74,\n\t0x65, 0x78, 0x74, 0x32, 0xca, 0x01, 0x0a, 0x0b, 0x43, 0x61, 0x72, 0x74, 0x53, 0x65, 0x72, 0x76,\n\t0x69, 0x63, 0x65, 0x12, 0x3c, 0x0a, 0x07, 0x41, 0x64, 0x64, 0x49, 0x74, 0x65, 0x6d, 0x12, 0x1b,\n\t0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x41, 0x64, 0x64,\n\t0x49, 0x74, 0x65, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x68, 0x69,\n\t0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22,\n\t0x00, 0x12, 0x3b, 0x0a, 0x07, 0x47, 0x65, 0x74, 0x43, 0x61, 0x72, 0x74, 0x12, 0x1b, 0x2e, 0x68,\n\t0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x61,\n\t0x72, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x11, 0x2e, 0x68, 0x69, 0x70, 0x73,\n\t0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x43, 0x61, 0x72, 0x74, 0x22, 0x00, 0x12, 0x40,\n\t0x0a, 0x09, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x43, 0x61, 0x72, 0x74, 0x12, 0x1d, 0x2e, 0x68, 0x69,\n\t0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x43,\n\t0x61, 0x72, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x68, 0x69, 0x70,\n\t0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00,\n\t0x32, 0x83, 0x01, 0x0a, 0x15, 0x52, 0x65, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x64, 0x61, 0x74,\n\t0x69, 0x6f, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x6a, 0x0a, 0x13, 0x4c, 0x69,\n\t0x73, 0x74, 0x52, 0x65, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e,\n\t0x73, 0x12, 0x27, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e,\n\t0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x64, 0x61, 0x74, 0x69,\n\t0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, 0x68, 0x69, 0x70,\n\t0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x63,\n\t0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70,\n\t0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x32, 0x83, 0x02, 0x0a, 0x15, 0x50, 0x72, 0x6f, 0x64, 0x75,\n\t0x63, 0x74, 0x43, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65,\n\t0x12, 0x47, 0x0a, 0x0c, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x73,\n\t0x12, 0x12, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x45,\n\t0x6d, 0x70, 0x74, 0x79, 0x1a, 0x21, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68,\n\t0x6f, 0x70, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x73, 0x52,\n\t0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x44, 0x0a, 0x0a, 0x47, 0x65, 0x74,\n\t0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x12, 0x1e, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65,\n\t0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x47, 0x65, 0x74, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74,\n\t0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65,\n\t0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x22, 0x00, 0x12,\n\t0x5b, 0x0a, 0x0e, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74,\n\t0x73, 0x12, 0x22, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e,\n\t0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x73, 0x52, 0x65,\n\t0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73,\n\t0x68, 0x6f, 0x70, 0x2e, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63,\n\t0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x32, 0xaa, 0x01, 0x0a,\n\t0x0f, 0x53, 0x68, 0x69, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65,\n\t0x12, 0x49, 0x0a, 0x08, 0x47, 0x65, 0x74, 0x51, 0x75, 0x6f, 0x74, 0x65, 0x12, 0x1c, 0x2e, 0x68,\n\t0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x47, 0x65, 0x74, 0x51, 0x75,\n\t0x6f, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x68, 0x69, 0x70,\n\t0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x47, 0x65, 0x74, 0x51, 0x75, 0x6f, 0x74,\n\t0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x4c, 0x0a, 0x09, 0x53,\n\t0x68, 0x69, 0x70, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x1d, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74,\n\t0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x53, 0x68, 0x69, 0x70, 0x4f, 0x72, 0x64, 0x65, 0x72,\n\t0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65,\n\t0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x53, 0x68, 0x69, 0x70, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52,\n\t0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x32, 0xb7, 0x01, 0x0a, 0x0f, 0x43, 0x75,\n\t0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x5b, 0x0a,\n\t0x16, 0x47, 0x65, 0x74, 0x53, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x43, 0x75, 0x72,\n\t0x72, 0x65, 0x6e, 0x63, 0x69, 0x65, 0x73, 0x12, 0x12, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65,\n\t0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x2b, 0x2e, 0x68, 0x69,\n\t0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x75, 0x70,\n\t0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x69, 0x65, 0x73,\n\t0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x47, 0x0a, 0x07, 0x43, 0x6f,\n\t0x6e, 0x76, 0x65, 0x72, 0x74, 0x12, 0x26, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73,\n\t0x68, 0x6f, 0x70, 0x2e, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x43, 0x6f, 0x6e, 0x76,\n\t0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e,\n\t0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x4d, 0x6f, 0x6e, 0x65,\n\t0x79, 0x22, 0x00, 0x32, 0x55, 0x0a, 0x0e, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x65,\n\t0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x43, 0x0a, 0x06, 0x43, 0x68, 0x61, 0x72, 0x67, 0x65, 0x12,\n\t0x1a, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x43, 0x68,\n\t0x61, 0x72, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x68, 0x69,\n\t0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x43, 0x68, 0x61, 0x72, 0x67, 0x65,\n\t0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x32, 0x68, 0x0a, 0x0c, 0x45, 0x6d,\n\t0x61, 0x69, 0x6c, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x58, 0x0a, 0x15, 0x53, 0x65,\n\t0x6e, 0x64, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x61, 0x74,\n\t0x69, 0x6f, 0x6e, 0x12, 0x29, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f,\n\t0x70, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69,\n\t0x72, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12,\n\t0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x45, 0x6d, 0x70,\n\t0x74, 0x79, 0x22, 0x00, 0x32, 0x62, 0x0a, 0x0f, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x6f, 0x75, 0x74,\n\t0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x4f, 0x0a, 0x0a, 0x50, 0x6c, 0x61, 0x63, 0x65,\n\t0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x1e, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73,\n\t0x68, 0x6f, 0x70, 0x2e, 0x50, 0x6c, 0x61, 0x63, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65,\n\t0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73,\n\t0x68, 0x6f, 0x70, 0x2e, 0x50, 0x6c, 0x61, 0x63, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65,\n\t0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x32, 0x48, 0x0a, 0x09, 0x41, 0x64, 0x53, 0x65,\n\t0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x3b, 0x0a, 0x06, 0x47, 0x65, 0x74, 0x41, 0x64, 0x73, 0x12,\n\t0x16, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x41, 0x64,\n\t0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65,\n\t0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x41, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,\n\t0x22, 0x00, 0x42, 0x40, 0x5a, 0x3e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d,\n\t0x2f, 0x47, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x43, 0x6c, 0x6f, 0x75, 0x64, 0x50, 0x6c, 0x61, 0x74,\n\t0x66, 0x6f, 0x72, 0x6d, 0x2f, 0x6d, 0x69, 0x73, 0x63, 0x72, 0x6f, 0x73, 0x65, 0x72, 0x76, 0x69,\n\t0x63, 0x65, 0x73, 0x2d, 0x64, 0x65, 0x6d, 0x6f, 0x2f, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72,\n\t0x73, 0x68, 0x6f, 0x70, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,\n}\n\nvar (\n\tfile_demo_proto_rawDescOnce sync.Once\n\tfile_demo_proto_rawDescData = file_demo_proto_rawDesc\n)\n\nfunc file_demo_proto_rawDescGZIP() []byte {\n\tfile_demo_proto_rawDescOnce.Do(func() {\n\t\tfile_demo_proto_rawDescData = protoimpl.X.CompressGZIP(file_demo_proto_rawDescData)\n\t})\n\treturn file_demo_proto_rawDescData\n}\n\nvar file_demo_proto_msgTypes = make([]protoimpl.MessageInfo, 32)\nvar file_demo_proto_goTypes = []any{\n\t(*CartItem)(nil),                       // 0: hipstershop.CartItem\n\t(*AddItemRequest)(nil),                 // 1: hipstershop.AddItemRequest\n\t(*EmptyCartRequest)(nil),               // 2: hipstershop.EmptyCartRequest\n\t(*GetCartRequest)(nil),                 // 3: hipstershop.GetCartRequest\n\t(*Cart)(nil),                           // 4: hipstershop.Cart\n\t(*Empty)(nil),                          // 5: hipstershop.Empty\n\t(*ListRecommendationsRequest)(nil),     // 6: hipstershop.ListRecommendationsRequest\n\t(*ListRecommendationsResponse)(nil),    // 7: hipstershop.ListRecommendationsResponse\n\t(*Product)(nil),                        // 8: hipstershop.Product\n\t(*ListProductsResponse)(nil),           // 9: hipstershop.ListProductsResponse\n\t(*GetProductRequest)(nil),              // 10: hipstershop.GetProductRequest\n\t(*SearchProductsRequest)(nil),          // 11: hipstershop.SearchProductsRequest\n\t(*SearchProductsResponse)(nil),         // 12: hipstershop.SearchProductsResponse\n\t(*GetQuoteRequest)(nil),                // 13: hipstershop.GetQuoteRequest\n\t(*GetQuoteResponse)(nil),               // 14: hipstershop.GetQuoteResponse\n\t(*ShipOrderRequest)(nil),               // 15: hipstershop.ShipOrderRequest\n\t(*ShipOrderResponse)(nil),              // 16: hipstershop.ShipOrderResponse\n\t(*Address)(nil),                        // 17: hipstershop.Address\n\t(*Money)(nil),                          // 18: hipstershop.Money\n\t(*GetSupportedCurrenciesResponse)(nil), // 19: hipstershop.GetSupportedCurrenciesResponse\n\t(*CurrencyConversionRequest)(nil),      // 20: hipstershop.CurrencyConversionRequest\n\t(*CreditCardInfo)(nil),                 // 21: hipstershop.CreditCardInfo\n\t(*ChargeRequest)(nil),                  // 22: hipstershop.ChargeRequest\n\t(*ChargeResponse)(nil),                 // 23: hipstershop.ChargeResponse\n\t(*OrderItem)(nil),                      // 24: hipstershop.OrderItem\n\t(*OrderResult)(nil),                    // 25: hipstershop.OrderResult\n\t(*SendOrderConfirmationRequest)(nil),   // 26: hipstershop.SendOrderConfirmationRequest\n\t(*PlaceOrderRequest)(nil),              // 27: hipstershop.PlaceOrderRequest\n\t(*PlaceOrderResponse)(nil),             // 28: hipstershop.PlaceOrderResponse\n\t(*AdRequest)(nil),                      // 29: hipstershop.AdRequest\n\t(*AdResponse)(nil),                     // 30: hipstershop.AdResponse\n\t(*Ad)(nil),                             // 31: hipstershop.Ad\n}\nvar file_demo_proto_depIdxs = []int32{\n\t0,  // 0: hipstershop.AddItemRequest.item:type_name -> hipstershop.CartItem\n\t0,  // 1: hipstershop.Cart.items:type_name -> hipstershop.CartItem\n\t18, // 2: hipstershop.Product.price_usd:type_name -> hipstershop.Money\n\t8,  // 3: hipstershop.ListProductsResponse.products:type_name -> hipstershop.Product\n\t8,  // 4: hipstershop.SearchProductsResponse.results:type_name -> hipstershop.Product\n\t17, // 5: hipstershop.GetQuoteRequest.address:type_name -> hipstershop.Address\n\t0,  // 6: hipstershop.GetQuoteRequest.items:type_name -> hipstershop.CartItem\n\t18, // 7: hipstershop.GetQuoteResponse.cost_usd:type_name -> hipstershop.Money\n\t17, // 8: hipstershop.ShipOrderRequest.address:type_name -> hipstershop.Address\n\t0,  // 9: hipstershop.ShipOrderRequest.items:type_name -> hipstershop.CartItem\n\t18, // 10: hipstershop.CurrencyConversionRequest.from:type_name -> hipstershop.Money\n\t18, // 11: hipstershop.ChargeRequest.amount:type_name -> hipstershop.Money\n\t21, // 12: hipstershop.ChargeRequest.credit_card:type_name -> hipstershop.CreditCardInfo\n\t0,  // 13: hipstershop.OrderItem.item:type_name -> hipstershop.CartItem\n\t18, // 14: hipstershop.OrderItem.cost:type_name -> hipstershop.Money\n\t18, // 15: hipstershop.OrderResult.shipping_cost:type_name -> hipstershop.Money\n\t17, // 16: hipstershop.OrderResult.shipping_address:type_name -> hipstershop.Address\n\t24, // 17: hipstershop.OrderResult.items:type_name -> hipstershop.OrderItem\n\t25, // 18: hipstershop.SendOrderConfirmationRequest.order:type_name -> hipstershop.OrderResult\n\t17, // 19: hipstershop.PlaceOrderRequest.address:type_name -> hipstershop.Address\n\t21, // 20: hipstershop.PlaceOrderRequest.credit_card:type_name -> hipstershop.CreditCardInfo\n\t25, // 21: hipstershop.PlaceOrderResponse.order:type_name -> hipstershop.OrderResult\n\t31, // 22: hipstershop.AdResponse.ads:type_name -> hipstershop.Ad\n\t1,  // 23: hipstershop.CartService.AddItem:input_type -> hipstershop.AddItemRequest\n\t3,  // 24: hipstershop.CartService.GetCart:input_type -> hipstershop.GetCartRequest\n\t2,  // 25: hipstershop.CartService.EmptyCart:input_type -> hipstershop.EmptyCartRequest\n\t6,  // 26: hipstershop.RecommendationService.ListRecommendations:input_type -> hipstershop.ListRecommendationsRequest\n\t5,  // 27: hipstershop.ProductCatalogService.ListProducts:input_type -> hipstershop.Empty\n\t10, // 28: hipstershop.ProductCatalogService.GetProduct:input_type -> hipstershop.GetProductRequest\n\t11, // 29: hipstershop.ProductCatalogService.SearchProducts:input_type -> hipstershop.SearchProductsRequest\n\t13, // 30: hipstershop.ShippingService.GetQuote:input_type -> hipstershop.GetQuoteRequest\n\t15, // 31: hipstershop.ShippingService.ShipOrder:input_type -> hipstershop.ShipOrderRequest\n\t5,  // 32: hipstershop.CurrencyService.GetSupportedCurrencies:input_type -> hipstershop.Empty\n\t20, // 33: hipstershop.CurrencyService.Convert:input_type -> hipstershop.CurrencyConversionRequest\n\t22, // 34: hipstershop.PaymentService.Charge:input_type -> hipstershop.ChargeRequest\n\t26, // 35: hipstershop.EmailService.SendOrderConfirmation:input_type -> hipstershop.SendOrderConfirmationRequest\n\t27, // 36: hipstershop.CheckoutService.PlaceOrder:input_type -> hipstershop.PlaceOrderRequest\n\t29, // 37: hipstershop.AdService.GetAds:input_type -> hipstershop.AdRequest\n\t5,  // 38: hipstershop.CartService.AddItem:output_type -> hipstershop.Empty\n\t4,  // 39: hipstershop.CartService.GetCart:output_type -> hipstershop.Cart\n\t5,  // 40: hipstershop.CartService.EmptyCart:output_type -> hipstershop.Empty\n\t7,  // 41: hipstershop.RecommendationService.ListRecommendations:output_type -> hipstershop.ListRecommendationsResponse\n\t9,  // 42: hipstershop.ProductCatalogService.ListProducts:output_type -> hipstershop.ListProductsResponse\n\t8,  // 43: hipstershop.ProductCatalogService.GetProduct:output_type -> hipstershop.Product\n\t12, // 44: hipstershop.ProductCatalogService.SearchProducts:output_type -> hipstershop.SearchProductsResponse\n\t14, // 45: hipstershop.ShippingService.GetQuote:output_type -> hipstershop.GetQuoteResponse\n\t16, // 46: hipstershop.ShippingService.ShipOrder:output_type -> hipstershop.ShipOrderResponse\n\t19, // 47: hipstershop.CurrencyService.GetSupportedCurrencies:output_type -> hipstershop.GetSupportedCurrenciesResponse\n\t18, // 48: hipstershop.CurrencyService.Convert:output_type -> hipstershop.Money\n\t23, // 49: hipstershop.PaymentService.Charge:output_type -> hipstershop.ChargeResponse\n\t5,  // 50: hipstershop.EmailService.SendOrderConfirmation:output_type -> hipstershop.Empty\n\t28, // 51: hipstershop.CheckoutService.PlaceOrder:output_type -> hipstershop.PlaceOrderResponse\n\t30, // 52: hipstershop.AdService.GetAds:output_type -> hipstershop.AdResponse\n\t38, // [38:53] is the sub-list for method output_type\n\t23, // [23:38] is the sub-list for method input_type\n\t23, // [23:23] is the sub-list for extension type_name\n\t23, // [23:23] is the sub-list for extension extendee\n\t0,  // [0:23] is the sub-list for field type_name\n}\n\nfunc init() { file_demo_proto_init() }\nfunc file_demo_proto_init() {\n\tif File_demo_proto != nil {\n\t\treturn\n\t}\n\tif !protoimpl.UnsafeEnabled {\n\t\tfile_demo_proto_msgTypes[0].Exporter = func(v any, i int) any {\n\t\t\tswitch v := v.(*CartItem); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_demo_proto_msgTypes[1].Exporter = func(v any, i int) any {\n\t\t\tswitch v := v.(*AddItemRequest); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_demo_proto_msgTypes[2].Exporter = func(v any, i int) any {\n\t\t\tswitch v := v.(*EmptyCartRequest); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_demo_proto_msgTypes[3].Exporter = func(v any, i int) any {\n\t\t\tswitch v := v.(*GetCartRequest); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_demo_proto_msgTypes[4].Exporter = func(v any, i int) any {\n\t\t\tswitch v := v.(*Cart); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_demo_proto_msgTypes[5].Exporter = func(v any, i int) any {\n\t\t\tswitch v := v.(*Empty); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_demo_proto_msgTypes[6].Exporter = func(v any, i int) any {\n\t\t\tswitch v := v.(*ListRecommendationsRequest); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_demo_proto_msgTypes[7].Exporter = func(v any, i int) any {\n\t\t\tswitch v := v.(*ListRecommendationsResponse); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_demo_proto_msgTypes[8].Exporter = func(v any, i int) any {\n\t\t\tswitch v := v.(*Product); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_demo_proto_msgTypes[9].Exporter = func(v any, i int) any {\n\t\t\tswitch v := v.(*ListProductsResponse); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_demo_proto_msgTypes[10].Exporter = func(v any, i int) any {\n\t\t\tswitch v := v.(*GetProductRequest); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_demo_proto_msgTypes[11].Exporter = func(v any, i int) any {\n\t\t\tswitch v := v.(*SearchProductsRequest); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_demo_proto_msgTypes[12].Exporter = func(v any, i int) any {\n\t\t\tswitch v := v.(*SearchProductsResponse); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_demo_proto_msgTypes[13].Exporter = func(v any, i int) any {\n\t\t\tswitch v := v.(*GetQuoteRequest); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_demo_proto_msgTypes[14].Exporter = func(v any, i int) any {\n\t\t\tswitch v := v.(*GetQuoteResponse); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_demo_proto_msgTypes[15].Exporter = func(v any, i int) any {\n\t\t\tswitch v := v.(*ShipOrderRequest); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_demo_proto_msgTypes[16].Exporter = func(v any, i int) any {\n\t\t\tswitch v := v.(*ShipOrderResponse); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_demo_proto_msgTypes[17].Exporter = func(v any, i int) any {\n\t\t\tswitch v := v.(*Address); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_demo_proto_msgTypes[18].Exporter = func(v any, i int) any {\n\t\t\tswitch v := v.(*Money); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_demo_proto_msgTypes[19].Exporter = func(v any, i int) any {\n\t\t\tswitch v := v.(*GetSupportedCurrenciesResponse); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_demo_proto_msgTypes[20].Exporter = func(v any, i int) any {\n\t\t\tswitch v := v.(*CurrencyConversionRequest); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_demo_proto_msgTypes[21].Exporter = func(v any, i int) any {\n\t\t\tswitch v := v.(*CreditCardInfo); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_demo_proto_msgTypes[22].Exporter = func(v any, i int) any {\n\t\t\tswitch v := v.(*ChargeRequest); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_demo_proto_msgTypes[23].Exporter = func(v any, i int) any {\n\t\t\tswitch v := v.(*ChargeResponse); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_demo_proto_msgTypes[24].Exporter = func(v any, i int) any {\n\t\t\tswitch v := v.(*OrderItem); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_demo_proto_msgTypes[25].Exporter = func(v any, i int) any {\n\t\t\tswitch v := v.(*OrderResult); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_demo_proto_msgTypes[26].Exporter = func(v any, i int) any {\n\t\t\tswitch v := v.(*SendOrderConfirmationRequest); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_demo_proto_msgTypes[27].Exporter = func(v any, i int) any {\n\t\t\tswitch v := v.(*PlaceOrderRequest); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_demo_proto_msgTypes[28].Exporter = func(v any, i int) any {\n\t\t\tswitch v := v.(*PlaceOrderResponse); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_demo_proto_msgTypes[29].Exporter = func(v any, i int) any {\n\t\t\tswitch v := v.(*AdRequest); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_demo_proto_msgTypes[30].Exporter = func(v any, i int) any {\n\t\t\tswitch v := v.(*AdResponse); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_demo_proto_msgTypes[31].Exporter = func(v any, i int) any {\n\t\t\tswitch v := v.(*Ad); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: file_demo_proto_rawDesc,\n\t\t\tNumEnums:      0,\n\t\t\tNumMessages:   32,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   9,\n\t\t},\n\t\tGoTypes:           file_demo_proto_goTypes,\n\t\tDependencyIndexes: file_demo_proto_depIdxs,\n\t\tMessageInfos:      file_demo_proto_msgTypes,\n\t}.Build()\n\tFile_demo_proto = out.File\n\tfile_demo_proto_rawDesc = nil\n\tfile_demo_proto_goTypes = nil\n\tfile_demo_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "src/shippingservice/genproto/demo_grpc.pb.go",
    "content": "// Copyright 2020 Google LLC\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// Code generated by protoc-gen-go-grpc. DO NOT EDIT.\n// versions:\n// - protoc-gen-go-grpc v1.5.1\n// - protoc             v3.6.1\n// source: demo.proto\n\npackage hipstershop\n\nimport (\n\tcontext \"context\"\n\tgrpc \"google.golang.org/grpc\"\n\tcodes \"google.golang.org/grpc/codes\"\n\tstatus \"google.golang.org/grpc/status\"\n)\n\n// This is a compile-time assertion to ensure that this generated file\n// is compatible with the grpc package it is being compiled against.\n// Requires gRPC-Go v1.64.0 or later.\nconst _ = grpc.SupportPackageIsVersion9\n\nconst (\n\tCartService_AddItem_FullMethodName   = \"/hipstershop.CartService/AddItem\"\n\tCartService_GetCart_FullMethodName   = \"/hipstershop.CartService/GetCart\"\n\tCartService_EmptyCart_FullMethodName = \"/hipstershop.CartService/EmptyCart\"\n)\n\n// CartServiceClient is the client API for CartService service.\n//\n// 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.\ntype CartServiceClient interface {\n\tAddItem(ctx context.Context, in *AddItemRequest, opts ...grpc.CallOption) (*Empty, error)\n\tGetCart(ctx context.Context, in *GetCartRequest, opts ...grpc.CallOption) (*Cart, error)\n\tEmptyCart(ctx context.Context, in *EmptyCartRequest, opts ...grpc.CallOption) (*Empty, error)\n}\n\ntype cartServiceClient struct {\n\tcc grpc.ClientConnInterface\n}\n\nfunc NewCartServiceClient(cc grpc.ClientConnInterface) CartServiceClient {\n\treturn &cartServiceClient{cc}\n}\n\nfunc (c *cartServiceClient) AddItem(ctx context.Context, in *AddItemRequest, opts ...grpc.CallOption) (*Empty, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(Empty)\n\terr := c.cc.Invoke(ctx, CartService_AddItem_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *cartServiceClient) GetCart(ctx context.Context, in *GetCartRequest, opts ...grpc.CallOption) (*Cart, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(Cart)\n\terr := c.cc.Invoke(ctx, CartService_GetCart_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *cartServiceClient) EmptyCart(ctx context.Context, in *EmptyCartRequest, opts ...grpc.CallOption) (*Empty, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(Empty)\n\terr := c.cc.Invoke(ctx, CartService_EmptyCart_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\n// CartServiceServer is the server API for CartService service.\n// All implementations must embed UnimplementedCartServiceServer\n// for forward compatibility.\ntype CartServiceServer interface {\n\tAddItem(context.Context, *AddItemRequest) (*Empty, error)\n\tGetCart(context.Context, *GetCartRequest) (*Cart, error)\n\tEmptyCart(context.Context, *EmptyCartRequest) (*Empty, error)\n\tmustEmbedUnimplementedCartServiceServer()\n}\n\n// UnimplementedCartServiceServer must be embedded to have\n// forward compatible implementations.\n//\n// NOTE: this should be embedded by value instead of pointer to avoid a nil\n// pointer dereference when methods are called.\ntype UnimplementedCartServiceServer struct{}\n\nfunc (UnimplementedCartServiceServer) AddItem(context.Context, *AddItemRequest) (*Empty, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method AddItem not implemented\")\n}\nfunc (UnimplementedCartServiceServer) GetCart(context.Context, *GetCartRequest) (*Cart, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method GetCart not implemented\")\n}\nfunc (UnimplementedCartServiceServer) EmptyCart(context.Context, *EmptyCartRequest) (*Empty, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method EmptyCart not implemented\")\n}\nfunc (UnimplementedCartServiceServer) mustEmbedUnimplementedCartServiceServer() {}\nfunc (UnimplementedCartServiceServer) testEmbeddedByValue()                     {}\n\n// UnsafeCartServiceServer may be embedded to opt out of forward compatibility for this service.\n// Use of this interface is not recommended, as added methods to CartServiceServer will\n// result in compilation errors.\ntype UnsafeCartServiceServer interface {\n\tmustEmbedUnimplementedCartServiceServer()\n}\n\nfunc RegisterCartServiceServer(s grpc.ServiceRegistrar, srv CartServiceServer) {\n\t// If the following call pancis, it indicates UnimplementedCartServiceServer was\n\t// embedded by pointer and is nil.  This will cause panics if an\n\t// unimplemented method is ever invoked, so we test this at initialization\n\t// time to prevent it from happening at runtime later due to I/O.\n\tif t, ok := srv.(interface{ testEmbeddedByValue() }); ok {\n\t\tt.testEmbeddedByValue()\n\t}\n\ts.RegisterService(&CartService_ServiceDesc, srv)\n}\n\nfunc _CartService_AddItem_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(AddItemRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(CartServiceServer).AddItem(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: CartService_AddItem_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(CartServiceServer).AddItem(ctx, req.(*AddItemRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _CartService_GetCart_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(GetCartRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(CartServiceServer).GetCart(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: CartService_GetCart_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(CartServiceServer).GetCart(ctx, req.(*GetCartRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _CartService_EmptyCart_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(EmptyCartRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(CartServiceServer).EmptyCart(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: CartService_EmptyCart_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(CartServiceServer).EmptyCart(ctx, req.(*EmptyCartRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\n// CartService_ServiceDesc is the grpc.ServiceDesc for CartService service.\n// It's only intended for direct use with grpc.RegisterService,\n// and not to be introspected or modified (even as a copy)\nvar CartService_ServiceDesc = grpc.ServiceDesc{\n\tServiceName: \"hipstershop.CartService\",\n\tHandlerType: (*CartServiceServer)(nil),\n\tMethods: []grpc.MethodDesc{\n\t\t{\n\t\t\tMethodName: \"AddItem\",\n\t\t\tHandler:    _CartService_AddItem_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"GetCart\",\n\t\t\tHandler:    _CartService_GetCart_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"EmptyCart\",\n\t\t\tHandler:    _CartService_EmptyCart_Handler,\n\t\t},\n\t},\n\tStreams:  []grpc.StreamDesc{},\n\tMetadata: \"demo.proto\",\n}\n\nconst (\n\tRecommendationService_ListRecommendations_FullMethodName = \"/hipstershop.RecommendationService/ListRecommendations\"\n)\n\n// RecommendationServiceClient is the client API for RecommendationService service.\n//\n// 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.\ntype RecommendationServiceClient interface {\n\tListRecommendations(ctx context.Context, in *ListRecommendationsRequest, opts ...grpc.CallOption) (*ListRecommendationsResponse, error)\n}\n\ntype recommendationServiceClient struct {\n\tcc grpc.ClientConnInterface\n}\n\nfunc NewRecommendationServiceClient(cc grpc.ClientConnInterface) RecommendationServiceClient {\n\treturn &recommendationServiceClient{cc}\n}\n\nfunc (c *recommendationServiceClient) ListRecommendations(ctx context.Context, in *ListRecommendationsRequest, opts ...grpc.CallOption) (*ListRecommendationsResponse, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(ListRecommendationsResponse)\n\terr := c.cc.Invoke(ctx, RecommendationService_ListRecommendations_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\n// RecommendationServiceServer is the server API for RecommendationService service.\n// All implementations must embed UnimplementedRecommendationServiceServer\n// for forward compatibility.\ntype RecommendationServiceServer interface {\n\tListRecommendations(context.Context, *ListRecommendationsRequest) (*ListRecommendationsResponse, error)\n\tmustEmbedUnimplementedRecommendationServiceServer()\n}\n\n// UnimplementedRecommendationServiceServer must be embedded to have\n// forward compatible implementations.\n//\n// NOTE: this should be embedded by value instead of pointer to avoid a nil\n// pointer dereference when methods are called.\ntype UnimplementedRecommendationServiceServer struct{}\n\nfunc (UnimplementedRecommendationServiceServer) ListRecommendations(context.Context, *ListRecommendationsRequest) (*ListRecommendationsResponse, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method ListRecommendations not implemented\")\n}\nfunc (UnimplementedRecommendationServiceServer) mustEmbedUnimplementedRecommendationServiceServer() {}\nfunc (UnimplementedRecommendationServiceServer) testEmbeddedByValue()                               {}\n\n// UnsafeRecommendationServiceServer may be embedded to opt out of forward compatibility for this service.\n// Use of this interface is not recommended, as added methods to RecommendationServiceServer will\n// result in compilation errors.\ntype UnsafeRecommendationServiceServer interface {\n\tmustEmbedUnimplementedRecommendationServiceServer()\n}\n\nfunc RegisterRecommendationServiceServer(s grpc.ServiceRegistrar, srv RecommendationServiceServer) {\n\t// If the following call pancis, it indicates UnimplementedRecommendationServiceServer was\n\t// embedded by pointer and is nil.  This will cause panics if an\n\t// unimplemented method is ever invoked, so we test this at initialization\n\t// time to prevent it from happening at runtime later due to I/O.\n\tif t, ok := srv.(interface{ testEmbeddedByValue() }); ok {\n\t\tt.testEmbeddedByValue()\n\t}\n\ts.RegisterService(&RecommendationService_ServiceDesc, srv)\n}\n\nfunc _RecommendationService_ListRecommendations_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(ListRecommendationsRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(RecommendationServiceServer).ListRecommendations(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: RecommendationService_ListRecommendations_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(RecommendationServiceServer).ListRecommendations(ctx, req.(*ListRecommendationsRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\n// RecommendationService_ServiceDesc is the grpc.ServiceDesc for RecommendationService service.\n// It's only intended for direct use with grpc.RegisterService,\n// and not to be introspected or modified (even as a copy)\nvar RecommendationService_ServiceDesc = grpc.ServiceDesc{\n\tServiceName: \"hipstershop.RecommendationService\",\n\tHandlerType: (*RecommendationServiceServer)(nil),\n\tMethods: []grpc.MethodDesc{\n\t\t{\n\t\t\tMethodName: \"ListRecommendations\",\n\t\t\tHandler:    _RecommendationService_ListRecommendations_Handler,\n\t\t},\n\t},\n\tStreams:  []grpc.StreamDesc{},\n\tMetadata: \"demo.proto\",\n}\n\nconst (\n\tProductCatalogService_ListProducts_FullMethodName   = \"/hipstershop.ProductCatalogService/ListProducts\"\n\tProductCatalogService_GetProduct_FullMethodName     = \"/hipstershop.ProductCatalogService/GetProduct\"\n\tProductCatalogService_SearchProducts_FullMethodName = \"/hipstershop.ProductCatalogService/SearchProducts\"\n)\n\n// ProductCatalogServiceClient is the client API for ProductCatalogService service.\n//\n// 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.\ntype ProductCatalogServiceClient interface {\n\tListProducts(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*ListProductsResponse, error)\n\tGetProduct(ctx context.Context, in *GetProductRequest, opts ...grpc.CallOption) (*Product, error)\n\tSearchProducts(ctx context.Context, in *SearchProductsRequest, opts ...grpc.CallOption) (*SearchProductsResponse, error)\n}\n\ntype productCatalogServiceClient struct {\n\tcc grpc.ClientConnInterface\n}\n\nfunc NewProductCatalogServiceClient(cc grpc.ClientConnInterface) ProductCatalogServiceClient {\n\treturn &productCatalogServiceClient{cc}\n}\n\nfunc (c *productCatalogServiceClient) ListProducts(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*ListProductsResponse, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(ListProductsResponse)\n\terr := c.cc.Invoke(ctx, ProductCatalogService_ListProducts_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *productCatalogServiceClient) GetProduct(ctx context.Context, in *GetProductRequest, opts ...grpc.CallOption) (*Product, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(Product)\n\terr := c.cc.Invoke(ctx, ProductCatalogService_GetProduct_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *productCatalogServiceClient) SearchProducts(ctx context.Context, in *SearchProductsRequest, opts ...grpc.CallOption) (*SearchProductsResponse, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(SearchProductsResponse)\n\terr := c.cc.Invoke(ctx, ProductCatalogService_SearchProducts_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\n// ProductCatalogServiceServer is the server API for ProductCatalogService service.\n// All implementations must embed UnimplementedProductCatalogServiceServer\n// for forward compatibility.\ntype ProductCatalogServiceServer interface {\n\tListProducts(context.Context, *Empty) (*ListProductsResponse, error)\n\tGetProduct(context.Context, *GetProductRequest) (*Product, error)\n\tSearchProducts(context.Context, *SearchProductsRequest) (*SearchProductsResponse, error)\n\tmustEmbedUnimplementedProductCatalogServiceServer()\n}\n\n// UnimplementedProductCatalogServiceServer must be embedded to have\n// forward compatible implementations.\n//\n// NOTE: this should be embedded by value instead of pointer to avoid a nil\n// pointer dereference when methods are called.\ntype UnimplementedProductCatalogServiceServer struct{}\n\nfunc (UnimplementedProductCatalogServiceServer) ListProducts(context.Context, *Empty) (*ListProductsResponse, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method ListProducts not implemented\")\n}\nfunc (UnimplementedProductCatalogServiceServer) GetProduct(context.Context, *GetProductRequest) (*Product, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method GetProduct not implemented\")\n}\nfunc (UnimplementedProductCatalogServiceServer) SearchProducts(context.Context, *SearchProductsRequest) (*SearchProductsResponse, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method SearchProducts not implemented\")\n}\nfunc (UnimplementedProductCatalogServiceServer) mustEmbedUnimplementedProductCatalogServiceServer() {}\nfunc (UnimplementedProductCatalogServiceServer) testEmbeddedByValue()                               {}\n\n// UnsafeProductCatalogServiceServer may be embedded to opt out of forward compatibility for this service.\n// Use of this interface is not recommended, as added methods to ProductCatalogServiceServer will\n// result in compilation errors.\ntype UnsafeProductCatalogServiceServer interface {\n\tmustEmbedUnimplementedProductCatalogServiceServer()\n}\n\nfunc RegisterProductCatalogServiceServer(s grpc.ServiceRegistrar, srv ProductCatalogServiceServer) {\n\t// If the following call pancis, it indicates UnimplementedProductCatalogServiceServer was\n\t// embedded by pointer and is nil.  This will cause panics if an\n\t// unimplemented method is ever invoked, so we test this at initialization\n\t// time to prevent it from happening at runtime later due to I/O.\n\tif t, ok := srv.(interface{ testEmbeddedByValue() }); ok {\n\t\tt.testEmbeddedByValue()\n\t}\n\ts.RegisterService(&ProductCatalogService_ServiceDesc, srv)\n}\n\nfunc _ProductCatalogService_ListProducts_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(Empty)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ProductCatalogServiceServer).ListProducts(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: ProductCatalogService_ListProducts_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ProductCatalogServiceServer).ListProducts(ctx, req.(*Empty))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _ProductCatalogService_GetProduct_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(GetProductRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ProductCatalogServiceServer).GetProduct(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: ProductCatalogService_GetProduct_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ProductCatalogServiceServer).GetProduct(ctx, req.(*GetProductRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _ProductCatalogService_SearchProducts_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(SearchProductsRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ProductCatalogServiceServer).SearchProducts(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: ProductCatalogService_SearchProducts_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ProductCatalogServiceServer).SearchProducts(ctx, req.(*SearchProductsRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\n// ProductCatalogService_ServiceDesc is the grpc.ServiceDesc for ProductCatalogService service.\n// It's only intended for direct use with grpc.RegisterService,\n// and not to be introspected or modified (even as a copy)\nvar ProductCatalogService_ServiceDesc = grpc.ServiceDesc{\n\tServiceName: \"hipstershop.ProductCatalogService\",\n\tHandlerType: (*ProductCatalogServiceServer)(nil),\n\tMethods: []grpc.MethodDesc{\n\t\t{\n\t\t\tMethodName: \"ListProducts\",\n\t\t\tHandler:    _ProductCatalogService_ListProducts_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"GetProduct\",\n\t\t\tHandler:    _ProductCatalogService_GetProduct_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"SearchProducts\",\n\t\t\tHandler:    _ProductCatalogService_SearchProducts_Handler,\n\t\t},\n\t},\n\tStreams:  []grpc.StreamDesc{},\n\tMetadata: \"demo.proto\",\n}\n\nconst (\n\tShippingService_GetQuote_FullMethodName  = \"/hipstershop.ShippingService/GetQuote\"\n\tShippingService_ShipOrder_FullMethodName = \"/hipstershop.ShippingService/ShipOrder\"\n)\n\n// ShippingServiceClient is the client API for ShippingService service.\n//\n// 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.\ntype ShippingServiceClient interface {\n\tGetQuote(ctx context.Context, in *GetQuoteRequest, opts ...grpc.CallOption) (*GetQuoteResponse, error)\n\tShipOrder(ctx context.Context, in *ShipOrderRequest, opts ...grpc.CallOption) (*ShipOrderResponse, error)\n}\n\ntype shippingServiceClient struct {\n\tcc grpc.ClientConnInterface\n}\n\nfunc NewShippingServiceClient(cc grpc.ClientConnInterface) ShippingServiceClient {\n\treturn &shippingServiceClient{cc}\n}\n\nfunc (c *shippingServiceClient) GetQuote(ctx context.Context, in *GetQuoteRequest, opts ...grpc.CallOption) (*GetQuoteResponse, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(GetQuoteResponse)\n\terr := c.cc.Invoke(ctx, ShippingService_GetQuote_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *shippingServiceClient) ShipOrder(ctx context.Context, in *ShipOrderRequest, opts ...grpc.CallOption) (*ShipOrderResponse, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(ShipOrderResponse)\n\terr := c.cc.Invoke(ctx, ShippingService_ShipOrder_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\n// ShippingServiceServer is the server API for ShippingService service.\n// All implementations must embed UnimplementedShippingServiceServer\n// for forward compatibility.\ntype ShippingServiceServer interface {\n\tGetQuote(context.Context, *GetQuoteRequest) (*GetQuoteResponse, error)\n\tShipOrder(context.Context, *ShipOrderRequest) (*ShipOrderResponse, error)\n\tmustEmbedUnimplementedShippingServiceServer()\n}\n\n// UnimplementedShippingServiceServer must be embedded to have\n// forward compatible implementations.\n//\n// NOTE: this should be embedded by value instead of pointer to avoid a nil\n// pointer dereference when methods are called.\ntype UnimplementedShippingServiceServer struct{}\n\nfunc (UnimplementedShippingServiceServer) GetQuote(context.Context, *GetQuoteRequest) (*GetQuoteResponse, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method GetQuote not implemented\")\n}\nfunc (UnimplementedShippingServiceServer) ShipOrder(context.Context, *ShipOrderRequest) (*ShipOrderResponse, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method ShipOrder not implemented\")\n}\nfunc (UnimplementedShippingServiceServer) mustEmbedUnimplementedShippingServiceServer() {}\nfunc (UnimplementedShippingServiceServer) testEmbeddedByValue()                         {}\n\n// UnsafeShippingServiceServer may be embedded to opt out of forward compatibility for this service.\n// Use of this interface is not recommended, as added methods to ShippingServiceServer will\n// result in compilation errors.\ntype UnsafeShippingServiceServer interface {\n\tmustEmbedUnimplementedShippingServiceServer()\n}\n\nfunc RegisterShippingServiceServer(s grpc.ServiceRegistrar, srv ShippingServiceServer) {\n\t// If the following call pancis, it indicates UnimplementedShippingServiceServer was\n\t// embedded by pointer and is nil.  This will cause panics if an\n\t// unimplemented method is ever invoked, so we test this at initialization\n\t// time to prevent it from happening at runtime later due to I/O.\n\tif t, ok := srv.(interface{ testEmbeddedByValue() }); ok {\n\t\tt.testEmbeddedByValue()\n\t}\n\ts.RegisterService(&ShippingService_ServiceDesc, srv)\n}\n\nfunc _ShippingService_GetQuote_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(GetQuoteRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ShippingServiceServer).GetQuote(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: ShippingService_GetQuote_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ShippingServiceServer).GetQuote(ctx, req.(*GetQuoteRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _ShippingService_ShipOrder_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(ShipOrderRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ShippingServiceServer).ShipOrder(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: ShippingService_ShipOrder_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ShippingServiceServer).ShipOrder(ctx, req.(*ShipOrderRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\n// ShippingService_ServiceDesc is the grpc.ServiceDesc for ShippingService service.\n// It's only intended for direct use with grpc.RegisterService,\n// and not to be introspected or modified (even as a copy)\nvar ShippingService_ServiceDesc = grpc.ServiceDesc{\n\tServiceName: \"hipstershop.ShippingService\",\n\tHandlerType: (*ShippingServiceServer)(nil),\n\tMethods: []grpc.MethodDesc{\n\t\t{\n\t\t\tMethodName: \"GetQuote\",\n\t\t\tHandler:    _ShippingService_GetQuote_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"ShipOrder\",\n\t\t\tHandler:    _ShippingService_ShipOrder_Handler,\n\t\t},\n\t},\n\tStreams:  []grpc.StreamDesc{},\n\tMetadata: \"demo.proto\",\n}\n\nconst (\n\tCurrencyService_GetSupportedCurrencies_FullMethodName = \"/hipstershop.CurrencyService/GetSupportedCurrencies\"\n\tCurrencyService_Convert_FullMethodName                = \"/hipstershop.CurrencyService/Convert\"\n)\n\n// CurrencyServiceClient is the client API for CurrencyService service.\n//\n// 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.\ntype CurrencyServiceClient interface {\n\tGetSupportedCurrencies(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*GetSupportedCurrenciesResponse, error)\n\tConvert(ctx context.Context, in *CurrencyConversionRequest, opts ...grpc.CallOption) (*Money, error)\n}\n\ntype currencyServiceClient struct {\n\tcc grpc.ClientConnInterface\n}\n\nfunc NewCurrencyServiceClient(cc grpc.ClientConnInterface) CurrencyServiceClient {\n\treturn &currencyServiceClient{cc}\n}\n\nfunc (c *currencyServiceClient) GetSupportedCurrencies(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*GetSupportedCurrenciesResponse, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(GetSupportedCurrenciesResponse)\n\terr := c.cc.Invoke(ctx, CurrencyService_GetSupportedCurrencies_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *currencyServiceClient) Convert(ctx context.Context, in *CurrencyConversionRequest, opts ...grpc.CallOption) (*Money, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(Money)\n\terr := c.cc.Invoke(ctx, CurrencyService_Convert_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\n// CurrencyServiceServer is the server API for CurrencyService service.\n// All implementations must embed UnimplementedCurrencyServiceServer\n// for forward compatibility.\ntype CurrencyServiceServer interface {\n\tGetSupportedCurrencies(context.Context, *Empty) (*GetSupportedCurrenciesResponse, error)\n\tConvert(context.Context, *CurrencyConversionRequest) (*Money, error)\n\tmustEmbedUnimplementedCurrencyServiceServer()\n}\n\n// UnimplementedCurrencyServiceServer must be embedded to have\n// forward compatible implementations.\n//\n// NOTE: this should be embedded by value instead of pointer to avoid a nil\n// pointer dereference when methods are called.\ntype UnimplementedCurrencyServiceServer struct{}\n\nfunc (UnimplementedCurrencyServiceServer) GetSupportedCurrencies(context.Context, *Empty) (*GetSupportedCurrenciesResponse, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method GetSupportedCurrencies not implemented\")\n}\nfunc (UnimplementedCurrencyServiceServer) Convert(context.Context, *CurrencyConversionRequest) (*Money, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method Convert not implemented\")\n}\nfunc (UnimplementedCurrencyServiceServer) mustEmbedUnimplementedCurrencyServiceServer() {}\nfunc (UnimplementedCurrencyServiceServer) testEmbeddedByValue()                         {}\n\n// UnsafeCurrencyServiceServer may be embedded to opt out of forward compatibility for this service.\n// Use of this interface is not recommended, as added methods to CurrencyServiceServer will\n// result in compilation errors.\ntype UnsafeCurrencyServiceServer interface {\n\tmustEmbedUnimplementedCurrencyServiceServer()\n}\n\nfunc RegisterCurrencyServiceServer(s grpc.ServiceRegistrar, srv CurrencyServiceServer) {\n\t// If the following call pancis, it indicates UnimplementedCurrencyServiceServer was\n\t// embedded by pointer and is nil.  This will cause panics if an\n\t// unimplemented method is ever invoked, so we test this at initialization\n\t// time to prevent it from happening at runtime later due to I/O.\n\tif t, ok := srv.(interface{ testEmbeddedByValue() }); ok {\n\t\tt.testEmbeddedByValue()\n\t}\n\ts.RegisterService(&CurrencyService_ServiceDesc, srv)\n}\n\nfunc _CurrencyService_GetSupportedCurrencies_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(Empty)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(CurrencyServiceServer).GetSupportedCurrencies(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: CurrencyService_GetSupportedCurrencies_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(CurrencyServiceServer).GetSupportedCurrencies(ctx, req.(*Empty))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _CurrencyService_Convert_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(CurrencyConversionRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(CurrencyServiceServer).Convert(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: CurrencyService_Convert_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(CurrencyServiceServer).Convert(ctx, req.(*CurrencyConversionRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\n// CurrencyService_ServiceDesc is the grpc.ServiceDesc for CurrencyService service.\n// It's only intended for direct use with grpc.RegisterService,\n// and not to be introspected or modified (even as a copy)\nvar CurrencyService_ServiceDesc = grpc.ServiceDesc{\n\tServiceName: \"hipstershop.CurrencyService\",\n\tHandlerType: (*CurrencyServiceServer)(nil),\n\tMethods: []grpc.MethodDesc{\n\t\t{\n\t\t\tMethodName: \"GetSupportedCurrencies\",\n\t\t\tHandler:    _CurrencyService_GetSupportedCurrencies_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"Convert\",\n\t\t\tHandler:    _CurrencyService_Convert_Handler,\n\t\t},\n\t},\n\tStreams:  []grpc.StreamDesc{},\n\tMetadata: \"demo.proto\",\n}\n\nconst (\n\tPaymentService_Charge_FullMethodName = \"/hipstershop.PaymentService/Charge\"\n)\n\n// PaymentServiceClient is the client API for PaymentService service.\n//\n// 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.\ntype PaymentServiceClient interface {\n\tCharge(ctx context.Context, in *ChargeRequest, opts ...grpc.CallOption) (*ChargeResponse, error)\n}\n\ntype paymentServiceClient struct {\n\tcc grpc.ClientConnInterface\n}\n\nfunc NewPaymentServiceClient(cc grpc.ClientConnInterface) PaymentServiceClient {\n\treturn &paymentServiceClient{cc}\n}\n\nfunc (c *paymentServiceClient) Charge(ctx context.Context, in *ChargeRequest, opts ...grpc.CallOption) (*ChargeResponse, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(ChargeResponse)\n\terr := c.cc.Invoke(ctx, PaymentService_Charge_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\n// PaymentServiceServer is the server API for PaymentService service.\n// All implementations must embed UnimplementedPaymentServiceServer\n// for forward compatibility.\ntype PaymentServiceServer interface {\n\tCharge(context.Context, *ChargeRequest) (*ChargeResponse, error)\n\tmustEmbedUnimplementedPaymentServiceServer()\n}\n\n// UnimplementedPaymentServiceServer must be embedded to have\n// forward compatible implementations.\n//\n// NOTE: this should be embedded by value instead of pointer to avoid a nil\n// pointer dereference when methods are called.\ntype UnimplementedPaymentServiceServer struct{}\n\nfunc (UnimplementedPaymentServiceServer) Charge(context.Context, *ChargeRequest) (*ChargeResponse, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method Charge not implemented\")\n}\nfunc (UnimplementedPaymentServiceServer) mustEmbedUnimplementedPaymentServiceServer() {}\nfunc (UnimplementedPaymentServiceServer) testEmbeddedByValue()                        {}\n\n// UnsafePaymentServiceServer may be embedded to opt out of forward compatibility for this service.\n// Use of this interface is not recommended, as added methods to PaymentServiceServer will\n// result in compilation errors.\ntype UnsafePaymentServiceServer interface {\n\tmustEmbedUnimplementedPaymentServiceServer()\n}\n\nfunc RegisterPaymentServiceServer(s grpc.ServiceRegistrar, srv PaymentServiceServer) {\n\t// If the following call pancis, it indicates UnimplementedPaymentServiceServer was\n\t// embedded by pointer and is nil.  This will cause panics if an\n\t// unimplemented method is ever invoked, so we test this at initialization\n\t// time to prevent it from happening at runtime later due to I/O.\n\tif t, ok := srv.(interface{ testEmbeddedByValue() }); ok {\n\t\tt.testEmbeddedByValue()\n\t}\n\ts.RegisterService(&PaymentService_ServiceDesc, srv)\n}\n\nfunc _PaymentService_Charge_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(ChargeRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(PaymentServiceServer).Charge(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: PaymentService_Charge_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(PaymentServiceServer).Charge(ctx, req.(*ChargeRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\n// PaymentService_ServiceDesc is the grpc.ServiceDesc for PaymentService service.\n// It's only intended for direct use with grpc.RegisterService,\n// and not to be introspected or modified (even as a copy)\nvar PaymentService_ServiceDesc = grpc.ServiceDesc{\n\tServiceName: \"hipstershop.PaymentService\",\n\tHandlerType: (*PaymentServiceServer)(nil),\n\tMethods: []grpc.MethodDesc{\n\t\t{\n\t\t\tMethodName: \"Charge\",\n\t\t\tHandler:    _PaymentService_Charge_Handler,\n\t\t},\n\t},\n\tStreams:  []grpc.StreamDesc{},\n\tMetadata: \"demo.proto\",\n}\n\nconst (\n\tEmailService_SendOrderConfirmation_FullMethodName = \"/hipstershop.EmailService/SendOrderConfirmation\"\n)\n\n// EmailServiceClient is the client API for EmailService service.\n//\n// 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.\ntype EmailServiceClient interface {\n\tSendOrderConfirmation(ctx context.Context, in *SendOrderConfirmationRequest, opts ...grpc.CallOption) (*Empty, error)\n}\n\ntype emailServiceClient struct {\n\tcc grpc.ClientConnInterface\n}\n\nfunc NewEmailServiceClient(cc grpc.ClientConnInterface) EmailServiceClient {\n\treturn &emailServiceClient{cc}\n}\n\nfunc (c *emailServiceClient) SendOrderConfirmation(ctx context.Context, in *SendOrderConfirmationRequest, opts ...grpc.CallOption) (*Empty, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(Empty)\n\terr := c.cc.Invoke(ctx, EmailService_SendOrderConfirmation_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\n// EmailServiceServer is the server API for EmailService service.\n// All implementations must embed UnimplementedEmailServiceServer\n// for forward compatibility.\ntype EmailServiceServer interface {\n\tSendOrderConfirmation(context.Context, *SendOrderConfirmationRequest) (*Empty, error)\n\tmustEmbedUnimplementedEmailServiceServer()\n}\n\n// UnimplementedEmailServiceServer must be embedded to have\n// forward compatible implementations.\n//\n// NOTE: this should be embedded by value instead of pointer to avoid a nil\n// pointer dereference when methods are called.\ntype UnimplementedEmailServiceServer struct{}\n\nfunc (UnimplementedEmailServiceServer) SendOrderConfirmation(context.Context, *SendOrderConfirmationRequest) (*Empty, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method SendOrderConfirmation not implemented\")\n}\nfunc (UnimplementedEmailServiceServer) mustEmbedUnimplementedEmailServiceServer() {}\nfunc (UnimplementedEmailServiceServer) testEmbeddedByValue()                      {}\n\n// UnsafeEmailServiceServer may be embedded to opt out of forward compatibility for this service.\n// Use of this interface is not recommended, as added methods to EmailServiceServer will\n// result in compilation errors.\ntype UnsafeEmailServiceServer interface {\n\tmustEmbedUnimplementedEmailServiceServer()\n}\n\nfunc RegisterEmailServiceServer(s grpc.ServiceRegistrar, srv EmailServiceServer) {\n\t// If the following call pancis, it indicates UnimplementedEmailServiceServer was\n\t// embedded by pointer and is nil.  This will cause panics if an\n\t// unimplemented method is ever invoked, so we test this at initialization\n\t// time to prevent it from happening at runtime later due to I/O.\n\tif t, ok := srv.(interface{ testEmbeddedByValue() }); ok {\n\t\tt.testEmbeddedByValue()\n\t}\n\ts.RegisterService(&EmailService_ServiceDesc, srv)\n}\n\nfunc _EmailService_SendOrderConfirmation_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(SendOrderConfirmationRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(EmailServiceServer).SendOrderConfirmation(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: EmailService_SendOrderConfirmation_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(EmailServiceServer).SendOrderConfirmation(ctx, req.(*SendOrderConfirmationRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\n// EmailService_ServiceDesc is the grpc.ServiceDesc for EmailService service.\n// It's only intended for direct use with grpc.RegisterService,\n// and not to be introspected or modified (even as a copy)\nvar EmailService_ServiceDesc = grpc.ServiceDesc{\n\tServiceName: \"hipstershop.EmailService\",\n\tHandlerType: (*EmailServiceServer)(nil),\n\tMethods: []grpc.MethodDesc{\n\t\t{\n\t\t\tMethodName: \"SendOrderConfirmation\",\n\t\t\tHandler:    _EmailService_SendOrderConfirmation_Handler,\n\t\t},\n\t},\n\tStreams:  []grpc.StreamDesc{},\n\tMetadata: \"demo.proto\",\n}\n\nconst (\n\tCheckoutService_PlaceOrder_FullMethodName = \"/hipstershop.CheckoutService/PlaceOrder\"\n)\n\n// CheckoutServiceClient is the client API for CheckoutService service.\n//\n// 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.\ntype CheckoutServiceClient interface {\n\tPlaceOrder(ctx context.Context, in *PlaceOrderRequest, opts ...grpc.CallOption) (*PlaceOrderResponse, error)\n}\n\ntype checkoutServiceClient struct {\n\tcc grpc.ClientConnInterface\n}\n\nfunc NewCheckoutServiceClient(cc grpc.ClientConnInterface) CheckoutServiceClient {\n\treturn &checkoutServiceClient{cc}\n}\n\nfunc (c *checkoutServiceClient) PlaceOrder(ctx context.Context, in *PlaceOrderRequest, opts ...grpc.CallOption) (*PlaceOrderResponse, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(PlaceOrderResponse)\n\terr := c.cc.Invoke(ctx, CheckoutService_PlaceOrder_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\n// CheckoutServiceServer is the server API for CheckoutService service.\n// All implementations must embed UnimplementedCheckoutServiceServer\n// for forward compatibility.\ntype CheckoutServiceServer interface {\n\tPlaceOrder(context.Context, *PlaceOrderRequest) (*PlaceOrderResponse, error)\n\tmustEmbedUnimplementedCheckoutServiceServer()\n}\n\n// UnimplementedCheckoutServiceServer must be embedded to have\n// forward compatible implementations.\n//\n// NOTE: this should be embedded by value instead of pointer to avoid a nil\n// pointer dereference when methods are called.\ntype UnimplementedCheckoutServiceServer struct{}\n\nfunc (UnimplementedCheckoutServiceServer) PlaceOrder(context.Context, *PlaceOrderRequest) (*PlaceOrderResponse, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method PlaceOrder not implemented\")\n}\nfunc (UnimplementedCheckoutServiceServer) mustEmbedUnimplementedCheckoutServiceServer() {}\nfunc (UnimplementedCheckoutServiceServer) testEmbeddedByValue()                         {}\n\n// UnsafeCheckoutServiceServer may be embedded to opt out of forward compatibility for this service.\n// Use of this interface is not recommended, as added methods to CheckoutServiceServer will\n// result in compilation errors.\ntype UnsafeCheckoutServiceServer interface {\n\tmustEmbedUnimplementedCheckoutServiceServer()\n}\n\nfunc RegisterCheckoutServiceServer(s grpc.ServiceRegistrar, srv CheckoutServiceServer) {\n\t// If the following call pancis, it indicates UnimplementedCheckoutServiceServer was\n\t// embedded by pointer and is nil.  This will cause panics if an\n\t// unimplemented method is ever invoked, so we test this at initialization\n\t// time to prevent it from happening at runtime later due to I/O.\n\tif t, ok := srv.(interface{ testEmbeddedByValue() }); ok {\n\t\tt.testEmbeddedByValue()\n\t}\n\ts.RegisterService(&CheckoutService_ServiceDesc, srv)\n}\n\nfunc _CheckoutService_PlaceOrder_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(PlaceOrderRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(CheckoutServiceServer).PlaceOrder(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: CheckoutService_PlaceOrder_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(CheckoutServiceServer).PlaceOrder(ctx, req.(*PlaceOrderRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\n// CheckoutService_ServiceDesc is the grpc.ServiceDesc for CheckoutService service.\n// It's only intended for direct use with grpc.RegisterService,\n// and not to be introspected or modified (even as a copy)\nvar CheckoutService_ServiceDesc = grpc.ServiceDesc{\n\tServiceName: \"hipstershop.CheckoutService\",\n\tHandlerType: (*CheckoutServiceServer)(nil),\n\tMethods: []grpc.MethodDesc{\n\t\t{\n\t\t\tMethodName: \"PlaceOrder\",\n\t\t\tHandler:    _CheckoutService_PlaceOrder_Handler,\n\t\t},\n\t},\n\tStreams:  []grpc.StreamDesc{},\n\tMetadata: \"demo.proto\",\n}\n\nconst (\n\tAdService_GetAds_FullMethodName = \"/hipstershop.AdService/GetAds\"\n)\n\n// AdServiceClient is the client API for AdService service.\n//\n// 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.\ntype AdServiceClient interface {\n\tGetAds(ctx context.Context, in *AdRequest, opts ...grpc.CallOption) (*AdResponse, error)\n}\n\ntype adServiceClient struct {\n\tcc grpc.ClientConnInterface\n}\n\nfunc NewAdServiceClient(cc grpc.ClientConnInterface) AdServiceClient {\n\treturn &adServiceClient{cc}\n}\n\nfunc (c *adServiceClient) GetAds(ctx context.Context, in *AdRequest, opts ...grpc.CallOption) (*AdResponse, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(AdResponse)\n\terr := c.cc.Invoke(ctx, AdService_GetAds_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\n// AdServiceServer is the server API for AdService service.\n// All implementations must embed UnimplementedAdServiceServer\n// for forward compatibility.\ntype AdServiceServer interface {\n\tGetAds(context.Context, *AdRequest) (*AdResponse, error)\n\tmustEmbedUnimplementedAdServiceServer()\n}\n\n// UnimplementedAdServiceServer must be embedded to have\n// forward compatible implementations.\n//\n// NOTE: this should be embedded by value instead of pointer to avoid a nil\n// pointer dereference when methods are called.\ntype UnimplementedAdServiceServer struct{}\n\nfunc (UnimplementedAdServiceServer) GetAds(context.Context, *AdRequest) (*AdResponse, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method GetAds not implemented\")\n}\nfunc (UnimplementedAdServiceServer) mustEmbedUnimplementedAdServiceServer() {}\nfunc (UnimplementedAdServiceServer) testEmbeddedByValue()                   {}\n\n// UnsafeAdServiceServer may be embedded to opt out of forward compatibility for this service.\n// Use of this interface is not recommended, as added methods to AdServiceServer will\n// result in compilation errors.\ntype UnsafeAdServiceServer interface {\n\tmustEmbedUnimplementedAdServiceServer()\n}\n\nfunc RegisterAdServiceServer(s grpc.ServiceRegistrar, srv AdServiceServer) {\n\t// If the following call pancis, it indicates UnimplementedAdServiceServer was\n\t// embedded by pointer and is nil.  This will cause panics if an\n\t// unimplemented method is ever invoked, so we test this at initialization\n\t// time to prevent it from happening at runtime later due to I/O.\n\tif t, ok := srv.(interface{ testEmbeddedByValue() }); ok {\n\t\tt.testEmbeddedByValue()\n\t}\n\ts.RegisterService(&AdService_ServiceDesc, srv)\n}\n\nfunc _AdService_GetAds_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(AdRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(AdServiceServer).GetAds(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: AdService_GetAds_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(AdServiceServer).GetAds(ctx, req.(*AdRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\n// AdService_ServiceDesc is the grpc.ServiceDesc for AdService service.\n// It's only intended for direct use with grpc.RegisterService,\n// and not to be introspected or modified (even as a copy)\nvar AdService_ServiceDesc = grpc.ServiceDesc{\n\tServiceName: \"hipstershop.AdService\",\n\tHandlerType: (*AdServiceServer)(nil),\n\tMethods: []grpc.MethodDesc{\n\t\t{\n\t\t\tMethodName: \"GetAds\",\n\t\t\tHandler:    _AdService_GetAds_Handler,\n\t\t},\n\t},\n\tStreams:  []grpc.StreamDesc{},\n\tMetadata: \"demo.proto\",\n}\n"
  },
  {
    "path": "src/shippingservice/genproto.sh",
    "content": "#!/bin/bash -eu\n#\n# Copyright 2018 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# [START gke_shippingservice_genproto]\n\nPATH=$PATH:$(go env GOPATH)/bin\nprotodir=../../protos\noutdir=./genproto\n\nprotoc --proto_path=$protodir --go_out=./$outdir --go_opt=paths=source_relative --go-grpc_out=./$outdir --go-grpc_opt=paths=source_relative $protodir/demo.proto\n\n# [END gke_shippingservice_genproto]"
  },
  {
    "path": "src/shippingservice/go.mod",
    "content": "module github.com/GoogleCloudPlatform/microservices-demo/src/shippingservice\n\ngo 1.25.0\n\ntoolchain go1.26.1\n\nrequire (\n\tcloud.google.com/go/profiler v0.4.3\n\tgithub.com/sirupsen/logrus v1.9.4\n\tgolang.org/x/net v0.51.0\n\tgoogle.golang.org/grpc v1.79.2\n\tgoogle.golang.org/protobuf v1.36.11\n)\n\nrequire (\n\tcloud.google.com/go v0.123.0 // indirect\n\tcloud.google.com/go/auth v0.17.0 // indirect\n\tcloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect\n\tcloud.google.com/go/compute/metadata v0.9.0 // indirect\n\tgithub.com/cespare/xxhash/v2 v2.3.0 // indirect\n\tgithub.com/go-logr/logr v1.4.3 // indirect\n\tgithub.com/go-logr/stdr v1.2.2 // indirect\n\tgithub.com/google/pprof v0.0.0-20251114195745-4902fdda35c8 // indirect\n\tgithub.com/google/s2a-go v0.1.9 // indirect\n\tgithub.com/googleapis/enterprise-certificate-proxy v0.3.7 // indirect\n\tgithub.com/googleapis/gax-go/v2 v2.15.0 // indirect\n\tgo.opentelemetry.io/auto/sdk v1.2.1 // indirect\n\tgo.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0 // indirect\n\tgo.opentelemetry.io/otel v1.39.0 // indirect\n\tgo.opentelemetry.io/otel/metric v1.39.0 // indirect\n\tgo.opentelemetry.io/otel/trace v1.39.0 // indirect\n\tgolang.org/x/crypto v0.48.0 // indirect\n\tgolang.org/x/oauth2 v0.34.0 // indirect\n\tgolang.org/x/sync v0.19.0 // indirect\n\tgolang.org/x/sys v0.41.0 // indirect\n\tgolang.org/x/text v0.34.0 // indirect\n\tgolang.org/x/time v0.14.0 // indirect\n\tgoogle.golang.org/api v0.256.0 // indirect\n\tgoogle.golang.org/genproto v0.0.0-20251124214823-79d6a2a48846 // indirect\n\tgoogle.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 // indirect\n\tgoogle.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect\n)\n"
  },
  {
    "path": "src/shippingservice/go.sum",
    "content": "cel.dev/expr v0.24.0 h1:56OvJKSH3hDGL0ml5uSxZmz3/3Pq4tJ+fb1unVLAFcY=\ncel.dev/expr v0.24.0/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw=\ncloud.google.com/go v0.123.0 h1:2NAUJwPR47q+E35uaJeYoNhuNEM9kM8SjgRgdeOJUSE=\ncloud.google.com/go v0.123.0/go.mod h1:xBoMV08QcqUGuPW65Qfm1o9Y4zKZBpGS+7bImXLTAZU=\ncloud.google.com/go/auth v0.17.0 h1:74yCm7hCj2rUyyAocqnFzsAYXgJhrG26XCFimrc/Kz4=\ncloud.google.com/go/auth v0.17.0/go.mod h1:6wv/t5/6rOPAX4fJiRjKkJCvswLwdet7G8+UGXt7nCQ=\ncloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc=\ncloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c=\ncloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs=\ncloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10=\ncloud.google.com/go/iam v1.5.3 h1:+vMINPiDF2ognBJ97ABAYYwRgsaqxPbQDlMnbHMjolc=\ncloud.google.com/go/iam v1.5.3/go.mod h1:MR3v9oLkZCTlaqljW6Eb2d3HGDGK5/bDv93jhfISFvU=\ncloud.google.com/go/monitoring v1.24.3 h1:dde+gMNc0UhPZD1Azu6at2e79bfdztVDS5lvhOdsgaE=\ncloud.google.com/go/monitoring v1.24.3/go.mod h1:nYP6W0tm3N9H/bOw8am7t62YTzZY+zUeQ+Bi6+2eonI=\ncloud.google.com/go/profiler v0.4.3 h1:IY3QNKlr8VbXwGWHcZbJQsMA/83ZTH6uAHf8jYyj7OI=\ncloud.google.com/go/profiler v0.4.3/go.mod h1:3xFodugWfPIQZWFcXdUmfa+yTiiyQ8fWrdT+d2Sg4J0=\ncloud.google.com/go/storage v1.56.0 h1:iixmq2Fse2tqxMbWhLWC9HfBj1qdxqAmiK8/eqtsLxI=\ncloud.google.com/go/storage v1.56.0/go.mod h1:Tpuj6t4NweCLzlNbw9Z9iwxEkrSem20AetIeH/shgVU=\ngithub.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0 h1:sBEjpZlNHzK1voKq9695PJSX2o5NEXl7/OL3coiIY0c=\ngithub.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0/go.mod h1:P4WPRUkOhJC13W//jWpyfJNDAIpvRbAUIYLX/4jtlE0=\ngithub.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.53.0 h1:owcC2UnmsZycprQ5RfRgjydWhuoxg71LUfyiQdijZuM=\ngithub.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.53.0/go.mod h1:ZPpqegjbE99EPKsu3iUWV22A04wzGPcAY/ziSIQEEgs=\ngithub.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.53.0 h1:Ron4zCA/yk6U7WOBXhTJcDpsUBG9npumK6xw2auFltQ=\ngithub.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.53.0/go.mod h1:cSgYe11MCNYunTnRXrKiR/tHc0eoKjICUuWpNZoVCOo=\ngithub.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=\ngithub.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=\ngithub.com/cncf/xds/go v0.0.0-20251022180443-0feb69152e9f h1:Y8xYupdHxryycyPlc9Y+bSQAYZnetRJ70VMVKm5CKI0=\ngithub.com/cncf/xds/go v0.0.0-20251022180443-0feb69152e9f/go.mod h1:HlzOvOjVBOfTGSRXRyY0OiCS/3J1akRGQQpRO/7zyF4=\ngithub.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=\ngithub.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/envoyproxy/go-control-plane v0.13.5-0.20251024222203-75eaa193e329 h1:K+fnvUM0VZ7ZFJf0n4L/BRlnsb9pL/GuDG6FqaH+PwM=\ngithub.com/envoyproxy/go-control-plane/envoy v1.35.0 h1:ixjkELDE+ru6idPxcHLj8LBVc2bFP7iBytj353BoHUo=\ngithub.com/envoyproxy/go-control-plane/envoy v1.35.0/go.mod h1:09qwbGVuSWWAyN5t/b3iyVfz5+z8QWGrzkoqm/8SbEs=\ngithub.com/envoyproxy/protoc-gen-validate v1.2.1 h1:DEo3O99U8j4hBFwbJfrz9VtgcDfUKS7KJ7spH3d86P8=\ngithub.com/envoyproxy/protoc-gen-validate v1.2.1/go.mod h1:d/C80l/jxXLdfEIhX1W2TmLfsJ31lvEjwamM4DxlWXU=\ngithub.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=\ngithub.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=\ngithub.com/go-jose/go-jose/v4 v4.1.3 h1:CVLmWDhDVRa6Mi/IgCgaopNosCaHz7zrMeF9MlZRkrs=\ngithub.com/go-jose/go-jose/v4 v4.1.3/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=\ngithub.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=\ngithub.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=\ngithub.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=\ngithub.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=\ngithub.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=\ngithub.com/golang/mock v1.7.0-rc.1 h1:YojYx61/OLFsiv6Rw1Z96LpldJIy31o+UHmwAUMJ6/U=\ngithub.com/golang/mock v1.7.0-rc.1/go.mod h1:s42URUywIqd+OcERslBJvOjepvNymP31m3q8d/GkuRs=\ngithub.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=\ngithub.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=\ngithub.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=\ngithub.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=\ngithub.com/google/pprof v0.0.0-20251114195745-4902fdda35c8 h1:3DsUAV+VNEQa2CUVLxCY3f87278uWfIDhJnbdvDjvmE=\ngithub.com/google/pprof v0.0.0-20251114195745-4902fdda35c8/go.mod h1:I6V7YzU0XDpsHqbsyrghnFZLO1gwK6NPTNvmetQIk9U=\ngithub.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0=\ngithub.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM=\ngithub.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=\ngithub.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=\ngithub.com/googleapis/enterprise-certificate-proxy v0.3.7 h1:zrn2Ee/nWmHulBx5sAVrGgAa0f2/R35S4DJwfFaUPFQ=\ngithub.com/googleapis/enterprise-certificate-proxy v0.3.7/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA=\ngithub.com/googleapis/gax-go/v2 v2.15.0 h1:SyjDc1mGgZU5LncH8gimWo9lW1DtIfPibOG81vgd/bo=\ngithub.com/googleapis/gax-go/v2 v2.15.0/go.mod h1:zVVkkxAQHa1RQpg9z2AUCMnKhi0Qld9rcmyfL1OZhoc=\ngithub.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo=\ngithub.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8=\ngithub.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=\ngithub.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=\ngithub.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=\ngithub.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w=\ngithub.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g=\ngithub.com/spiffe/go-spiffe/v2 v2.6.0 h1:l+DolpxNWYgruGQVV0xsfeya3CsC7m8iBzDnMpsbLuo=\ngithub.com/spiffe/go-spiffe/v2 v2.6.0/go.mod h1:gm2SeUoMZEtpnzPNs2Csc0D/gX33k1xIx7lEzqblHEs=\ngithub.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngithub.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=\ngithub.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=\ngo.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=\ngo.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=\ngo.opentelemetry.io/contrib/detectors/gcp v1.38.0 h1:ZoYbqX7OaA/TAikspPl3ozPI6iY6LiIY9I8cUfm+pJs=\ngo.opentelemetry.io/contrib/detectors/gcp v1.38.0/go.mod h1:SU+iU7nu5ud4oCb3LQOhIZ3nRLj6FNVrKgtflbaf2ts=\ngo.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0 h1:YH4g8lQroajqUwWbq/tr2QX1JFmEXaDLgG+ew9bLMWo=\ngo.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0/go.mod h1:fvPi2qXDqFs8M4B4fmJhE92TyQs9Ydjlg3RvfUp+NbQ=\ngo.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus=\ngo.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q=\ngo.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8=\ngo.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM=\ngo.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48=\ngo.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8=\ngo.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA=\ngo.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI=\ngo.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0=\ngo.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs=\ngo.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E=\ngo.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg=\ngo.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM=\ngo.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA=\ngo.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE=\ngo.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs=\ngo.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI=\ngo.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA=\ngolang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=\ngolang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=\ngolang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU=\ngolang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0=\ngolang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8=\ngolang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A=\ngolang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=\ngolang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=\ngolang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=\ngolang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=\ngolang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=\ngolang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=\ngolang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o=\ngolang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8=\ngolang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=\ngolang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=\ngolang.org/x/oauth2 v0.33.0 h1:4Q+qn+E5z8gPRJfmRy7C2gGG3T4jIprK6aSYgTXGRpo=\ngolang.org/x/oauth2 v0.33.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=\ngolang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw=\ngolang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=\ngolang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=\ngolang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=\ngolang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=\ngolang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=\ngolang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=\ngolang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=\ngolang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=\ngolang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=\ngolang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=\ngolang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=\ngolang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=\ngolang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=\ngolang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=\ngolang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=\ngolang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=\ngolang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=\ngolang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=\ngolang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8=\ngolang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=\ngolang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=\ngolang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=\ngolang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=\ngonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=\ngonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=\ngoogle.golang.org/api v0.256.0 h1:u6Khm8+F9sxbCTYNoBHg6/Hwv0N/i+V94MvkOSor6oI=\ngoogle.golang.org/api v0.256.0/go.mod h1:KIgPhksXADEKJlnEoRa9qAII4rXcy40vfI8HRqcU964=\ngoogle.golang.org/genproto v0.0.0-20251124214823-79d6a2a48846 h1:dDbsTLIK7EzwUq36kCSAsk0slouq/S0tWHeeGi97cD8=\ngoogle.golang.org/genproto v0.0.0-20251124214823-79d6a2a48846/go.mod h1:PP0g88Dz3C7hRAfbQCQggeWAXjuqGsNPLE4s7jh0RGU=\ngoogle.golang.org/genproto/googleapis/api v0.0.0-20251124214823-79d6a2a48846 h1:ZdyUkS9po3H7G0tuh955QVyyotWvOD4W0aEapeGeUYk=\ngoogle.golang.org/genproto/googleapis/api v0.0.0-20251124214823-79d6a2a48846/go.mod h1:Fk4kyraUvqD7i5H6S43sj2W98fbZa75lpZz/eUyhfO0=\ngoogle.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 h1:fCvbg86sFXwdrl5LgVcTEvNC+2txB5mgROGmRL5mrls=\ngoogle.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:+rXWjjaukWZun3mLfjmVnQi18E1AsFbDN9QdJ5YXLto=\ngoogle.golang.org/genproto/googleapis/rpc v0.0.0-20251124214823-79d6a2a48846 h1:Wgl1rcDNThT+Zn47YyCXOXyX/COgMTIdhJ717F0l4xk=\ngoogle.golang.org/genproto/googleapis/rpc v0.0.0-20251124214823-79d6a2a48846/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=\ngoogle.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 h1:gRkg/vSppuSQoDjxyiGfN4Upv/h/DQmIR10ZU8dh4Ww=\ngoogle.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=\ngoogle.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM=\ngoogle.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig=\ngoogle.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc=\ngoogle.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U=\ngoogle.golang.org/grpc v1.79.2 h1:fRMD94s2tITpyJGtBBn7MkMseNpOZU8ZxgC3MMBaXRU=\ngoogle.golang.org/grpc v1.79.2/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=\ngoogle.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=\ngoogle.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=\ngoogle.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=\ngoogle.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=\ngopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\n"
  },
  {
    "path": "src/shippingservice/main.go",
    "content": "// Copyright 2018 Google LLC\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage main\n\nimport (\n\t\"fmt\"\n\t\"net\"\n\t\"os\"\n\t\"time\"\n\n\t\"cloud.google.com/go/profiler\"\n\t\"github.com/sirupsen/logrus\"\n\t\"golang.org/x/net/context\"\n\t\"google.golang.org/grpc\"\n\t\"google.golang.org/grpc/codes\"\n\t\"google.golang.org/grpc/health\"\n\t\"google.golang.org/grpc/reflection\"\n\t\"google.golang.org/grpc/status\"\n\n\tpb \"github.com/GoogleCloudPlatform/microservices-demo/src/shippingservice/genproto\"\n\thealthpb \"google.golang.org/grpc/health/grpc_health_v1\"\n)\n\nconst (\n\tdefaultPort = \"50051\"\n)\n\nvar log *logrus.Logger\n\nfunc init() {\n\tlog = logrus.New()\n\tlog.Level = logrus.DebugLevel\n\tlog.Formatter = &logrus.JSONFormatter{\n\t\tFieldMap: logrus.FieldMap{\n\t\t\tlogrus.FieldKeyTime:  \"timestamp\",\n\t\t\tlogrus.FieldKeyLevel: \"severity\",\n\t\t\tlogrus.FieldKeyMsg:   \"message\",\n\t\t},\n\t\tTimestampFormat: time.RFC3339Nano,\n\t}\n\tlog.Out = os.Stdout\n}\n\nfunc main() {\n\tif os.Getenv(\"DISABLE_TRACING\") == \"\" {\n\t\tlog.Info(\"Tracing enabled, but temporarily unavailable\")\n\t\tlog.Info(\"See https://github.com/GoogleCloudPlatform/microservices-demo/issues/422 for more info.\")\n\t\tgo initTracing()\n\t} else {\n\t\tlog.Info(\"Tracing disabled.\")\n\t}\n\n\tif os.Getenv(\"DISABLE_PROFILER\") == \"\" {\n\t\tlog.Info(\"Profiling enabled.\")\n\t\tgo initProfiling(\"shippingservice\", \"1.0.0\")\n\t} else {\n\t\tlog.Info(\"Profiling disabled.\")\n\t}\n\n\tport := defaultPort\n\tif value, ok := os.LookupEnv(\"PORT\"); ok {\n\t\tport = value\n\t}\n\tport = fmt.Sprintf(\":%s\", port)\n\n\tlis, err := net.Listen(\"tcp\", port)\n\tif err != nil {\n\t\tlog.Fatalf(\"failed to listen: %v\", err)\n\t}\n\n\tvar srv *grpc.Server\n\tif os.Getenv(\"DISABLE_STATS\") == \"\" {\n\t\tlog.Info(\"Stats enabled, but temporarily unavailable\")\n\t\tsrv = grpc.NewServer()\n\t} else {\n\t\tlog.Info(\"Stats disabled.\")\n\t\tsrv = grpc.NewServer()\n\t}\n\tsvc := &server{}\n\tpb.RegisterShippingServiceServer(srv, svc)\n\thealthcheck := health.NewServer()\n\thealthpb.RegisterHealthServer(srv, healthcheck)\n\tlog.Infof(\"Shipping Service listening on port %s\", port)\n\n\t// Register reflection service on gRPC server.\n\treflection.Register(srv)\n\tif err := srv.Serve(lis); err != nil {\n\t\tlog.Fatalf(\"failed to serve: %v\", err)\n\t}\n}\n\n// server controls RPC service responses.\ntype server struct {\n\tpb.UnimplementedShippingServiceServer\n}\n\n// Check is for health checking.\nfunc (s *server) Check(ctx context.Context, req *healthpb.HealthCheckRequest) (*healthpb.HealthCheckResponse, error) {\n\treturn &healthpb.HealthCheckResponse{Status: healthpb.HealthCheckResponse_SERVING}, nil\n}\n\nfunc (s *server) Watch(req *healthpb.HealthCheckRequest, ws healthpb.Health_WatchServer) error {\n\treturn status.Errorf(codes.Unimplemented, \"health check via Watch not implemented\")\n}\n\n// GetQuote produces a shipping quote (cost) in USD.\nfunc (s *server) GetQuote(ctx context.Context, in *pb.GetQuoteRequest) (*pb.GetQuoteResponse, error) {\n\tlog.Info(\"[GetQuote] received request\")\n\tdefer log.Info(\"[GetQuote] completed request\")\n\n\t// 1. Generate a quote based on the total number of items to be shipped.\n\tcount := 0\n\tfor _, item := range in.Items {\n\t\tcount += int(item.Quantity)\n\t}\n\tquote := CreateQuoteFromCount(count)\n\n\t// 2. Generate a response.\n\treturn &pb.GetQuoteResponse{\n\t\tCostUsd: &pb.Money{\n\t\t\tCurrencyCode: \"USD\",\n\t\t\tUnits:        int64(quote.Dollars),\n\t\t\tNanos:        int32(quote.Cents * 10000000)},\n\t}, nil\n\n}\n\n// ShipOrder mocks that the requested items will be shipped.\n// It supplies a tracking ID for notional lookup of shipment delivery status.\nfunc (s *server) ShipOrder(ctx context.Context, in *pb.ShipOrderRequest) (*pb.ShipOrderResponse, error) {\n\tlog.Info(\"[ShipOrder] received request\")\n\tdefer log.Info(\"[ShipOrder] completed request\")\n\t// 1. Create a Tracking ID\n\tbaseAddress := fmt.Sprintf(\"%s, %s, %s\", in.Address.StreetAddress, in.Address.City, in.Address.State)\n\tid := CreateTrackingId(baseAddress)\n\n\t// 2. Generate a response.\n\treturn &pb.ShipOrderResponse{\n\t\tTrackingId: id,\n\t}, nil\n}\n\nfunc initStats() {\n\t//TODO(arbrown) Implement OpenTelemetry stats\n}\n\nfunc initTracing() {\n\t// TODO(arbrown) Implement OpenTelemetry tracing\n}\n\nfunc initProfiling(service, version string) {\n\t// TODO(ahmetb) this method is duplicated in other microservices using Go\n\t// since they are not sharing packages.\n\tfor i := 1; i <= 3; i++ {\n\t\tif err := profiler.Start(profiler.Config{\n\t\t\tService:        service,\n\t\t\tServiceVersion: version,\n\t\t\t// ProjectID must be set if not running on GCP.\n\t\t\t// ProjectID: \"my-project\",\n\t\t}); err != nil {\n\t\t\tlog.Warnf(\"failed to start profiler: %+v\", err)\n\t\t} else {\n\t\t\tlog.Info(\"started Stackdriver profiler\")\n\t\t\treturn\n\t\t}\n\t\td := time.Second * 10 * time.Duration(i)\n\t\tlog.Infof(\"sleeping %v to retry initializing Stackdriver profiler\", d)\n\t\ttime.Sleep(d)\n\t}\n\tlog.Warn(\"could not initialize Stackdriver profiler after retrying, giving up\")\n}\n"
  },
  {
    "path": "src/shippingservice/quote.go",
    "content": "// Copyright 2018 Google LLC\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage main\n\nimport (\n\t\"fmt\"\n\t\"math\"\n)\n\n// Quote represents a currency value.\ntype Quote struct {\n\tDollars uint32\n\tCents   uint32\n}\n\n// String representation of the Quote.\nfunc (q Quote) String() string {\n\treturn fmt.Sprintf(\"$%d.%d\", q.Dollars, q.Cents)\n}\n\n// CreateQuoteFromCount takes a number of items and returns a shipping quote.\nfunc CreateQuoteFromCount(count int) Quote {\n\tif count == 0 {\n\t\treturn CreateQuoteFromFloat(0)\n\t}\n\treturn CreateQuoteFromFloat(8.99)\n}\n\n// CreateQuoteFromFloat takes a price represented as a float and creates a Price struct.\nfunc CreateQuoteFromFloat(value float64) Quote {\n\tunits, fraction := math.Modf(value)\n\treturn Quote{\n\t\tuint32(units),\n\t\tuint32(math.Trunc(fraction * 100)),\n\t}\n}"
  },
  {
    "path": "src/shippingservice/shippingservice_test.go",
    "content": "// Copyright 2018 Google LLC\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage main\n\nimport (\n\t\"regexp\"\n\t\"testing\"\n\n\t\"golang.org/x/net/context\"\n\n\tpb \"github.com/GoogleCloudPlatform/microservices-demo/src/shippingservice/genproto\"\n)\n\n// TestGetQuote is a basic check on the GetQuote RPC service.\nfunc TestGetQuote(t *testing.T) {\n\ts := server{}\n\n\t// A basic test case to test logic and protobuf interactions.\n\treq := &pb.GetQuoteRequest{\n\t\tAddress: &pb.Address{\n\t\t\tStreetAddress: \"Muffin Man\",\n\t\t\tCity:          \"London\",\n\t\t\tState:         \"\",\n\t\t\tCountry:       \"England\",\n\t\t},\n\t\tItems: []*pb.CartItem{\n\t\t\t{\n\t\t\t\tProductId: \"23\",\n\t\t\t\tQuantity:  1,\n\t\t\t},\n\t\t\t{\n\t\t\t\tProductId: \"46\",\n\t\t\t\tQuantity:  3,\n\t\t\t},\n\t\t},\n\t}\n\n\tres, err := s.GetQuote(context.Background(), req)\n\tif err != nil {\n\t\tt.Errorf(\"TestGetQuote (%v) failed\", err)\n\t}\n\tif res.CostUsd.GetUnits() != 8 || res.CostUsd.GetNanos() != 990000000 {\n\t\tt.Errorf(\"TestGetQuote: Quote value '%d.%d' does not match expected '8.990000000'\", res.CostUsd.GetUnits(), res.CostUsd.GetNanos())\n\t}\n}\n\n// TestGetQuoteEmptyCart verifies that an empty cart returns a zero quote.\nfunc TestGetQuoteEmptyCart(t *testing.T) {\n\ts := server{}\n\n\treq := &pb.GetQuoteRequest{\n\t\tAddress: &pb.Address{\n\t\t\tStreetAddress: \"221B Baker Street\",\n\t\t\tCity:          \"London\",\n\t\t\tState:         \"\",\n\t\t\tCountry:       \"England\",\n\t\t},\n\t\tItems: []*pb.CartItem{},\n\t}\n\n\tres, err := s.GetQuote(context.Background(), req)\n\tif err != nil {\n\t\tt.Errorf(\"TestGetQuoteEmptyCart (%v) failed\", err)\n\t}\n\tif res.CostUsd.GetUnits() != 0 || res.CostUsd.GetNanos() != 0 {\n\t\tt.Errorf(\"TestGetQuoteEmptyCart: expected zero quote for empty cart, got '%d.%d'\", res.CostUsd.GetUnits(), res.CostUsd.GetNanos())\n\t}\n}\n\n// TestShipOrder is a basic check on the ShipOrder RPC service.\nfunc TestShipOrder(t *testing.T) {\n\ts := server{}\n\n\t// A basic test case to test logic and protobuf interactions.\n\treq := &pb.ShipOrderRequest{\n\t\tAddress: &pb.Address{\n\t\t\tStreetAddress: \"Muffin Man\",\n\t\t\tCity:          \"London\",\n\t\t\tState:         \"\",\n\t\t\tCountry:       \"England\",\n\t\t},\n\t\tItems: []*pb.CartItem{\n\t\t\t{\n\t\t\t\tProductId: \"23\",\n\t\t\t\tQuantity:  1,\n\t\t\t},\n\t\t\t{\n\t\t\t\tProductId: \"46\",\n\t\t\t\tQuantity:  3,\n\t\t\t},\n\t\t},\n\t}\n\n\tres, err := s.ShipOrder(context.Background(), req)\n\tif err != nil {\n\t\tt.Errorf(\"TestShipOrder (%v) failed\", err)\n\t}\n\tif len(res.TrackingId) != 18 {\n\t\tt.Errorf(\"TestShipOrder: Tracking ID is malformed - has %d characters, %d expected\", len(res.TrackingId), 18)\n\t}\n}\n\n// TestTrackingIdFormat verifies the tracking ID matches the expected pattern.\nfunc TestTrackingIdFormat(t *testing.T) {\n\tpattern := regexp.MustCompile(`^[A-Z]{2}-\\d+-\\d+$`)\n\n\tfor i := 0; i < 20; i++ {\n\t\tid := CreateTrackingId(\"test-salt-value\")\n\t\tif !pattern.MatchString(id) {\n\t\t\tt.Errorf(\"CreateTrackingId: '%s' does not match expected pattern '[A-Z]{2}-\\\\d+-\\\\d+'\", id)\n\t\t}\n\t}\n}\n\n// TestTrackingIdUniqueness checks that generated IDs are not all identical.\nfunc TestTrackingIdUniqueness(t *testing.T) {\n\tseen := make(map[string]bool)\n\tfor i := 0; i < 50; i++ {\n\t\tid := CreateTrackingId(\"same-salt\")\n\t\tseen[id] = true\n\t}\n\tif len(seen) < 2 {\n\t\tt.Errorf(\"CreateTrackingId: expected unique IDs but got %d distinct values out of 50\", len(seen))\n\t}\n}\n\n// TestCreateQuoteFromFloat verifies quote creation from float values.\nfunc TestCreateQuoteFromFloat(t *testing.T) {\n\ttests := []struct {\n\t\tname    string\n\t\tvalue   float64\n\t\tdollars uint32\n\t\tcents   uint32\n\t}{\n\t\t{\"zero\", 0.0, 0, 0},\n\t\t{\"whole dollars\", 10.0, 10, 0},\n\t\t{\"with cents\", 8.99, 8, 99},\n\t\t{\"small value\", 0.50, 0, 50},\n\t\t{\"large value\", 100.01, 100, 1},\n\t}\n\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tq := CreateQuoteFromFloat(tc.value)\n\t\t\tif q.Dollars != tc.dollars || q.Cents != tc.cents {\n\t\t\t\tt.Errorf(\"CreateQuoteFromFloat(%v) = $%d.%d, want $%d.%d\",\n\t\t\t\t\ttc.value, q.Dollars, q.Cents, tc.dollars, tc.cents)\n\t\t\t}\n\t\t})\n\t}\n}\n\n// TestCreateQuoteFromCount verifies count-based quote generation.\nfunc TestCreateQuoteFromCount(t *testing.T) {\n\tzeroQuote := CreateQuoteFromCount(0)\n\tif zeroQuote.Dollars != 0 || zeroQuote.Cents != 0 {\n\t\tt.Errorf(\"CreateQuoteFromCount(0) = %s, want $0.0\", zeroQuote)\n\t}\n\n\tnonZeroQuote := CreateQuoteFromCount(5)\n\tif nonZeroQuote.Dollars == 0 && nonZeroQuote.Cents == 0 {\n\t\tt.Error(\"CreateQuoteFromCount(5) returned zero, expected a non-zero quote\")\n\t}\n}\n\n// TestGetRandomLetterCode verifies the output is a valid uppercase letter.\nfunc TestGetRandomLetterCode(t *testing.T) {\n\tfor i := 0; i < 100; i++ {\n\t\tcode := getRandomLetterCode()\n\t\tif code < 65 || code > 90 {\n\t\t\tt.Errorf(\"getRandomLetterCode: got %d (%c), expected range 65-90 (A-Z)\", code, code)\n\t\t}\n\t}\n}\n\n// TestGetRandomNumber verifies the output has the correct number of digits.\nfunc TestGetRandomNumber(t *testing.T) {\n\tfor _, digits := range []int{1, 3, 5, 7, 10} {\n\t\tresult := getRandomNumber(digits)\n\t\tif len(result) != digits {\n\t\t\tt.Errorf(\"getRandomNumber(%d) = '%s' (len %d), expected length %d\",\n\t\t\t\tdigits, result, len(result), digits)\n\t\t}\n\t}\n}\n\n// TestQuoteString verifies the string representation of a Quote.\nfunc TestQuoteString(t *testing.T) {\n\tq := Quote{Dollars: 8, Cents: 99}\n\texpected := \"$8.99\"\n\tif q.String() != expected {\n\t\tt.Errorf(\"Quote.String() = '%s', want '%s'\", q.String(), expected)\n\t}\n}\n"
  },
  {
    "path": "src/shippingservice/tracker.go",
    "content": "// Copyright 2018 Google LLC\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage main\n\nimport (\n\t\"fmt\"\n\t\"math/rand\"\n)\n\n// CreateTrackingId generates a tracking ID.\nfunc CreateTrackingId(salt string) string {\n\treturn fmt.Sprintf(\"%c%c-%d%s-%d%s\",\n\t\tgetRandomLetterCode(),\n\t\tgetRandomLetterCode(),\n\t\tlen(salt),\n\t\tgetRandomNumber(3),\n\t\tlen(salt)/2,\n\t\tgetRandomNumber(7),\n\t)\n}\n\n// getRandomLetterCode generates a code point value for a capital letter.\nfunc getRandomLetterCode() uint32 {\n\treturn 65 + uint32(rand.Intn(25))\n}\n\n// getRandomNumber generates a string representation of a number with the requested number of digits.\nfunc getRandomNumber(digits int) string {\n\tstr := \"\"\n\tfor i := 0; i < digits; i++ {\n\t\tstr = fmt.Sprintf(\"%s%d\", str, rand.Intn(10))\n\t}\n\n\treturn str\n}\n"
  },
  {
    "path": "src/shoppingassistantservice/Dockerfile",
    "content": "# Copyright 2024 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      https://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# Define a default value so it's not empty if the builder fails to provide it\nARG BUILDPLATFORM=linux/amd64\n\nFROM --platform=$BUILDPLATFORM python:3.14.3-slim@sha256:6a27522252aef8432841f224d9baaa6e9fce07b07584154fa0b9a96603af7456 AS base\n\nFROM base AS builder\n\nRUN apt-get -qq update \\\n    && apt-get install -y --no-install-recommends g++ \\\n    && rm -rf /var/lib/apt/lists/*\n\n# get packages\nCOPY requirements.txt .\nRUN pip install -r requirements.txt\n\nFROM base\n# Enable unbuffered logging\nENV PYTHONUNBUFFERED=1\n\n# get packages\nWORKDIR /shoppingassistantservice\n\n# Grab packages from builder\nCOPY --from=builder /usr/local/lib/python3.14/ /usr/local/lib/python3.14/\n\n# Add the application\nCOPY . .\n\n# set listen port\nENV PORT \"8080\"\nEXPOSE 8080\n\nENTRYPOINT [\"python\", \"shoppingassistantservice.py\"]\n"
  },
  {
    "path": "src/shoppingassistantservice/requirements.in",
    "content": "flask==3.1.3\nlangchain-google-genai==4.1.2\nlangchain==1.2.0\npillow==12.1.1\nlangchain-google-alloydb-pg==0.13.0\ngoogle-cloud-secret-manager==2.26.0\n"
  },
  {
    "path": "src/shoppingassistantservice/requirements.txt",
    "content": "# This file was autogenerated by uv via the following command:\n#    uv pip compile requirements.in -o requirements.txt\naiofiles==24.1.0\n    # via google-cloud-alloydb-connector\nannotated-types==0.7.0\n    # via pydantic\nanyio==4.10.0\n    # via\n    #   google-genai\n    #   httpx\nasyncpg==0.30.0\n    # via\n    #   google-cloud-alloydb-connector\n    #   langchain-postgres\nblinker==1.9.0\n    # via flask\ncachetools==5.5.2\n    # via google-auth\ncertifi==2025.8.3\n    # via\n    #   httpcore\n    #   httpx\n    #   requests\ncffi==2.0.0\n    # via cryptography\ncharset-normalizer==3.4.3\n    # via requests\nclick==8.3.0\n    # via flask\ncryptography==46.0.5\n    # via google-cloud-alloydb-connector\ndistro==1.9.0\n    # via google-genai\nfiletype==1.2.0\n    # via langchain-google-genai\nflask==3.1.3\n    # via -r requirements.in\ngoogle-api-core[grpc]==2.25.1\n    # via\n    #   google-cloud-alloydb\n    #   google-cloud-alloydb-connector\n    #   google-cloud-core\n    #   google-cloud-secret-manager\n    #   google-cloud-storage\ngoogle-auth[requests]==2.45.0\n    # via\n    #   google-api-core\n    #   google-cloud-alloydb\n    #   google-cloud-alloydb-connector\n    #   google-cloud-core\n    #   google-cloud-secret-manager\n    #   google-cloud-storage\n    #   google-genai\ngoogle-cloud-alloydb==0.4.9\n    # via google-cloud-alloydb-connector\ngoogle-cloud-alloydb-connector[asyncpg]==1.9.1\n    # via langchain-google-alloydb-pg\ngoogle-cloud-core==2.4.3\n    # via google-cloud-storage\ngoogle-cloud-secret-manager==2.26.0\n    # via -r requirements.in\ngoogle-cloud-storage==3.4.0\n    # via langchain-google-alloydb-pg\ngoogle-crc32c==1.7.1\n    # via\n    #   google-cloud-storage\n    #   google-resumable-media\ngoogle-genai==1.56.0\n    # via langchain-google-genai\ngoogle-resumable-media==2.7.2\n    # via google-cloud-storage\ngoogleapis-common-protos[grpc]==1.70.0\n    # via\n    #   google-api-core\n    #   grpc-google-iam-v1\n    #   grpcio-status\ngreenlet==3.3.0\n    # via sqlalchemy\ngrpc-google-iam-v1==0.14.2\n    # via\n    #   google-cloud-alloydb\n    #   google-cloud-secret-manager\ngrpcio==1.76.0\n    # via\n    #   google-api-core\n    #   google-cloud-secret-manager\n    #   googleapis-common-protos\n    #   grpc-google-iam-v1\n    #   grpcio-status\ngrpcio-status==1.75.0\n    # via google-api-core\nh11==0.16.0\n    # via httpcore\nhttpcore==1.0.9\n    # via httpx\nhttpx==0.28.1\n    # via\n    #   google-genai\n    #   langgraph-sdk\n    #   langsmith\nidna==3.10\n    # via\n    #   anyio\n    #   httpx\n    #   requests\nitsdangerous==2.2.0\n    # via flask\njinja2==3.1.6\n    # via flask\njsonpatch==1.33\n    # via langchain-core\njsonpointer==3.0.0\n    # via jsonpatch\nlangchain==1.2.0\n    # via -r requirements.in\nlangchain-core==1.2.11\n    # via\n    #   langchain\n    #   langchain-google-genai\n    #   langchain-postgres\n    #   langgraph\n    #   langgraph-checkpoint\n    #   langgraph-prebuilt\nlangchain-google-alloydb-pg==0.13.0\n    # via -r requirements.in\nlangchain-google-genai==4.1.2\n    # via -r requirements.in\nlangchain-postgres==0.0.16\n    # via langchain-google-alloydb-pg\nlanggraph==1.0.10rc1\n    # via langchain\nlanggraph-checkpoint==3.0.1\n    # via\n    #   langgraph\n    #   langgraph-prebuilt\nlanggraph-prebuilt==1.0.8\n    # via langgraph\nlanggraph-sdk==0.3.1\n    # via langgraph\nlangsmith==0.6.3\n    # via langchain-core\nmarkupsafe==3.0.2\n    # via\n    #   flask\n    #   jinja2\n    #   werkzeug\nnumpy==2.3.3\n    # via\n    #   langchain-google-alloydb-pg\n    #   langchain-postgres\n    #   pgvector\norjson==3.11.6\n    # via\n    #   langgraph-sdk\n    #   langsmith\normsgpack==1.12.1\n    # via langgraph-checkpoint\npackaging==25.0\n    # via\n    #   langchain-core\n    #   langsmith\npgvector==0.3.6\n    # via langchain-postgres\npillow==12.1.1\n    # via -r requirements.in\nproto-plus==1.26.1\n    # via\n    #   google-api-core\n    #   google-cloud-alloydb\n    #   google-cloud-secret-manager\nprotobuf==6.33.5\n    # via\n    #   google-api-core\n    #   google-cloud-alloydb\n    #   google-cloud-alloydb-connector\n    #   google-cloud-secret-manager\n    #   googleapis-common-protos\n    #   grpc-google-iam-v1\n    #   grpcio-status\n    #   proto-plus\npsycopg[binary]==3.2.10\n    # via langchain-postgres\npsycopg-binary==3.2.10\n    # via psycopg\npsycopg-pool==3.2.6\n    # via langchain-postgres\npyasn1==0.6.3\n    # via\n    #   pyasn1-modules\n    #   rsa\npyasn1-modules==0.4.2\n    # via google-auth\npycparser==2.23\n    # via cffi\npydantic==2.12.5\n    # via\n    #   google-genai\n    #   langchain\n    #   langchain-core\n    #   langchain-google-genai\n    #   langgraph\n    #   langsmith\npydantic-core==2.41.5\n    # via pydantic\npyyaml==6.0.2\n    # via langchain-core\nrequests==2.32.5\n    # via\n    #   google-api-core\n    #   google-auth\n    #   google-cloud-alloydb-connector\n    #   google-cloud-storage\n    #   google-genai\n    #   langsmith\n    #   requests-toolbelt\nrequests-toolbelt==1.0.0\n    # via langsmith\nrsa==4.9.1\n    # via google-auth\nsniffio==1.3.1\n    # via\n    #   anyio\n    #   google-genai\nsqlalchemy[asyncio]==2.0.43\n    # via langchain-postgres\ntenacity==9.1.2\n    # via\n    #   google-genai\n    #   langchain-core\ntyping-extensions==4.15.0\n    # via\n    #   google-genai\n    #   grpcio\n    #   langchain-core\n    #   psycopg-pool\n    #   pydantic\n    #   pydantic-core\n    #   sqlalchemy\n    #   typing-inspection\ntyping-inspection==0.4.2\n    # via pydantic\nurllib3==2.6.3\n    # via requests\nuuid-utils==0.12.0\n    # via\n    #   langchain-core\n    #   langsmith\nwebsockets==15.0.1\n    # via google-genai\nwerkzeug==3.1.6\n    # via flask\nxxhash==3.6.0\n    # via langgraph\nzstandard==0.25.0\n    # via langsmith\n"
  },
  {
    "path": "src/shoppingassistantservice/shoppingassistantservice.py",
    "content": "#!/usr/bin/python\n#\n# Copyright 2024 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      https://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport os\n\nfrom google.cloud import secretmanager_v1\nfrom urllib.parse import unquote\nfrom langchain_core.messages import HumanMessage\nfrom langchain_google_genai import ChatGoogleGenerativeAI, GoogleGenerativeAIEmbeddings\nfrom flask import Flask, request\n\nfrom langchain_google_alloydb_pg import AlloyDBEngine, AlloyDBVectorStore\n\nPROJECT_ID = os.environ[\"PROJECT_ID\"]\nREGION = os.environ[\"REGION\"]\nALLOYDB_DATABASE_NAME = os.environ[\"ALLOYDB_DATABASE_NAME\"]\nALLOYDB_TABLE_NAME = os.environ[\"ALLOYDB_TABLE_NAME\"]\nALLOYDB_CLUSTER_NAME = os.environ[\"ALLOYDB_CLUSTER_NAME\"]\nALLOYDB_INSTANCE_NAME = os.environ[\"ALLOYDB_INSTANCE_NAME\"]\nALLOYDB_SECRET_NAME = os.environ[\"ALLOYDB_SECRET_NAME\"]\n\nsecret_manager_client = secretmanager_v1.SecretManagerServiceClient()\nsecret_name = secret_manager_client.secret_version_path(project=PROJECT_ID, secret=ALLOYDB_SECRET_NAME, secret_version=\"latest\")\nsecret_request = secretmanager_v1.AccessSecretVersionRequest(name=secret_name)\nsecret_response = secret_manager_client.access_secret_version(request=secret_request)\nPGPASSWORD = secret_response.payload.data.decode(\"UTF-8\").strip()\n\nengine = AlloyDBEngine.from_instance(\n    project_id=PROJECT_ID,\n    region=REGION,\n    cluster=ALLOYDB_CLUSTER_NAME,\n    instance=ALLOYDB_INSTANCE_NAME,\n    database=ALLOYDB_DATABASE_NAME,\n    user=\"postgres\",\n    password=PGPASSWORD\n)\n\n# Create a synchronous connection to our vectorstore\nvectorstore = AlloyDBVectorStore.create_sync(\n    engine=engine,\n    table_name=ALLOYDB_TABLE_NAME,\n    embedding_service=GoogleGenerativeAIEmbeddings(model=\"models/embedding-001\"),\n    id_column=\"id\",\n    content_column=\"description\",\n    embedding_column=\"product_embedding\",\n    metadata_columns=[\"id\", \"name\", \"categories\"]\n)\n\ndef create_app():\n    app = Flask(__name__)\n\n    @app.route(\"/\", methods=['POST'])\n    def talkToGemini():\n        print(\"Beginning RAG call\")\n        prompt = request.json['message']\n        prompt = unquote(prompt)\n\n        # Step 1 – Get a room description from Gemini-vision-pro\n        llm_vision = ChatGoogleGenerativeAI(model=\"gemini-1.5-flash\")\n        message = HumanMessage(\n            content=[\n                {\n                    \"type\": \"text\",\n                    \"text\": \"You are a professional interior designer, give me a detailed decsription of the style of the room in this image\",\n                },\n                {\"type\": \"image_url\", \"image_url\": request.json['image']},\n            ]\n        )\n        response = llm_vision.invoke([message])\n        print(\"Description step:\")\n        print(response)\n        description_response = response.content\n\n        # Step 2 – Similarity search with the description & user prompt\n        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} \"\"\"\n        print(vector_search_prompt)\n\n        docs = vectorstore.similarity_search(vector_search_prompt)\n        print(f\"Vector search: {description_response}\")\n        print(f\"Retrieved documents: {len(docs)}\")\n        #Prepare relevant documents for inclusion in final prompt\n        relevant_docs = \"\"\n        for doc in docs:\n            doc_details = doc.to_json()\n            print(f\"Adding relevant document to prompt context: {doc_details}\")\n            relevant_docs += str(doc_details) + \", \"\n\n        # Step 3 – Tie it all together by augmenting our call to Gemini-pro\n        llm = ChatGoogleGenerativeAI(model=\"gemini-1.5-flash\")\n        design_prompt = (\n            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\"\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: [<first product ID>], [<second product ID>], [<third product ID>] \")\n        print(\"Final design prompt: \")\n        print(design_prompt)\n        design_response = llm.invoke(\n            design_prompt\n        )\n\n        data = {'content': design_response.content}\n        return data\n\n    return app\n\nif __name__ == \"__main__\":\n    # Create an instance of flask server when called directly\n    app = create_app()\n    app.run(host='0.0.0.0', port=8080)\n"
  },
  {
    "path": "terraform/README.md",
    "content": "<!-- Copyright 2022 Google LLC\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\nhttp://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License. -->\n\n# Use Terraform to deploy Online Boutique on a GKE cluster\n\nThis 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.\n\n## Prerequisites\n\n1. [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.\n\n## Deploy the sample application\n\n1. Clone the Github repository.\n\n    ```bash\n    git clone https://github.com/GoogleCloudPlatform/microservices-demo.git\n    ```\n\n1. Move into the `terraform/` directory which contains the Terraform installation scripts.\n\n    ```bash\n    cd microservices-demo/terraform\n    ```\n\n1. Open the `terraform.tfvars` file and replace `<project_id_here>` 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.\n\n1. (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.\n\n1. Initialize Terraform.\n\n    ```bash\n    terraform init\n    ```\n\n1. See what resources will be created.\n\n    ```bash\n    terraform plan\n    ```\n\n1. Create the resources and deploy the sample.\n\n    ```bash\n    terraform apply\n    ```\n\n    1. If there is a confirmation prompt, type `yes` and hit Enter/Return.\n\n    Note: This step can take about 10 minutes. Do not interrupt the process.\n\nOnce the Terraform script has finished, you can locate the frontend's external IP address to access the sample application.\n\n- Option 1:\n\n    ```bash\n    kubectl get service frontend-external | awk '{print $4}'\n    ```\n\n- Option 2: On Google Cloud Console, navigate to \"Kubernetes Engine\" and then \"Services & Ingress\" to locate the Endpoint associated with \"frontend-external\".\n\n## Clean up\n\nTo 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.\n\nTo remove the individual resources created for by Terraform without deleting the project:\n\n1. Navigate to the `terraform/` directory.\n\n1. Set `deletion_protection` to `false` for the `google_container_cluster` resource (GKE cluster).\n\n   ```bash\n   # Uncomment the line: \"deletion_protection = false\"\n   sed -i \"s/# deletion_protection/deletion_protection/g\" main.tf\n\n   # Re-apply the Terraform to update the state\n   terraform apply\n   ```\n\n1. Run the following command:\n\n   ```bash\n   terraform destroy\n   ```\n\n   1. If there is a confirmation prompt, type `yes` and hit Enter/Return.\n"
  },
  {
    "path": "terraform/main.tf",
    "content": "# Copyright 2022 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# Definition of local variables\nlocals {\n  base_apis = [\n    \"container.googleapis.com\",\n    \"monitoring.googleapis.com\",\n    \"cloudtrace.googleapis.com\",\n    \"cloudprofiler.googleapis.com\"\n  ]\n  memorystore_apis = [\"redis.googleapis.com\"]\n  cluster_name     = google_container_cluster.my_cluster.name\n}\n\n# Enable Google Cloud APIs\nmodule \"enable_google_apis\" {\n  source  = \"terraform-google-modules/project-factory/google//modules/project_services\"\n  version = \"~> 18.0\"\n\n  project_id                  = var.gcp_project_id\n  disable_services_on_destroy = false\n\n  # activate_apis is the set of base_apis and the APIs required by user-configured deployment options\n  activate_apis = concat(local.base_apis, var.memorystore ? local.memorystore_apis : [])\n}\n\n# Create GKE cluster\nresource \"google_container_cluster\" \"my_cluster\" {\n\n  name     = var.name\n  location = var.region\n\n  # Enable autopilot for this cluster\n  enable_autopilot = true\n\n  # Set an empty ip_allocation_policy to allow autopilot cluster to spin up correctly\n  ip_allocation_policy {\n  }\n\n  # Avoid setting deletion_protection to false\n  # until you're ready (and certain you want) to destroy the cluster.\n  # deletion_protection = false\n\n  depends_on = [\n    module.enable_google_apis\n  ]\n}\n\n# Get credentials for cluster\nmodule \"gcloud\" {\n  source  = \"terraform-google-modules/gcloud/google\"\n  version = \"~> 4.0\"\n\n  platform              = \"linux\"\n  additional_components = [\"kubectl\", \"beta\"]\n\n  create_cmd_entrypoint = \"gcloud\"\n  # Module does not support explicit dependency\n  # Enforce implicit dependency through use of local variable\n  create_cmd_body = \"container clusters get-credentials ${local.cluster_name} --zone=${var.region} --project=${var.gcp_project_id}\"\n}\n\n# Apply YAML kubernetes-manifest configurations\nresource \"null_resource\" \"apply_deployment\" {\n  provisioner \"local-exec\" {\n    interpreter = [\"bash\", \"-exc\"]\n    command     = \"kubectl apply -k ${var.filepath_manifest} -n ${var.namespace}\"\n  }\n\n  depends_on = [\n    module.gcloud\n  ]\n}\n\n# Wait condition for all Pods to be ready before finishing\nresource \"null_resource\" \"wait_conditions\" {\n  provisioner \"local-exec\" {\n    interpreter = [\"bash\", \"-exc\"]\n    command     = <<-EOT\n    kubectl wait --for=condition=AVAILABLE apiservice/v1beta1.metrics.k8s.io --timeout=180s\n    kubectl wait --for=condition=ready pods --all -n ${var.namespace} --timeout=280s\n    EOT\n  }\n\n  depends_on = [\n    resource.null_resource.apply_deployment\n  ]\n}\n"
  },
  {
    "path": "terraform/memorystore.tf",
    "content": "# Copyright 2022 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# Create the Memorystore (redis) instance\nresource \"google_redis_instance\" \"redis-cart\" {\n  name           = \"redis-cart\"\n  memory_size_gb = 1\n  region         = var.region\n\n  # count specifies the number of instances to create;\n  # if var.memorystore is true then the resource is enabled\n  count          = var.memorystore ? 1 : 0\n\n  redis_version  = \"REDIS_7_0\"\n  project        = var.gcp_project_id\n\n  depends_on = [\n    module.enable_google_apis\n  ]\n}\n\n# Edit contents of Memorystore kustomization.yaml file to target new Memorystore (redis) instance\nresource \"null_resource\" \"kustomization-update\" {\n  provisioner \"local-exec\" {\n    interpreter = [\"bash\", \"-exc\"]\n    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\"\n  }\n\n  # count specifies the number of instances to create;\n  # if var.memorystore is true then the resource is enabled\n  count          = var.memorystore ? 1 : 0\n\n  depends_on = [\n    resource.google_redis_instance.redis-cart\n  ]\n}\n"
  },
  {
    "path": "terraform/output.tf",
    "content": "# Copyright 2022 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\noutput \"cluster_location\" {\n  description = \"Location of the cluster\"\n  value       = resource.google_container_cluster.my_cluster.location\n}\n\noutput \"cluster_name\" {\n  description = \"Name of the cluster\"\n  value       = resource.google_container_cluster.my_cluster.name\n}\n"
  },
  {
    "path": "terraform/providers.tf",
    "content": "# Copyright 2022 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nterraform {\n  required_providers {\n    google = {\n      source  = \"hashicorp/google\"\n      version = \"7.16.0\"\n    }\n  }\n}\n\nprovider \"google\" {\n  project = var.gcp_project_id\n  region  = var.region\n}\n"
  },
  {
    "path": "terraform/variables.tf",
    "content": "# Copyright 2022 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nvariable \"gcp_project_id\" {\n  type        = string\n  description = \"The GCP project ID to apply this config to\"\n}\n\nvariable \"name\" {\n  type        = string\n  description = \"Name given to the new GKE cluster\"\n  default     = \"online-boutique\"\n}\n\nvariable \"region\" {\n  type        = string\n  description = \"Region of the new GKE cluster\"\n  default     = \"us-central1\"\n}\n\nvariable \"namespace\" {\n  type        = string\n  description = \"Kubernetes Namespace in which the Online Boutique resources are to be deployed\"\n  default     = \"default\"\n}\n\nvariable \"filepath_manifest\" {\n  type        = string\n  description = \"Path to Online Boutique's Kubernetes resources, written using Kustomize\"\n  default     = \"../kustomize/\"\n}\n\nvariable \"memorystore\" {\n  type        = bool\n  description = \"If true, Online Boutique's in-cluster Redis cache will be replaced with a Google Cloud Memorystore Redis cache\"\n}\n"
  }
]