commit dafcd9777f5f913cb678b1a8cb4226c987d7b9c0 Author: Subodh Date: Wed Feb 4 20:47:56 2026 +0530 Initial commit diff --git a/.deploystack/deploystack.yaml b/.deploystack/deploystack.yaml new file mode 100644 index 0000000..669bc11 --- /dev/null +++ b/.deploystack/deploystack.yaml @@ -0,0 +1,15 @@ +# The fields inside this deploystack.yaml file are documented in https://github.com/GoogleCloudPlatform/deploystack. + +title: Microservices Demo (Online Boutique) +name: microservices-demo +duration: 5 +collect_project: true +collect_region: true +region_type: compute +region_default: us-central1 +hard_settings: + filepath_manifest: ../kustomize/ + memorystore: "false" + name: online-boutique + namespace: default +documentation_link: https://cloud.google.com/shell/docs/cloud-shell-tutorials/deploystack/microservices-demo diff --git a/.deploystack/messages/description.txt b/.deploystack/messages/description.txt new file mode 100644 index 0000000..ec1b6a2 --- /dev/null +++ b/.deploystack/messages/description.txt @@ -0,0 +1,4 @@ +Online Boutique is a cloud-first microservices demo application. Online Boutique +consists of an 11-tier microservices application. The application is a web-based +e-commerce app where users can browse items, add them to the cart, and purchase +them. \ No newline at end of file diff --git a/.deploystack/messages/success.txt b/.deploystack/messages/success.txt new file mode 100644 index 0000000..82f48d9 --- /dev/null +++ b/.deploystack/messages/success.txt @@ -0,0 +1,4 @@ +Congrats! +You have successfully provisioned a GKE (Google Kubernetes Engine) cluster and +deployed Online Boutique's 11 microservices, which includes a load generator. + diff --git a/.deploystack/scripts/preinit.sh b/.deploystack/scripts/preinit.sh new file mode 100755 index 0000000..78d3f49 --- /dev/null +++ b/.deploystack/scripts/preinit.sh @@ -0,0 +1,16 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +ROOT=$(pwd) +sed -i.tmp "s/project_id/gcp_project_id/" $ROOT/terraform/terraform.tfvars diff --git a/.deploystack/test b/.deploystack/test new file mode 100755 index 0000000..dab5c98 --- /dev/null +++ b/.deploystack/test @@ -0,0 +1,207 @@ +#! /bin/bash +# Copyright 2021 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# DEPLOYSTACK - this file is a test script that is used by DeployStack's +# testing rig to make sure that the Terraform script installs and uninstalls +# cleanly + +# DON'T REMOVE FROM test script. + +CYAN='\033[0;36m' +BCYAN='\033[1;36m' +NC='\033[0m' # No Color +DIVIDER=$(printf %"$(tput cols)"s | tr " " "*") +DIVIDER+="\n" + +function get_project_id() { + local __resultvar=$1 + VALUE=$(gcloud config get-value project | xargs) + eval $__resultvar="'$VALUE'" +} + +function get_project_number() { + local __resultvar=$1 + local PRO=$2 + VALUE=$(gcloud projects list --filter="project_id=$PRO" --format="value(PROJECT_NUMBER)" | xargs) + eval $__resultvar="'$VALUE'" +} + +# DISPLAY HELPERS + +function section_open() { + section_description=$1 + printf "$DIVIDER" + printf "${CYAN}$section_description${NC} \n" + printf "$DIVIDER" +} + +function section_close() { + printf "$DIVIDER" + printf "${CYAN}$section_description ${BCYAN}- done${NC}\n" + printf "\n\n" +} + +function evalTest() { + local command=$1 + local expected=$2 + + local ERR="" + got=$(eval $command 2>errFile) + ERR=$( /dev/null" "deployment.apps/adservice" + evalTest "kubectl get deployment cartservice --no-headers -o=name 2> /dev/null" "deployment.apps/cartservice" + evalTest "kubectl get deployment checkoutservice --no-headers -o=name 2> /dev/null" "deployment.apps/checkoutservice" + evalTest "kubectl get deployment currencyservice --no-headers -o=name 2> /dev/null" "deployment.apps/currencyservice" + evalTest "kubectl get deployment emailservice --no-headers -o=name 2> /dev/null" "deployment.apps/emailservice" + evalTest "kubectl get deployment loadgenerator --no-headers -o=name 2> /dev/null" "deployment.apps/loadgenerator" + evalTest "kubectl get deployment paymentservice --no-headers -o=name 2> /dev/null" "deployment.apps/paymentservice" + evalTest "kubectl get deployment productcatalogservice --no-headers -o=name 2> /dev/null" "deployment.apps/productcatalogservice" + evalTest "kubectl get deployment recommendationservice --no-headers -o=name 2> /dev/null" "deployment.apps/recommendationservice" + evalTest "kubectl get deployment redis-cart --no-headers -o=name 2> /dev/null" "deployment.apps/redis-cart" + evalTest "kubectl get deployment shippingservice --no-headers -o=name 2> /dev/null" "deployment.apps/shippingservice" +section_close + +sleep 120 + +ENDPOINT=$( kubectl get service frontend-external --no-headers 2> /dev/null | awk '{print $4}') + +section_open "Testing Online Boutique's front-end is working" + evalTest 'curl -s -o /dev/null -w "%{http_code}" $ENDPOINT' "200" +section_close + +# Uncomment the line: "deletion_protection = false" +sed -i "s/# deletion_protection/deletion_protection/g" ${DIR}/main.tf +terraform -chdir="$DIR" apply -auto-approve \ + -var gcp_project_id="${PROJECT}" \ + -var name="${NAME}" \ + -var region="${REGION}" \ + -var namespace="${NAMESPACE}" \ + -var filepath_manifest="${FILEPATH_MANIFEST}" \ + -var memorystore="${MEMORYSTORE}" + +terraform -chdir="$DIR" destroy -auto-approve \ + -var gcp_project_id="${PROJECT}" \ + -var name="${NAME}" \ + -var region="${REGION}" \ + -var namespace="${NAMESPACE}" \ + -var filepath_manifest="${FILEPATH_MANIFEST}" \ + -var memorystore="${MEMORYSTORE}" + +section_open "Testing Google Kubernetes Engine cluster does NOT exist" + evalTest 'gcloud container clusters describe online-boutique --format="value(name)" --region $REGION' "EXPECTERROR" +section_close + +# This is only needed if you tests fail alot because of overlapping runs of the +# same set of tests. Really don't do this if you don't want to severely irritate +# @tpryan +section_open "Delete Test Project" + gcloud projects delete $PROJECT -q +section_close + +printf "$DIVIDER" +printf "CONGRATS!!!!!!! \n" +printf "You got the end the of your test with everything working. \n" +printf "$DIVIDER" diff --git a/.deploystack/test.yaml b/.deploystack/test.yaml new file mode 100644 index 0000000..c9d6a34 --- /dev/null +++ b/.deploystack/test.yaml @@ -0,0 +1,35 @@ +# Copyright 2021 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# DEPLOYSTACK - this file is the cloudbuild for running testing automatically +# in the testing rig + +steps: + - name: 'bash' + id: "creds" + args: ['-c','echo $$CREDS > .deploystack/creds.json'] + secretEnv: ['CREDS'] + - name: 'gcr.io/cloudshell-images/cloudshell:latest' + entrypoint: bash + args: [ '.deploystack/test' ] + secretEnv: ['BA'] +timeout: 4200s +options: + machineType: 'E2_HIGHCPU_8' +availableSecrets: + secretManager: + - versionName: projects/$PROJECT_ID/secrets/creds/versions/latest + env: 'CREDS' + - versionName: projects/$PROJECT_ID/secrets/billing_account/versions/latest + env: 'BA' \ No newline at end of file diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..c6f0194 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,26 @@ +# The .editorconfig is used to maintain consistent code style. +# The .editorconfig file is supported by most text editors. +# See https://editorconfig.org + +root = true + +[*] +insert_final_newline = true +trim_trailing_whitespace = true +indent_style = space +indent_size = 2 + +[*.cs] +indent_size = 4 + +[Dockerfile*] +indent_size = 4 + +[*.go] +indent_style = tab + +[*.java] +indent_size = 4 + +[*.py] +indent_size = 4 diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..000c1c4 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,4 @@ +# This file configures the git settings for this repository. + +# Converts "CR + LF" to "LF", for all "text" files — for local files on all OSes and files pushed to the remote repo. +* text=auto eol=lf diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..b663703 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,6 @@ +# See https://help.github.com/en/articles/about-code-owners +# for more info about CODEOWNERS file. + +# These owners will be the default owners for everything in +# the repo. Unless a later match takes precedence. +* @GoogleCloudPlatform/devrel-flagship-app-maintainers @yoshi-approver diff --git a/.github/CODE_OF_CONDUCT.md b/.github/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..46b2a08 --- /dev/null +++ b/.github/CODE_OF_CONDUCT.md @@ -0,0 +1,43 @@ +# Contributor Code of Conduct + +As contributors and maintainers of this project, +and in the interest of fostering an open and welcoming community, +we pledge to respect all people who contribute through reporting issues, +posting feature requests, updating documentation, +submitting pull requests or patches, and other activities. + +We are committed to making participation in this project +a harassment-free experience for everyone, +regardless of level of experience, gender, gender identity and expression, +sexual orientation, disability, personal appearance, +body size, race, ethnicity, age, religion, or nationality. + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery +* Personal attacks +* Trolling or insulting/derogatory comments +* Public or private harassment +* Publishing other's private information, +such as physical or electronic +addresses, without explicit permission +* Other unethical or unprofessional conduct. + +Project maintainers have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct. +By adopting this Code of Conduct, +project maintainers commit themselves to fairly and consistently +applying these principles to every aspect of managing this project. +Project maintainers who do not follow or enforce the Code of Conduct +may be permanently removed from the project team. + +This code of conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. + +Instances of abusive, harassing, or otherwise unacceptable behavior +may be reported by opening an issue +or contacting one or more of the project maintainers. + +This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.2.0, +available at [http://contributor-covenant.org/version/1/2/0/](http://contributor-covenant.org/version/1/2/0/) diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md new file mode 100644 index 0000000..2c9a77a --- /dev/null +++ b/.github/CONTRIBUTING.md @@ -0,0 +1,34 @@ +# How to Contribute + +Thank you so much for your interest in contributing to Online Boutique. +Before contributing, you must: +* Sign the [Contributor License Agreement (CLA)](#contributor-license-agreement). +* Follow the [Google Open Source Community Guidelines](https://opensource.google.com/conduct/). +* Follow the [Contribution Process](#contribution-process). + +## Contributor License Agreement + +Contributions to Online Boutique must be accompanied by a Contributor License +Agreement (CLA). You (or your employer) retain the copyright to your contribution. +The CLA gives us permission to use and redistribute your contributions as +part of the project. Head over to to see +your current agreements on file or to sign a new one. + +You generally only need to submit a CLA once, so if you've already submitted one +(even if it was for a different project), you probably don't need to do it +again. + +## Contribution Process + +Here's the process for making a change to this repository: + +1. Review Online Boutique's [purpose](/docs/purpose.md) and [product requirements](/docs/product-requirements.md). +1. If your proposed changes **do not align** with the purpose and product requirements of Online Boutique, you may be asked to instead maintain your own fork of this repository. +1. For **small changes** (such as a bug fixes or spelling corrections): + 1. Fork this repository and submit a [pull request](https://help.github.com/articles/about-pull-requests/). + 1. Wait for a maintainer of this repository to review your change. +1. For **bigger changes**: + 1. Create a [GitHub issue](https://github.com/GoogleCloudPlatform/microservices-demo/issues/new/choose) describing the change **before** working on the implementation. This is important to avoid potentially having to discard your development efforts. + 1. Wait for a maintainer of this repository to review your GitHub issue. For significantly complex proposals, you may be asked to start a Google Doc to discuss design decisions. + +If you have any questions, please [create a GitHub issue](https://github.com/GoogleCloudPlatform/microservices-demo/issues/new/choose). diff --git a/.github/ISSUE_TEMPLATE/bug-report.md b/.github/ISSUE_TEMPLATE/bug-report.md new file mode 100644 index 0000000..c501698 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug-report.md @@ -0,0 +1,34 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: '' +assignees: '' + +--- + +### Describe the bug + + +### To Reproduce + + + + + +### Logs + + +### Screenshots + + +### Environment + + + + +### Additional context + + +### Exposure + diff --git a/.github/ISSUE_TEMPLATE/feature-request.md b/.github/ISSUE_TEMPLATE/feature-request.md new file mode 100644 index 0000000..e5cd00b --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature-request.md @@ -0,0 +1,14 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: '' +assignees: '' + +--- + +### Describe request or inquiry + + +### What purpose/environment will this feature serve? + diff --git a/.github/ISSUE_TEMPLATE/other.md b/.github/ISSUE_TEMPLATE/other.md new file mode 100644 index 0000000..9637cfa --- /dev/null +++ b/.github/ISSUE_TEMPLATE/other.md @@ -0,0 +1,10 @@ +--- +name: Other +about: Have a question or need clarification? +title: '' +labels: '' +assignees: '' + +--- +### Write down your inquiry + diff --git a/.github/SECURITY.md b/.github/SECURITY.md new file mode 100644 index 0000000..8b58ae9 --- /dev/null +++ b/.github/SECURITY.md @@ -0,0 +1,7 @@ +# Security Policy + +To report a security issue, please use [g.co/vulnz](https://g.co/vulnz). + +The Google Security Team will respond within 5 working days of your report on g.co/vulnz. + +We use g.co/vulnz for our intake, and do coordination and disclosure here using GitHub Security Advisory to privately discuss and fix the issue. diff --git a/.github/auto-approve.yml b/.github/auto-approve.yml new file mode 100644 index 0000000..e5d98c4 --- /dev/null +++ b/.github/auto-approve.yml @@ -0,0 +1,23 @@ +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# https://github.com/googleapis/repo-automation-bots/tree/main/packages/auto-approve +processes: + - "PythonDependency" + - "PythonSampleAppDependency" + - "JavaDependency" + - "JavaSampleAppDependency" + - "GoDependency" + - "NodeDependency" + - "DockerDependency" diff --git a/.github/header-checker-lint.yml b/.github/header-checker-lint.yml new file mode 100644 index 0000000..39733ac --- /dev/null +++ b/.github/header-checker-lint.yml @@ -0,0 +1,47 @@ +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This file configures a GitHub Bot called "License Header Lint GCF": https://github.com/apps/license-header-lint-gcf +# The bot runs a GitHub check called "header-check" (inside pull-requests) that warns us about invalid/missing license headers. +# The schema for this configutation file is documented at https://github.com/googleapis/repo-automation-bots/tree/main/packages/header-checker-lint#header-checker-lint. + +allowedCopyrightHolders: + - 'Google LLC' + +allowedLicenses: + - 'Apache-2.0' + +# If you want to ignore certain files/folders, use ignoreFiles. +# ignoreFiles: +# - '**/requirements.txt' + +# If you want to ignore checking the license year, use ignoreLicenseYear. +# ignoreLicenseYear: true # Useful when migrating in code licensed at previous years. + +sourceFileExtensions: + - 'cs' + - 'css' + - 'Dockerfile' + - 'dockerignore' + - 'gitignore' + - 'go' + - 'html' + - 'java' + - 'js' + - 'proto' + - 'py' + - 'sh' + - 'tf' + - 'yaml' + - 'yml' diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..80ae26f --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,16 @@ +### Background + + +### Fixes + +### Change Summary + + +### Additional Notes + + +### Testing Procedure + + +### Related PRs or Issues + diff --git a/.github/release-cluster/README.md b/.github/release-cluster/README.md new file mode 100644 index 0000000..23cd149 --- /dev/null +++ b/.github/release-cluster/README.md @@ -0,0 +1,75 @@ +# cymbal-shops.retail.cymbal.dev manifests + +This directory contains extra deploy manifests for configuring Online Boutique solution on GKE for cymbal-shops.retail.cymbal.dev. + +_Note: before moving forward, the Online Boutique apps should already be deployed [on the online-boutique-release GKE cluster](/docs/releasing#10-deploy-releasekubernetes-manifestsyaml-to-our-online-boutique-release-gke-cluster)._ + +## Public static IP address + +Create the static public IP address: +``` +STATIC_IP_NAME=online-boutique-ip # name hard-coded in: frontend-ingress.yaml +gcloud compute addresses create $STATIC_IP_NAME --global +``` + +When ready to do so, you could grab this public IP address and update your DNS: +``` +gcloud compute addresses describe $STATIC_IP_NAME \ + --global \ + --format "value(address)" +``` + +## Cloud Armor + +Set up Cloud Armor: +``` +SECURITY_POLICY_NAME=online-boutique-security-policy # Name hard-coded in: backendconfig.yaml +gcloud compute security-policies create $SECURITY_POLICY_NAME \ + --description "Block various attacks" +gcloud compute security-policies rules create 1000 \ + --security-policy $SECURITY_POLICY_NAME \ + --expression "evaluatePreconfiguredExpr('xss-stable')" \ + --action "deny-403" \ + --description "XSS attack filtering" +gcloud compute security-policies rules create 12345 \ + --security-policy $SECURITY_POLICY_NAME \ + --expression "evaluatePreconfiguredExpr('cve-canary')" \ + --action "deny-403" \ + --description "CVE-2021-44228 and CVE-2021-45046" +gcloud compute security-policies update $SECURITY_POLICY_NAME \ + --enable-layer7-ddos-defense +gcloud compute security-policies update $SECURITY_POLICY_NAME \ + --log-level=VERBOSE +``` + +## SSL Policy + +Set up an SSL policy in order to later set up a redirect from HTTP to HTTPs: +``` +SSL_POLICY_NAME=online-boutique-ssl-policy # Name hard-coded in: frontendconfig.yaml +gcloud compute ssl-policies create $SSL_POLICY_NAME \ + --profile COMPATIBLE \ + --min-tls-version 1.0 +``` + +## Deploy Kubernetes manifests + +Deploy the Kubernetes manifests in this current folder: +``` +kubectl apply -f . +``` + +Wait for the `ManagedCertificate` to be provisioned. This usually takes about 30 minutes. +``` +kubectl get managedcertificates +``` + +Remove the default `LoadBalancer` `Service` not used at this point: +``` +kubectl delete service frontend-external +``` + +Remove the `loadgenerator` `Deployment` not used at this point: +``` +kubectl delete deployment loadgenerator +``` \ No newline at end of file diff --git a/.github/release-cluster/backend-config.yaml b/.github/release-cluster/backend-config.yaml new file mode 100644 index 0000000..8e5d976 --- /dev/null +++ b/.github/release-cluster/backend-config.yaml @@ -0,0 +1,21 @@ +# Copyright 2024 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: cloud.google.com/v1 +kind: BackendConfig +metadata: + name: frontend-backend-config +spec: + securityPolicy: + name: online-boutique-security-policy \ No newline at end of file diff --git a/.github/release-cluster/frontend-config.yaml b/.github/release-cluster/frontend-config.yaml new file mode 100644 index 0000000..6cd700c --- /dev/null +++ b/.github/release-cluster/frontend-config.yaml @@ -0,0 +1,23 @@ +# Copyright 2024 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: networking.gke.io/v1beta1 +kind: FrontendConfig +metadata: + name: frontend-frontend-config +spec: + sslPolicy: online-boutique-ssl-policy + redirectToHttps: + enabled: true + responseCodeName: MOVED_PERMANENTLY_DEFAULT \ No newline at end of file diff --git a/.github/release-cluster/frontend-ingress.yaml b/.github/release-cluster/frontend-ingress.yaml new file mode 100644 index 0000000..42029ac --- /dev/null +++ b/.github/release-cluster/frontend-ingress.yaml @@ -0,0 +1,38 @@ +# Copyright 2024 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: frontend-ingress + annotations: + kubernetes.io/ingress.global-static-ip-name: online-boutique-ip + networking.gke.io/managed-certificates: online-boutique-certificate + networking.gke.io/v1beta1.FrontendConfig: frontend-frontend-config +spec: + defaultBackend: + service: + name: frontend + port: + number: 80 + rules: + - http: + paths: + - path: /* + pathType: ImplementationSpecific + backend: + service: + name: frontend + port: + number: 80 diff --git a/.github/release-cluster/frontend-service.yaml b/.github/release-cluster/frontend-service.yaml new file mode 100644 index 0000000..e4a304a --- /dev/null +++ b/.github/release-cluster/frontend-service.yaml @@ -0,0 +1,29 @@ +# Copyright 2024 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: v1 +kind: Service +metadata: + name: frontend + annotations: + cloud.google.com/neg: '{"ingress": true}' + cloud.google.com/backend-config: '{"default": "frontend-backend-config"}' +spec: + type: ClusterIP + selector: + app: frontend + ports: + - name: http + port: 80 + targetPort: 8080 \ No newline at end of file diff --git a/.github/release-cluster/managed-cert.yaml b/.github/release-cluster/managed-cert.yaml new file mode 100644 index 0000000..afcd4c4 --- /dev/null +++ b/.github/release-cluster/managed-cert.yaml @@ -0,0 +1,21 @@ +# Copyright 2024 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: networking.gke.io/v1 +kind: ManagedCertificate +metadata: + name: online-boutique-certificate +spec: + domains: + - cymbal-shops.retail.cymbal.dev diff --git a/.github/renovate.json5 b/.github/renovate.json5 new file mode 100644 index 0000000..a46ebf9 --- /dev/null +++ b/.github/renovate.json5 @@ -0,0 +1,27 @@ +{ + extends: [ + 'github>GoogleCloudPlatform/kubernetes-engine-samples//.github/renovate-configs/dee-platform-ops.json5', + 'schedule:earlyMondays', + ], + 'pip-compile': { + enabled: true, + managerFilePatterns: [ + '/(^|/)requirements\\.txt$/', + ], + }, + pip_requirements: { + enabled: false, + }, + constraints: { + python: '~=3.11.0', + }, + kubernetes: { + managerFilePatterns: [ + '/\\.yaml$/', + ], + ignorePaths: [ + 'release/**', + 'kustomize/base/**', + ], + }, +} diff --git a/.github/snippet-bot.yml b/.github/snippet-bot.yml new file mode 100644 index 0000000..26d99f1 --- /dev/null +++ b/.github/snippet-bot.yml @@ -0,0 +1,14 @@ + +# Copyright 2021 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. diff --git a/.github/terraform/README.md b/.github/terraform/README.md new file mode 100644 index 0000000..cf4e525 --- /dev/null +++ b/.github/terraform/README.md @@ -0,0 +1,15 @@ +This folder contains the Terraform for some of the infrastructure used by the CICD (continuous integration and continuous delivery/continuous deployment) of this repository. + +## Update this Terraform + +To make changes to this Terraform, follow these steps: + +1. Make sure you have access to the `online-boutique-ci` Google Cloud project. +1. Move into this folder: `cd .github/terraform` +1. Set the PROJECT_ID environment variable: `export PROJECT_ID=online-boutique-ci` +1. Prepare Terraform and download the necessary Terraform dependencies (such as the "hashicorp/google" Terraform provider): `terraform init` +1. Apply the Terraform: `terraform apply -var project_id=${PROJECT_ID}` + * Ideally, you would see `Apply complete! Resources: 0 added, 0 changed, 0 destroyed.` in the output. +1. Make your desired changes to the Terraform code. +1. Apply the Terraform: `terraform apply -var project_id=${PROJECT_ID}` + * This time, Terraform will prompt you confirm your changes before applying them. diff --git a/.github/terraform/main.tf b/.github/terraform/main.tf new file mode 100644 index 0000000..15dd92c --- /dev/null +++ b/.github/terraform/main.tf @@ -0,0 +1,116 @@ +/** + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +# Set defaults for the google Terraform provider. +provider "google" { + project = var.project_id + region = "us-central1" + zone = "us-central1-a" +} + +terraform { + # Store the state inside a Google Cloud Storage bucket. + backend "gcs" { + bucket = "cicd-terraform-state" + prefix = "terraform-state" + } +} + +# Enable Google Cloud APIs. +module "enable_google_apis" { + source = "terraform-google-modules/project-factory/google//modules/project_services" + version = "~> 18.0" + disable_services_on_destroy = false + activate_apis = [ + "cloudresourcemanager.googleapis.com", + "container.googleapis.com", + "iam.googleapis.com", + "storage.googleapis.com", + ] + project_id = var.project_id +} + +# Google Cloud Storage for storing Terraform state (.tfstate). +resource "google_storage_bucket" "terraform_state_storage_bucket" { + name = "cicd-terraform-state" + location = "us" + storage_class = "STANDARD" + force_destroy = false + public_access_prevention = "enforced" + uniform_bucket_level_access = true + versioning { + enabled = true + } +} + +# Google Cloud IAM service account for GKE clusters. +# We avoid using the Compute Engine default service account because it's too permissive. +resource "google_service_account" "gke_clusters_service_account" { + account_id = "gke-clusters-service-account" + display_name = "My Service Account" + depends_on = [ + module.enable_google_apis + ] +} + +# See https://cloud.google.com/kubernetes-engine/docs/how-to/hardening-your-cluster#use_least_privilege_sa +resource "google_project_iam_member" "gke_clusters_service_account_role_metric_writer" { + project = var.project_id + role = "roles/monitoring.metricWriter" + member = "serviceAccount:${google_service_account.gke_clusters_service_account.email}" +} + +# See https://cloud.google.com/kubernetes-engine/docs/how-to/hardening-your-cluster#use_least_privilege_sa +resource "google_project_iam_member" "gke_clusters_service_account_role_logging_writer" { + project = var.project_id + role = "roles/logging.logWriter" + member = "serviceAccount:${google_service_account.gke_clusters_service_account.email}" +} + +# See https://cloud.google.com/kubernetes-engine/docs/how-to/hardening-your-cluster#use_least_privilege_sa +resource "google_project_iam_member" "gke_clusters_service_account_role_monitoring_viewer" { + project = var.project_id + role = "roles/monitoring.viewer" + member = "serviceAccount:${google_service_account.gke_clusters_service_account.email}" +} + +# See https://cloud.google.com/kubernetes-engine/docs/how-to/hardening-your-cluster#use_least_privilege_sa +resource "google_project_iam_member" "gke_clusters_service_account_role_stackdriver_writer" { + project = var.project_id + role = "roles/stackdriver.resourceMetadata.writer" + member = "serviceAccount:${google_service_account.gke_clusters_service_account.email}" +} + +# The GKE cluster used for pull-request (PR) staging deployments. +resource "google_container_cluster" "prs_gke_cluster" { + name = "prs-gke-cluster" + location = "us-central1" + enable_autopilot = true + project = var.project_id + deletion_protection = true + depends_on = [ + module.enable_google_apis + ] + cluster_autoscaling { + auto_provisioning_defaults { + service_account = google_service_account.gke_clusters_service_account.email + } + } + # Need an empty ip_allocation_policy to overcome an error related to autopilot node pool constraints. + # Workaround from https://github.com/hashicorp/terraform-provider-google/issues/10782#issuecomment-1024488630 + ip_allocation_policy { + } +} diff --git a/.github/terraform/variables.tf b/.github/terraform/variables.tf new file mode 100644 index 0000000..e103a7b --- /dev/null +++ b/.github/terraform/variables.tf @@ -0,0 +1,23 @@ +/** + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +# This file lists variables that you can set using the -var flag during "terraform apply". +# Example: terraform apply -var project_id="${PROJECT_ID}" + +variable "project_id" { + type = string + description = "The Google Cloud project ID." +} diff --git a/.github/terraform/versions.tf b/.github/terraform/versions.tf new file mode 100644 index 0000000..396c371 --- /dev/null +++ b/.github/terraform/versions.tf @@ -0,0 +1,25 @@ +/** + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +terraform { + required_version = ">= 0.13" + required_providers { + google = { + source = "hashicorp/google" + version = "~> 7.0" + } + } +} diff --git a/.github/workflows/README.md b/.github/workflows/README.md new file mode 100644 index 0000000..a4ebb93 --- /dev/null +++ b/.github/workflows/README.md @@ -0,0 +1,65 @@ +# GitHub Actions Workflows + +This page describes the CI/CD workflows for the Online Boutique app, which run in [Github Actions](https://github.com/GoogleCloudPlatform/microservices-demo/actions). + +## Infrastructure + +The CI/CD pipelines for Online Boutique run in Github Actions, using a pool of two [self-hosted runners]((https://help.github.com/en/actions/automating-your-workflow-with-github-actions/about-self-hosted-runners)). These runners are GCE instances (virtual machines) that, for every open Pull Request in the repo, run the code test pipeline, deploy test pipeline, and (on main) deploy the latest version of the app to [cymbal-shops.retail.cymbal.dev](https://cymbal-shops.retail.cymbal.dev) + +We also host a test GKE cluster, which is where the deploy tests run. Every PR has its own namespace in the cluster. + +## Workflows + +**Note**: In order for the current CI/CD setup to work on your pull request, you must branch directly off the repo (no forks). This is because the Github secrets necessary for these tests aren't copied over when you fork. + +### Code Tests - [ci-pr.yaml](ci-pr.yaml) + +These tests run on every commit for every open PR, as well as any commit to main / any release branch. Currently, this workflow runs only Go unit tests. + + +### Deploy Tests- [ci-pr.yaml](ci-pr.yaml) + +These tests run on every commit for every open PR, as well as any commit to main / any release branch. This workflow: + +1. Creates a dedicated GKE namespace for that PR, if it doesn't already exist, in the PR GKE cluster. +2. Uses `skaffold run` to build and push the images specific to that PR commit. Then skaffold deploys those images, via `kubernetes-manifests`, to the PR namespace in the test cluster. +3. Tests to make sure all the pods start up and become ready. +4. Gets the LoadBalancer IP for the frontend service. +5. Comments that IP in the pull request, for staging. + +### Push and Deploy Latest - [push-deploy](push-deploy.yml) + +This is the Continuous Deployment workflow, and it runs on every commit to the main branch. This workflow: + +1. Builds the container images for every service, tagging as `latest`. +2. Pushes those images to Google Container Registry. + +Note that this workflow does not update the image tags used in `release/kubernetes-manifests.yaml` - these release manifests are tied to a stable `v0.x.x` release. + +### Cleanup - [cleanup.yaml](cleanup.yaml) + +This workflow runs when a PR closes, regardless of whether it was merged into main. This workflow deletes the PR-specific GKE namespace in the test cluster. + +## Appendix - Creating a new Actions runner + +Should one of the two self-hosted Github Actions runners (GCE instances) fail, or you want to add more runner capacity, this is how to provision a new runner. Note that you need IAM access to the admin Online Boutique GCP project in order to do this. + +1. Create a GCE instance. + - VM should be at least n1-standard-4 with 50GB persistent disk + - VM should use custom service account with permissions to: access a GKE cluster, create GCS storage buckets, and push to GCR. +2. SSH into new VM through the Google Cloud Console. +3. Install project-specific dependencies, including go, docker, skaffold, and kubectl: + +``` +wget -O - https://raw.githubusercontent.com/GoogleCloudPlatform/microservices-demo/main/.github/workflows/install-dependencies.sh | bash +``` + +The instance will restart when the script completes in order to finish the Docker install. + +4. SSH back into the VM. + +5. Follow the instructions to add a new runner on the [Actions Settings page](https://github.com/GoogleCloudPlatform/microservices-demo/settings/actions) to authenticate the new runner +6. Start GitHub Actions as a background service: +``` +sudo ~/actions-runner/svc.sh install ; sudo ~/actions-runner/svc.sh start +``` diff --git a/.github/workflows/ci-main.yaml b/.github/workflows/ci-main.yaml new file mode 100644 index 0000000..bad6375 --- /dev/null +++ b/.github/workflows/ci-main.yaml @@ -0,0 +1,122 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +name: "Continuous Integration - Main/Release" +on: + push: + # run on pushes to main or release/* + branches: + - main + - release/* + paths-ignore: + - '**/README.md' + - 'kustomize/**' + - '.github/workflows/kustomize-build-ci.yaml' + - 'terraform/**' + - '.github/workflows/terraform-validate-ci.yaml' + - 'helm-chart/**' + - '.github/workflows/helm-chart-ci.yaml' +jobs: + code-tests: + runs-on: [self-hosted, is-enabled] + steps: + - uses: actions/checkout@v6 + - uses: actions/setup-dotnet@v5 + env: + DOTNET_INSTALL_DIR: "./.dotnet" + with: + dotnet-version: '10.0' + - uses: actions/setup-go@v6 + with: + go-version: '1.25' + - name: Go Unit Tests + timeout-minutes: 10 + run: | + for SERVICE in "shippingservice" "productcatalogservice"; do + echo "testing $SERVICE..." + pushd src/$SERVICE + go test + popd + done + - name: C# Unit Tests + timeout-minutes: 10 + run: | + dotnet test src/cartservice/ + deployment-tests: + runs-on: [self-hosted, is-enabled] + needs: code-tests + strategy: + matrix: + profile: ["local-code"] + fail-fast: true + steps: + - uses: actions/checkout@v6 + - name: Build + Deploy PR images to GKE + timeout-minutes: 20 + run: | + PR_NUMBER=$(echo $GITHUB_REF | awk 'BEGIN { FS = "/" } ; { print $3 }') + NAMESPACE="pr${PR_NUMBER}" + echo "::set-env name=NAMESPACE::$NAMESPACE" + echo "::set-env name=PR_NUMBER::$PR_NUMBER" + + yes | gcloud auth configure-docker us-docker.pkg.dev + gcloud container clusters get-credentials $PR_CLUSTER --region $REGION --project $PROJECT_ID + cat < helm-template.yaml + cat helm-template.yaml + kustomize create --resources helm-template.yaml + kustomize build . + - name: helm template grpc health probes + run: | + # Test related to https://medium.com/google-cloud/b5bd26253a4c + cd helm-chart/ + SPANNER_CONNECTION_STRING=projects/PROJECT_ID/instances/SPANNER_INSTANCE_NAME/databases/SPANNER_DATABASE_NAME + helm template . \ + --set nativeGrpcHealthCheck=true \ + -n onlineboutique \ + > helm-template.yaml + cat helm-template.yaml + kustomize build . + - name: helm template spanner + run: | + # Test related to https://medium.com/google-cloud/f7248e077339 + cd helm-chart/ + SPANNER_CONNECTION_STRING=projects/PROJECT_ID/instances/SPANNER_INSTANCE_NAME/databases/SPANNER_DATABASE_NAME + SPANNER_DB_USER_GSA_ID=spanner-db-user@my-project.iam.gserviceaccount.com + helm template . \ + --set cartDatabase.inClusterRedis.create=false \ + --set cartDatabase.type=spanner \ + --set cartDatabase.connectionString=${SPANNER_CONNECTION_STRING} \ + --set serviceAccounts.create=true \ + --set serviceAccounts.annotationsOnlyForCartservice=true \ + --set "serviceAccounts.annotations.iam\.gke\.io/gcp-service-account=${SPANNER_DB_USER_GSA_ID}" \ + -n onlineboutique \ + > helm-template.yaml + cat helm-template.yaml + kustomize build . + - name: helm template asm + run: | + # Test related to https://medium.com/google-cloud/246119e46d53 + cd helm-chart/ + helm template . \ + --set networkPolicies.create=true \ + --set sidecars.create=true \ + --set serviceAccounts.create=true \ + --set authorizationPolicies.create=true \ + --set frontend.externalService=false \ + --set frontend.virtualService.create=true \ + --set frontend.virtualService.gateway.name=asm-ingressgateway \ + --set frontend.virtualService.gateway.namespace=asm-ingress \ + --set frontend.virtualService.gateway.labelKey=asm \ + --set frontend.virtualService.gateway.labelValue=ingressgateway \ + -n onlineboutique \ + > helm-template.yaml + cat helm-template.yaml + kustomize build . + - name: helm template memorystore istio tls origination + run: | + # Test related to https://medium.com/google-cloud/64b71969318d + cd helm-chart/ + REDIS_IP=0.0.0.0 + REDIS_PORT=7378 + REDIS_CERT=dsjfgkldsjflkdsjflksdajfkldsjkfljsdaklfjaskjfakdsjfaklsdjflskadjfklasjfkls + helm template . \ + --set cartDatabase.inClusterRedis.create=false \ + --set cartDatabase.connectionString=${REDIS_IP}:${REDIS_PORT} \ + --set cartDatabase.externalRedisTlsOrigination.enable=true \ + --set cartDatabase.externalRedisTlsOrigination.certificate="${REDIS_CERT}" \ + --set cartDatabase.externalRedisTlsOrigination.endpointAddress=${REDIS_IP} \ + --set cartDatabase.externalRedisTlsOrigination.endpointPort=${REDIS_PORT} \ + -n onlineboutique \ + > helm-template.yaml + cat helm-template.yaml + kustomize build . diff --git a/.github/workflows/install-dependencies.sh b/.github/workflows/install-dependencies.sh new file mode 100755 index 0000000..b82a505 --- /dev/null +++ b/.github/workflows/install-dependencies.sh @@ -0,0 +1,74 @@ +#!/bin/bash +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set -euo pipefail + +# install wget +sudo apt install -y wget + +# install dotnet CLI +sudo apt-get update +sudo apt-get install wget +wget -O - https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor > microsoft.asc.gpg +sudo mv microsoft.asc.gpg /etc/apt/trusted.gpg.d/ +wget https://packages.microsoft.com/config/debian/9/prod.list +sudo mv prod.list /etc/apt/sources.list.d/microsoft-prod.list +sudo chown root:root /etc/apt/trusted.gpg.d/microsoft.asc.gpg +sudo chown root:root /etc/apt/sources.list.d/microsoft-prod.list + +sudo apt-get install -y apt-transport-https && \ +sudo apt-get update && \ +sudo apt-get install -y dotnet-sdk-10.0 +echo "✅ dotnet installed" + +# install kubectl +sudo apt-get install -yqq kubectl git +echo "✅ kubectl installed" + +# install go +wget https://golang.org/dl/go1.25.linux-amd64.tar.gz +sudo tar -C /usr/local -xzf go1.25.linux-amd64.tar.gz +echo 'export GOPATH=$HOME/go' >> ~/.profile +echo 'export PATH=$PATH:/usr/local/go/bin:$GOPATH/bin' >> ~/.profile +source ~/.profile +echo "✅ golang installed" + +# install build-essential (gcc, used for go test) +sudo apt install -y build-essential + +# install addlicense +go install github.com/google/addlicense@latest +sudo ln -s $HOME/go/bin/addlicense /bin + +# install build-essential (gcc, used for go test) +sudo apt install -y build-essential + +# install skaffold +curl -Lo skaffold https://storage.googleapis.com/skaffold/releases/latest/skaffold-linux-amd64 && \ +chmod +x skaffold && \ +sudo mv skaffold /usr/local/bin +echo "✅ skaffold installed" + +# install docker +sudo apt install -yqq apt-transport-https ca-certificates curl gnupg2 software-properties-common && \ +curl -fsSL https://download.docker.com/linux/debian/gpg | sudo apt-key add - && \ +sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/debian $(lsb_release -cs) stable" && \ +sudo apt-get update && \ +sudo apt-get install -yqq docker-ce && \ +sudo usermod -aG docker ${USER} +echo "✅ docker installed, rebooting..." + +# reboot for docker setup +sudo reboot diff --git a/.github/workflows/kubevious-manifests-ci.yaml b/.github/workflows/kubevious-manifests-ci.yaml new file mode 100644 index 0000000..f2e0aaa --- /dev/null +++ b/.github/workflows/kubevious-manifests-ci.yaml @@ -0,0 +1,56 @@ +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +name: kubevious-manifests-ci +on: + push: + branches: + - main + paths: + - 'helm-chart/**' + - 'kustomize/**' + - '.github/workflows/kubevious-manifests-ci.yaml' + pull_request: + paths: + - 'helm-chart/**' + - 'kustomize/**' + - '.github/workflows/kubevious-manifests-ci.yaml' +permissions: + contents: read +jobs: + kubevious-manifests-ci: + runs-on: ubuntu-24.04 + timeout-minutes: 1 + steps: + - uses: actions/checkout@v6 + + - name: Validate kubernetes-manifests + id: kubernetes-manifests-validation + uses: kubevious/cli@v1.0.64 + with: + manifests: kubernetes-manifests + skip_rules: container-latest-image + + - name: Validate helm-chart + id: helm-chart-validation + uses: kubevious/cli@v1.0.64 + with: + manifests: helm-chart + + - name: Validate kustomize + id: kustomize-validation + uses: kubevious/cli@v1.0.64 + with: + manifests: kustomize + skip_rules: container-latest-image diff --git a/.github/workflows/kustomize-build-ci.yaml b/.github/workflows/kustomize-build-ci.yaml new file mode 100644 index 0000000..8edef63 --- /dev/null +++ b/.github/workflows/kustomize-build-ci.yaml @@ -0,0 +1,45 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +name: kustomize-build-ci +on: + push: + branches: + - main + paths: + - 'kustomize/**' + - '.github/workflows/kustomize-build-ci.yaml' + pull_request: + paths: + - 'kustomize/**' + - '.github/workflows/kustomize-build-ci.yaml' +jobs: + kustomize-build-ci: + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v6 + - name: kustomize build base + run: | + cd kustomize/ + kubectl kustomize . + # Build the different combinations of Kustomize components found in kustomize/tests. + - name: kustomize build tests + run: | + cd kustomize/tests + KUSTOMIZE_TESTS_SUBFOLDERS=$(ls -d */) + for test in $KUSTOMIZE_TESTS_SUBFOLDERS; + do + echo "## kustomize build for " + $test + kustomize build $test + done diff --git a/.github/workflows/terraform-validate-ci.yaml b/.github/workflows/terraform-validate-ci.yaml new file mode 100644 index 0000000..835469b --- /dev/null +++ b/.github/workflows/terraform-validate-ci.yaml @@ -0,0 +1,37 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +name: terraform-validate-ci +on: + push: + branches: + - main + paths: + - 'terraform/**' + - '.github/workflows/terraform-validate-ci.yaml' + pull_request: + paths: + - 'terraform/**' + - '.github/workflows/terraform-validate-ci.yaml' +jobs: + terraform-validate-ci: + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v6 + - uses: hashicorp/setup-terraform@v3 + - name: terraform init & validate + run: | + cd terraform/ + terraform init -backend=false + terraform validate diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..09b19e9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,28 @@ +.DS_Store +.eclipse.buildship.core.prefs +.gradle/ +.idea/ +.kubernetes-manifests-*/ +.project +.skaffold-*.yaml +.terraform.lock.hcl +.terraform/* +.venv/ +.vs/ +.vscode/ +*.iml +*.ipr +*.iws +*.pyc +*.swp +*.tfstate* +*.tfvars +*~ +bin/ +build/ +Dockerfile.pip +node_modules/ +obj/ +pkg/ +release/wi-kubernetes-manifests.yaml +vendor/ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/Online-Boutique b/Online-Boutique new file mode 160000 index 0000000..45a4f0e --- /dev/null +++ b/Online-Boutique @@ -0,0 +1 @@ +Subproject commit 45a4f0ea5e00c91c833b9902125eeae95ca76c53 diff --git a/README.md b/README.md new file mode 100644 index 0000000..7ffd5f3 --- /dev/null +++ b/README.md @@ -0,0 +1,169 @@ + +![Continuous Integration](https://github.com/GoogleCloudPlatform/microservices-demo/workflows/Continuous%20Integration%20-%20Main/Release/badge.svg) + +**Online Boutique** is a cloud-first microservices demo application. The application is a +web-based e-commerce app where users can browse items, add them to the cart, and purchase them. + +Google uses this application to demonstrate how developers can modernize enterprise applications using Google Cloud products, including: [Google Kubernetes Engine (GKE)](https://cloud.google.com/kubernetes-engine), [Cloud Service Mesh (CSM)](https://cloud.google.com/service-mesh), [gRPC](https://grpc.io/), [Cloud Operations](https://cloud.google.com/products/operations), [Spanner](https://cloud.google.com/spanner), [Memorystore](https://cloud.google.com/memorystore), [AlloyDB](https://cloud.google.com/alloydb), and [Gemini](https://ai.google.dev/). This application works on any Kubernetes cluster. + +If you’re using this demo, please **★Star** this repository to show your interest! + +**Note to Googlers:** Please fill out the form at [go/microservices-demo](http://go/microservices-demo). + +## Architecture + +**Online Boutique** is composed of 11 microservices written in different +languages that talk to each other over gRPC. + +[![Architecture of +microservices](/docs/img/architecture-diagram.png)](/docs/img/architecture-diagram.png) + +Find **Protocol Buffers Descriptions** at the [`./protos` directory](/protos). + +| Service | Language | Description | +| ---------------------------------------------------- | ------------- | --------------------------------------------------------------------------------------------------------------------------------- | +| [frontend](/src/frontend) | Go | Exposes an HTTP server to serve the website. Does not require signup/login and generates session IDs for all users automatically. | +| [cartservice](/src/cartservice) | C# | Stores the items in the user's shopping cart in Redis and retrieves it. | +| [productcatalogservice](/src/productcatalogservice) | Go | Provides the list of products from a JSON file and ability to search products and get individual products. | +| [currencyservice](/src/currencyservice) | Node.js | Converts one money amount to another currency. Uses real values fetched from European Central Bank. It's the highest QPS service. | +| [paymentservice](/src/paymentservice) | Node.js | Charges the given credit card info (mock) with the given amount and returns a transaction ID. | +| [shippingservice](/src/shippingservice) | Go | Gives shipping cost estimates based on the shopping cart. Ships items to the given address (mock) | +| [emailservice](/src/emailservice) | Python | Sends users an order confirmation email (mock). | +| [checkoutservice](/src/checkoutservice) | Go | Retrieves user cart, prepares order and orchestrates the payment, shipping and the email notification. | +| [recommendationservice](/src/recommendationservice) | Python | Recommends other products based on what's given in the cart. | +| [adservice](/src/adservice) | Java | Provides text ads based on given context words. | +| [loadgenerator](/src/loadgenerator) | Python/Locust | Continuously sends requests imitating realistic user shopping flows to the frontend. | + +## Screenshots + +| Home Page | Checkout Screen | +| ----------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ | +| [![Screenshot of store homepage](/docs/img/online-boutique-frontend-1.png)](/docs/img/online-boutique-frontend-1.png) | [![Screenshot of checkout screen](/docs/img/online-boutique-frontend-2.png)](/docs/img/online-boutique-frontend-2.png) | + +## Quickstart (GKE) + +1. Ensure you have the following requirements: + - [Google Cloud project](https://cloud.google.com/resource-manager/docs/creating-managing-projects#creating_a_project). + - Shell environment with `gcloud`, `git`, and `kubectl`. + +2. Clone the latest major version. + + ```sh + git clone --depth 1 --branch v0 https://github.com/GoogleCloudPlatform/microservices-demo.git + cd microservices-demo/ + ``` + + The `--depth 1` argument skips downloading git history. + +3. Set the Google Cloud project and region and ensure the Google Kubernetes Engine API is enabled. + + ```sh + export PROJECT_ID= + export REGION=us-central1 + gcloud services enable container.googleapis.com \ + --project=${PROJECT_ID} + ``` + + Substitute `` with the ID of your Google Cloud project. + +4. Create a GKE cluster and get the credentials for it. + + ```sh + gcloud container clusters create-auto online-boutique \ + --project=${PROJECT_ID} --region=${REGION} + ``` + + Creating the cluster may take a few minutes. + +5. Deploy Online Boutique to the cluster. + + ```sh + kubectl apply -f ./release/kubernetes-manifests.yaml + ``` + +6. Wait for the pods to be ready. + + ```sh + kubectl get pods + ``` + + After a few minutes, you should see the Pods in a `Running` state: + + ``` + NAME READY STATUS RESTARTS AGE + adservice-76bdd69666-ckc5j 1/1 Running 0 2m58s + cartservice-66d497c6b7-dp5jr 1/1 Running 0 2m59s + checkoutservice-666c784bd6-4jd22 1/1 Running 0 3m1s + currencyservice-5d5d496984-4jmd7 1/1 Running 0 2m59s + emailservice-667457d9d6-75jcq 1/1 Running 0 3m2s + frontend-6b8d69b9fb-wjqdg 1/1 Running 0 3m1s + loadgenerator-665b5cd444-gwqdq 1/1 Running 0 3m + paymentservice-68596d6dd6-bf6bv 1/1 Running 0 3m + productcatalogservice-557d474574-888kr 1/1 Running 0 3m + recommendationservice-69c56b74d4-7z8r5 1/1 Running 0 3m1s + redis-cart-5f59546cdd-5jnqf 1/1 Running 0 2m58s + shippingservice-6ccc89f8fd-v686r 1/1 Running 0 2m58s + ``` + +7. Access the web frontend in a browser using the frontend's external IP. + + ```sh + kubectl get service frontend-external | awk '{print $4}' + ``` + + Visit `http://EXTERNAL_IP` in a web browser to access your instance of Online Boutique. + +8. Congrats! You've deployed the default Online Boutique. To deploy a different variation of Online Boutique (e.g., with Google Cloud Operations tracing, Istio, etc.), see [Deploy Online Boutique variations with Kustomize](#deploy-online-boutique-variations-with-kustomize). + +9. Once you are done with it, delete the GKE cluster. + + ```sh + gcloud container clusters delete online-boutique \ + --project=${PROJECT_ID} --region=${REGION} + ``` + + Deleting the cluster may take a few minutes. + +## Additional deployment options + +- **Terraform**: [See these instructions](/terraform) to learn how to deploy Online Boutique using [Terraform](https://www.terraform.io/intro). +- **Istio / Cloud Service Mesh**: [See these instructions](/kustomize/components/service-mesh-istio/README.md) to deploy Online Boutique alongside an Istio-backed service mesh. +- **Non-GKE clusters (Minikube, Kind, etc)**: See the [Development guide](/docs/development-guide.md) to learn how you can deploy Online Boutique on non-GKE clusters. +- **AI assistant using Gemini**: [See these instructions](/kustomize/components/shopping-assistant/README.md) to deploy a Gemini-powered AI assistant that suggests products to purchase based on an image. +- **And more**: The [`/kustomize` directory](/kustomize) contains instructions for customizing the deployment of Online Boutique with other variations. + +## Documentation + +- [Development](/docs/development-guide.md) to learn how to run and develop this app locally. + +## Demos featuring Online Boutique + +- [Security hardening of the OnlineBoutique sample apps with the Docker Hardened Images (DHI)](https://medium.com/google-cloud/security-hardening-of-the-onlineboutique-sample-apps-with-docker-hardened-images-dhi-ca1fad348343) +- [alpine, distroless or scratch?](https://medium.com/google-cloud/alpine-distroless-or-scratch-caac35250e0b) +- [Platform Engineering in action: Deploy the Online Boutique sample apps with Score and Humanitec](https://medium.com/p/d99101001e69) +- [The new Kubernetes Gateway API with Istio and Anthos Service Mesh (ASM)](https://medium.com/p/9d64c7009cd) +- [Use Azure Redis Cache with the Online Boutique sample on AKS](https://medium.com/p/981bd98b53f8) +- [Sail Sharp, 8 tips to optimize and secure your .NET containers for Kubernetes](https://medium.com/p/c68ba253844a) +- [Deploy multi-region application with Anthos and Google cloud Spanner](https://medium.com/google-cloud/a2ea3493ed0) +- [Use Google Cloud Memorystore (Redis) with the Online Boutique sample on GKE](https://medium.com/p/82f7879a900d) +- [Use Helm to simplify the deployment of Online Boutique, with a Service Mesh, GitOps, and more!](https://medium.com/p/246119e46d53) +- [How to reduce microservices complexity with Apigee and Anthos Service Mesh](https://cloud.google.com/blog/products/application-modernization/api-management-and-service-mesh-go-together) +- [gRPC health probes with Kubernetes 1.24+](https://medium.com/p/b5bd26253a4c) +- [Use Google Cloud Spanner with the Online Boutique sample](https://medium.com/p/f7248e077339) +- [Seamlessly encrypt traffic from any apps in your Mesh to Memorystore (redis)](https://medium.com/google-cloud/64b71969318d) +- [Strengthen your app's security with Cloud Service Mesh and Anthos Config Management](https://cloud.google.com/service-mesh/docs/strengthen-app-security) +- [From edge to mesh: Exposing service mesh applications through GKE Ingress](https://cloud.google.com/architecture/exposing-service-mesh-apps-through-gke-ingress) +- [Take the first step toward SRE with Cloud Operations Sandbox](https://cloud.google.com/blog/products/operations/on-the-road-to-sre-with-cloud-operations-sandbox) +- [Deploying the Online Boutique sample application on Cloud Service Mesh](https://cloud.google.com/service-mesh/docs/onlineboutique-install-kpt) +- [Anthos Service Mesh Workshop: Lab Guide](https://codelabs.developers.google.com/codelabs/anthos-service-mesh-workshop) +- [KubeCon EU 2019 - Reinventing Networking: A Deep Dive into Istio's Multicluster Gateways - Steve Dake, Independent](https://youtu.be/-t2BfT59zJA?t=982) +- Google Cloud Next'18 SF + - [Day 1 Keynote](https://youtu.be/vJ9OaAqfxo4?t=2416) showing GKE On-Prem + - [Day 3 Keynote](https://youtu.be/JQPOPV_VH5w?t=815) showing Stackdriver + APM (Tracing, Code Search, Profiler, Google Cloud Build) + - [Introduction to Service Management with Istio](https://www.youtube.com/watch?v=wCJrdKdD6UM&feature=youtu.be&t=586) +- [Google Cloud Next'18 London – Keynote](https://youtu.be/nIq2pkNcfEI?t=3071) + showing Stackdriver Incident Response Management +- [Microservices demo showcasing Go Micro](https://github.com/go-micro/demo) diff --git a/cloudbuild.yaml b/cloudbuild.yaml new file mode 100644 index 0000000..fa45ecd --- /dev/null +++ b/cloudbuild.yaml @@ -0,0 +1,43 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# [START cloudbuild_microservice_demo_cloudbuild] + +# This configuration file is used to build and deploy the app into a +# GKE cluster using Google Cloud Build. +# +# PREREQUISITES: +# - Cloud Build service account must have role: "Kubernetes Engine Developer" + +# USAGE: +# GCP zone and GKE target cluster must be specified as substitutions +# Example invocation: +# `gcloud builds submit --config=cloudbuild.yaml --substitutions=_ZONE=us-central1-b,_CLUSTER=demo-app-staging .` + +steps: +- id: 'Deploy application to cluster' + name: 'gcr.io/k8s-skaffold/skaffold:v2.16.1' + entrypoint: 'bash' + args: + - '-c' + - > + gcloud container clusters get-credentials --zone=$_ZONE $_CLUSTER; + skaffold run -f=skaffold.yaml --default-repo=gcr.io/$PROJECT_ID; + +# Add more power, and more time, for heavy Skaffold build +timeout: '3600s' +options: + machineType: 'N1_HIGHCPU_8' + +# [END cloudbuild_microservice_demo_cloudbuild] \ No newline at end of file diff --git a/docs/adding-new-microservice.md b/docs/adding-new-microservice.md new file mode 100644 index 0000000..b527515 --- /dev/null +++ b/docs/adding-new-microservice.md @@ -0,0 +1,61 @@ +# Adding a new microservice + +This document outlines the steps required to add a new microservice to the Online Boutique application. + +## 1. Create a new directory + +Create a new directory for your microservice within the `src/` directory. The directory name should be the name of your microservice. + +## 2. Add source code + +Place your microservice's source code inside the newly created directory. The structure of this directory should follow the conventions of the existing microservices. For example, a Python-based service would include at minimum the following files: + +- `README.md`: The service's description and documentation. +- `main.py`: The application's entry point. +- `requirements.in`: A list of Python dependencies. +- `Dockerfile`: To containerize the application. + +Take a look at existing microservices for inspiration. + +## 3. Create a Dockerfile + +Create a `Dockerfile` in your microservice's directory. This file will define the steps to build a container image for your service. + +Refer to this example and tweak based on your new service's needs: https://github.com/GoogleCloudPlatform/microservices-demo/blob/main/src/frontend/Dockerfile + +## 4. Create Kubernetes manifests + +Create a new directory under `kustomize/components/` in the root of the repository for your microservice. Inside this directory, add the necessary Kubernetes YAML files for your new microservice. This typically includes: + +- A **Deployment** to manage your service's pods. +- A **Service** to expose your microservice to other services within the cluster. + +Ensure you follow the existing naming conventions and that the container image specified in the Deployment matches the one built by your `cloudbuild.yaml` and `skaffold.yaml` files. + +Refer to this example and tweak based on your new service's needs: https://github.com/GoogleCloudPlatform/microservices-demo/tree/main/kustomize/components/shopping-assistant + +## 5. Update the root `kustomization.yaml` file + +Add your newly created component to the root kustomization file so it gets picked up by the deployment cycle. + +The file is available here: https://github.com/GoogleCloudPlatform/microservices-demo/blob/main/kustomize/kustomization.yaml + +## 6. Update the root `skaffold.yaml` + +Add your newly created service to the root skaffold file so the images build correctly. + +The file is available here: https://github.com/GoogleCloudPlatform/microservices-demo/blob/main/skaffold.yaml + +## 7. Update the Helm chart + +Add your newly created service to the Helm chart templates and default values. + +The chart is available here: https://github.com/GoogleCloudPlatform/microservices-demo/tree/main/helm-chart + +## 8. Update the documentation + +Finally, update the project's documentation to reflect the addition of your new microservice. This may include: + +- Adding a section to the main `README.md` if the service introduces significant new functionality. +- Updating the architecture diagrams in the `docs/img` directory. +- Adding a new document in the `docs` directory if the service requires detailed explanation. diff --git a/docs/cloudshell-tutorial.md b/docs/cloudshell-tutorial.md new file mode 100644 index 0000000..7e4e3a6 --- /dev/null +++ b/docs/cloudshell-tutorial.md @@ -0,0 +1,106 @@ +# Online Boutique quickstart + +This tutorial shows you how to deploy **[Online Boutique](https://github.com/GoogleCloudPlatform/microservices-demo)** to a Kubernetes cluster. + +You'll be able to run Online Boutique on: +- a local **[minikube](https://minikube.sigs.k8s.io/docs/)** cluster, which comes built in to the Cloud Shell instance +- a **[Google Kubernetes Engine](https://cloud.google.com/kubernetes-engine)** cluster using a new or existing [Google Cloud project](https://cloud.google.com/resource-manager/docs/creating-managing-projects#creating_a_project) + +Let's get started! + + +## Kubernetes cluster setup + +Set up a Kubernetes cluster using the instructions below for either **minikube** or **GKE**. + +### Minikube instructions + +Minikube creates a local Kubernetes cluster on Cloud Shell. + +1. Click minikube on the status bar located at the bottom of the editor window. + +2. The command palette will prompt you to choose which minikube cluster to control. Select **minikube** and, in the next prompt, click **Start** if the cluster has not already been started. + +3. If prompted, authorize Cloud Shell to make a GCP API call with your credentials. + +*It may take a few minutes for minikube to finish starting.* + +Once minikube has started, you're ready to move on to the next step. + +### GKE instructions + +In order to create a GKE cluster, you'll need to **[create a Google Cloud project](https://cloud.google.com/resource-manager/docs/creating-managing-projects#creating_a_project)** or use an existing project. + +1. Access the command palette by going to **View > Find Command**. + +2. Run the command **"Cloud Code: Create GKE cluster"**. + +3. Select your GCP project. + +4. Apply the following configurations in the GKE wizard: +> - Zone: us-central1-b +> - Cluster name: onlineboutique +> - Node count: 4 +> - Machine type: e2-standard-2 + +5. Click **Create Cluster**. Once your cluster has been created successfully, you can move on to the next step. + + +## Run on Kubernetes + +Now you can run Online Boutique on your Kubernetes cluster! + +1. Launch the Cloud Code menu from the status bar and select Run on Kubernetes. + +2. If prompted to select a Skaffold Profile, select **[default]**. + +3. Select **Yes** to confirm your current context. + +4. If you're using a GKE cluster, you'll need to confirm your container image registry. + +5. If prompted, authorize Cloud Shell to make a GCP API call with your credentials. + +Cloud Code uses configurations defined in skaffold.yaml to build and deploy the app. *It may take a few minutes for the deploy to complete.* + +6. Once the app is running, the local URLs will be displayed in the Output terminal. + +7. To access your Online Boutique frontend service, click on the Web Preview button in the upper right of the editor window. + +8. Select **Change Port** and enter '4503' as the port, then click **Change and Preview**. Your app will open in a new window. + + +## Stop the app + +To stop running the app: + +1. Go to the Debug view + +2. Click the **Stop** icon. + +3. Select **Yes** to clean up deployed resources. + +You can start, stop, and debug apps from the Debug view. + +### Clean up + +If you've deployed your app to a GKE cluster in your Google Cloud project, you'll want to delete the cluster to avoid incurring charges. + +1. Navigate to the Cloud Code - Kubernetes view in the Activity bar. + +2. Under the Google Kubernetes Engine Explorer tab, right-click on your cluster and select **Delete Cluster**. + + +## Conclusion + + + +Congratulations! You've successfully deployed Online Boutique using Cloud Shell. + + + +##### What's next? + +Try other deployment options for Online Boutique: +- **Istio/Cloud Service Mesh**: See these instructions. + +Learn more about the [Cloud Shell](https://cloud.google.com/shell) IDE environment and the [Cloud Code](https://cloud.google.com/code) extension. diff --git a/docs/deploystack.md b/docs/deploystack.md new file mode 100644 index 0000000..40e444c --- /dev/null +++ b/docs/deploystack.md @@ -0,0 +1,12 @@ +## Deploy Online Boutique with DeployStack + +The "Open in Google Cloud Shell" button below will use [DeployStack](https://cloud.google.com/shell/docs/cloud-shell-tutorials/deploystack/overview) to deploy Online Boutique to a new Google Kubernetes Engine (GKE) cluster. + + + + Open in Cloud Shell + + +The button will open up a [Cloud Shell](https://cloud.google.com/shell) session where you will select your Google Cloud project. After project selection, the following will happen automatically: +1. a GKE cluster will be created inside the select project +2. Online Boutique (and its load generator) will be deployed to that cluster diff --git a/docs/development-guide.md b/docs/development-guide.md new file mode 100644 index 0000000..8f9e4af --- /dev/null +++ b/docs/development-guide.md @@ -0,0 +1,131 @@ +# Development Guide + +This doc explains how to build and run the Online Boutique source code locally using the `skaffold` command-line tool. + +## Prerequisites + +- [Docker for Desktop](https://www.docker.com/products/docker-desktop) +- [kubectl](https://kubernetes.io/docs/tasks/tools/) (can be installed via `gcloud components install kubectl` for Option 1 - GKE) +- [skaffold **2.0.2+**](https://skaffold.dev/docs/install/) (latest version recommended), a tool that builds and deploys Docker images in bulk. +- Clone the repository. + ```sh + git clone https://github.com/GoogleCloudPlatform/microservices-demo + cd microservices-demo/ + ``` +- A Google Cloud project with Google Container Registry enabled. (for Option 1 - GKE) +- [Minikube](https://minikube.sigs.k8s.io/docs/start/) (optional for Option 2 - Local Cluster) +- [Kind](https://kind.sigs.k8s.io/) (optional for Option 2 - Local Cluster) + +## Option 1: Google Kubernetes Engine (GKE) + +> 💡 Recommended if you're using Google Cloud and want to try it on +> a realistic cluster. **Note**: If your cluster has Workload Identity enabled, +> [see these instructions](https://cloud.google.com/kubernetes-engine/docs/how-to/workload-identity#enable) + +1. Create a Google Kubernetes Engine cluster and make sure `kubectl` is pointing + to the cluster. + + ```sh + gcloud services enable container.googleapis.com + ``` + + ```sh + gcloud container clusters create-auto demo --region=us-central1 + ``` + + ``` + kubectl get nodes + ``` + +2. Enable Artifact Registry (AR) on your GCP project and configure the + `docker` CLI to authenticate to AR: + + ```sh + gcloud services enable artifactregistry.googleapis.com + ``` + + ```sh + gcloud artifacts repositories create microservices-demo \ + --repository-format=docker \ + --location=us \ + ``` + + ```sh + gcloud auth configure-docker -q + ``` + +3. In the root of this repository, run: + + ``` + skaffold run --default-repo=us-docker.pkg.dev/PROJECT_ID/microservices-demo + ``` + + Where `PROJECT_ID` is replaced by your Google Cloud project ID. + + This command: + + - Builds the container images. + - Pushes them to AR. + - Applies the `./kubernetes-manifests` deploying the application to + Kubernetes. + + **Troubleshooting:** If you get "No space left on device" error on Google + Cloud Shell, you can build the images on Google Cloud Build: [Enable the + Cloud Build + API](https://console.cloud.google.com/flows/enableapi?apiid=cloudbuild.googleapis.com), + then run `skaffold run -p gcb --default-repo=us-docker.pkg.dev/[PROJECT_ID]/microservices-demo` instead. + +4. Find the IP address of your application, then visit the application on your + browser to confirm installation. + + kubectl get service frontend-external + +5. Navigate to `http://EXTERNAL-IP` to access the web frontend. + +## Option 2 - Local Cluster + +1. Launch a local Kubernetes cluster with one of the following tools: + + - To launch **Minikube** (tested with Ubuntu Linux). Please, ensure that the + local Kubernetes cluster has at least: + - 4 CPUs + - 4.0 GiB memory + - 32 GB disk space + + ```shell + minikube start --cpus=4 --memory 4096 --disk-size 32g + ``` + + - To launch **Docker for Desktop** (tested with Mac/Windows). Go to Preferences: + - choose “Enable Kubernetes”, + - set CPUs to at least 3, and Memory to at least 6.0 GiB + - on the "Disk" tab, set at least 32 GB disk space + + - To launch a **Kind** cluster: + + ```shell + kind create cluster + ``` + +2. Run `kubectl get nodes` to verify you're connected to the respective control plane. + +3. Run `skaffold run` (first time will be slow, it can take ~20 minutes). + This will build and deploy the application. If you need to rebuild the images + automatically as you refactor the code, run `skaffold dev` command. + +4. Run `kubectl get pods` to verify the Pods are ready and running. + +5. Run `kubectl port-forward deployment/frontend 8080:8080` to forward a port to the frontend service. + +6. Navigate to `localhost:8080` to access the web frontend. + +## Adding a new microservice + +In general, the set of core microservices for Online Boutique is fairly complete and unlikely to change in the future, but it can be useful to add an additional optional microservice that can be deployed to complement the core services. + +See the [Adding a new microservice](adding-new-microservice.md) guide for instructions on how to add a new microservice. + +## Cleanup + +If you've deployed the application with `skaffold run` command, you can run +`skaffold delete` to clean up the deployed resources. diff --git a/docs/img/architecture-diagram.png b/docs/img/architecture-diagram.png new file mode 100644 index 0000000..af64f52 Binary files /dev/null and b/docs/img/architecture-diagram.png differ diff --git a/docs/img/memorystore.png b/docs/img/memorystore.png new file mode 100644 index 0000000..470bdc6 Binary files /dev/null and b/docs/img/memorystore.png differ diff --git a/docs/img/online-boutique-frontend-1.png b/docs/img/online-boutique-frontend-1.png new file mode 100644 index 0000000..b63776a Binary files /dev/null and b/docs/img/online-boutique-frontend-1.png differ diff --git a/docs/img/online-boutique-frontend-2.png b/docs/img/online-boutique-frontend-2.png new file mode 100644 index 0000000..b012166 Binary files /dev/null and b/docs/img/online-boutique-frontend-2.png differ diff --git a/docs/product-requirements.md b/docs/product-requirements.md new file mode 100644 index 0000000..b8c1de4 --- /dev/null +++ b/docs/product-requirements.md @@ -0,0 +1,39 @@ +## Product Requirements + +This document contains a list of requirements that every change made to this repository should meet. +Every change must: +1. Preserve the golden user journey taken by Kubernetes beginners. +1. Preserve the simplicity of demos. +1. Preserve the simplicity of the GKE quickstart. + +These requirements are about the default deployment (default configuration) of Online Boutique. +Changes that will violate any of these rules should not be built into the default configuration of Online Boutique. +Such changes should be opt-in only — ideally, as a [Kustomize Component](https://github.com/GoogleCloudPlatform/microservices-demo/tree/main/kustomize) if they align with the [purpose of Online Boutique](/docs/purpose.md). + +### 1. Preserve the golden user journey taken by Kubernetes beginners + +The following statement about Online Boutique should always be true: + +> A user outside of Google can deploy Online Boutique's default configuration on a [_kind_ Kubernetes cluster](https://kind.sigs.k8s.io/). + +This statement describes the golden user journey that we expect new Kubernetes users to take while onboarding to Online Boutique. + +Being able to run Online Boutique on a _kind_ cluster ensures that Online Boutique is free and cloud-agnostic. This is aligned with [Google's mission](https://about.google/) of making information universally accessible and useful. To be specific, Online Boutique should be useful and accessible to developers that are new to Kubernetes. + +### 2. Preserve the simplicity of demos + +New changes should not complicate the primary user journey showcased in live demos and tutorials. + +Today, the primary user journey is as follows: +1. Visit Online Boutique on a web browser. +2. Select an item from the homepage and add the item to the cart. +3. The checkout form is pre-populated with placeholder data (e.g. the shipping address). +4. The user checks out and completes the order. + +### 3. Preserve the simplicity of the GKE quickstart + +New changes should not add additional complexity in the [main Online Boutique quickstart](https://github.com/GoogleCloudPlatform/microservices-demo#quickstart-gke). + +In particular, new changes should not add extra required steps or additional required tools in that quickstart. + +Ideally, extensions to Online Boutique's default functionality (such as a new microservice or a new cloud service integration) should be added as a [Kustomize Component](https://github.com/GoogleCloudPlatform/microservices-demo/tree/main/kustomize/components) which users can optionally opt into. diff --git a/docs/purpose.md b/docs/purpose.md new file mode 100644 index 0000000..85021da --- /dev/null +++ b/docs/purpose.md @@ -0,0 +1,15 @@ +## Purpose + +Today, the primary purpose of Online Boutique is to demonstrate: + +* [Google Kubernetes Engine (GKE)](https://cloud.google.com/kubernetes-engine) +* [Anthos](https://cloud.google.com/anthos) +* [Google Cloud Operations](https://cloud.google.com/products/operations) +* tools and technologies commonly used alongside the above products + +while being accessible and useful to all new Kubernetes users. + +### Why does the purpose matter? + +We filter and prioritize the work to be done in this repository based on the purpose defined above. +If you wish to make changes to this repository that do not align with the above purpose, we encourage you to maintain your own fork of Online Boutique. diff --git a/docs/releasing/README.md b/docs/releasing/README.md new file mode 100644 index 0000000..a5611e9 --- /dev/null +++ b/docs/releasing/README.md @@ -0,0 +1,102 @@ +# Releasing Online Boutique + +This document walks through the process of creating a new release of Online Boutique. + +## Prerequisites for tagging a release + +1. Choose the logical [next release tag](https://github.com/GoogleCloudPlatform/bank-of-anthos/releases), using [semantic versioning](https://semver.org/): `vX.Y.Z`. + + If this release includes significant feature changes, update the minor version (`Y`). Otherwise, for bug-fix releases or standard quarterly release, update the patch version `Z`). + +2. Ensure that the following commands are in your `PATH`: + - `gsed` (found in the `gnu-sed` Brew package for macOS, or by symlinking `sed` for Linux) + - `gcloud` + - `helm` + +3. Make sure that your `gcloud` is authenticated: + + ```sh + gcloud auth login + gcloud auth configure-docker us-central1-docker.pkg.dev + ``` + +## Create and tag the new release + +Run the `make-release.sh` script found inside the `docs/releasing/` directory: + +```sh +# assuming you are inside the root path of the bank-of-anthos repository +export TAG=vX.Y.Z # This is the new version (e.g. `v0.3.5`) +export REPO_PREFIX=us-central1-docker.pkg.dev/google-samples/microservices-demo # This is the Docker repository for tagged images +export PROJECT_ID=google-samples # This is the Google Cloud project for the release CI +./docs/releasing/make-release.sh +``` + +This script does the following: +1. Uses `make-docker-images.sh` to build and push a Docker image for each microservice to the previously specified repository. +2. Uses `make-release-artifacts.sh` to regenerates (and update the image $TAGS) YAML file at `./release/kubernetes-manifests.yaml` and `./kustomize/base/`. +3. Runs `git tag` and pushes a new branch (e.g., `release/v0.3.5`) with the changes to `./release/kubernetes-manifests.yaml`. + +You can then browse the [Container Registry repository](https://pantheon.corp.google.com/gcr/images/google-samples/global/microservices-demo?project=google-samples) to make sure a Docker image was created for each microservice (with the new version tag). + +## Create the PR + +Now that the release branch has been created, you can find it in the [list of branches](https://github.com/GoogleCloudPlatform/microservices-demo/branches) and create a pull request targeting `main` (the default branch). + +This process is going to trigger multiple CI checks as well as stage the release onto a temporary cluster. Once the PR has been approved and all checks are successfully passing, you can then merge the branch. Make sure to include the release draft (see next section) in the pull-request description for reviewers to see. + +Once reviewed and you're ready to merge, make sure to not delete the release branch or the tags during that process. + +## Add notes to the release + +Once the PR has been fully merged, you are ready to create a new release for the newly created [tag](https://github.com/GoogleCloudPlatform/microservices-demo/tags). +- Click the breadcrumbs on the row of the latest tag that was created in the [tags](https://github.com/GoogleCloudPlatform/microservices-demo/tags) page +- Select the `Create release` option + +The release notes should contain a brief description of the changes since the previous release (like bug fixed and new features). For inspiration, you can look at the list of [releases](https://github.com/GoogleCloudPlatform/microservices-demo/releases). + +> ***Note:*** No assets need to be uploaded. They are picked up automatically from the tagged revision + +## Deploy on the production environment + +Once the release notes are published, you should then replace the version of the production environment to the newly published version. + +1. Connect to the [online-boutique-release GKE cluster](https://pantheon.corp.google.com/kubernetes/clusters/details/us-central1-c/online-boutique-release/details?project=online-boutique-ci): + + ```sh + gcloud container clusters get-credentials online-boutique-release \ + --zone us-central1-c --project online-boutique-ci + ``` + +2. Deploy `release/kubernetes-manifests.yaml` to it: + + ```sh + kubectl apply -f ./release/kubernetes-manifests.yaml + ``` + +3. Remove unnecessary objects: + + ```sh + kubectl delete service frontend-external + kubectl delete deployment loadgenerator + ``` + +3. Make sure [cymbal-shops.retail.cymbal.dev](https://cymbal-shops.retail.cymbal.dev) works. + +## Update major tags + +1. Update the relevant major tag (for example, `v1`): + + ```sh + export MAJOR_TAG=v0 # Edit this as needed (to v1/v2/v3/etc) + git checkout release/${TAG} + git pull + git push --delete origin ${MAJOR_TAG} # Delete the remote tag (if it exists) + git tag --delete ${MAJOR_TAG} # Delete the local tag (if it exists) + git tag -a ${MAJOR_TAG} -m "Updating ${MAJOR_TAG} to its most recent release: ${TAG}" + git push origin ${MAJOR_TAG} # Push the new tag to origin + ``` + +## Announce the new release internally + +Once the new release is out, you can now announce it via [g/online-boutique-announce](https://groups.google.com/a/google.com/g/online-boutique-announce). diff --git a/docs/releasing/license_header.txt b/docs/releasing/license_header.txt new file mode 100644 index 0000000..c37e93b --- /dev/null +++ b/docs/releasing/license_header.txt @@ -0,0 +1,13 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. diff --git a/docs/releasing/make-docker-images.sh b/docs/releasing/make-docker-images.sh new file mode 100755 index 0000000..2c919e0 --- /dev/null +++ b/docs/releasing/make-docker-images.sh @@ -0,0 +1,48 @@ +#!/usr/bin/env bash + +# Copyright 2019 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Builds and pushes docker image for each demo microservice. + +set -euo pipefail +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +REPO_ROOT=$SCRIPT_DIR/../.. + +log() { echo "$1" >&2; } + +TAG="${TAG:?TAG env variable must be specified}" +REPO_PREFIX="${REPO_PREFIX:?REPO_PREFIX env variable must be specified}" +PROJECT_ID="${PROJECT_ID:?PROJECT_ID env variable must be specified e.g. google-samples}" + +while IFS= read -d $'\0' -r dir; do + # build image + svcname="$(basename "${dir}")" + builddir="${dir}" + #PR 516 moved cartservice build artifacts one level down to src + if [ $svcname == "cartservice" ] + then + builddir="${dir}/src" + fi + image="${REPO_PREFIX}/$svcname:$TAG" + image_with_sample_public_image_tag="${REPO_PREFIX}/$svcname:sample-public-image-$TAG" + ( + cd "${builddir}" + log "Building (and pushing) image on Google Cloud Build: ${image}" + gcloud builds submit --project=${PROJECT_ID} --tag=${image} + gcloud artifacts docker tags add ${image} ${image_with_sample_public_image_tag} + ) +done < <(find "${REPO_ROOT}/src" -mindepth 1 -maxdepth 1 -type d -print0) + +log "Successfully built and pushed all images." diff --git a/docs/releasing/make-helm-chart.sh b/docs/releasing/make-helm-chart.sh new file mode 100755 index 0000000..17832b3 --- /dev/null +++ b/docs/releasing/make-helm-chart.sh @@ -0,0 +1,36 @@ +#!/usr/bin/env bash + +# Copyright 2019 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Packages and pushes Online Boutique's Helm chart in public Artifact Registry. + +set -euo pipefail +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +REPO_ROOT=$SCRIPT_DIR/../.. + +log() { echo "$1" >&2; } + +TAG="${TAG:?TAG env variable must be specified}" +HELM_CHART_REPO="us-docker.pkg.dev/online-boutique-ci/charts" + +cd ${REPO_ROOT}/helm-chart +gsed -i "s/^appVersion:.*/appVersion: \"${TAG}\"/" Chart.yaml +gsed -i "s/^version:.*/version: ${TAG:1}/" Chart.yaml +helm package . +helm push onlineboutique-${TAG:1}.tgz oci://$HELM_CHART_REPO + +rm ./onlineboutique-${TAG:1}.tgz + +log "Successfully built and pushed the Helm chart." diff --git a/docs/releasing/make-release-artifacts.sh b/docs/releasing/make-release-artifacts.sh new file mode 100755 index 0000000..0fec5e5 --- /dev/null +++ b/docs/releasing/make-release-artifacts.sh @@ -0,0 +1,137 @@ +#!/usr/bin/env bash + +# Copyright 2019 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This script compiles manifest files with the image tags and places them in +# /release/... + +set -euo pipefail +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +REPO_ROOT=$SCRIPT_DIR/../.. +[[ -n "${DEBUG:-}" ]] && set -x + +log() { echo "$1" >&2; } + +TAG="${TAG:?TAG env variable must be specified}" +REPO_PREFIX="${REPO_PREFIX:?REPO_PREFIX env variable must be specified}" +OUT_DIR="${OUT_DIR:-${REPO_ROOT}/release}" + +print_license_header() { + cat "${SCRIPT_DIR}/license_header.txt" + echo +} + +print_autogenerated_warning() { + cat< "${k8s_manifests_file}" + log "Written ${k8s_manifests_file}" + + istio_manifests_file="${OUT_DIR}/istio-manifests.yaml" + mk_istio_manifests > "${istio_manifests_file}" + log "Written ${istio_manifests_file}" + + mk_kustomize_base + log "Written Kustomize base" +} + +main diff --git a/docs/releasing/make-release.sh b/docs/releasing/make-release.sh new file mode 100755 index 0000000..7a8e9c2 --- /dev/null +++ b/docs/releasing/make-release.sh @@ -0,0 +1,69 @@ +#!/usr/bin/env bash + +# Copyright 2019 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This script creates a new release by: +# - 1. building/pushing images +# - 2. injecting tags into YAML manifests +# - 3. creating a new git tag +# - 4. pushing the tag/commit to main. + +set -euo pipefail +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +REPO_ROOT=$SCRIPT_DIR/../.. +[[ -n "${DEBUG:-}" ]] && set -x + +log() { echo "$1" >&2; } +fail() { log "$1"; exit 1; } + +TAG="${TAG:?TAG env variable must be specified}" +REPO_PREFIX="${REPO_PREFIX:?REPO_PREFIX env variable must be specified e.g. us-central1-docker.pkg.dev\/google-samples\/microservices-demo}" +PROJECT_ID="${PROJECT_ID:?PROJECT_ID env variable must be specified e.g. google-samples}" + +if [[ "$TAG" != v* ]]; then + fail "\$TAG must start with 'v', e.g. v0.1.0 (got: $TAG)" +fi + +# ensure there are no uncommitted changes +if [[ $(git status -s | wc -l) -gt 0 ]]; then + echo "error: can't have uncommitted changes" + exit 1 +fi + +# make sure local source is up to date +git checkout main +git pull + +# build and push images +"${SCRIPT_DIR}"/make-docker-images.sh + +# update yaml +"${SCRIPT_DIR}"/make-release-artifacts.sh + +# build and push images +"${SCRIPT_DIR}"/make-helm-chart.sh + +# create git release / push to new branch +git checkout -b "release/${TAG}" +git add "${REPO_ROOT}/release/" +git add "${REPO_ROOT}/kustomize/base/" +git add "${REPO_ROOT}/helm-chart/" +git commit --allow-empty -m "Release $TAG" +log "Pushing k8s manifests to release/${TAG}..." +git tag "$TAG" +git push --set-upstream origin "release/${TAG}" +git push --tags + +log "Successfully tagged release $TAG." diff --git a/helm-chart/Chart.yaml b/helm-chart/Chart.yaml new file mode 100644 index 0000000..03c9f55 --- /dev/null +++ b/helm-chart/Chart.yaml @@ -0,0 +1,38 @@ +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: v2 +name: onlineboutique +description: A Helm chart for Kubernetes for Online Boutique + +# A chart can be either an 'application' or a 'library' chart. +# +# Application charts are a collection of templates that can be packaged into versioned archives +# to be deployed. +# +# Library charts provide useful utilities or functions for the chart developer. They're included as +# a dependency of application charts to inject those utilities and functions into the rendering +# pipeline. Library charts do not define any templates and therefore cannot be deployed. +type: application + +# This is the chart version. This version number should be incremented each time you make changes +# to the chart and its templates, including the app version. +# Versions are expected to follow Semantic Versioning (https://semver.org/) +version: 0.10.4 + +# This is the version number of the application being deployed. This version number should be +# incremented each time you make changes to the application. Versions are not expected to +# follow Semantic Versioning. They should reflect the version the application is using. +# It is recommended to use it with quotes. +appVersion: "v0.10.4" diff --git a/helm-chart/README.md b/helm-chart/README.md new file mode 100644 index 0000000..0a47e12 --- /dev/null +++ b/helm-chart/README.md @@ -0,0 +1,38 @@ +# Helm chart for Online Boutique + +If you'd like to deploy Online Boutique via its Helm chart, you could leverage the following instructions. + +**Warning:** Online Boutique's Helm chart is currently experimental. If you have feedback or run into issues, let us know inside [GitHub Issue #1319](https://github.com/GoogleCloudPlatform/microservices-demo/issues/1319) or by creating a [new GitHub Issue](https://github.com/GoogleCloudPlatform/microservices-demo/issues/new/choose). + +Deploy the default setup of Online Boutique: +```sh +helm upgrade onlineboutique oci://us-docker.pkg.dev/online-boutique-ci/charts/onlineboutique \ + --install +``` + +Deploy advanced scenario of Online Boutique: +```sh +helm upgrade onlineboutique oci://us-docker.pkg.dev/online-boutique-ci/charts/onlineboutique \ + --install \ + --create-namespace \ + --set images.repository=us-docker.pkg.dev/my-project/microservices-demo \ + --set frontend.externalService=false \ + --set redis.create=false \ + --set cartservice.database.type=spanner \ + --set cartservice.database.connectionString=projects/my-project/instances/onlineboutique/databases/carts \ + --set serviceAccounts.create=true \ + --set authorizationPolicies.create=true \ + --set networkPolicies.create=true \ + --set sidecars.create=true \ + --set frontend.virtualService.create=true \ + --set 'serviceAccounts.annotations.iam\.gke\.io/gcp-service-account=spanner-db-user@my-project.iam.gserviceaccount.com' \ + --set serviceAccounts.annotationsOnlyForCartservice=true \ + -n onlineboutique +``` + +For the full list of configurations, see [values.yaml](./values.yaml). + +You could also find advanced scenarios with these blogs below: +- [Online Boutique sample’s Helm chart, to simplify the setup of advanced and secured scenarios with Service Mesh and GitOps](https://medium.com/google-cloud/246119e46d53) +- [gRPC health probes with Kubernetes 1.24+](https://medium.com/google-cloud/b5bd26253a4c) +- [Use Google Cloud Spanner with the Online Boutique sample](https://medium.com/google-cloud/f7248e077339) \ No newline at end of file diff --git a/helm-chart/templates/NOTES.txt b/helm-chart/templates/NOTES.txt new file mode 100644 index 0000000..35cb49a --- /dev/null +++ b/helm-chart/templates/NOTES.txt @@ -0,0 +1,15 @@ +{{- if and .Values.frontend.create .Values.frontend.externalService }} +Note: It may take a few minutes for the LoadBalancer IP to be available. + +Watch the status of the frontend IP address with: + kubectl get --namespace {{ .Release.Namespace }} svc -w {{ .Values.frontend.name }}-external + +Get the external IP address of the frontend: + export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ .Values.frontend.name }}-external --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}") + echo http://$SERVICE_IP +{{- end }} +{{- if .Values.frontend.virtualService.create }} +Get the external IP address of the ingress gateway: + export SERVICE_IP=$(kubectl get svc --namespace {{ .Values.frontend.virtualService.gateway.namespace }} {{ .Values.frontend.virtualService.gateway.name }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}") + echo http://$SERVICE_IP +{{- end }} diff --git a/helm-chart/templates/adservice.yaml b/helm-chart/templates/adservice.yaml new file mode 100644 index 0000000..6c07400 --- /dev/null +++ b/helm-chart/templates/adservice.yaml @@ -0,0 +1,178 @@ +# Copyright 2024 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +{{- if .Values.adService.create }} +{{- if .Values.serviceAccounts.create }} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ .Values.adService.name }} + namespace: {{.Release.Namespace}} + {{- if not .Values.serviceAccounts.annotationsOnlyForCartservice }} + {{- with .Values.serviceAccounts.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} + {{- end }} +--- +{{- end }} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ .Values.adService.name }} + namespace: {{ .Release.Namespace }} + labels: + app: {{ .Values.adService.name }} +spec: + selector: + matchLabels: + app: {{ .Values.adService.name }} + template: + metadata: + labels: + app: {{ .Values.adService.name }} + spec: + {{- if .Values.serviceAccounts.create }} + serviceAccountName: {{ .Values.adService.name }} + {{- else }} + serviceAccountName: default + {{- end }} + terminationGracePeriodSeconds: 5 + {{- if .Values.securityContext.enable }} + securityContext: + fsGroup: 1000 + runAsGroup: 1000 + runAsNonRoot: true + runAsUser: 1000 + {{- if .Values.seccompProfile.enable }} + seccompProfile: + type: {{ .Values.seccompProfile.type }} + {{- end }} + {{- end }} + containers: + - name: server + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + privileged: false + readOnlyRootFilesystem: true + image: {{ .Values.images.repository }}/{{ .Values.adService.name }}:{{ .Values.images.tag | default .Chart.AppVersion }} + ports: + - containerPort: 9555 + env: + - name: PORT + value: "9555" + resources: + {{- toYaml .Values.adService.resources | nindent 10 }} + readinessProbe: + initialDelaySeconds: 20 + periodSeconds: 15 + grpc: + port: 9555 + livenessProbe: + initialDelaySeconds: 20 + periodSeconds: 15 + grpc: + port: 9555 +--- +apiVersion: v1 +kind: Service +metadata: + name: {{ .Values.adService.name }} + namespace: {{ .Release.Namespace }} + labels: + app: {{ .Values.adService.name }} +spec: + type: ClusterIP + selector: + app: {{ .Values.adService.name }} + ports: + - name: grpc + port: 9555 + targetPort: 9555 +{{- if .Values.networkPolicies.create }} +--- +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: {{ .Values.adService.name }} + namespace: {{ .Release.Namespace }} +spec: + podSelector: + matchLabels: + app: {{ .Values.adService.name }} + policyTypes: + - Ingress + - Egress + ingress: + - from: + - podSelector: + matchLabels: + app: {{ .Values.frontend.name }} + ports: + - port: 9555 + protocol: TCP + egress: + - {} +{{- end }} +{{- if .Values.sidecars.create }} +--- +apiVersion: networking.istio.io/v1beta1 +kind: Sidecar +metadata: + name: {{ .Values.adService.name }} + namespace: {{ .Release.Namespace }} +spec: + workloadSelector: + labels: + app: {{ .Values.adService.name }} + egress: + - hosts: + - istio-system/* + {{- if .Values.opentelemetryCollector.create }} + - ./{{ .Values.opentelemetryCollector.name }}.{{ .Release.Namespace }}.svc.cluster.local + {{- end }} +{{- end }} +{{- if .Values.authorizationPolicies.create }} +--- +apiVersion: security.istio.io/v1beta1 +kind: AuthorizationPolicy +metadata: + name: {{ .Values.adService.name }} + namespace: {{ .Release.Namespace }} +spec: + selector: + matchLabels: + app: {{ .Values.adService.name }} + rules: + - from: + - source: + principals: + {{- if .Values.serviceAccounts.create }} + - cluster.local/ns/{{ .Release.Namespace }}/sa/{{ .Values.frontend.name }} + {{- else }} + - cluster.local/ns/{{ .Release.Namespace }}/sa/default + {{- end }} + to: + - operation: + paths: + - /hipstershop.AdService/GetAds + methods: + - POST + ports: + - "9555" +{{- end }} +{{- end }} diff --git a/helm-chart/templates/cartservice.yaml b/helm-chart/templates/cartservice.yaml new file mode 100644 index 0000000..688a390 --- /dev/null +++ b/helm-chart/templates/cartservice.yaml @@ -0,0 +1,405 @@ +# Copyright 2024 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +{{- if .Values.cartService.create }} +{{- if .Values.serviceAccounts.create }} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ .Values.cartService.name }} + namespace: {{.Release.Namespace}} + {{- with .Values.serviceAccounts.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +--- +{{- end }} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ .Values.cartService.name }} + namespace: {{ .Release.Namespace }} + labels: + app: {{ .Values.cartService.name }} +spec: + selector: + matchLabels: + app: {{ .Values.cartService.name }} + template: + metadata: + {{- if .Values.cartDatabase.externalRedisTlsOrigination.enable }} + annotations: + sidecar.istio.io/userVolumeMount: '[{"name": "{{ .Values.cartDatabase.externalRedisTlsOrigination.name }}", "mountPath": "/etc/certs", "readonly": true}]' + sidecar.istio.io/userVolume: '[{"name": "{{ .Values.cartDatabase.externalRedisTlsOrigination.name }}", "secret": {"secretName": "{{ .Values.cartDatabase.externalRedisTlsOrigination.name }}"}}]' + proxy.istio.io/config: '{"holdApplicationUntilProxyStarts": true}' + {{- end }} + labels: + app: {{ .Values.cartService.name }} + spec: + {{- if .Values.serviceAccounts.create }} + serviceAccountName: {{ .Values.cartService.name }} + {{- else }} + serviceAccountName: default + {{- end }} + terminationGracePeriodSeconds: 5 + {{- if .Values.securityContext.enable }} + securityContext: + fsGroup: 1000 + runAsGroup: 1000 + runAsNonRoot: true + runAsUser: 1000 + {{- end }} + {{- if .Values.seccompProfile.enable }} + seccompProfile: + type: {{ .Values.seccompProfile.type }} + {{- end }} + containers: + - name: server + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + privileged: false + readOnlyRootFilesystem: true + image: {{ .Values.images.repository }}/{{ .Values.cartService.name }}:{{ .Values.images.tag | default .Chart.AppVersion }} + ports: + - containerPort: 7070 + env: + {{- if eq .Values.cartDatabase.type "spanner" }} + - name: SPANNER_CONNECTION_STRING + {{- else }} + - name: REDIS_ADDR + {{- end }} + value: {{ .Values.cartDatabase.connectionString | quote }} + resources: + {{- toYaml .Values.cartService.resources | nindent 10 }} + readinessProbe: + initialDelaySeconds: 15 + grpc: + port: 7070 + livenessProbe: + initialDelaySeconds: 15 + periodSeconds: 10 + grpc: + port: 7070 +--- +apiVersion: v1 +kind: Service +metadata: + name: {{ .Values.cartService.name }} + namespace: {{ .Release.Namespace }} + labels: + app: {{ .Values.cartService.name }} +spec: + type: ClusterIP + selector: + app: {{ .Values.cartService.name }} + ports: + - name: grpc + port: 7070 + targetPort: 7070 +{{- if .Values.networkPolicies.create }} +--- +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: {{ .Values.cartService.name }} + namespace: {{ .Release.Namespace }} +spec: + podSelector: + matchLabels: + app: {{ .Values.cartService.name }} + policyTypes: + - Ingress + - Egress + ingress: + - from: + - podSelector: + matchLabels: + app: {{ .Values.frontend.name }} + - podSelector: + matchLabels: + app: {{ .Values.checkoutService.name }} + ports: + - port: 7070 + protocol: TCP + egress: + - {} +{{- end }} +{{- if .Values.sidecars.create }} +--- +apiVersion: networking.istio.io/v1beta1 +kind: Sidecar +metadata: + name: {{ .Values.cartService.name }} + namespace: {{ .Release.Namespace }} +spec: + workloadSelector: + labels: + app: {{ .Values.cartService.name }} + egress: + - hosts: + - istio-system/* + {{- if eq .Values.cartDatabase.type "redis" }} + {{- if .Values.cartDatabase.externalRedisTlsOrigination.enable }} + - ./{{ .Values.cartDatabase.externalRedisTlsOrigination.name }}.{{ .Release.Namespace }} + {{- else }} + - ./{{ .Values.cartDatabase.inClusterRedis.name }}.{{ .Release.Namespace }}.svc.cluster.local + {{- end }} + {{- end }} + {{- if .Values.opentelemetryCollector.create }} + - ./{{ .Values.opentelemetryCollector.name }}.{{ .Release.Namespace }}.svc.cluster.local + {{- end }} +{{- end }} +{{- if .Values.authorizationPolicies.create }} +--- +apiVersion: security.istio.io/v1beta1 +kind: AuthorizationPolicy +metadata: + name: {{ .Values.cartService.name }} + namespace: {{ .Release.Namespace }} +spec: + selector: + matchLabels: + app: {{ .Values.cartService.name }} + rules: + - from: + - source: + principals: + {{- if .Values.serviceAccounts.create }} + - cluster.local/ns/{{ .Release.Namespace }}/sa/{{ .Values.frontend.name }} + - cluster.local/ns/{{ .Release.Namespace }}/sa/{{ .Values.checkoutService.name }} + {{- else }} + - cluster.local/ns/{{ .Release.Namespace }}/sa/default + {{- end }} + to: + - operation: + paths: + - /hipstershop.CartService/AddItem + - /hipstershop.CartService/GetCart + - /hipstershop.CartService/EmptyCart + methods: + - POST + ports: + - "7070" +{{- end }} + +{{- if .Values.cartDatabase.inClusterRedis.create }} +--- +{{- if .Values.serviceAccounts.create }} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ .Values.cartDatabase.inClusterRedis.name }} + namespace: {{.Release.Namespace}} + {{- if not .Values.serviceAccounts.annotationsOnlyForCartservice }} + {{- with .Values.serviceAccounts.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} + {{- end }} +--- +{{- end }} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ .Values.cartDatabase.inClusterRedis.name }} + namespace: {{ .Release.Namespace }} +spec: + selector: + matchLabels: + app: {{ .Values.cartDatabase.inClusterRedis.name }} + template: + metadata: + labels: + app: {{ .Values.cartDatabase.inClusterRedis.name }} + spec: + {{- if .Values.serviceAccounts.create }} + serviceAccountName: {{ .Values.cartDatabase.inClusterRedis.name }} + {{- else }} + serviceAccountName: default + {{- end }} + {{- if .Values.securityContext.enable }} + securityContext: + fsGroup: 1000 + runAsGroup: 1000 + runAsNonRoot: true + runAsUser: 1000 + {{- if .Values.seccompProfile.enable }} + seccompProfile: + type: {{ .Values.seccompProfile.type }} + {{- end }} + {{- end }} + containers: + - name: redis + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + privileged: false + readOnlyRootFilesystem: true + {{- if .Values.cartDatabase.inClusterRedis.publicRepository }} + image: redis:alpine@sha256:4eec4565e45aa0b3966554c866bc73211e281b0b3d89fe9a33c982e6faca809d + {{- else }} + image: {{ .Values.images.repository }}/redis:alpine + {{- end }} + ports: + - containerPort: 6379 + readinessProbe: + periodSeconds: 5 + tcpSocket: + port: 6379 + livenessProbe: + periodSeconds: 5 + tcpSocket: + port: 6379 + volumeMounts: + - mountPath: /data + name: redis-data + resources: + limits: + memory: 256Mi + cpu: 125m + requests: + cpu: 70m + memory: 200Mi + volumes: + - name: redis-data + emptyDir: {} +--- +apiVersion: v1 +kind: Service +metadata: + name: {{ .Values.cartDatabase.inClusterRedis.name }} + namespace: {{ .Release.Namespace }} +spec: + type: ClusterIP + selector: + app: {{ .Values.cartDatabase.inClusterRedis.name }} + ports: + - name: tcp-redis + port: 6379 + targetPort: 6379 +{{- if .Values.networkPolicies.create }} +--- +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: {{ .Values.cartDatabase.inClusterRedis.name }} + namespace: {{ .Release.Namespace }} +spec: + podSelector: + matchLabels: + app: {{ .Values.cartDatabase.inClusterRedis.name }} + policyTypes: + - Ingress + - Egress + ingress: + - from: + - podSelector: + matchLabels: + app: {{ .Values.cartService.name }} + ports: + - port: 6379 + protocol: TCP + egress: + - {} +{{- end }} +{{- if .Values.sidecars.create }} +--- +apiVersion: networking.istio.io/v1beta1 +kind: Sidecar +metadata: + name: {{ .Values.cartDatabase.inClusterRedis.name }} + namespace: {{ .Release.Namespace }} +spec: + workloadSelector: + labels: + app: {{ .Values.cartDatabase.inClusterRedis.name }} + egress: + - hosts: + - istio-system/* +{{- end }} +{{- if .Values.authorizationPolicies.create }} +--- +apiVersion: security.istio.io/v1beta1 +kind: AuthorizationPolicy +metadata: + name: {{ .Values.cartDatabase.inClusterRedis.name }} + namespace: {{ .Release.Namespace }} +spec: + selector: + matchLabels: + app: {{ .Values.cartDatabase.inClusterRedis.name }} + rules: + - from: + - source: + principals: + {{- if .Values.serviceAccounts.create }} + - cluster.local/ns/{{ .Release.Namespace }}/sa/{{ .Values.cartService.name }} + {{- else }} + - cluster.local/ns/{{ .Release.Namespace }}/sa/default + {{- end }} + to: + - operation: + ports: + - "6379" +{{- end }} +{{- end }} +{{- if .Values.cartDatabase.externalRedisTlsOrigination.enable }} +--- +apiVersion: v1 +data: + {{ .Values.cartDatabase.externalRedisTlsOrigination.name }}.pem: {{ .Values.cartDatabase.externalRedisTlsOrigination.certificate | b64enc | quote }} +kind: Secret +metadata: + name: {{ .Values.cartDatabase.externalRedisTlsOrigination.name }} + namespace: {{ .Release.Namespace }} +--- +apiVersion: networking.istio.io/v1beta1 +kind: DestinationRule +metadata: + name: {{ .Values.cartDatabase.externalRedisTlsOrigination.name }} + namespace: {{ .Release.Namespace }} +spec: + exportTo: + - '.' + host: {{ .Values.cartDatabase.externalRedisTlsOrigination.name }}.{{ .Release.Namespace }} + trafficPolicy: + tls: + mode: SIMPLE + caCertificates: /etc/certs/{{ .Values.cartDatabase.externalRedisTlsOrigination.name }}.pem +--- +apiVersion: networking.istio.io/v1beta1 +kind: ServiceEntry +metadata: + name: {{ .Values.cartDatabase.externalRedisTlsOrigination.name }} + namespace: {{ .Release.Namespace }} +spec: + hosts: + - {{ .Values.cartDatabase.externalRedisTlsOrigination.name }}.{{ .Release.Namespace }} + addresses: + - {{ .Values.cartDatabase.externalRedisTlsOrigination.endpointAddress }}/32 + endpoints: + - address: {{ .Values.cartDatabase.externalRedisTlsOrigination.endpointAddress }} + location: MESH_EXTERNAL + resolution: STATIC + ports: + - number: {{ .Values.cartDatabase.externalRedisTlsOrigination.endpointPort }} + name: tcp-redis + protocol: TCP +{{- end }} +{{- end }} diff --git a/helm-chart/templates/checkoutservice.yaml b/helm-chart/templates/checkoutservice.yaml new file mode 100644 index 0000000..c93a7f5 --- /dev/null +++ b/helm-chart/templates/checkoutservice.yaml @@ -0,0 +1,205 @@ +# Copyright 2024 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +{{- if .Values.checkoutService.create }} +{{- if .Values.serviceAccounts.create }} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ .Values.checkoutService.name }} + namespace: {{.Release.Namespace}} + {{- if not .Values.serviceAccounts.annotationsOnlyForCartservice }} + {{- with .Values.serviceAccounts.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} + {{- end }} +--- +{{- end }} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ .Values.checkoutService.name }} + namespace: {{ .Release.Namespace }} + labels: + app: {{ .Values.checkoutService.name }} +spec: + selector: + matchLabels: + app: {{ .Values.checkoutService.name }} + template: + metadata: + labels: + app: {{ .Values.checkoutService.name }} + spec: + {{- if .Values.serviceAccounts.create }} + serviceAccountName: {{ .Values.checkoutService.name }} + {{- else }} + serviceAccountName: default + {{- end }} + {{- if .Values.securityContext.enable }} + securityContext: + fsGroup: 1000 + runAsGroup: 1000 + runAsNonRoot: true + runAsUser: 1000 + {{- if .Values.seccompProfile.enable }} + seccompProfile: + type: {{ .Values.seccompProfile.type }} + {{- end }} + {{- end }} + containers: + - name: server + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + privileged: false + readOnlyRootFilesystem: true + image: {{ .Values.images.repository }}/{{ .Values.checkoutService.name }}:{{ .Values.images.tag | default .Chart.AppVersion }} + ports: + - containerPort: 5050 + readinessProbe: + grpc: + port: 5050 + livenessProbe: + grpc: + port: 5050 + env: + - name: PORT + value: "5050" + - name: PRODUCT_CATALOG_SERVICE_ADDR + value: "{{ .Values.productCatalogService.name }}:3550" + - name: SHIPPING_SERVICE_ADDR + value: "{{ .Values.shippingService.name }}:50051" + - name: PAYMENT_SERVICE_ADDR + value: "{{ .Values.paymentService.name }}:50051" + - name: EMAIL_SERVICE_ADDR + value: "{{ .Values.emailService.name }}:5000" + - name: CURRENCY_SERVICE_ADDR + value: "{{ .Values.currencyService.name }}:7000" + - name: CART_SERVICE_ADDR + value: "{{ .Values.cartService.name }}:7070" + {{- if .Values.opentelemetryCollector.create }} + - name: COLLECTOR_SERVICE_ADDR + value: "{{ .Values.opentelemetryCollector.name }}:4317" + - name: OTEL_SERVICE_NAME + value: "{{ .Values.checkoutService.name }}" + {{- end }} + {{- if .Values.googleCloudOperations.tracing }} + - name: ENABLE_TRACING + value: "1" + {{- end }} + {{- if .Values.googleCloudOperations.profiler }} + - name: ENABLE_PROFILER + value: "1" + {{- end }} + resources: + {{- toYaml .Values.checkoutService.resources | nindent 10 }} +--- +apiVersion: v1 +kind: Service +metadata: + name: {{ .Values.checkoutService.name }} + namespace: {{ .Release.Namespace }} + labels: + app: {{ .Values.checkoutService.name }} +spec: + type: ClusterIP + selector: + app: {{ .Values.checkoutService.name }} + ports: + - name: grpc + port: 5050 + targetPort: 5050 +{{- if .Values.networkPolicies.create }} +--- +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: {{ .Values.checkoutService.name }} + namespace: {{ .Release.Namespace }} +spec: + podSelector: + matchLabels: + app: {{ .Values.checkoutService.name }} + policyTypes: + - Ingress + - Egress + ingress: + - from: + - podSelector: + matchLabels: + app: {{ .Values.frontend.name }} + ports: + - port: 5050 + protocol: TCP + egress: + - {} +{{- end }} +{{- if .Values.sidecars.create }} +--- +apiVersion: networking.istio.io/v1beta1 +kind: Sidecar +metadata: + name: {{ .Values.checkoutService.name }} + namespace: {{ .Release.Namespace }} +spec: + workloadSelector: + labels: + app: {{ .Values.checkoutService.name }} + egress: + - hosts: + - istio-system/* + - ./{{ .Values.cartService.name }}.{{ .Release.Namespace }}.svc.cluster.local + - ./{{ .Values.currencyService.name }}.{{ .Release.Namespace }}.svc.cluster.local + - ./{{ .Values.emailService.name }}.{{ .Release.Namespace }}.svc.cluster.local + - ./{{ .Values.paymentService.name }}.{{ .Release.Namespace }}.svc.cluster.local + - ./{{ .Values.productCatalogService.name }}.{{ .Release.Namespace }}.svc.cluster.local + - ./{{ .Values.shippingService.name }}.{{ .Release.Namespace }}.svc.cluster.local + {{- if .Values.opentelemetryCollector.create }} + - ./{{ .Values.opentelemetryCollector.name }}.{{ .Release.Namespace }}.svc.cluster.local + {{- end }} +{{- end }} +{{- if .Values.authorizationPolicies.create }} +--- +apiVersion: security.istio.io/v1beta1 +kind: AuthorizationPolicy +metadata: + name: {{ .Values.checkoutService.name }} + namespace: {{ .Release.Namespace }} +spec: + selector: + matchLabels: + app: {{ .Values.checkoutService.name }} + rules: + - from: + - source: + principals: + {{- if .Values.serviceAccounts.create }} + - cluster.local/ns/{{ .Release.Namespace }}/sa/{{ .Values.frontend.name }} + {{- else }} + - cluster.local/ns/{{ .Release.Namespace }}/sa/default + {{- end }} + to: + - operation: + paths: + - /hipstershop.CheckoutService/PlaceOrder + methods: + - POST + ports: + - "5050" +{{- end }} +{{- end }} diff --git a/helm-chart/templates/common.yaml b/helm-chart/templates/common.yaml new file mode 100644 index 0000000..a3d53b0 --- /dev/null +++ b/helm-chart/templates/common.yaml @@ -0,0 +1,35 @@ +# Copyright 2024 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +{{- if .Values.networkPolicies.create }} +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: deny-all + namespace: {{ .Release.Namespace }} +spec: + podSelector: {} + policyTypes: + - Ingress + - Egress +{{- end }} +{{- if .Values.authorizationPolicies.create }} +--- +apiVersion: security.istio.io/v1beta1 +kind: AuthorizationPolicy +metadata: + name: deny-all + namespace: {{ .Release.Namespace }} +spec: {} +{{- end }} diff --git a/helm-chart/templates/currencyservice.yaml b/helm-chart/templates/currencyservice.yaml new file mode 100644 index 0000000..7b547d2 --- /dev/null +++ b/helm-chart/templates/currencyservice.yaml @@ -0,0 +1,194 @@ +# Copyright 2024 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +{{- if .Values.currencyService.create }} +{{- if .Values.serviceAccounts.create }} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ .Values.currencyService.name }} + namespace: {{.Release.Namespace}} + {{- if not .Values.serviceAccounts.annotationsOnlyForCartservice }} + {{- with .Values.serviceAccounts.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} + {{- end }} +--- +{{- end }} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ .Values.currencyService.name }} + namespace: {{ .Release.Namespace }} + labels: + app: {{ .Values.currencyService.name }} +spec: + selector: + matchLabels: + app: {{ .Values.currencyService.name }} + template: + metadata: + labels: + app: {{ .Values.currencyService.name }} + spec: + {{- if .Values.serviceAccounts.create }} + serviceAccountName: {{ .Values.currencyService.name }} + {{- else }} + serviceAccountName: default + {{- end }} + terminationGracePeriodSeconds: 5 + {{- if .Values.securityContext.enable }} + securityContext: + fsGroup: 1000 + runAsGroup: 1000 + runAsNonRoot: true + runAsUser: 1000 + {{- if .Values.seccompProfile.enable }} + seccompProfile: + type: {{ .Values.seccompProfile.type }} + {{- end }} + {{- end }} + containers: + - name: server + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + privileged: false + readOnlyRootFilesystem: true + image: {{ .Values.images.repository }}/{{ .Values.currencyService.name }}:{{ .Values.images.tag | default .Chart.AppVersion }} + ports: + - name: grpc + containerPort: 7000 + env: + - name: PORT + value: "7000" + {{- if .Values.opentelemetryCollector.create }} + - name: COLLECTOR_SERVICE_ADDR + value: "{{ .Values.opentelemetryCollector.name }}:4317" + - name: OTEL_SERVICE_NAME + value: "{{ .Values.currencyService.name }}" + {{- end }} + {{- if .Values.googleCloudOperations.tracing }} + - name: ENABLE_TRACING + value: "1" + {{- end }} + {{- if not .Values.googleCloudOperations.profiler }} + - name: DISABLE_PROFILER + value: "1" + {{- end }} + readinessProbe: + grpc: + port: 7000 + livenessProbe: + grpc: + port: 7000 + resources: + {{- toYaml .Values.currencyService.resources | nindent 10 }} +--- +apiVersion: v1 +kind: Service +metadata: + name: {{ .Values.currencyService.name }} + namespace: {{ .Release.Namespace }} + labels: + app: {{ .Values.currencyService.name }} +spec: + type: ClusterIP + selector: + app: {{ .Values.currencyService.name }} + ports: + - name: grpc + port: 7000 + targetPort: 7000 +{{- if .Values.networkPolicies.create }} +--- +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: {{ .Values.currencyService.name }} + namespace: {{ .Release.Namespace }} +spec: + podSelector: + matchLabels: + app: {{ .Values.currencyService.name }} + policyTypes: + - Ingress + - Egress + ingress: + - from: + - podSelector: + matchLabels: + app: {{ .Values.frontend.name }} + - podSelector: + matchLabels: + app: {{ .Values.checkoutService.name }} + ports: + - port: 7000 + protocol: TCP + egress: + - {} +{{- end }} +{{- if .Values.sidecars.create }} +--- +apiVersion: networking.istio.io/v1beta1 +kind: Sidecar +metadata: + name: {{ .Values.currencyService.name }} + namespace: {{ .Release.Namespace }} +spec: + workloadSelector: + labels: + app: {{ .Values.currencyService.name }} + egress: + - hosts: + - istio-system/* + {{- if .Values.opentelemetryCollector.create }} + - ./{{ .Values.opentelemetryCollector.name }}.{{ .Release.Namespace }}.svc.cluster.local + {{- end }} +{{- end }} +{{- if .Values.authorizationPolicies.create }} +--- +apiVersion: security.istio.io/v1beta1 +kind: AuthorizationPolicy +metadata: + name: {{ .Values.currencyService.name }} + namespace: {{ .Release.Namespace }} +spec: + selector: + matchLabels: + app: {{ .Values.currencyService.name }} + rules: + - from: + - source: + principals: + {{- if .Values.serviceAccounts.create }} + - cluster.local/ns/{{ .Release.Namespace }}/sa/{{ .Values.frontend.name }} + - cluster.local/ns/{{ .Release.Namespace }}/sa/{{ .Values.checkoutService.name }} + {{- else }} + - cluster.local/ns/{{ .Release.Namespace }}/sa/default + {{- end }} + to: + - operation: + paths: + - /hipstershop.CurrencyService/Convert + - /hipstershop.CurrencyService/GetSupportedCurrencies + methods: + - POST + ports: + - "7000" +{{- end }} +{{- end }} diff --git a/helm-chart/templates/emailservice.yaml b/helm-chart/templates/emailservice.yaml new file mode 100644 index 0000000..8a725a9 --- /dev/null +++ b/helm-chart/templates/emailservice.yaml @@ -0,0 +1,190 @@ +# Copyright 2024 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +{{- if .Values.emailService.create }} +{{- if .Values.serviceAccounts.create }} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ .Values.emailService.name }} + namespace: {{.Release.Namespace}} + {{- if not .Values.serviceAccounts.annotationsOnlyForCartservice }} + {{- with .Values.serviceAccounts.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} + {{- end }} +--- +{{- end }} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ .Values.emailService.name }} + namespace: {{ .Release.Namespace }} + labels: + app: {{ .Values.emailService.name }} +spec: + selector: + matchLabels: + app: {{ .Values.emailService.name }} + template: + metadata: + labels: + app: {{ .Values.emailService.name }} + spec: + {{- if .Values.serviceAccounts.create }} + serviceAccountName: {{ .Values.emailService.name }} + {{- else }} + serviceAccountName: default + {{- end }} + terminationGracePeriodSeconds: 5 + {{- if .Values.securityContext.enable }} + securityContext: + fsGroup: 1000 + runAsGroup: 1000 + runAsNonRoot: true + runAsUser: 1000 + {{- if .Values.seccompProfile.enable }} + seccompProfile: + type: {{ .Values.seccompProfile.type }} + {{- end }} + {{- end }} + containers: + - name: server + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + privileged: false + readOnlyRootFilesystem: true + image: {{ .Values.images.repository }}/{{ .Values.emailService.name }}:{{ .Values.images.tag | default .Chart.AppVersion }} + ports: + - containerPort: 8080 + env: + - name: PORT + value: "8080" + {{- if .Values.opentelemetryCollector.create }} + - name: COLLECTOR_SERVICE_ADDR + value: "{{ .Values.opentelemetryCollector.name }}:4317" + - name: OTEL_SERVICE_NAME + value: "{{ .Values.emailService.name }}" + {{- end }} + {{- if .Values.googleCloudOperations.tracing }} + - name: ENABLE_TRACING + value: "1" + {{- end }} + {{- if not .Values.googleCloudOperations.profiler }} + - name: DISABLE_PROFILER + value: "1" + {{- end }} + readinessProbe: + periodSeconds: 5 + grpc: + port: 8080 + livenessProbe: + periodSeconds: 5 + grpc: + port: 8080 + resources: + {{- toYaml .Values.emailService.resources | nindent 10 }} +--- +apiVersion: v1 +kind: Service +metadata: + name: {{ .Values.emailService.name }} + namespace: {{ .Release.Namespace }} + labels: + app: {{ .Values.emailService.name }} +spec: + type: ClusterIP + selector: + app: {{ .Values.emailService.name }} + ports: + - name: grpc + port: 5000 + targetPort: 8080 +{{- if .Values.networkPolicies.create }} +--- +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: {{ .Values.emailService.name }} + namespace: {{ .Release.Namespace }} +spec: + podSelector: + matchLabels: + app: {{ .Values.emailService.name }} + policyTypes: + - Ingress + - Egress + ingress: + - from: + - podSelector: + matchLabels: + app: {{ .Values.checkoutService.name }} + ports: + - port: 8080 + protocol: TCP + egress: + - {} +{{- end }} +{{- if .Values.sidecars.create }} +--- +apiVersion: networking.istio.io/v1beta1 +kind: Sidecar +metadata: + name: {{ .Values.emailService.name }} + namespace: {{ .Release.Namespace }} +spec: + workloadSelector: + labels: + app: {{ .Values.emailService.name }} + egress: + - hosts: + - istio-system/* + {{- if .Values.opentelemetryCollector.create }} + - ./{{ .Values.opentelemetryCollector.name }}.{{ .Release.Namespace }}.svc.cluster.local + {{- end }} +{{- end }} +{{- if .Values.authorizationPolicies.create }} +--- +apiVersion: security.istio.io/v1beta1 +kind: AuthorizationPolicy +metadata: + name: {{ .Values.emailService.name }} + namespace: {{ .Release.Namespace }} +spec: + selector: + matchLabels: + app: {{ .Values.emailService.name }} + rules: + - from: + - source: + principals: + {{- if .Values.serviceAccounts.create }} + - cluster.local/ns/{{ .Release.Namespace }}/sa/{{ .Values.checkoutService.name }} + {{- else }} + - cluster.local/ns/{{ .Release.Namespace }}/sa/default + {{- end }} + to: + - operation: + paths: + - /hipstershop.EmailService/SendOrderConfirmation + methods: + - POST + ports: + - "8080" +{{- end }} +{{- end }} diff --git a/helm-chart/templates/frontend.yaml b/helm-chart/templates/frontend.yaml new file mode 100644 index 0000000..2d6adec --- /dev/null +++ b/helm-chart/templates/frontend.yaml @@ -0,0 +1,285 @@ +# Copyright 2024 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +{{- if .Values.frontend.create }} +{{- if .Values.serviceAccounts.create }} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ .Values.frontend.name }} + namespace: {{.Release.Namespace}} + {{- if not .Values.serviceAccounts.annotationsOnlyForCartservice }} + {{- with .Values.serviceAccounts.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} + {{- end }} +--- +{{- end }} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ .Values.frontend.name }} + namespace: {{ .Release.Namespace }} + labels: + app: {{ .Values.frontend.name }} +spec: + selector: + matchLabels: + app: {{ .Values.frontend.name }} + template: + metadata: + labels: + app: {{ .Values.frontend.name }} + annotations: + sidecar.istio.io/rewriteAppHTTPProbers: "true" + spec: + {{- if .Values.serviceAccounts.create }} + serviceAccountName: {{ .Values.frontend.name }} + {{- else }} + serviceAccountName: default + {{- end }} + {{- if .Values.securityContext.enable }} + securityContext: + fsGroup: 1000 + runAsGroup: 1000 + runAsNonRoot: true + runAsUser: 1000 + {{- if .Values.seccompProfile.enable }} + seccompProfile: + type: {{ .Values.seccompProfile.type }} + {{- end }} + {{- end }} + containers: + - name: server + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + privileged: false + readOnlyRootFilesystem: true + image: {{ .Values.images.repository }}/{{ .Values.frontend.name }}:{{ .Values.images.tag | default .Chart.AppVersion }} + ports: + - containerPort: 8080 + readinessProbe: + initialDelaySeconds: 10 + httpGet: + path: "/_healthz" + port: 8080 + httpHeaders: + - name: "Cookie" + value: "shop_session-id=x-readiness-probe" + livenessProbe: + initialDelaySeconds: 10 + httpGet: + path: "/_healthz" + port: 8080 + httpHeaders: + - name: "Cookie" + value: "shop_session-id=x-liveness-probe" + env: + - name: PORT + value: "8080" + - name: PRODUCT_CATALOG_SERVICE_ADDR + value: "{{ .Values.productCatalogService.name }}:3550" + - name: CURRENCY_SERVICE_ADDR + value: "{{ .Values.currencyService.name }}:7000" + - name: CART_SERVICE_ADDR + value: "{{ .Values.cartService.name }}:7070" + - name: RECOMMENDATION_SERVICE_ADDR + value: "{{ .Values.recommendationService.name }}:8080" + - name: SHIPPING_SERVICE_ADDR + value: "{{ .Values.shippingService.name }}:50051" + - name: CHECKOUT_SERVICE_ADDR + value: "{{ .Values.checkoutService.name }}:5050" + - name: AD_SERVICE_ADDR + value: "{{ .Values.adService.name }}:9555" + - name: SHOPPING_ASSISTANT_SERVICE_ADDR + value: "{{ .Values.shoppingAssistantService.name }}:80" + - name: ENV_PLATFORM + value: {{ .Values.frontend.platform | quote }} + {{- if .Values.opentelemetryCollector.create }} + - name: COLLECTOR_SERVICE_ADDR + value: "{{ .Values.opentelemetryCollector.name }}:4317" + - name: OTEL_SERVICE_NAME + value: "{{ .Values.frontend.name }}" + {{- end }} + {{- if .Values.googleCloudOperations.tracing }} + - name: ENABLE_TRACING + value: "1" + {{- end }} + {{- if .Values.googleCloudOperations.profiler }} + - name: ENABLE_PROFILER + value: "1" + {{- end }} + - name: CYMBAL_BRANDING + value: {{ .Values.frontend.cymbalBranding | quote }} + - name: ENABLE_ASSISTANT + value: {{ .Values.shoppingAssistantService.create | quote }} + - name: ENABLE_SINGLE_SHARED_SESSION + value: {{ .Values.frontend.singleSharedSession | quote }} + resources: + {{- toYaml .Values.frontend.resources | nindent 12 }} +--- +apiVersion: v1 +kind: Service +metadata: + name: {{ .Values.frontend.name }} + namespace: {{ .Release.Namespace }} + labels: + app: {{ .Values.frontend.name }} +spec: + type: ClusterIP + selector: + app: {{ .Values.frontend.name }} + ports: + - name: http + port: 80 + targetPort: 8080 +{{- if .Values.frontend.externalService }} +--- +apiVersion: v1 +kind: Service +metadata: + name: {{ .Values.frontend.name }}-external + namespace: {{ .Release.Namespace }} +spec: + type: LoadBalancer + selector: + app: {{ .Values.frontend.name }} + ports: + - name: http + port: 80 + targetPort: 8080 +{{- end }} +{{- if .Values.networkPolicies.create }} +--- +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: {{ .Values.frontend.name }} + namespace: {{ .Release.Namespace }} +spec: + podSelector: + matchLabels: + app: {{ .Values.frontend.name }} + policyTypes: + - Ingress + - Egress + ingress: + {{- if .Values.frontend.externalService }} + - {} + {{- else }} + - from: + - podSelector: + matchLabels: + app: {{ .Values.loadGenerator.name }} + {{- if .Values.frontend.virtualService.create }} + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: {{ .Values.frontend.virtualService.gateway.namespace }} + podSelector: + matchLabels: + {{ .Values.frontend.virtualService.gateway.labelKey }}: {{ .Values.frontend.virtualService.gateway.labelValue }} + {{- end }} + ports: + - port: 8080 + protocol: TCP + {{- end }} + egress: + - {} +{{- end }} +{{- if .Values.sidecars.create }} +--- +apiVersion: networking.istio.io/v1beta1 +kind: Sidecar +metadata: + name: {{ .Values.frontend.name }} + namespace: {{ .Release.Namespace }} +spec: + workloadSelector: + labels: + app: {{ .Values.frontend.name }} + egress: + - hosts: + - istio-system/* + - ./{{ .Values.adService.name }}.{{ .Release.Namespace }}.svc.cluster.local + - ./{{ .Values.cartService.name }}.{{ .Release.Namespace }}.svc.cluster.local + - ./{{ .Values.checkoutService.name }}.{{ .Release.Namespace }}.svc.cluster.local + - ./{{ .Values.currencyService.name }}.{{ .Release.Namespace }}.svc.cluster.local + - ./{{ .Values.productCatalogService.name }}.{{ .Release.Namespace }}.svc.cluster.local + - ./{{ .Values.recommendationService.name }}.{{ .Release.Namespace }}.svc.cluster.local + - ./{{ .Values.shippingService.name }}.{{ .Release.Namespace }}.svc.cluster.local + {{- if .Values.opentelemetryCollector.create }} + - ./{{ .Values.opentelemetryCollector.name }}.{{ .Release.Namespace }}.svc.cluster.local + {{- end }} +{{- end }} +{{- if .Values.authorizationPolicies.create }} +--- +apiVersion: security.istio.io/v1beta1 +kind: AuthorizationPolicy +metadata: + name: {{ .Values.frontend.name }} + namespace: {{ .Release.Namespace }} +spec: + selector: + matchLabels: + app: {{ .Values.frontend.name }} + rules: + {{- if .Values.frontend.externalService }} + - to: + {{- else }} + - from: + - source: + principals: + {{- if .Values.serviceAccounts.create }} + - cluster.local/ns/{{ .Release.Namespace }}/sa/{{ .Values.loadGenerator.name }} + {{- else }} + - cluster.local/ns/{{ .Release.Namespace }}/sa/default + {{- end }} + {{- if .Values.frontend.virtualService.create }} + - cluster.local/ns/{{ .Values.frontend.virtualService.gateway.namespace }}/sa/{{ .Values.frontend.virtualService.gateway.name }} + {{- end }} + to: + {{- end }} + - operation: + methods: + - GET + - POST + ports: + - "8080" +{{- end }} +{{- if .Values.frontend.virtualService.create }} +--- +apiVersion: networking.istio.io/v1alpha3 +kind: VirtualService +metadata: + name: {{ .Values.frontend.name }} + namespace: {{ .Release.Namespace }} +spec: + {{- with .Values.frontend.virtualService.hosts }} + hosts: + {{- toYaml . | nindent 2 }} + {{- end }} + gateways: + - {{ .Values.frontend.virtualService.gateway.namespace }}/{{ .Values.frontend.virtualService.gateway.name }} + http: + - route: + - destination: + host: {{ .Values.frontend.name }} + port: + number: 80 +{{- end }} +{{- end }} diff --git a/helm-chart/templates/loadgenerator.yaml b/helm-chart/templates/loadgenerator.yaml new file mode 100644 index 0000000..b509616 --- /dev/null +++ b/helm-chart/templates/loadgenerator.yaml @@ -0,0 +1,156 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +{{- if .Values.loadGenerator.create }} +{{- if .Values.serviceAccounts.create }} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ .Values.loadGenerator.name }} + namespace: {{.Release.Namespace}} + {{- if not .Values.serviceAccounts.annotationsOnlyForCartservice }} + {{- with .Values.serviceAccounts.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} + {{- end }} +--- +{{- end }} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ .Values.loadGenerator.name }} + namespace: {{ .Release.Namespace }} + labels: + app: {{ .Values.loadGenerator.name }} +spec: + selector: + matchLabels: + app: {{ .Values.loadGenerator.name }} + replicas: 1 + template: + metadata: + labels: + app: {{ .Values.loadGenerator.name }} + annotations: + sidecar.istio.io/rewriteAppHTTPProbers: "true" + spec: + {{- if .Values.serviceAccounts.create }} + serviceAccountName: {{ .Values.loadGenerator.name }} + {{- else }} + serviceAccountName: default + {{- end }} + terminationGracePeriodSeconds: 5 + restartPolicy: Always + {{- if .Values.securityContext.enable }} + securityContext: + fsGroup: 1000 + runAsGroup: 1000 + runAsNonRoot: true + runAsUser: 1000 + {{- if .Values.seccompProfile.enable }} + seccompProfile: + type: {{ .Values.seccompProfile.type }} + {{- end }} + {{- end }} + {{- if .Values.loadGenerator.checkFrontendInitContainer }} + initContainers: + - command: + - /bin/sh + - -exc + - | + MAX_RETRIES=12 + RETRY_INTERVAL=10 + for i in $(seq 1 $MAX_RETRIES); do + echo "Attempt $i: Pinging frontend: ${FRONTEND_ADDR}..." + STATUSCODE=$(wget --server-response http://${FRONTEND_ADDR} 2>&1 | awk '/^ HTTP/{print $2}') + if [ $STATUSCODE -eq 200 ]; then + echo "Frontend is reachable." + exit 0 + fi + echo "Error: Could not reach frontend - Status code: ${STATUSCODE}" + sleep $RETRY_INTERVAL + done + echo "Failed to reach frontend after $MAX_RETRIES attempts." + exit 1 + name: frontend-check + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + privileged: false + readOnlyRootFilesystem: true + image: busybox:latest@sha256:e226d6308690dbe282443c8c7e57365c96b5228f0fe7f40731b5d84d37a06839 + env: + - name: FRONTEND_ADDR + value: "{{ .Values.frontend.name }}:80" + {{- end }} + containers: + - name: main + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + privileged: false + readOnlyRootFilesystem: true + image: {{ .Values.images.repository }}/{{ .Values.loadGenerator.name }}:{{ .Values.images.tag | default .Chart.AppVersion }} + env: + - name: FRONTEND_ADDR + value: "{{ .Values.frontend.name }}:80" + - name: USERS + value: "10" + - name: RATE + value: "1" + resources: + {{- toYaml .Values.loadGenerator.resources | nindent 10 }} +{{- if .Values.networkPolicies.create }} +--- +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: {{ .Values.loadGenerator.name }} + namespace: {{ .Release.Namespace }} + labels: + app: {{ .Values.loadGenerator.name }} +spec: + podSelector: + matchLabels: + app: {{ .Values.loadGenerator.name }} + policyTypes: + - Egress + egress: + - {} +{{- end }} +{{- if .Values.sidecars.create }} +--- +apiVersion: networking.istio.io/v1beta1 +kind: Sidecar +metadata: + name: {{ .Values.loadGenerator.name }} + namespace: {{ .Release.Namespace }} +spec: + workloadSelector: + labels: + app: {{ .Values.loadGenerator.name }} + egress: + - hosts: + - istio-system/* + - ./{{ .Values.frontend.name }}.{{ .Release.Namespace }}.svc.cluster.local + {{- if .Values.opentelemetryCollector.create }} + - ./{{ .Values.opentelemetryCollector.name }}.{{ .Release.Namespace }}.svc.cluster.local + {{- end }} +{{- end }} +{{- end }} diff --git a/helm-chart/templates/opentelemetry-collector.yaml b/helm-chart/templates/opentelemetry-collector.yaml new file mode 100644 index 0000000..92515e3 --- /dev/null +++ b/helm-chart/templates/opentelemetry-collector.yaml @@ -0,0 +1,262 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +{{- if .Values.opentelemetryCollector.create }} +{{- if .Values.serviceAccounts.create }} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ .Values.opentelemetryCollector.name }} + namespace: {{.Release.Namespace}} + {{- if not .Values.serviceAccounts.annotationsOnlyForCartservice }} + {{- with .Values.serviceAccounts.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} + {{- end }} +--- +{{- end }} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ .Values.opentelemetryCollector.name }} + namespace: {{ .Release.Namespace }} +spec: + replicas: 1 + selector: + matchLabels: + app: {{ .Values.opentelemetryCollector.name }} + template: + metadata: + labels: + app: {{ .Values.opentelemetryCollector.name }} + spec: + {{- if .Values.serviceAccounts.create }} + serviceAccountName: {{ .Values.opentelemetryCollector.name }} + {{- else }} + serviceAccountName: default + {{- end }} + {{- if .Values.securityContext.enable }} + securityContext: + fsGroup: 1000 + runAsGroup: 1000 + runAsNonRoot: true + runAsUser: 1000 + {{- if .Values.seccompProfile.enable }} + seccompProfile: + type: {{ .Values.seccompProfile.type }} + {{- end }} + {{- end }} + {{- if eq .Values.opentelemetryCollector.projectId "PROJECT_ID" }} + initContainers: + # Init container retrieves the current cloud project id from the metadata server + # and inserts it into the collector config template + # https://cloud.google.com/compute/docs/storing-retrieving-metadata + - name: otel-gateway-init + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + privileged: false + readOnlyRootFilesystem: true + image: busybox:latest@sha256:e226d6308690dbe282443c8c7e57365c96b5228f0fe7f40731b5d84d37a06839 + command: + - '/bin/sh' + - '-c' + - | + sed "s/PROJECT_ID/$(curl -H 'Metadata-Flavor: Google' http://metadata.google.internal/computeMetadata/v1/project/project-id)/" /template/collector-gateway-config-template.yaml >> /conf/collector-gateway-config.yaml + volumeMounts: + - name: collector-gateway-config-template + mountPath: /template + - name: collector-gateway-config + mountPath: /conf + {{- end }} + containers: + # This gateway container will receive traces and metrics from each microservice + # and forward it to GCP + - name: otel-gateway + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + privileged: false + readOnlyRootFilesystem: true + args: + - --config=/conf/collector-gateway-config.yaml + image: otel/opentelemetry-collector-contrib:0.144.0@sha256:213886eb6407af91b87fa47551c3632be1a6419ff3a5114ef1e6fc364628496f + volumeMounts: + - name: collector-gateway-config + mountPath: /conf + volumes: + # Simple ConfigMap volume with template file + - name: collector-gateway-config-template + configMap: + items: + - key: collector-gateway-config-template.yaml + path: collector-gateway-config-template.yaml + name: collector-gateway-config-template + # Create a volume to store the expanded template (with correct cloud project ID) + - name: collector-gateway-config + emptyDir: {} +--- +apiVersion: v1 +kind: Service +metadata: + name: {{ .Values.opentelemetryCollector.name }} + namespace: {{ .Release.Namespace }} +spec: + ports: + - name: grpc-otlp + port: 4317 + protocol: TCP + targetPort: 4317 + selector: + app: {{ .Values.opentelemetryCollector.name }} + type: ClusterIP +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: collector-gateway-config-template + namespace: {{ .Release.Namespace }} +# Open Telemetry Collector config +# https://opentelemetry.io/docs/collector/configuration/ +data: + collector-gateway-config-template.yaml: | + receivers: + otlp: + protocols: + grpc: + processors: + exporters: + googlecloud: + project: {{ .Values.opentelemetryCollector.projectId | quote }} + service: + pipelines: + traces: + receivers: [otlp] # Receive otlp-formatted data from other collector instances + processors: [] + exporters: [googlecloud] # Export traces directly to Google Cloud + metrics: + receivers: [otlp] + processors: [] + exporters: [googlecloud] # Export metrics to Google Cloud +{{- if .Values.networkPolicies.create }} +--- +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: {{ .Values.opentelemetryCollector.name }} + namespace: {{ .Release.Namespace }} +spec: + podSelector: + matchLabels: + app: {{ .Values.opentelemetryCollector.name }} + policyTypes: + - Ingress + - Egress + ingress: + - from: + - podSelector: + matchLabels: + app: {{ .Values.adService.name }} + - podSelector: + matchLabels: + app: {{ .Values.cartService.name }} + - podSelector: + matchLabels: + app: {{ .Values.checkoutService.name }} + - podSelector: + matchLabels: + app: {{ .Values.currencyService.name }} + - podSelector: + matchLabels: + app: {{ .Values.emailService.name }} + - podSelector: + matchLabels: + app: {{ .Values.frontend.name }} + - podSelector: + matchLabels: + app: {{ .Values.loadGenerator.name }} + - podSelector: + matchLabels: + app: {{ .Values.paymentService.name }} + - podSelector: + matchLabels: + app: {{ .Values.productCatalogService.name }} + - podSelector: + matchLabels: + app: {{ .Values.recommendationService.name }} + - podSelector: + matchLabels: + app: {{ .Values.shippingService.name }} + ports: + - port: 4317 + protocol: TCP + egress: + - {} +{{- end }} +{{- if .Values.sidecars.create }} +--- +apiVersion: networking.istio.io/v1beta1 +kind: Sidecar +metadata: + name: {{ .Values.opentelemetryCollector.name }} + namespace: {{ .Release.Namespace }} +spec: + workloadSelector: + labels: + app: {{ .Values.opentelemetryCollector.name }} + egress: + - hosts: + - istio-system/* +{{- end }} +{{- if .Values.authorizationPolicies.create }} +--- +apiVersion: security.istio.io/v1beta1 +kind: AuthorizationPolicy +metadata: + name: {{ .Values.opentelemetryCollector.name }} + namespace: {{ .Release.Namespace }} +spec: + selector: + matchLabels: + app: {{ .Values.opentelemetryCollector.name }} + rules: + - from: + - source: + principals: + {{- if .Values.serviceAccounts.create }} + - cluster.local/ns/{{ .Release.Namespace }}/sa/{{ .Values.adService.name }} + - cluster.local/ns/{{ .Release.Namespace }}/sa/{{ .Values.cartService.name }} + - cluster.local/ns/{{ .Release.Namespace }}/sa/{{ .Values.checkoutService.name }} + - cluster.local/ns/{{ .Release.Namespace }}/sa/{{ .Values.currencyService.name }} + - cluster.local/ns/{{ .Release.Namespace }}/sa/{{ .Values.emailService.name }} + - cluster.local/ns/{{ .Release.Namespace }}/sa/{{ .Values.frontend.name }} + - cluster.local/ns/{{ .Release.Namespace }}/sa/{{ .Values.loadGenerator.name }} + - cluster.local/ns/{{ .Release.Namespace }}/sa/{{ .Values.paymentService.name }} + - cluster.local/ns/{{ .Release.Namespace }}/sa/{{ .Values.productCatalogService.name }} + - cluster.local/ns/{{ .Release.Namespace }}/sa/{{ .Values.recommendationService.name }} + - cluster.local/ns/{{ .Release.Namespace }}/sa/{{ .Values.shippingService.name }} + {{- else }} + - cluster.local/ns/{{ .Release.Namespace }}/sa/default + {{- end }} + to: + - operation: + ports: + - "4317" +{{- end }} +{{- end }} diff --git a/helm-chart/templates/paymentservice.yaml b/helm-chart/templates/paymentservice.yaml new file mode 100644 index 0000000..60ad274 --- /dev/null +++ b/helm-chart/templates/paymentservice.yaml @@ -0,0 +1,188 @@ +# Copyright 2024 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +{{- if .Values.paymentService.create }} +{{- if .Values.serviceAccounts.create }} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ .Values.paymentService.name }} + namespace: {{.Release.Namespace}} + {{- if not .Values.serviceAccounts.annotationsOnlyForCartservice }} + {{- with .Values.serviceAccounts.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} + {{- end }} +--- +{{- end }} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ .Values.paymentService.name }} + namespace: {{ .Release.Namespace }} + labels: + app: {{ .Values.paymentService.name }} +spec: + selector: + matchLabels: + app: {{ .Values.paymentService.name }} + template: + metadata: + labels: + app: {{ .Values.paymentService.name }} + spec: + {{- if .Values.serviceAccounts.create }} + serviceAccountName: {{ .Values.paymentService.name }} + {{- else }} + serviceAccountName: default + {{- end }} + terminationGracePeriodSeconds: 5 + {{- if .Values.securityContext.enable }} + securityContext: + fsGroup: 1000 + runAsGroup: 1000 + runAsNonRoot: true + runAsUser: 1000 + {{- if .Values.seccompProfile.enable }} + seccompProfile: + type: {{ .Values.seccompProfile.type }} + {{- end }} + {{- end }} + containers: + - name: server + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + privileged: false + readOnlyRootFilesystem: true + image: {{ .Values.images.repository }}/{{ .Values.paymentService.name }}:{{ .Values.images.tag | default .Chart.AppVersion }} + ports: + - containerPort: 50051 + env: + - name: PORT + value: "50051" + {{- if .Values.opentelemetryCollector.create }} + - name: COLLECTOR_SERVICE_ADDR + value: "{{ .Values.opentelemetryCollector.name }}:4317" + - name: OTEL_SERVICE_NAME + value: "{{ .Values.paymentService.name }}" + {{- end }} + {{- if .Values.googleCloudOperations.tracing }} + - name: ENABLE_TRACING + value: "1" + {{- end }} + {{- if not .Values.googleCloudOperations.profiler }} + - name: DISABLE_PROFILER + value: "1" + {{- end }} + readinessProbe: + grpc: + port: 50051 + livenessProbe: + grpc: + port: 50051 + resources: + {{- toYaml .Values.paymentService.resources | nindent 10 }} +--- +apiVersion: v1 +kind: Service +metadata: + name: {{ .Values.paymentService.name }} + namespace: {{ .Release.Namespace }} + labels: + app: {{ .Values.paymentService.name }} +spec: + type: ClusterIP + selector: + app: {{ .Values.paymentService.name }} + ports: + - name: grpc + port: 50051 + targetPort: 50051 +{{- if .Values.networkPolicies.create }} +--- +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: {{ .Values.paymentService.name }} + namespace: {{ .Release.Namespace }} +spec: + podSelector: + matchLabels: + app: {{ .Values.paymentService.name }} + policyTypes: + - Ingress + - Egress + ingress: + - from: + - podSelector: + matchLabels: + app: {{ .Values.checkoutService.name }} + ports: + - port: 50051 + protocol: TCP + egress: + - {} +{{- end }} +{{- if .Values.sidecars.create }} +--- +apiVersion: networking.istio.io/v1beta1 +kind: Sidecar +metadata: + name: {{ .Values.paymentService.name }} + namespace: {{ .Release.Namespace }} +spec: + workloadSelector: + labels: + app: {{ .Values.paymentService.name }} + egress: + - hosts: + - istio-system/* + {{- if .Values.opentelemetryCollector.create }} + - ./{{ .Values.opentelemetryCollector.name }}.{{ .Release.Namespace }}.svc.cluster.local + {{- end }} +{{- end }} +{{- if .Values.authorizationPolicies.create }} +--- +apiVersion: security.istio.io/v1beta1 +kind: AuthorizationPolicy +metadata: + name: {{ .Values.paymentService.name }} + namespace: {{ .Release.Namespace }} +spec: + selector: + matchLabels: + app: {{ .Values.paymentService.name }} + rules: + - from: + - source: + principals: + {{- if .Values.serviceAccounts.create }} + - cluster.local/ns/{{ .Release.Namespace }}/sa/{{ .Values.checkoutService.name }} + {{- else }} + - cluster.local/ns/{{ .Release.Namespace }}/sa/default + {{- end }} + to: + - operation: + paths: + - /hipstershop.PaymentService/Charge + methods: + - POST + ports: + - "50051" +{{- end }} +{{- end }} diff --git a/helm-chart/templates/productcatalogservice.yaml b/helm-chart/templates/productcatalogservice.yaml new file mode 100644 index 0000000..dec0e2d --- /dev/null +++ b/helm-chart/templates/productcatalogservice.yaml @@ -0,0 +1,199 @@ +# Copyright 2024 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +{{- if .Values.productCatalogService.create }} +{{- if .Values.serviceAccounts.create }} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ .Values.productCatalogService.name }} + namespace: {{.Release.Namespace}} + {{- if not .Values.serviceAccounts.annotationsOnlyForCartservice }} + {{- with .Values.serviceAccounts.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} + {{- end }} +--- +{{- end }} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ .Values.productCatalogService.name }} + namespace: {{ .Release.Namespace }} + labels: + app: {{ .Values.productCatalogService.name }} +spec: + selector: + matchLabels: + app: {{ .Values.productCatalogService.name }} + template: + metadata: + labels: + app: {{ .Values.productCatalogService.name }} + spec: + {{- if .Values.serviceAccounts.create }} + serviceAccountName: {{ .Values.productCatalogService.name }} + {{- else }} + serviceAccountName: default + {{- end }} + terminationGracePeriodSeconds: 5 + {{- if .Values.securityContext.enable }} + securityContext: + fsGroup: 1000 + runAsGroup: 1000 + runAsNonRoot: true + runAsUser: 1000 + {{- if .Values.seccompProfile.enable }} + seccompProfile: + type: {{ .Values.seccompProfile.type }} + {{- end }} + {{- end }} + containers: + - name: server + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + privileged: false + readOnlyRootFilesystem: true + image: {{ .Values.images.repository }}/{{ .Values.productCatalogService.name }}:{{ .Values.images.tag | default .Chart.AppVersion }} + ports: + - containerPort: 3550 + env: + - name: PORT + value: "3550" + {{- if .Values.opentelemetryCollector.create }} + - name: COLLECTOR_SERVICE_ADDR + value: "{{ .Values.opentelemetryCollector.name }}:4317" + - name: OTEL_SERVICE_NAME + value: "{{ .Values.productCatalogService.name }}" + {{- end }} + {{- if .Values.googleCloudOperations.tracing }} + - name: ENABLE_TRACING + value: "1" + {{- end }} + {{- if not .Values.googleCloudOperations.profiler }} + - name: DISABLE_PROFILER + value: "1" + {{- end }} + - name: EXTRA_LATENCY + value: {{ .Values.productCatalogService.extraLatency }} + readinessProbe: + grpc: + port: 3550 + livenessProbe: + grpc: + port: 3550 + resources: + {{- toYaml .Values.productCatalogService.resources | nindent 10 }} +--- +apiVersion: v1 +kind: Service +metadata: + name: {{ .Values.productCatalogService.name }} + namespace: {{ .Release.Namespace }} + labels: + app: {{ .Values.productCatalogService.name }} +spec: + type: ClusterIP + selector: + app: {{ .Values.productCatalogService.name }} + ports: + - name: grpc + port: 3550 + targetPort: 3550 +{{- if .Values.networkPolicies.create }} +--- +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: {{ .Values.productCatalogService.name }} + namespace: {{ .Release.Namespace }} +spec: + podSelector: + matchLabels: + app: {{ .Values.productCatalogService.name }} + policyTypes: + - Ingress + - Egress + ingress: + - from: + - podSelector: + matchLabels: + app: {{ .Values.frontend.name }} + - podSelector: + matchLabels: + app: {{ .Values.checkoutService.name }} + - podSelector: + matchLabels: + app: {{ .Values.recommendationService.name }} + ports: + - port: 3550 + protocol: TCP + egress: + - {} +{{- end }} +{{- if .Values.sidecars.create }} +--- +apiVersion: networking.istio.io/v1beta1 +kind: Sidecar +metadata: + name: {{ .Values.productCatalogService.name }} + namespace: {{ .Release.Namespace }} +spec: + workloadSelector: + labels: + app: {{ .Values.productCatalogService.name }} + egress: + - hosts: + - istio-system/* + {{- if .Values.opentelemetryCollector.create }} + - ./{{ .Values.opentelemetryCollector.name }}.{{ .Release.Namespace }}.svc.cluster.local + {{- end }} +{{- end }} +{{- if .Values.authorizationPolicies.create }} +--- +apiVersion: security.istio.io/v1beta1 +kind: AuthorizationPolicy +metadata: + name: {{ .Values.productCatalogService.name }} + namespace: {{ .Release.Namespace }} +spec: + selector: + matchLabels: + app: {{ .Values.productCatalogService.name }} + rules: + - from: + - source: + principals: + {{- if .Values.serviceAccounts.create }} + - cluster.local/ns/{{ .Release.Namespace }}/sa/{{ .Values.frontend.name }} + - cluster.local/ns/{{ .Release.Namespace }}/sa/{{ .Values.checkoutService.name }} + - cluster.local/ns/{{ .Release.Namespace }}/sa/{{ .Values.recommendationService.name }} + {{- else }} + - cluster.local/ns/{{ .Release.Namespace }}/sa/default + {{- end }} + to: + - operation: + paths: + - /hipstershop.ProductCatalogService/GetProduct + - /hipstershop.ProductCatalogService/ListProducts + methods: + - POST + ports: + - "3550" +{{- end }} +{{- end }} diff --git a/helm-chart/templates/recommendationservice.yaml b/helm-chart/templates/recommendationservice.yaml new file mode 100644 index 0000000..828a14c --- /dev/null +++ b/helm-chart/templates/recommendationservice.yaml @@ -0,0 +1,193 @@ +# Copyright 2024 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +{{- if .Values.recommendationService.create }} +{{- if .Values.serviceAccounts.create }} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ .Values.recommendationService.name }} + namespace: {{.Release.Namespace}} + {{- if not .Values.serviceAccounts.annotationsOnlyForCartservice }} + {{- with .Values.serviceAccounts.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} + {{- end }} +--- +{{- end }} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ .Values.recommendationService.name }} + namespace: {{ .Release.Namespace }} + labels: + app: {{ .Values.recommendationService.name }} +spec: + selector: + matchLabels: + app: {{ .Values.recommendationService.name }} + template: + metadata: + labels: + app: {{ .Values.recommendationService.name }} + spec: + {{- if .Values.serviceAccounts.create }} + serviceAccountName: {{ .Values.recommendationService.name }} + {{- else }} + serviceAccountName: default + {{- end }} + terminationGracePeriodSeconds: 5 + {{- if .Values.securityContext.enable }} + securityContext: + fsGroup: 1000 + runAsGroup: 1000 + runAsNonRoot: true + runAsUser: 1000 + {{- if .Values.seccompProfile.enable }} + seccompProfile: + type: {{ .Values.seccompProfile.type }} + {{- end }} + {{- end }} + containers: + - name: server + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + privileged: false + readOnlyRootFilesystem: true + image: {{ .Values.images.repository }}/{{ .Values.recommendationService.name }}:{{ .Values.images.tag | default .Chart.AppVersion }} + ports: + - containerPort: 8080 + readinessProbe: + periodSeconds: 5 + grpc: + port: 8080 + livenessProbe: + periodSeconds: 5 + grpc: + port: 8080 + env: + - name: PORT + value: "8080" + - name: PRODUCT_CATALOG_SERVICE_ADDR + value: "{{ .Values.productCatalogService.name }}:3550" + {{- if .Values.opentelemetryCollector.create }} + - name: COLLECTOR_SERVICE_ADDR + value: "{{ .Values.opentelemetryCollector.name }}:4317" + - name: OTEL_SERVICE_NAME + value: "{{ .Values.recommendationService.name }}" + {{- end }} + {{- if .Values.googleCloudOperations.tracing }} + - name: ENABLE_TRACING + value: "1" + {{- end }} + {{- if not .Values.googleCloudOperations.profiler }} + - name: DISABLE_PROFILER + value: "1" + {{- end }} + resources: + {{- toYaml .Values.recommendationService.resources | nindent 10 }} +--- +apiVersion: v1 +kind: Service +metadata: + name: {{ .Values.recommendationService.name }} + namespace: {{ .Release.Namespace }} + labels: + app: {{ .Values.recommendationService.name }} +spec: + type: ClusterIP + selector: + app: {{ .Values.recommendationService.name }} + ports: + - name: grpc + port: 8080 + targetPort: 8080 +{{- if .Values.networkPolicies.create }} +--- +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: {{ .Values.recommendationService.name }} + namespace: {{ .Release.Namespace }} +spec: + podSelector: + matchLabels: + app: {{ .Values.recommendationService.name }} + policyTypes: + - Ingress + - Egress + ingress: + - from: + - podSelector: + matchLabels: + app: {{ .Values.frontend.name }} + ports: + - port: 8080 + protocol: TCP + egress: + - {} +{{- end }} +{{- if .Values.sidecars.create }} +--- +apiVersion: networking.istio.io/v1beta1 +kind: Sidecar +metadata: + name: {{ .Values.recommendationService.name }} + namespace: {{ .Release.Namespace }} +spec: + workloadSelector: + labels: + app: {{ .Values.recommendationService.name }} + egress: + - hosts: + - istio-system/* + - ./{{ .Values.productCatalogService.name }}.{{ .Release.Namespace }}.svc.cluster.local + {{- if .Values.opentelemetryCollector.create }} + - ./{{ .Values.opentelemetryCollector.name }}.{{ .Release.Namespace }}.svc.cluster.local + {{- end }} +{{- end }} +{{- if .Values.authorizationPolicies.create }} +--- +apiVersion: security.istio.io/v1beta1 +kind: AuthorizationPolicy +metadata: + name: {{ .Values.recommendationService.name }} + namespace: {{ .Release.Namespace }} +spec: + selector: + matchLabels: + app: {{ .Values.recommendationService.name }} + rules: + - from: + - source: + principals: + {{- if .Values.serviceAccounts.create }} + - cluster.local/ns/{{ .Release.Namespace }}/sa/{{ .Values.frontend.name }} + {{- else }} + - cluster.local/ns/{{ .Release.Namespace }}/sa/default + {{- end }} + to: + - operation: + paths: + - /hipstershop.RecommendationService/ListRecommendations + methods: + - POST + ports: + - "8080" +{{- end }} +{{- end }} diff --git a/helm-chart/templates/shippingservice.yaml b/helm-chart/templates/shippingservice.yaml new file mode 100644 index 0000000..193bbd8 --- /dev/null +++ b/helm-chart/templates/shippingservice.yaml @@ -0,0 +1,183 @@ +# Copyright 2024 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +{{- if .Values.shippingService.create }} +{{- if .Values.serviceAccounts.create }} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ .Values.shippingService.name }} + namespace: {{.Release.Namespace}} + {{- if not .Values.serviceAccounts.annotationsOnlyForCartservice }} + {{- with .Values.serviceAccounts.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} + {{- end }} +--- +{{- end }} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ .Values.shippingService.name }} + namespace: {{ .Release.Namespace }} + labels: + app: {{ .Values.shippingService.name }} +spec: + selector: + matchLabels: + app: {{ .Values.shippingService.name }} + template: + metadata: + labels: + app: {{ .Values.shippingService.name }} + spec: + {{- if .Values.serviceAccounts.create }} + serviceAccountName: {{ .Values.shippingService.name }} + {{- else }} + serviceAccountName: default + {{- end }} + {{- if .Values.securityContext.enable }} + securityContext: + fsGroup: 1000 + runAsGroup: 1000 + runAsNonRoot: true + runAsUser: 1000 + {{- if .Values.seccompProfile.enable }} + seccompProfile: + type: {{ .Values.seccompProfile.type }} + {{- end }} + {{- end }} + containers: + - name: server + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + privileged: false + readOnlyRootFilesystem: true + image: {{ .Values.images.repository }}/{{ .Values.shippingService.name }}:{{ .Values.images.tag | default .Chart.AppVersion }} + ports: + - containerPort: 50051 + env: + - name: PORT + value: "50051" + {{- if not .Values.googleCloudOperations.profiler }} + - name: DISABLE_PROFILER + value: "1" + {{- end }} + readinessProbe: + periodSeconds: 5 + grpc: + port: 50051 + livenessProbe: + grpc: + port: 50051 + resources: + {{- toYaml .Values.shippingService.resources | nindent 10 }} +--- +apiVersion: v1 +kind: Service +metadata: + name: {{ .Values.shippingService.name }} + namespace: {{ .Release.Namespace }} + labels: + app: {{ .Values.shippingService.name }} +spec: + type: ClusterIP + selector: + app: {{ .Values.shippingService.name }} + ports: + - name: grpc + port: 50051 + targetPort: 50051 +{{- if .Values.networkPolicies.create }} +--- +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: {{ .Values.shippingService.name }} + namespace: {{ .Release.Namespace }} +spec: + podSelector: + matchLabels: + app: {{ .Values.shippingService.name }} + policyTypes: + - Ingress + - Egress + ingress: + - from: + - podSelector: + matchLabels: + app: {{ .Values.frontend.name }} + - podSelector: + matchLabels: + app: {{ .Values.checkoutService.name }} + ports: + - port: 50051 + protocol: TCP + egress: + - {} +{{- end }} +{{- if .Values.sidecars.create }} +--- +apiVersion: networking.istio.io/v1beta1 +kind: Sidecar +metadata: + name: {{ .Values.shippingService.name }} + namespace: {{ .Release.Namespace }} +spec: + workloadSelector: + labels: + app: {{ .Values.shippingService.name }} + egress: + - hosts: + - istio-system/* + {{- if .Values.opentelemetryCollector.create }} + - ./{{ .Values.opentelemetryCollector.name }}.{{ .Release.Namespace }}.svc.cluster.local + {{- end }} +{{- end }} +{{- if .Values.authorizationPolicies.create }} +--- +apiVersion: security.istio.io/v1beta1 +kind: AuthorizationPolicy +metadata: + name: {{ .Values.shippingService.name }} + namespace: {{ .Release.Namespace }} +spec: + selector: + matchLabels: + app: {{ .Values.shippingService.name }} + rules: + - from: + - source: + principals: + {{- if .Values.serviceAccounts.create }} + - cluster.local/ns/{{ .Release.Namespace }}/sa/{{ .Values.frontend.name }} + - cluster.local/ns/{{ .Release.Namespace }}/sa/{{ .Values.checkoutService.name }} + {{- else }} + - cluster.local/ns/{{ .Release.Namespace }}/sa/default + {{- end }} + to: + - operation: + paths: + - /hipstershop.ShippingService/GetQuote + - /hipstershop.ShippingService/ShipOrder + methods: + - POST + ports: + - "50051" +{{- end }} +{{- end }} diff --git a/helm-chart/values.yaml b/helm-chart/values.yaml new file mode 100644 index 0000000..7f403b1 --- /dev/null +++ b/helm-chart/values.yaml @@ -0,0 +1,220 @@ +# Copyright 2024 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Default values for onlineboutique. +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. + +images: + repository: us-central1-docker.pkg.dev/google-samples/microservices-demo + # Overrides the image tag whose default is the chart appVersion. + tag: "" + +serviceAccounts: + # Specifies whether service accounts should be created. + create: true + # Annotations to add to the service accounts. + annotations: {} + # Annotations to add only for the cartservice app. This allows to follow the least privilege principle where only cartservice needs to connect to external database for example via Workload Identity. + annotationsOnlyForCartservice: false + +networkPolicies: + # Specifies if the NetworkPolicies are created or not. If true, one fine granular NetworkPolicy per app is created. + create: false + +sidecars: + # Specifies if the Sidecars are created or not. If true, one fine granular Sidecar per app is created. + create: false + +authorizationPolicies: + # Specifies if the AuthorizationPolicies are created or not. If true, one fine granular AuthorizationPolicy per app is created. + create: false + +opentelemetryCollector: + create: false + name: opentelemetrycollector + # Specifies the project id for the otel collector. If set as "PROJECT_ID" (default value), an initContainer will automatically retrieve the project id value from the metadata server. + projectId: "PROJECT_ID" + +googleCloudOperations: + profiler: false + tracing: false + metrics: false + +seccompProfile: + enable: false + type: RuntimeDefault + +securityContext: + enable: true + +adService: + create: true + name: adservice + resources: + requests: + cpu: 200m + memory: 180Mi + limits: + cpu: 300m + memory: 300Mi + +cartService: + create: true + name: cartservice + resources: + requests: + cpu: 200m + memory: 128Mi + limits: + cpu: 300m + memory: 256Mi + +checkoutService: + create: true + name: checkoutservice + resources: + requests: + cpu: 100m + memory: 64Mi + limits: + cpu: 200m + memory: 128Mi + +currencyService: + create: true + name: currencyservice + resources: + requests: + cpu: 100m + memory: 128Mi + limits: + cpu: 200m + memory: 256Mi + +emailService: + create: true + name: emailservice + resources: + requests: + cpu: 100m + memory: 64Mi + limits: + cpu: 200m + memory: 128Mi + +frontend: + create: true + name: frontend + externalService: true + cymbalBranding: false + # One of: local, gcp, aws, azure, onprem, alibaba. When not set, defaults to "local" unless running in GKE, otherwise auto-sets to gcp. + platform: local + singleSharedSession: false + virtualService: + create: false + hosts: + - "*" + gateway: + name: asm-ingressgateway + namespace: asm-ingress + labelKey: asm + labelValue: ingressgateway + resources: + requests: + cpu: 100m + memory: 64Mi + limits: + cpu: 200m + memory: 128Mi + +loadGenerator: + create: true + name: loadgenerator + checkFrontendInitContainer: true + resources: + requests: + cpu: 300m + memory: 256Mi + limits: + cpu: 500m + memory: 512Mi + +paymentService: + create: true + name: paymentservice + resources: + requests: + cpu: 100m + memory: 128Mi + limits: + cpu: 200m + memory: 256Mi + +productCatalogService: + create: true + name: productcatalogservice + # Specifies an extra latency to any request on productcatalogservice, by default no extra latency. + extraLatency: "" + resources: + requests: + cpu: 100m + memory: 64Mi + limits: + cpu: 200m + memory: 128Mi + +recommendationService: + create: true + name: recommendationservice + resources: + requests: + cpu: 100m + memory: 220Mi + limits: + cpu: 200m + memory: 450Mi + +shippingService: + create: true + name: shippingservice + resources: + requests: + cpu: 100m + memory: 64Mi + limits: + cpu: 200m + memory: 128Mi + +cartDatabase: + # Specifies the type of the cartservice's database, could be either redis or spanner. + type: redis + connectionString: "redis-cart:6379" + inClusterRedis: + create: true + name: redis-cart + # Uses the public redis image from Docker Hub, otherwise will use the images.repository. + publicRepository: true + externalRedisTlsOrigination: + enable: false + name: exernal-redis-tls-origination + endpointAddress: "" + endpointPort: "" + certificate: "" + +# @TODO: This service is not currently available in Helm. +# https://github.com/GoogleCloudPlatform/microservices-demo/tree/main/kustomize/components/shopping-assistant +shoppingAssistantService: + create: false + name: shoppingassistantservice diff --git a/istio-manifests/allow-egress-googleapis.yaml b/istio-manifests/allow-egress-googleapis.yaml new file mode 100644 index 0000000..50410b2 --- /dev/null +++ b/istio-manifests/allow-egress-googleapis.yaml @@ -0,0 +1,46 @@ +# Copyright 2018 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: networking.istio.io/v1alpha3 +kind: ServiceEntry +metadata: + name: allow-egress-googleapis +spec: + hosts: + - "accounts.google.com" # Used to get token + - "*.googleapis.com" + ports: + - number: 80 + protocol: HTTP + name: http + - number: 443 + protocol: HTTPS + name: https +--- +apiVersion: networking.istio.io/v1alpha3 +kind: ServiceEntry +metadata: + name: allow-egress-google-metadata +spec: + hosts: + - metadata.google.internal + addresses: + - 169.254.169.254 # GCE metadata server + ports: + - number: 80 + name: http + protocol: HTTP + - number: 443 + name: https + protocol: HTTPS diff --git a/istio-manifests/frontend-gateway.yaml b/istio-manifests/frontend-gateway.yaml new file mode 100644 index 0000000..b3a1a64 --- /dev/null +++ b/istio-manifests/frontend-gateway.yaml @@ -0,0 +1,44 @@ +# Copyright 2018 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: networking.istio.io/v1alpha3 +kind: Gateway +metadata: + name: frontend-gateway +spec: + selector: + istio: ingressgateway # use Istio default gateway implementation + servers: + - port: + number: 80 + name: http + protocol: HTTP + hosts: + - "*" +--- +apiVersion: networking.istio.io/v1alpha3 +kind: VirtualService +metadata: + name: frontend-ingress +spec: + hosts: + - "*" + gateways: + - frontend-gateway + http: + - route: + - destination: + host: frontend + port: + number: 80 diff --git a/istio-manifests/frontend.yaml b/istio-manifests/frontend.yaml new file mode 100644 index 0000000..23cd648 --- /dev/null +++ b/istio-manifests/frontend.yaml @@ -0,0 +1,27 @@ +# Copyright 2018 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: networking.istio.io/v1alpha3 +kind: VirtualService +metadata: + name: frontend +spec: + hosts: + - "frontend.default.svc.cluster.local" + http: + - route: + - destination: + host: frontend + port: + number: 80 diff --git a/kubernetes-manifests/README.md b/kubernetes-manifests/README.md new file mode 100644 index 0000000..ed852b7 --- /dev/null +++ b/kubernetes-manifests/README.md @@ -0,0 +1,8 @@ +# ./kubernetes-manifests + +:warning: Kubernetes manifests provided in this directory are not directly +deployable to a cluster. They are meant to be used with `skaffold` command to +insert the correct `image:` tags. + +Use the manifests in [/release](/release) directory which are configured with +pre-built public images. diff --git a/kubernetes-manifests/adservice.yaml b/kubernetes-manifests/adservice.yaml new file mode 100644 index 0000000..25ff1b7 --- /dev/null +++ b/kubernetes-manifests/adservice.yaml @@ -0,0 +1,88 @@ +# Copyright 2018 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: apps/v1 +kind: Deployment +metadata: + name: adservice + labels: + app: adservice +spec: + selector: + matchLabels: + app: adservice + template: + metadata: + labels: + app: adservice + spec: + serviceAccountName: adservice + terminationGracePeriodSeconds: 5 + securityContext: + fsGroup: 1000 + runAsGroup: 1000 + runAsNonRoot: true + runAsUser: 1000 + containers: + - name: server + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + privileged: false + readOnlyRootFilesystem: true + image: adservice + ports: + - containerPort: 9555 + env: + - name: PORT + value: "9555" + resources: + requests: + cpu: 200m + memory: 180Mi + limits: + cpu: 300m + memory: 300Mi + readinessProbe: + initialDelaySeconds: 20 + periodSeconds: 15 + grpc: + port: 9555 + livenessProbe: + initialDelaySeconds: 20 + periodSeconds: 15 + grpc: + port: 9555 +--- +apiVersion: v1 +kind: Service +metadata: + name: adservice + labels: + app: adservice +spec: + type: ClusterIP + selector: + app: adservice + ports: + - name: grpc + port: 9555 + targetPort: 9555 +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: adservice diff --git a/kubernetes-manifests/cartservice.yaml b/kubernetes-manifests/cartservice.yaml new file mode 100644 index 0000000..5470d36 --- /dev/null +++ b/kubernetes-manifests/cartservice.yaml @@ -0,0 +1,156 @@ +# Copyright 2018 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: apps/v1 +kind: Deployment +metadata: + name: cartservice + labels: + app: cartservice +spec: + selector: + matchLabels: + app: cartservice + template: + metadata: + labels: + app: cartservice + spec: + serviceAccountName: cartservice + terminationGracePeriodSeconds: 5 + securityContext: + fsGroup: 1000 + runAsGroup: 1000 + runAsNonRoot: true + runAsUser: 1000 + containers: + - name: server + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + privileged: false + readOnlyRootFilesystem: true + image: cartservice + ports: + - containerPort: 7070 + env: + - name: REDIS_ADDR + value: "redis-cart:6379" + resources: + requests: + cpu: 200m + memory: 64Mi + limits: + cpu: 300m + memory: 128Mi + readinessProbe: + initialDelaySeconds: 15 + grpc: + port: 7070 + livenessProbe: + initialDelaySeconds: 15 + periodSeconds: 10 + grpc: + port: 7070 +--- +apiVersion: v1 +kind: Service +metadata: + name: cartservice + labels: + app: cartservice +spec: + type: ClusterIP + selector: + app: cartservice + ports: + - name: grpc + port: 7070 + targetPort: 7070 +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: cartservice +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: redis-cart + labels: + app: redis-cart +spec: + selector: + matchLabels: + app: redis-cart + template: + metadata: + labels: + app: redis-cart + spec: + securityContext: + fsGroup: 1000 + runAsGroup: 1000 + runAsNonRoot: true + runAsUser: 1000 + containers: + - name: redis + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + privileged: false + readOnlyRootFilesystem: true + image: redis:alpine + ports: + - containerPort: 6379 + readinessProbe: + periodSeconds: 5 + tcpSocket: + port: 6379 + livenessProbe: + periodSeconds: 5 + tcpSocket: + port: 6379 + volumeMounts: + - mountPath: /data + name: redis-data + resources: + limits: + memory: 256Mi + cpu: 125m + requests: + cpu: 70m + memory: 200Mi + volumes: + - name: redis-data + emptyDir: {} +--- +apiVersion: v1 +kind: Service +metadata: + name: redis-cart + labels: + app: redis-cart +spec: + type: ClusterIP + selector: + app: redis-cart + ports: + - name: tcp-redis + port: 6379 + targetPort: 6379 diff --git a/kubernetes-manifests/checkoutservice.yaml b/kubernetes-manifests/checkoutservice.yaml new file mode 100644 index 0000000..c7dc5a0 --- /dev/null +++ b/kubernetes-manifests/checkoutservice.yaml @@ -0,0 +1,95 @@ +# Copyright 2018 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: apps/v1 +kind: Deployment +metadata: + name: checkoutservice + labels: + app: checkoutservice +spec: + selector: + matchLabels: + app: checkoutservice + template: + metadata: + labels: + app: checkoutservice + spec: + serviceAccountName: checkoutservice + securityContext: + fsGroup: 1000 + runAsGroup: 1000 + runAsNonRoot: true + runAsUser: 1000 + containers: + - name: server + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + privileged: false + readOnlyRootFilesystem: true + image: checkoutservice + ports: + - containerPort: 5050 + readinessProbe: + grpc: + port: 5050 + livenessProbe: + grpc: + port: 5050 + env: + - name: PORT + value: "5050" + - name: PRODUCT_CATALOG_SERVICE_ADDR + value: "productcatalogservice:3550" + - name: SHIPPING_SERVICE_ADDR + value: "shippingservice:50051" + - name: PAYMENT_SERVICE_ADDR + value: "paymentservice:50051" + - name: EMAIL_SERVICE_ADDR + value: "emailservice:5000" + - name: CURRENCY_SERVICE_ADDR + value: "currencyservice:7000" + - name: CART_SERVICE_ADDR + value: "cartservice:7070" + resources: + requests: + cpu: 100m + memory: 64Mi + limits: + cpu: 200m + memory: 128Mi +--- +apiVersion: v1 +kind: Service +metadata: + name: checkoutservice + labels: + app: checkoutservice +spec: + type: ClusterIP + selector: + app: checkoutservice + ports: + - name: grpc + port: 5050 + targetPort: 5050 +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: checkoutservice diff --git a/kubernetes-manifests/currencyservice.yaml b/kubernetes-manifests/currencyservice.yaml new file mode 100644 index 0000000..58fc032 --- /dev/null +++ b/kubernetes-manifests/currencyservice.yaml @@ -0,0 +1,87 @@ +# Copyright 2018 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: apps/v1 +kind: Deployment +metadata: + name: currencyservice + labels: + app: currencyservice +spec: + selector: + matchLabels: + app: currencyservice + template: + metadata: + labels: + app: currencyservice + spec: + serviceAccountName: currencyservice + terminationGracePeriodSeconds: 5 + securityContext: + fsGroup: 1000 + runAsGroup: 1000 + runAsNonRoot: true + runAsUser: 1000 + containers: + - name: server + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + privileged: false + readOnlyRootFilesystem: true + image: currencyservice + ports: + - name: grpc + containerPort: 7000 + env: + - name: PORT + value: "7000" + - name: DISABLE_PROFILER + value: "1" + readinessProbe: + grpc: + port: 7000 + livenessProbe: + grpc: + port: 7000 + resources: + requests: + cpu: 100m + memory: 64Mi + limits: + cpu: 200m + memory: 128Mi +--- +apiVersion: v1 +kind: Service +metadata: + name: currencyservice + labels: + app: currencyservice +spec: + type: ClusterIP + selector: + app: currencyservice + ports: + - name: grpc + port: 7000 + targetPort: 7000 +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: currencyservice diff --git a/kubernetes-manifests/emailservice.yaml b/kubernetes-manifests/emailservice.yaml new file mode 100644 index 0000000..bea781a --- /dev/null +++ b/kubernetes-manifests/emailservice.yaml @@ -0,0 +1,88 @@ +# Copyright 2018 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: apps/v1 +kind: Deployment +metadata: + name: emailservice + labels: + app: emailservice +spec: + selector: + matchLabels: + app: emailservice + template: + metadata: + labels: + app: emailservice + spec: + serviceAccountName: emailservice + terminationGracePeriodSeconds: 5 + securityContext: + fsGroup: 1000 + runAsGroup: 1000 + runAsNonRoot: true + runAsUser: 1000 + containers: + - name: server + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + privileged: false + readOnlyRootFilesystem: true + image: emailservice + ports: + - containerPort: 8080 + env: + - name: PORT + value: "8080" + - name: DISABLE_PROFILER + value: "1" + readinessProbe: + periodSeconds: 5 + grpc: + port: 8080 + livenessProbe: + periodSeconds: 5 + grpc: + port: 8080 + resources: + requests: + cpu: 100m + memory: 64Mi + limits: + cpu: 200m + memory: 128Mi +--- +apiVersion: v1 +kind: Service +metadata: + name: emailservice + labels: + app: emailservice +spec: + type: ClusterIP + selector: + app: emailservice + ports: + - name: grpc + port: 5000 + targetPort: 8080 +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: emailservice diff --git a/kubernetes-manifests/frontend.yaml b/kubernetes-manifests/frontend.yaml new file mode 100644 index 0000000..5aec4f3 --- /dev/null +++ b/kubernetes-manifests/frontend.yaml @@ -0,0 +1,141 @@ +# Copyright 2018 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: apps/v1 +kind: Deployment +metadata: + name: frontend + labels: + app: frontend +spec: + selector: + matchLabels: + app: frontend + template: + metadata: + labels: + app: frontend + annotations: + sidecar.istio.io/rewriteAppHTTPProbers: "true" + spec: + serviceAccountName: frontend + securityContext: + fsGroup: 1000 + runAsGroup: 1000 + runAsNonRoot: true + runAsUser: 1000 + containers: + - name: server + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + privileged: false + readOnlyRootFilesystem: true + image: frontend + ports: + - containerPort: 8080 + readinessProbe: + initialDelaySeconds: 10 + httpGet: + path: "/_healthz" + port: 8080 + httpHeaders: + - name: "Cookie" + value: "shop_session-id=x-readiness-probe" + livenessProbe: + initialDelaySeconds: 10 + httpGet: + path: "/_healthz" + port: 8080 + httpHeaders: + - name: "Cookie" + value: "shop_session-id=x-liveness-probe" + env: + - name: PORT + value: "8080" + - name: PRODUCT_CATALOG_SERVICE_ADDR + value: "productcatalogservice:3550" + - name: CURRENCY_SERVICE_ADDR + value: "currencyservice:7000" + - name: CART_SERVICE_ADDR + value: "cartservice:7070" + - name: RECOMMENDATION_SERVICE_ADDR + value: "recommendationservice:8080" + - name: SHIPPING_SERVICE_ADDR + value: "shippingservice:50051" + - name: CHECKOUT_SERVICE_ADDR + value: "checkoutservice:5050" + - name: AD_SERVICE_ADDR + value: "adservice:9555" + - name: SHOPPING_ASSISTANT_SERVICE_ADDR + value: "shoppingassistantservice:80" + # # ENV_PLATFORM: One of: local, gcp, aws, azure, onprem, alibaba + # # When not set, defaults to "local" unless running in GKE, otherwies auto-sets to gcp + # - name: ENV_PLATFORM + # value: "aws" + - name: ENABLE_PROFILER + value: "0" + # - name: CYMBAL_BRANDING + # value: "true" + # - name: ENABLE_ASSISTANT + # value: "true" + # - name: FRONTEND_MESSAGE + # value: "Replace this with a message you want to display on all pages." + # As part of an optional Google Cloud demo, you can run an optional microservice called the "packaging service". + # - name: PACKAGING_SERVICE_URL + # value: "" # This value would look like "http://123.123.123" + resources: + requests: + cpu: 100m + memory: 64Mi + limits: + cpu: 200m + memory: 128Mi +--- +apiVersion: v1 +kind: Service +metadata: + name: frontend + labels: + app: frontend +spec: + type: ClusterIP + selector: + app: frontend + ports: + - name: http + port: 80 + targetPort: 8080 +--- +apiVersion: v1 +kind: Service +metadata: + name: frontend-external + labels: + app: frontend +spec: + type: LoadBalancer + selector: + app: frontend + ports: + - name: http + port: 80 + targetPort: 8080 +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: frontend diff --git a/kubernetes-manifests/kustomization.yaml b/kubernetes-manifests/kustomization.yaml new file mode 100644 index 0000000..bf69f4e --- /dev/null +++ b/kubernetes-manifests/kustomization.yaml @@ -0,0 +1,39 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: + - adservice.yaml + - cartservice.yaml + - checkoutservice.yaml + - currencyservice.yaml + - emailservice.yaml + - frontend.yaml +# - loadgenerator.yaml # During development, the loadgenerator module inside skaffold.yaml will be used. + - paymentservice.yaml + - productcatalogservice.yaml + - recommendationservice.yaml + - shippingservice.yaml +# components: +# - ../kustomize/components/cymbal-branding +# - ../kustomize/components/google-cloud-operations +# - ../kustomize/components/memorystore +# - ../kustomize/components/network-policies +# - ../kustomize/components/alloydb +# - ../kustomize/components/shopping-assistant +# - ../kustomize/components/spanner +# - ../kustomize/components/container-images-tag +# - ../kustomize/components/container-images-tag-suffix +# - ../kustomize/components/container-images-registry diff --git a/kubernetes-manifests/loadgenerator.yaml b/kubernetes-manifests/loadgenerator.yaml new file mode 100644 index 0000000..54c2db2 --- /dev/null +++ b/kubernetes-manifests/loadgenerator.yaml @@ -0,0 +1,99 @@ +# Copyright 2018 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +apiVersion: apps/v1 +kind: Deployment +metadata: + name: loadgenerator + labels: + app: loadgenerator +spec: + selector: + matchLabels: + app: loadgenerator + replicas: 1 + template: + metadata: + labels: + app: loadgenerator + annotations: + sidecar.istio.io/rewriteAppHTTPProbers: "true" + spec: + serviceAccountName: loadgenerator + terminationGracePeriodSeconds: 5 + restartPolicy: Always + securityContext: + fsGroup: 1000 + runAsGroup: 1000 + runAsNonRoot: true + runAsUser: 1000 + initContainers: + - command: + - /bin/sh + - -exc + - | + MAX_RETRIES=12 + RETRY_INTERVAL=10 + for i in $(seq 1 $MAX_RETRIES); do + echo "Attempt $i: Pinging frontend: ${FRONTEND_ADDR}..." + STATUSCODE=$(wget --server-response http://${FRONTEND_ADDR} 2>&1 | awk '/^ HTTP/{print $2}') + if [ $STATUSCODE -eq 200 ]; then + echo "Frontend is reachable." + exit 0 + fi + echo "Error: Could not reach frontend - Status code: ${STATUSCODE}" + sleep $RETRY_INTERVAL + done + echo "Failed to reach frontend after $MAX_RETRIES attempts." + exit 1 + name: frontend-check + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + privileged: false + readOnlyRootFilesystem: true + image: busybox:latest + env: + - name: FRONTEND_ADDR + value: "frontend:80" + containers: + - name: main + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + privileged: false + readOnlyRootFilesystem: true + image: loadgenerator + env: + - name: FRONTEND_ADDR + value: "frontend:80" + - name: USERS + value: "10" + - name: RATE + value: "1" + resources: + requests: + cpu: 300m + memory: 256Mi + limits: + cpu: 500m + memory: 512Mi +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: loadgenerator diff --git a/kubernetes-manifests/paymentservice.yaml b/kubernetes-manifests/paymentservice.yaml new file mode 100644 index 0000000..a0a1526 --- /dev/null +++ b/kubernetes-manifests/paymentservice.yaml @@ -0,0 +1,86 @@ +# Copyright 2018 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: apps/v1 +kind: Deployment +metadata: + name: paymentservice + labels: + app: paymentservice +spec: + selector: + matchLabels: + app: paymentservice + template: + metadata: + labels: + app: paymentservice + spec: + serviceAccountName: paymentservice + terminationGracePeriodSeconds: 5 + securityContext: + fsGroup: 1000 + runAsGroup: 1000 + runAsNonRoot: true + runAsUser: 1000 + containers: + - name: server + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + privileged: false + readOnlyRootFilesystem: true + image: paymentservice + ports: + - containerPort: 50051 + env: + - name: PORT + value: "50051" + - name: DISABLE_PROFILER + value: "1" + readinessProbe: + grpc: + port: 50051 + livenessProbe: + grpc: + port: 50051 + resources: + requests: + cpu: 100m + memory: 64Mi + limits: + cpu: 200m + memory: 128Mi +--- +apiVersion: v1 +kind: Service +metadata: + name: paymentservice + labels: + app: paymentservice +spec: + type: ClusterIP + selector: + app: paymentservice + ports: + - name: grpc + port: 50051 + targetPort: 50051 +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: paymentservice diff --git a/kubernetes-manifests/productcatalogservice.yaml b/kubernetes-manifests/productcatalogservice.yaml new file mode 100644 index 0000000..6774650 --- /dev/null +++ b/kubernetes-manifests/productcatalogservice.yaml @@ -0,0 +1,86 @@ +# Copyright 2018 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: apps/v1 +kind: Deployment +metadata: + name: productcatalogservice + labels: + app: productcatalogservice +spec: + selector: + matchLabels: + app: productcatalogservice + template: + metadata: + labels: + app: productcatalogservice + spec: + serviceAccountName: productcatalogservice + terminationGracePeriodSeconds: 5 + securityContext: + fsGroup: 1000 + runAsGroup: 1000 + runAsNonRoot: true + runAsUser: 1000 + containers: + - name: server + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + privileged: false + readOnlyRootFilesystem: true + image: productcatalogservice + ports: + - containerPort: 3550 + env: + - name: PORT + value: "3550" + - name: DISABLE_PROFILER + value: "1" + readinessProbe: + grpc: + port: 3550 + livenessProbe: + grpc: + port: 3550 + resources: + requests: + cpu: 100m + memory: 64Mi + limits: + cpu: 200m + memory: 128Mi +--- +apiVersion: v1 +kind: Service +metadata: + name: productcatalogservice + labels: + app: productcatalogservice +spec: + type: ClusterIP + selector: + app: productcatalogservice + ports: + - name: grpc + port: 3550 + targetPort: 3550 +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: productcatalogservice diff --git a/kubernetes-manifests/recommendationservice.yaml b/kubernetes-manifests/recommendationservice.yaml new file mode 100644 index 0000000..2d2c6ee --- /dev/null +++ b/kubernetes-manifests/recommendationservice.yaml @@ -0,0 +1,90 @@ +# Copyright 2018 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: apps/v1 +kind: Deployment +metadata: + name: recommendationservice + labels: + app: recommendationservice +spec: + selector: + matchLabels: + app: recommendationservice + template: + metadata: + labels: + app: recommendationservice + spec: + serviceAccountName: recommendationservice + terminationGracePeriodSeconds: 5 + securityContext: + fsGroup: 1000 + runAsGroup: 1000 + runAsNonRoot: true + runAsUser: 1000 + containers: + - name: server + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + privileged: false + readOnlyRootFilesystem: true + image: recommendationservice + ports: + - containerPort: 8080 + readinessProbe: + periodSeconds: 5 + grpc: + port: 8080 + livenessProbe: + periodSeconds: 5 + grpc: + port: 8080 + env: + - name: PORT + value: "8080" + - name: PRODUCT_CATALOG_SERVICE_ADDR + value: "productcatalogservice:3550" + - name: DISABLE_PROFILER + value: "1" + resources: + requests: + cpu: 100m + memory: 220Mi + limits: + cpu: 200m + memory: 450Mi +--- +apiVersion: v1 +kind: Service +metadata: + name: recommendationservice + labels: + app: recommendationservice +spec: + type: ClusterIP + selector: + app: recommendationservice + ports: + - name: grpc + port: 8080 + targetPort: 8080 +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: recommendationservice diff --git a/kubernetes-manifests/shippingservice.yaml b/kubernetes-manifests/shippingservice.yaml new file mode 100644 index 0000000..41cd526 --- /dev/null +++ b/kubernetes-manifests/shippingservice.yaml @@ -0,0 +1,86 @@ +# Copyright 2018 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: apps/v1 +kind: Deployment +metadata: + name: shippingservice + labels: + app: shippingservice +spec: + selector: + matchLabels: + app: shippingservice + template: + metadata: + labels: + app: shippingservice + spec: + serviceAccountName: shippingservice + securityContext: + fsGroup: 1000 + runAsGroup: 1000 + runAsNonRoot: true + runAsUser: 1000 + containers: + - name: server + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + privileged: false + readOnlyRootFilesystem: true + image: shippingservice + ports: + - containerPort: 50051 + env: + - name: PORT + value: "50051" + - name: DISABLE_PROFILER + value: "1" + readinessProbe: + periodSeconds: 5 + grpc: + port: 50051 + livenessProbe: + grpc: + port: 50051 + resources: + requests: + cpu: 100m + memory: 64Mi + limits: + cpu: 200m + memory: 128Mi +--- +apiVersion: v1 +kind: Service +metadata: + name: shippingservice + labels: + app: shippingservice +spec: + type: ClusterIP + selector: + app: shippingservice + ports: + - name: grpc + port: 50051 + targetPort: 50051 +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: shippingservice diff --git a/kustomize/README.md b/kustomize/README.md new file mode 100644 index 0000000..c153342 --- /dev/null +++ b/kustomize/README.md @@ -0,0 +1,140 @@ +# Use Online Boutique with Kustomize + +This page contains instructions on deploying variations of the [Online Boutique](https://github.com/GoogleCloudPlatform/microservices-demo) sample application using [Kustomize](https://kustomize.io/). Each variations is designed as a [**Kustomize component**](https://github.com/kubernetes-sigs/kustomize/blob/master/examples/components.md), so multiple variations can be composed together in the deployment. + +## What is Kustomize? + +Kustomize is a Kubernetes configuration management tool that allows users to customize their manifest configurations without duplication. Its commands are built into `kubectl` as `apply -k`. More information on Kustomize can be found on the [official Kustomize website](https://kustomize.io/). + +## Prerequisites + +Optionally, [install the `kustomize` binary](https://kubectl.docs.kubernetes.io/installation/) to avoid manually editing a `kustomization.yaml` file. Online Boutique's instructions will often use `kustomize edit` (like `kustomize edit add component components/some-component`), but you can skip these commands and instead add components manually to the [`/kustomize/kustomization.yaml` file](/kustomize/kustomization.yaml). + +You need to have a Kubernetes cluster where you will deploy the Online Boutique's Kubernetes manifests. To set up a GKE (Google Kubernetes Engine) cluster, you can follow the instruction in the [root `/README.md`](/). + +## Deploy Online Boutique with Kustomize + +1. From the root folder of this repository, navigate to the `kustomize/` directory. + + ```bash + cd kustomize/ + ``` + +1. See what the default Kustomize configuration defined by `kustomize/kustomization.yaml` will generate (without actually deploying them yet). + + ```bash + kubectl kustomize . + ``` + +1. Apply the default Kustomize configuration (`kustomize/kustomization.yaml`). + + ```bash + kubectl apply -k . + ``` + +1. Wait for all Pods to show `STATUS` of `Running`. + + ```bash + kubectl get pods + ``` + + The output should be similar to the following: + + ```terminal + NAME READY STATUS RESTARTS AGE + adservice-76bdd69666-ckc5j 1/1 Running 0 2m58s + cartservice-66d497c6b7-dp5jr 1/1 Running 0 2m59s + checkoutservice-666c784bd6-4jd22 1/1 Running 0 3m1s + currencyservice-5d5d496984-4jmd7 1/1 Running 0 2m59s + emailservice-667457d9d6-75jcq 1/1 Running 0 3m2s + frontend-6b8d69b9fb-wjqdg 1/1 Running 0 3m1s + loadgenerator-665b5cd444-gwqdq 1/1 Running 0 3m + paymentservice-68596d6dd6-bf6bv 1/1 Running 0 3m + productcatalogservice-557d474574-888kr 1/1 Running 0 3m + recommendationservice-69c56b74d4-7z8r5 1/1 Running 0 3m1s + shippingservice-6ccc89f8fd-v686r 1/1 Running 0 2m58s + ``` + + _Note: It may take 2-3 minutes before the changes are reflected on the deployment._ + +1. Access the web frontend in a browser using the frontend's `EXTERNAL_IP`. + + ```bash + kubectl get service frontend-external | awk '{print $4}' + ``` + + Note: you may see `` while GCP provisions the load balancer. If this happens, wait a few minutes and re-run the command. + +## Deploy Online Boutique variations with Kustomize + +Here is the list of the variations available as Kustomize components that you could leverage: + +- [**Change to the Cymbal Shops Branding**](components/cymbal-branding) + - Changes all Online Boutique-related branding to Google Cloud's fictitious company — Cymbal Shops. The code adds/enables an environment variable `CYMBAL_BRANDING` in the `frontend` service. +- [**Integrate with Google Cloud Operations**](components/google-cloud-operations) + - Enables Monitoring (Stats), Tracing, and Profiler for various services within Online Boutique. The code adds the appropriare environment variables (`ENABLE_STATS`, `ENABLE_TRACING`, `DISABLE_PROFILER`) for each YAML config file. +- [**Integrate with Memorystore (Redis)**](components/memorystore) + - The default Online Boutique deployment uses the in-cluster `redis` database for storing the contents of its shopping cart. The Memorystore deployment variation overrides the default database with its own Memorystore (Redis) database. These changes directly affect `cartservice`. +- [**Integrate with Spanner**](components/spanner) + - The default Online Boutique deployment uses the in-cluster `redis` database for storing the contents of its shopping cart. The Spanner deployment variation overrides the default database with its own Spanner database. These changes directly affect `cartservice`. +- [**Integrate with AlloyDB**](components/alloydb) + - The default Online Boutique deployment uses the in-cluster `redis` database for storing the contents of its shopping cart. The AlloyDB deployment variation overrides the default database with its own AlloyDB database. +These changes directly affect `cartservice`. +- [**Secure with Network Policies**](components/network-policies) + - Deploy fine granular `NetworkPolicies` for Online Boutique. +- [**Update the registry name of the container images**](components/container-images-registry) +- [**Update the image tag of the container images**](components/container-images-tag) +- [**Add an image tag suffix to the container images**](components/container-images-tag-suffix) +- [**Do not expose the `frontend` publicly**](components/non-public-frontend) +- [**Set the `frontend` to manage only one single shared session**](components/single-shared-session) +- [**Configure `Istio` service mesh resources**](components/service-mesh-istio) + +### Select variations + +To customize Online Boutique with its variations, you need to update the default `kustomize/kustomization.yaml` file. You could do that manually, use `sed`, or use the `kustomize edit` command like illustrated below. + +#### Use `kustomize edit` to select variations + +Here is an example with the [**Cymbal Shops Branding**](components/cymbal-branding) variation, from the `kustomize/` folder, run the command below: + +```bash +kustomize edit add component components/cymbal-branding +``` + +You could now combine it with other variations, like for example with the [**Google Cloud Operations**](components/google-cloud-operations) variation: + +```bash +kustomize edit add component components/google-cloud-operations +``` + +### Deploy selected variations + +Like explained earlier, you can locally render these manifests by running `kubectl kustomize .` as well as deploying them by running `kubectl apply -k .`. + +So for example, the associated `kustomization.yaml` could look like: + +```yaml +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: +- base +components: +- components/cymbal-branding +- components/google-cloud-operations +``` + +### Use remote Kustomize targets + +Kustomize allows you to reference public remote resources so the `kustomization.yaml` could look like: + +```yaml +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: +- github.com/GoogleCloudPlatform/microservices-demo/kustomize/base +components: +- github.com/GoogleCloudPlatform/microservices-demo/kustomize/components/cymbal-branding +- github.com/GoogleCloudPlatform/microservices-demo/kustomize/components/google-cloud-operations +``` + +Learn more about [Kustomize remote targets](https://github.com/kubernetes-sigs/kustomize/blob/master/examples/remoteBuild.md). diff --git a/kustomize/base/adservice.yaml b/kustomize/base/adservice.yaml new file mode 100644 index 0000000..369e87d --- /dev/null +++ b/kustomize/base/adservice.yaml @@ -0,0 +1,88 @@ +# Copyright 2018 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: apps/v1 +kind: Deployment +metadata: + name: adservice + labels: + app: adservice +spec: + selector: + matchLabels: + app: adservice + template: + metadata: + labels: + app: adservice + spec: + serviceAccountName: adservice + terminationGracePeriodSeconds: 5 + securityContext: + fsGroup: 1000 + runAsGroup: 1000 + runAsNonRoot: true + runAsUser: 1000 + containers: + - name: server + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + privileged: false + readOnlyRootFilesystem: true + image: us-central1-docker.pkg.dev/google-samples/microservices-demo/adservice:v0.10.4 + ports: + - containerPort: 9555 + env: + - name: PORT + value: "9555" + resources: + requests: + cpu: 200m + memory: 180Mi + limits: + cpu: 300m + memory: 300Mi + readinessProbe: + initialDelaySeconds: 20 + periodSeconds: 15 + grpc: + port: 9555 + livenessProbe: + initialDelaySeconds: 20 + periodSeconds: 15 + grpc: + port: 9555 +--- +apiVersion: v1 +kind: Service +metadata: + name: adservice + labels: + app: adservice +spec: + type: ClusterIP + selector: + app: adservice + ports: + - name: grpc + port: 9555 + targetPort: 9555 +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: adservice diff --git a/kustomize/base/cartservice.yaml b/kustomize/base/cartservice.yaml new file mode 100644 index 0000000..1d4a49f --- /dev/null +++ b/kustomize/base/cartservice.yaml @@ -0,0 +1,156 @@ +# Copyright 2018 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: apps/v1 +kind: Deployment +metadata: + name: cartservice + labels: + app: cartservice +spec: + selector: + matchLabels: + app: cartservice + template: + metadata: + labels: + app: cartservice + spec: + serviceAccountName: cartservice + terminationGracePeriodSeconds: 5 + securityContext: + fsGroup: 1000 + runAsGroup: 1000 + runAsNonRoot: true + runAsUser: 1000 + containers: + - name: server + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + privileged: false + readOnlyRootFilesystem: true + image: us-central1-docker.pkg.dev/google-samples/microservices-demo/cartservice:v0.10.4 + ports: + - containerPort: 7070 + env: + - name: REDIS_ADDR + value: "redis-cart:6379" + resources: + requests: + cpu: 200m + memory: 64Mi + limits: + cpu: 300m + memory: 128Mi + readinessProbe: + initialDelaySeconds: 15 + grpc: + port: 7070 + livenessProbe: + initialDelaySeconds: 15 + periodSeconds: 10 + grpc: + port: 7070 +--- +apiVersion: v1 +kind: Service +metadata: + name: cartservice + labels: + app: cartservice +spec: + type: ClusterIP + selector: + app: cartservice + ports: + - name: grpc + port: 7070 + targetPort: 7070 +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: cartservice +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: redis-cart + labels: + app: redis-cart +spec: + selector: + matchLabels: + app: redis-cart + template: + metadata: + labels: + app: redis-cart + spec: + securityContext: + fsGroup: 1000 + runAsGroup: 1000 + runAsNonRoot: true + runAsUser: 1000 + containers: + - name: redis + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + privileged: false + readOnlyRootFilesystem: true + image: redis:alpine + ports: + - containerPort: 6379 + readinessProbe: + periodSeconds: 5 + tcpSocket: + port: 6379 + livenessProbe: + periodSeconds: 5 + tcpSocket: + port: 6379 + volumeMounts: + - mountPath: /data + name: redis-data + resources: + limits: + memory: 256Mi + cpu: 125m + requests: + cpu: 70m + memory: 200Mi + volumes: + - name: redis-data + emptyDir: {} +--- +apiVersion: v1 +kind: Service +metadata: + name: redis-cart + labels: + app: redis-cart +spec: + type: ClusterIP + selector: + app: redis-cart + ports: + - name: tcp-redis + port: 6379 + targetPort: 6379 diff --git a/kustomize/base/checkoutservice.yaml b/kustomize/base/checkoutservice.yaml new file mode 100644 index 0000000..11ef645 --- /dev/null +++ b/kustomize/base/checkoutservice.yaml @@ -0,0 +1,95 @@ +# Copyright 2018 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: apps/v1 +kind: Deployment +metadata: + name: checkoutservice + labels: + app: checkoutservice +spec: + selector: + matchLabels: + app: checkoutservice + template: + metadata: + labels: + app: checkoutservice + spec: + serviceAccountName: checkoutservice + securityContext: + fsGroup: 1000 + runAsGroup: 1000 + runAsNonRoot: true + runAsUser: 1000 + containers: + - name: server + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + privileged: false + readOnlyRootFilesystem: true + image: us-central1-docker.pkg.dev/google-samples/microservices-demo/checkoutservice:v0.10.4 + ports: + - containerPort: 5050 + readinessProbe: + grpc: + port: 5050 + livenessProbe: + grpc: + port: 5050 + env: + - name: PORT + value: "5050" + - name: PRODUCT_CATALOG_SERVICE_ADDR + value: "productcatalogservice:3550" + - name: SHIPPING_SERVICE_ADDR + value: "shippingservice:50051" + - name: PAYMENT_SERVICE_ADDR + value: "paymentservice:50051" + - name: EMAIL_SERVICE_ADDR + value: "emailservice:5000" + - name: CURRENCY_SERVICE_ADDR + value: "currencyservice:7000" + - name: CART_SERVICE_ADDR + value: "cartservice:7070" + resources: + requests: + cpu: 100m + memory: 64Mi + limits: + cpu: 200m + memory: 128Mi +--- +apiVersion: v1 +kind: Service +metadata: + name: checkoutservice + labels: + app: checkoutservice +spec: + type: ClusterIP + selector: + app: checkoutservice + ports: + - name: grpc + port: 5050 + targetPort: 5050 +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: checkoutservice diff --git a/kustomize/base/currencyservice.yaml b/kustomize/base/currencyservice.yaml new file mode 100644 index 0000000..26ec6c3 --- /dev/null +++ b/kustomize/base/currencyservice.yaml @@ -0,0 +1,87 @@ +# Copyright 2018 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: apps/v1 +kind: Deployment +metadata: + name: currencyservice + labels: + app: currencyservice +spec: + selector: + matchLabels: + app: currencyservice + template: + metadata: + labels: + app: currencyservice + spec: + serviceAccountName: currencyservice + terminationGracePeriodSeconds: 5 + securityContext: + fsGroup: 1000 + runAsGroup: 1000 + runAsNonRoot: true + runAsUser: 1000 + containers: + - name: server + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + privileged: false + readOnlyRootFilesystem: true + image: us-central1-docker.pkg.dev/google-samples/microservices-demo/currencyservice:v0.10.4 + ports: + - name: grpc + containerPort: 7000 + env: + - name: PORT + value: "7000" + - name: DISABLE_PROFILER + value: "1" + readinessProbe: + grpc: + port: 7000 + livenessProbe: + grpc: + port: 7000 + resources: + requests: + cpu: 100m + memory: 64Mi + limits: + cpu: 200m + memory: 128Mi +--- +apiVersion: v1 +kind: Service +metadata: + name: currencyservice + labels: + app: currencyservice +spec: + type: ClusterIP + selector: + app: currencyservice + ports: + - name: grpc + port: 7000 + targetPort: 7000 +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: currencyservice diff --git a/kustomize/base/emailservice.yaml b/kustomize/base/emailservice.yaml new file mode 100644 index 0000000..162e36b --- /dev/null +++ b/kustomize/base/emailservice.yaml @@ -0,0 +1,88 @@ +# Copyright 2018 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: apps/v1 +kind: Deployment +metadata: + name: emailservice + labels: + app: emailservice +spec: + selector: + matchLabels: + app: emailservice + template: + metadata: + labels: + app: emailservice + spec: + serviceAccountName: emailservice + terminationGracePeriodSeconds: 5 + securityContext: + fsGroup: 1000 + runAsGroup: 1000 + runAsNonRoot: true + runAsUser: 1000 + containers: + - name: server + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + privileged: false + readOnlyRootFilesystem: true + image: us-central1-docker.pkg.dev/google-samples/microservices-demo/emailservice:v0.10.4 + ports: + - containerPort: 8080 + env: + - name: PORT + value: "8080" + - name: DISABLE_PROFILER + value: "1" + readinessProbe: + periodSeconds: 5 + grpc: + port: 8080 + livenessProbe: + periodSeconds: 5 + grpc: + port: 8080 + resources: + requests: + cpu: 100m + memory: 64Mi + limits: + cpu: 200m + memory: 128Mi +--- +apiVersion: v1 +kind: Service +metadata: + name: emailservice + labels: + app: emailservice +spec: + type: ClusterIP + selector: + app: emailservice + ports: + - name: grpc + port: 5000 + targetPort: 8080 +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: emailservice diff --git a/kustomize/base/frontend.yaml b/kustomize/base/frontend.yaml new file mode 100644 index 0000000..464f557 --- /dev/null +++ b/kustomize/base/frontend.yaml @@ -0,0 +1,141 @@ +# Copyright 2018 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: apps/v1 +kind: Deployment +metadata: + name: frontend + labels: + app: frontend +spec: + selector: + matchLabels: + app: frontend + template: + metadata: + labels: + app: frontend + annotations: + sidecar.istio.io/rewriteAppHTTPProbers: "true" + spec: + serviceAccountName: frontend + securityContext: + fsGroup: 1000 + runAsGroup: 1000 + runAsNonRoot: true + runAsUser: 1000 + containers: + - name: server + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + privileged: false + readOnlyRootFilesystem: true + image: us-central1-docker.pkg.dev/google-samples/microservices-demo/frontend:v0.10.4 + ports: + - containerPort: 8080 + readinessProbe: + initialDelaySeconds: 10 + httpGet: + path: "/_healthz" + port: 8080 + httpHeaders: + - name: "Cookie" + value: "shop_session-id=x-readiness-probe" + livenessProbe: + initialDelaySeconds: 10 + httpGet: + path: "/_healthz" + port: 8080 + httpHeaders: + - name: "Cookie" + value: "shop_session-id=x-liveness-probe" + env: + - name: PORT + value: "8080" + - name: PRODUCT_CATALOG_SERVICE_ADDR + value: "productcatalogservice:3550" + - name: CURRENCY_SERVICE_ADDR + value: "currencyservice:7000" + - name: CART_SERVICE_ADDR + value: "cartservice:7070" + - name: RECOMMENDATION_SERVICE_ADDR + value: "recommendationservice:8080" + - name: SHIPPING_SERVICE_ADDR + value: "shippingservice:50051" + - name: CHECKOUT_SERVICE_ADDR + value: "checkoutservice:5050" + - name: AD_SERVICE_ADDR + value: "adservice:9555" + - name: SHOPPING_ASSISTANT_SERVICE_ADDR + value: "shoppingassistantservice:80" + # # ENV_PLATFORM: One of: local, gcp, aws, azure, onprem, alibaba + # # When not set, defaults to "local" unless running in GKE, otherwies auto-sets to gcp + # - name: ENV_PLATFORM + # value: "aws" + - name: ENABLE_PROFILER + value: "0" + # - name: CYMBAL_BRANDING + # value: "true" + # - name: ENABLE_ASSISTANT + # value: "true" + # - name: FRONTEND_MESSAGE + # value: "Replace this with a message you want to display on all pages." + # As part of an optional Google Cloud demo, you can run an optional microservice called the "packaging service". + # - name: PACKAGING_SERVICE_URL + # value: "" # This value would look like "http://123.123.123" + resources: + requests: + cpu: 100m + memory: 64Mi + limits: + cpu: 200m + memory: 128Mi +--- +apiVersion: v1 +kind: Service +metadata: + name: frontend + labels: + app: frontend +spec: + type: ClusterIP + selector: + app: frontend + ports: + - name: http + port: 80 + targetPort: 8080 +--- +apiVersion: v1 +kind: Service +metadata: + name: frontend-external + labels: + app: frontend +spec: + type: LoadBalancer + selector: + app: frontend + ports: + - name: http + port: 80 + targetPort: 8080 +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: frontend diff --git a/kustomize/base/kustomization.yaml b/kustomize/base/kustomization.yaml new file mode 100644 index 0000000..44b660d --- /dev/null +++ b/kustomize/base/kustomization.yaml @@ -0,0 +1,28 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: +- adservice.yaml +- cartservice.yaml +- checkoutservice.yaml +- currencyservice.yaml +- emailservice.yaml +- frontend.yaml +- loadgenerator.yaml +- paymentservice.yaml +- productcatalogservice.yaml +- recommendationservice.yaml +- shippingservice.yaml diff --git a/kustomize/base/loadgenerator.yaml b/kustomize/base/loadgenerator.yaml new file mode 100644 index 0000000..426a51e --- /dev/null +++ b/kustomize/base/loadgenerator.yaml @@ -0,0 +1,99 @@ +# Copyright 2018 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +apiVersion: apps/v1 +kind: Deployment +metadata: + name: loadgenerator + labels: + app: loadgenerator +spec: + selector: + matchLabels: + app: loadgenerator + replicas: 1 + template: + metadata: + labels: + app: loadgenerator + annotations: + sidecar.istio.io/rewriteAppHTTPProbers: "true" + spec: + serviceAccountName: loadgenerator + terminationGracePeriodSeconds: 5 + restartPolicy: Always + securityContext: + fsGroup: 1000 + runAsGroup: 1000 + runAsNonRoot: true + runAsUser: 1000 + initContainers: + - command: + - /bin/sh + - -exc + - | + MAX_RETRIES=12 + RETRY_INTERVAL=10 + for i in $(seq 1 $MAX_RETRIES); do + echo "Attempt $i: Pinging frontend: ${FRONTEND_ADDR}..." + STATUSCODE=$(wget --server-response http://${FRONTEND_ADDR} 2>&1 | awk '/^ HTTP/{print $2}') + if [ $STATUSCODE -eq 200 ]; then + echo "Frontend is reachable." + exit 0 + fi + echo "Error: Could not reach frontend - Status code: ${STATUSCODE}" + sleep $RETRY_INTERVAL + done + echo "Failed to reach frontend after $MAX_RETRIES attempts." + exit 1 + name: frontend-check + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + privileged: false + readOnlyRootFilesystem: true + image: busybox:latest + env: + - name: FRONTEND_ADDR + value: "frontend:80" + containers: + - name: main + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + privileged: false + readOnlyRootFilesystem: true + image: us-central1-docker.pkg.dev/google-samples/microservices-demo/loadgenerator:v0.10.4 + env: + - name: FRONTEND_ADDR + value: "frontend:80" + - name: USERS + value: "10" + - name: RATE + value: "1" + resources: + requests: + cpu: 300m + memory: 256Mi + limits: + cpu: 500m + memory: 512Mi +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: loadgenerator diff --git a/kustomize/base/paymentservice.yaml b/kustomize/base/paymentservice.yaml new file mode 100644 index 0000000..87ba3ca --- /dev/null +++ b/kustomize/base/paymentservice.yaml @@ -0,0 +1,86 @@ +# Copyright 2018 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: apps/v1 +kind: Deployment +metadata: + name: paymentservice + labels: + app: paymentservice +spec: + selector: + matchLabels: + app: paymentservice + template: + metadata: + labels: + app: paymentservice + spec: + serviceAccountName: paymentservice + terminationGracePeriodSeconds: 5 + securityContext: + fsGroup: 1000 + runAsGroup: 1000 + runAsNonRoot: true + runAsUser: 1000 + containers: + - name: server + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + privileged: false + readOnlyRootFilesystem: true + image: us-central1-docker.pkg.dev/google-samples/microservices-demo/paymentservice:v0.10.4 + ports: + - containerPort: 50051 + env: + - name: PORT + value: "50051" + - name: DISABLE_PROFILER + value: "1" + readinessProbe: + grpc: + port: 50051 + livenessProbe: + grpc: + port: 50051 + resources: + requests: + cpu: 100m + memory: 64Mi + limits: + cpu: 200m + memory: 128Mi +--- +apiVersion: v1 +kind: Service +metadata: + name: paymentservice + labels: + app: paymentservice +spec: + type: ClusterIP + selector: + app: paymentservice + ports: + - name: grpc + port: 50051 + targetPort: 50051 +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: paymentservice diff --git a/kustomize/base/productcatalogservice.yaml b/kustomize/base/productcatalogservice.yaml new file mode 100644 index 0000000..67d63c9 --- /dev/null +++ b/kustomize/base/productcatalogservice.yaml @@ -0,0 +1,86 @@ +# Copyright 2018 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: apps/v1 +kind: Deployment +metadata: + name: productcatalogservice + labels: + app: productcatalogservice +spec: + selector: + matchLabels: + app: productcatalogservice + template: + metadata: + labels: + app: productcatalogservice + spec: + serviceAccountName: productcatalogservice + terminationGracePeriodSeconds: 5 + securityContext: + fsGroup: 1000 + runAsGroup: 1000 + runAsNonRoot: true + runAsUser: 1000 + containers: + - name: server + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + privileged: false + readOnlyRootFilesystem: true + image: us-central1-docker.pkg.dev/google-samples/microservices-demo/productcatalogservice:v0.10.4 + ports: + - containerPort: 3550 + env: + - name: PORT + value: "3550" + - name: DISABLE_PROFILER + value: "1" + readinessProbe: + grpc: + port: 3550 + livenessProbe: + grpc: + port: 3550 + resources: + requests: + cpu: 100m + memory: 64Mi + limits: + cpu: 200m + memory: 128Mi +--- +apiVersion: v1 +kind: Service +metadata: + name: productcatalogservice + labels: + app: productcatalogservice +spec: + type: ClusterIP + selector: + app: productcatalogservice + ports: + - name: grpc + port: 3550 + targetPort: 3550 +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: productcatalogservice diff --git a/kustomize/base/recommendationservice.yaml b/kustomize/base/recommendationservice.yaml new file mode 100644 index 0000000..85e8e73 --- /dev/null +++ b/kustomize/base/recommendationservice.yaml @@ -0,0 +1,90 @@ +# Copyright 2018 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: apps/v1 +kind: Deployment +metadata: + name: recommendationservice + labels: + app: recommendationservice +spec: + selector: + matchLabels: + app: recommendationservice + template: + metadata: + labels: + app: recommendationservice + spec: + serviceAccountName: recommendationservice + terminationGracePeriodSeconds: 5 + securityContext: + fsGroup: 1000 + runAsGroup: 1000 + runAsNonRoot: true + runAsUser: 1000 + containers: + - name: server + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + privileged: false + readOnlyRootFilesystem: true + image: us-central1-docker.pkg.dev/google-samples/microservices-demo/recommendationservice:v0.10.4 + ports: + - containerPort: 8080 + readinessProbe: + periodSeconds: 5 + grpc: + port: 8080 + livenessProbe: + periodSeconds: 5 + grpc: + port: 8080 + env: + - name: PORT + value: "8080" + - name: PRODUCT_CATALOG_SERVICE_ADDR + value: "productcatalogservice:3550" + - name: DISABLE_PROFILER + value: "1" + resources: + requests: + cpu: 100m + memory: 220Mi + limits: + cpu: 200m + memory: 450Mi +--- +apiVersion: v1 +kind: Service +metadata: + name: recommendationservice + labels: + app: recommendationservice +spec: + type: ClusterIP + selector: + app: recommendationservice + ports: + - name: grpc + port: 8080 + targetPort: 8080 +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: recommendationservice diff --git a/kustomize/base/shippingservice.yaml b/kustomize/base/shippingservice.yaml new file mode 100644 index 0000000..0a5d1d6 --- /dev/null +++ b/kustomize/base/shippingservice.yaml @@ -0,0 +1,86 @@ +# Copyright 2018 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: apps/v1 +kind: Deployment +metadata: + name: shippingservice + labels: + app: shippingservice +spec: + selector: + matchLabels: + app: shippingservice + template: + metadata: + labels: + app: shippingservice + spec: + serviceAccountName: shippingservice + securityContext: + fsGroup: 1000 + runAsGroup: 1000 + runAsNonRoot: true + runAsUser: 1000 + containers: + - name: server + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + privileged: false + readOnlyRootFilesystem: true + image: us-central1-docker.pkg.dev/google-samples/microservices-demo/shippingservice:v0.10.4 + ports: + - containerPort: 50051 + env: + - name: PORT + value: "50051" + - name: DISABLE_PROFILER + value: "1" + readinessProbe: + periodSeconds: 5 + grpc: + port: 50051 + livenessProbe: + grpc: + port: 50051 + resources: + requests: + cpu: 100m + memory: 64Mi + limits: + cpu: 200m + memory: 128Mi +--- +apiVersion: v1 +kind: Service +metadata: + name: shippingservice + labels: + app: shippingservice +spec: + type: ClusterIP + selector: + app: shippingservice + ports: + - name: grpc + port: 50051 + targetPort: 50051 +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: shippingservice diff --git a/kustomize/components/alloydb/README.md b/kustomize/components/alloydb/README.md new file mode 100644 index 0000000..9a70844 --- /dev/null +++ b/kustomize/components/alloydb/README.md @@ -0,0 +1,155 @@ +# Integrate Online Boutique with AlloyDB + +By default the `cartservice` stores its data in an in-cluster Redis database. +Using a fully managed database service outside your GKE cluster (such as [AlloyDB](https://cloud.google.com/alloydb)) could bring more resiliency and more security. + +Note that because of AlloyDB's current connectivity, you'll need to run all this from a VM with +VPC access to the network you want to use for everything (out of the box this should just use the +default network). The Cloud Shell doesn't work because of transitive VPC peering not working. + +## Provision an AlloyDB database and the supporting infrastructure + +Environmental variables needed for setup. These should be set in a .bashrc or similar as some of the variables are used in the application itself. Default values are supplied in this readme, but any of them can be changed. Anything in <> needs to be replaced. + +```bash +# PROJECT_ID should be set to the project ID that was created to hold the demo +PROJECT_ID= + +#Pick a region near you that also has AlloyDB available. See available regions: https://cloud.google.com/alloydb/docs/locations +REGION= +USE_GKE_GCLOUD_AUTH_PLUGIN=True +ALLOYDB_NETWORK=default +ALLOYDB_SERVICE_NAME=onlineboutique-network-range +ALLOYDB_CLUSTER_NAME=onlineboutique-cluster +ALLOYDB_INSTANCE_NAME=onlineboutique-instance + +# **Note:** Primary and Read IP will need to be set after you create the instance. The command to set this in the shell is included below, but it would also be a good idea to run the command, and manually set the IP address in the .bashrc +ALLOYDB_PRIMARY_IP= +ALLOYDB_READ_IP= + +ALLOYDB_DATABASE_NAME=carts +ALLOYDB_TABLE_NAME=cart_items +ALLOYDB_USER_GSA_NAME=alloydb-user-sa +ALLOYDB_USER_GSA_ID=${ALLOYDB_USER_GSA_NAME}@${PROJECT_ID}.iam.gserviceaccount.com +CARTSERVICE_KSA_NAME=cartservice +ALLOYDB_SECRET_NAME=alloydb-secret + +# PGPASSWORD needs to be set in order to run the psql from the CLI easily. The value for this +# needs to be set behind the Secret mentioned above +PGPASSWORD= +``` + +To provision an AlloyDB instance you can follow the following instructions: +```bash +gcloud services enable alloydb.googleapis.com +gcloud services enable servicenetworking.googleapis.com +gcloud services enable secretmanager.googleapis.com + +# Set our DB credentials behind the secret. Replace with whatever you want +# to use as the credentials for the database. Don't use $ in the password. +echo | gcloud secrets create ${ALLOYDB_SECRET_NAME} --data-file=- + +# Setting up needed service connection +gcloud compute addresses create ${ALLOYDB_SERVICE_NAME} \ + --global \ + --purpose=VPC_PEERING \ + --prefix-length=16 \ + --description="Online Boutique Private Services" \ + --network=${ALLOYDB_NETWORK} + +gcloud services vpc-peerings connect \ + --service=servicenetworking.googleapis.com \ + --ranges=${ALLOYDB_SERVICE_NAME} \ + --network=${ALLOYDB_NETWORK} + +gcloud alloydb clusters create ${ALLOYDB_CLUSTER_NAME} \ + --region=${REGION} \ + --password=${PGPASSWORD} \ + --disable-automated-backup \ + --network=${ALLOYDB_NETWORK} + +gcloud alloydb instances create ${ALLOYDB_INSTANCE_NAME} \ + --cluster=${ALLOYDB_CLUSTER_NAME} \ + --region=${REGION} \ + --cpu-count=4 \ + --instance-type=PRIMARY + +gcloud alloydb instances create ${ALLOYDB_INSTANCE_NAME}-replica \ + --cluster=${ALLOYDB_CLUSTER_NAME} \ + --region=${REGION} \ + --cpu-count=4 \ + --instance-type=READ_POOL \ + --read-pool-node-count=2 + +# Need to grab and store the IP addresses for our primary and read replicas +# Don't forget to set these two values in the environment for later use. +ALLOYDB_PRIMARY_IP=gcloud alloydb instances list --region=${REGION} --cluster=${ALLOYDB_CLUSTER_NAME} --filter="INSTANCE_TYPE:PRIMARY" --format=flattened | sed -nE "s/ipAddress:\s*(.*)/\1/p" +ALLOYDB_READ_IP=gcloud alloydb instances list --region=${REGION} --cluster=${ALLOYDB_CLUSTER_NAME} --filter="INSTANCE_TYPE:READ_POOL" --format=flattened | sed -nE "s/ipAddress:\s*(.*)/\1/p" + +psql -h ${ALLOYDB_PRIMARY_IP} -U postgres -c "CREATE DATABASE ${ALLOYDB_DATABASE_NAME}" +psql -h ${ALLOYDB_PRIMARY_IP} -U postgres -d ${ALLOYDB_DATABASE_NAME} -c "CREATE TABLE ${ALLOYDB_TABLE_NAME} (userId text, productId text, quantity int, PRIMARY KEY(userId, productId))" +psql -h ${ALLOYDB_PRIMARY_IP} -U postgres -d ${ALLOYDB_DATABASE_NAME} -c "CREATE INDEX cartItemsByUserId ON ${ALLOYDB_TABLE_NAME}(userId)" +``` + +_Note: It can take more than 20 minutes for the AlloyDB instances to be created._ + +## Grant the `cartservice`'s service account access to the AlloyDB database + +**Important note:** Your GKE cluster should have [Workload Identity enabled](https://cloud.google.com/kubernetes-engine/docs/how-to/workload-identity#enable). + +As a good practice, let's create a dedicated least privilege Google Service Account to allow the `cartservice` to communicate with the AlloyDB database and grab the database password from the Secret manager.: +```bash +gcloud iam service-accounts create ${ALLOYDB_USER_GSA_NAME} \ + --display-name=${ALLOYDB_USER_GSA_NAME} + +gcloud projects add-iam-policy-binding ${PROJECT_ID} --member=serviceAccount:${ALLOYDB_USER_GSA_ID} --role=roles/alloydb.client +gcloud projects add-iam-policy-binding ${PROJECT_ID} --member=serviceAccount:${ALLOYDB_USER_GSA_ID} --role=roles/secretmanager.secretAccessor + +gcloud iam service-accounts add-iam-policy-binding ${ALLOYDB_USER_GSA_ID} \ + --member "serviceAccount:${PROJECT_ID}.svc.id.goog[default/${CARTSERVICE_KSA_NAME}]" \ + --role roles/iam.workloadIdentityUser +``` + +## Deploy Online Boutique connected to an AlloyDB database + +To automate the deployment of Online Boutique integrated with AlloyDB you can leverage the following variation with [Kustomize](../..). + +From the `kustomize/` folder at the root level of this repository, execute these commands: +```bash +kustomize edit add component components/alloydb +``` +_**Note:** this Kustomize component will also remove the `redis-cart` `Deployment` and `Service` not used anymore._ + +This will update the `kustomize/kustomization.yaml` file which could be similar to: +```yaml +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: +- base +components: +- components/alloydb +``` + +Update current Kustomize manifest to target this AlloyDB database. +```bash +sed -i "s/PROJECT_ID_VAL/${PROJECT_ID}/g" components/alloydb/kustomization.yaml +sed -i "s/ALLOYDB_PRIMARY_IP_VAL/${ALLOYDB_PRIMARY_IP}/g" components/alloydb/kustomization.yaml +sed -i "s/ALLOYDB_USER_GSA_ID/${ALLOYDB_USER_GSA_ID}/g" components/alloydb/kustomization.yaml +sed -i "s/ALLOYDB_DATABASE_NAME_VAL/${ALLOYDB_DATABASE_NAME}/g" components/alloydb/kustomization.yaml +sed -i "s/ALLOYDB_TABLE_NAME_VAL/${ALLOYDB_TABLE_NAME}/g" components/alloydb/kustomization.yaml +sed -i "s/ALLOYDB_SECRET_NAME_VAL/${ALLOYDB_SECRET_NAME}/g" components/alloydb/kustomization.yaml +``` + +You can locally render these manifests by running `kubectl kustomize .` as well as deploying them by running `kubectl apply -k .`. + +## Extra cleanup steps +```bash +gcloud compute addresses delete ${ALLOYDB_SERVICE_NAME} --global + +# Force takes care of cleaning up the instances inside the cluster automatically +gcloud alloydb clusters delete ${ALLOYDB_CLUSTER_NAME} --force --region ${REGION} + +gcloud iam service-accounts delete ${ALLOYDB_USER_GSA_ID} + +gcloud secrets delete ${ALLOYDB_SECRET_NAME} +``` diff --git a/kustomize/components/alloydb/kustomization.yaml b/kustomize/components/alloydb/kustomization.yaml new file mode 100644 index 0000000..3327bb0 --- /dev/null +++ b/kustomize/components/alloydb/kustomization.yaml @@ -0,0 +1,99 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: kustomize.config.k8s.io/v1alpha1 +kind: Component +patches: +# cartservice - replace REDIS_ADDR by ALLOYDB_PRIMARY_IP for the cartservice Deployment +# Potentially later we'll factor in splitting traffic to primary/read pool, but for now +# we'll just manage the primary instance +- patch: |- + apiVersion: apps/v1 + kind: Deployment + metadata: + name: cartservice + spec: + template: + spec: + containers: + - name: server + env: + - name: REDIS_ADDR + $patch: delete + - name: ALLOYDB_PRIMARY_IP + value: ALLOYDB_PRIMARY_IP_VAL + - name: ALLOYDB_DATABASE_NAME + value: ALLOYDB_CARTS_DATABASE_NAME_VAL + - name: ALLOYDB_TABLE_NAME + value: ALLOYDB_CARTS_TABLE_NAME_VAL + - name: ALLOYDB_SECRET_NAME + value: ALLOYDB_SECRET_NAME_VAL + - name: PROJECT_ID + value: PROJECT_ID_VAL +# cartservice - add the GSA annotation for the cartservice KSA +- patch: |- + apiVersion: v1 + kind: ServiceAccount + metadata: + name: cartservice + annotations: + iam.gke.io/gcp-service-account: ALLOYDB_USER_GSA_ID +# productcatalogservice - replace ALLOYDB environments +- patch: |- + apiVersion: apps/v1 + kind: Deployment + metadata: + name: productcatalogservice + spec: + template: + spec: + containers: + - name: server + env: + - name: ALLOYDB_CLUSTER_NAME + value: ALLOYDB_CLUSTER_NAME_VAL + - name: ALLOYDB_INSTANCE_NAME + value: ALLOYDB_INSTANCE_NAME_VAL + - name: ALLOYDB_DATABASE_NAME + value: ALLOYDB_PRODUCTS_DATABASE_NAME_VAL + - name: ALLOYDB_TABLE_NAME + value: ALLOYDB_PRODUCTS_TABLE_NAME_VAL + - name: ALLOYDB_SECRET_NAME + value: ALLOYDB_SECRET_NAME_VAL + - name: PROJECT_ID + value: PROJECT_ID_VAL + - name: REGION + value: REGION_VAL +# productcatalogservice - add the GSA annotation for the productcatalogservice KSA +- patch: |- + apiVersion: v1 + kind: ServiceAccount + metadata: + name: productcatalogservice + annotations: + iam.gke.io/gcp-service-account: ALLOYDB_USER_GSA_ID +# redis - remove the redis-cart Deployment +- patch: |- + apiVersion: apps/v1 + kind: Deployment + metadata: + name: redis-cart + $patch: delete +# redis - remove the redis-cart Service +- patch: |- + apiVersion: v1 + kind: Service + metadata: + name: redis-cart + $patch: delete diff --git a/kustomize/components/container-images-registry/README.md b/kustomize/components/container-images-registry/README.md new file mode 100644 index 0000000..27f622a --- /dev/null +++ b/kustomize/components/container-images-registry/README.md @@ -0,0 +1,31 @@ +# Update the container registry of the Online Boutique apps + +By default, Online Boutique's services' container images are pulled from a public container registry (`us-central1-docker.pkg.dev/google-samples/microservices-demo`). One best practice is to have these container images in your own private container registry. The Kustomize variation in this folder can help with using your own private container registry. + +## Change the default container registry via Kustomize + +To automate the deployment of Online Boutique integrated with your own container registry, you can leverage the following variation with [Kustomize](../..). + +From the `kustomize/` folder at the root level of this repository, execute this command: + +```bash +REGISTRY=my-registry # Example: us-central1-docker.pkg.dev/my-project/my-directory +sed -i "s|CONTAINER_IMAGES_REGISTRY|${REGISTRY}|g" components/container-images-registry/kustomization.yaml +kustomize edit add component components/container-images-registry +``` + +_Note: this Kustomize component will update the container registry in the `image:` field in all `Deployments`._ + +This will update the `kustomize/kustomization.yaml` file which could be similar to: + +```yaml +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: +- base +components: +- components/container-images-registry +``` + +You can (optionally) locally render these manifests by running `kubectl kustomize .`. +You can deploy them by running `kubectl apply -k .`. diff --git a/kustomize/components/container-images-registry/kustomization.yaml b/kustomize/components/container-images-registry/kustomization.yaml new file mode 100644 index 0000000..eb49214 --- /dev/null +++ b/kustomize/components/container-images-registry/kustomization.yaml @@ -0,0 +1,41 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: kustomize.config.k8s.io/v1alpha1 +kind: Component +images: +- name: us-central1-docker.pkg.dev/google-samples/microservices-demo/adservice + newName: CONTAINER_IMAGES_REGISTRY/adservice +- name: us-central1-docker.pkg.dev/google-samples/microservices-demo/cartservice + newName: CONTAINER_IMAGES_REGISTRY/cartservice +- name: us-central1-docker.pkg.dev/google-samples/microservices-demo/checkoutservice + newName: CONTAINER_IMAGES_REGISTRY/checkoutservice +- name: us-central1-docker.pkg.dev/google-samples/microservices-demo/currencyservice + newName: CONTAINER_IMAGES_REGISTRY/currencyservice +- name: us-central1-docker.pkg.dev/google-samples/microservices-demo/emailservice + newName: CONTAINER_IMAGES_REGISTRY/emailservice +- name: us-central1-docker.pkg.dev/google-samples/microservices-demo/frontend + newName: CONTAINER_IMAGES_REGISTRY/frontend +- name: us-central1-docker.pkg.dev/google-samples/microservices-demo/loadgenerator + newName: CONTAINER_IMAGES_REGISTRY/loadgenerator +- name: us-central1-docker.pkg.dev/google-samples/microservices-demo/paymentservice + newName: CONTAINER_IMAGES_REGISTRY/paymentservice +- name: us-central1-docker.pkg.dev/google-samples/microservices-demo/productcatalogservice + newName: CONTAINER_IMAGES_REGISTRY/productcatalogservice +- name: us-central1-docker.pkg.dev/google-samples/microservices-demo/recommendationservice + newName: CONTAINER_IMAGES_REGISTRY/recommendationservice +- name: us-central1-docker.pkg.dev/google-samples/microservices-demo/shippingservice + newName: CONTAINER_IMAGES_REGISTRY/shippingservice +- name: redis + newName: CONTAINER_IMAGES_REGISTRY/redis diff --git a/kustomize/components/container-images-tag-suffix/README.md b/kustomize/components/container-images-tag-suffix/README.md new file mode 100644 index 0000000..4678dba --- /dev/null +++ b/kustomize/components/container-images-tag-suffix/README.md @@ -0,0 +1,53 @@ +# Add a suffix to the image tag of the Online Boutique container images + +You may want to add a suffix to the Online Boutique container image tag to target a specific version. +The Kustomize Component inside this folder can help. + +## Add a suffix to the container image tag via Kustomize + +To automate the deployment of the Online Boutique apps with a suffix added to the container imag tag, you can leverage the following variation with [Kustomize](../..). + +From the `kustomize/` folder at the root level of this repository, execute this command: + +```bash +SUFFIX=-my-suffix +sed -i "s/CONTAINER_IMAGES_TAG_SUFFIX/$SUFFIX/g" components/container-images-tag-suffix/kustomization.yaml +kustomize edit add component components/container-images-tag-suffix +``` + +_Note: this Kustomize component will add a suffix to the container image tag of the `image:` field in all `Deployments`._ + +This will update the `kustomize/kustomization.yaml` file which could be similar to: + +```yaml +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: +- base +components: +- components/container-images-tag-suffix +``` + +You can locally render these manifests by running `kubectl kustomize . | sed "s/$SUFFIX$SUFFIX/$SUFFIX/g"` as well as deploying them by running `kubectl kustomize . | sed "s/$SUFFIX$SUFFIX/$SUFFIX/g" | kubectl apply -f`. + +_Note: for this variation, `kubectl apply -k .` alone won't work because there is a [known issue currently in Kustomize](https://github.com/kubernetes-sigs/kustomize/issues/4814) where the `tagSuffix` is duplicated. The `sed "s/$SUFFIX$SUFFIX/$SUFFIX/g"` commands above are a temporary workaround._ + +## Combine with other Kustomize Components + +If you're combining this Kustomize Component with other variations, here are some considerations: + +- `components/container-images-tag-suffix` should be placed before `components/container-images-registry` +- `components/container-images-tag-suffix` should be placed after `components/container-images-tag` + +So for example here is the order respected: + +```yaml +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: +- base +components: +- components/container-images-tag +- components/container-images-tag-suffix +- components/container-images-registry +``` diff --git a/kustomize/components/container-images-tag-suffix/kustomization.yaml b/kustomize/components/container-images-tag-suffix/kustomization.yaml new file mode 100644 index 0000000..89bd0a8 --- /dev/null +++ b/kustomize/components/container-images-tag-suffix/kustomization.yaml @@ -0,0 +1,39 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: kustomize.config.k8s.io/v1alpha1 +kind: Component +images: +- name: us-central1-docker.pkg.dev/google-samples/microservices-demo/adservice + tagSuffix: CONTAINER_IMAGES_TAG_SUFFIX +- name: us-central1-docker.pkg.dev/google-samples/microservices-demo/cartservice + tagSuffix: CONTAINER_IMAGES_TAG_SUFFIX +- name: us-central1-docker.pkg.dev/google-samples/microservices-demo/checkoutservice + tagSuffix: CONTAINER_IMAGES_TAG_SUFFIX +- name: us-central1-docker.pkg.dev/google-samples/microservices-demo/currencyservice + tagSuffix: CONTAINER_IMAGES_TAG_SUFFIX +- name: us-central1-docker.pkg.dev/google-samples/microservices-demo/emailservice + tagSuffix: CONTAINER_IMAGES_TAG_SUFFIX +- name: us-central1-docker.pkg.dev/google-samples/microservices-demo/frontend + tagSuffix: CONTAINER_IMAGES_TAG_SUFFIX +- name: us-central1-docker.pkg.dev/google-samples/microservices-demo/loadgenerator + tagSuffix: CONTAINER_IMAGES_TAG_SUFFIX +- name: us-central1-docker.pkg.dev/google-samples/microservices-demo/paymentservice + tagSuffix: CONTAINER_IMAGES_TAG_SUFFIX +- name: us-central1-docker.pkg.dev/google-samples/microservices-demo/productcatalogservice + tagSuffix: CONTAINER_IMAGES_TAG_SUFFIX +- name: us-central1-docker.pkg.dev/google-samples/microservices-demo/recommendationservice + tagSuffix: CONTAINER_IMAGES_TAG_SUFFIX +- name: us-central1-docker.pkg.dev/google-samples/microservices-demo/shippingservice + tagSuffix: CONTAINER_IMAGES_TAG_SUFFIX \ No newline at end of file diff --git a/kustomize/components/container-images-tag/README.md b/kustomize/components/container-images-tag/README.md new file mode 100644 index 0000000..4cc188f --- /dev/null +++ b/kustomize/components/container-images-tag/README.md @@ -0,0 +1,46 @@ +# Update the container image tag of the Online Boutique apps + +By default, the Online Boutique apps are targeting the latest release version (see the list of versions [here](https://github.com/GoogleCloudPlatform/microservices-demo/releases)). You may need to change this image tag to target a specific version, this Kustomize variation will help you setting this up. + +## Change the default container image tag via Kustomize + +To automate the deployment of the Online Boutique apps with a specific container imag tag, you can leverage the following variation with [Kustomize](../..). + +From the `kustomize/` folder at the root level of this repository, execute this command: + +```bash +TAG=v1.0.0 +sed -i "s/CONTAINER_IMAGES_TAG/$TAG/g" components/container-images-tag/kustomization.yaml +kustomize edit add component components/container-images-tag +``` + +_Note: this Kustomize component will update the container image tag of the `image:` field in all `Deployments`._ + +This will update the `kustomize/kustomization.yaml` file which could be similar to: + +```yaml +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: +- base +components: +- components/container-images-tag +``` + +You can locally render these manifests by running `kubectl kustomize .` as well as deploying them by running `kubectl apply -k .`. + +**Important notes:** if combining with the other variations, here are some considerations: + +- should be placed before `components/container-images-registry` + +So for example here is the order respected: + +```yaml +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: +- base +components: +- components/container-images-tag +- components/container-images-registry +``` diff --git a/kustomize/components/container-images-tag/kustomization.yaml b/kustomize/components/container-images-tag/kustomization.yaml new file mode 100644 index 0000000..7391740 --- /dev/null +++ b/kustomize/components/container-images-tag/kustomization.yaml @@ -0,0 +1,39 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: kustomize.config.k8s.io/v1alpha1 +kind: Component +images: +- name: us-central1-docker.pkg.dev/google-samples/microservices-demo/adservice + newTag: CONTAINER_IMAGES_TAG +- name: us-central1-docker.pkg.dev/google-samples/microservices-demo/cartservice + newTag: CONTAINER_IMAGES_TAG +- name: us-central1-docker.pkg.dev/google-samples/microservices-demo/checkoutservice + newTag: CONTAINER_IMAGES_TAG +- name: us-central1-docker.pkg.dev/google-samples/microservices-demo/currencyservice + newTag: CONTAINER_IMAGES_TAG +- name: us-central1-docker.pkg.dev/google-samples/microservices-demo/emailservice + newTag: CONTAINER_IMAGES_TAG +- name: us-central1-docker.pkg.dev/google-samples/microservices-demo/frontend + newTag: CONTAINER_IMAGES_TAG +- name: us-central1-docker.pkg.dev/google-samples/microservices-demo/loadgenerator + newTag: CONTAINER_IMAGES_TAG +- name: us-central1-docker.pkg.dev/google-samples/microservices-demo/paymentservice + newTag: CONTAINER_IMAGES_TAG +- name: us-central1-docker.pkg.dev/google-samples/microservices-demo/productcatalogservice + newTag: CONTAINER_IMAGES_TAG +- name: us-central1-docker.pkg.dev/google-samples/microservices-demo/recommendationservice + newTag: CONTAINER_IMAGES_TAG +- name: us-central1-docker.pkg.dev/google-samples/microservices-demo/shippingservice + newTag: CONTAINER_IMAGES_TAG diff --git a/kustomize/components/custom-base-url/README.md b/kustomize/components/custom-base-url/README.md new file mode 100644 index 0000000..3664c5b --- /dev/null +++ b/kustomize/components/custom-base-url/README.md @@ -0,0 +1,62 @@ +# Customize the Base URL for Online Boutique + +This component allows you to change the base URL for the Online Boutique application. By default, the application uses the root path ("/") as its base URL. This customization sets the base URL to "/online-boutique" and updates the health check paths accordingly. + +## What it does + +1. Sets the `BASE_URL` environment variable to "/online-boutique" for the frontend deployment. +2. Updates the liveness probe path to "/online-boutique/_healthz". +3. Updates the readiness probe path to "/online-boutique/_healthz". + +## How to use + +To apply this customization, you can use Kustomize to include this component in your deployment. + +From the `kustomize/` folder at the root level of this repository, execute this command: + +```bash +kustomize edit add component components/custom-base-url +``` + +This will update the `kustomize/kustomization.yaml` file, which could look similar to: + +```yaml +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: +- base +components: +- components/custom-base-url +``` + +## Render and Deploy + +You can locally render these manifests by running: + +```bash +kubectl kustomize . +``` + +To deploy the customized application, run: + +```bash +kubectl apply -k . +``` + +## Customizing the Base URL + +If you want to use a different base URL, you can modify the `value` fields in the kustomization.yaml file. Make sure to update all three occurrences: + +1. The `BASE_URL` environment variable +2. The liveness probe path +3. The readiness probe path + +For example, to change the base URL to "/shop", you would modify the values as follows: + +```yaml +value: /shop +value: /shop/_healthz +value: /shop/_healthz +``` + +Note: After changing the base URL, make sure to update any internal links or references within your application to use the new base URL. diff --git a/kustomize/components/custom-base-url/kustomization.yaml b/kustomize/components/custom-base-url/kustomization.yaml new file mode 100644 index 0000000..2a41c15 --- /dev/null +++ b/kustomize/components/custom-base-url/kustomization.yaml @@ -0,0 +1,32 @@ +# Copyright 2024 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: kustomize.config.k8s.io/v1alpha1 +kind: Component +patches: +- target: + kind: Deployment + name: frontend + patch: |- + - op: add + path: /spec/template/spec/containers/0/env/- + value: + name: BASE_URL + value: /online-boutique + - op: replace + path: /spec/template/spec/containers/0/livenessProbe/httpGet/path + value: /online-boutique/_healthz + - op: replace + path: /spec/template/spec/containers/0/readinessProbe/httpGet/path + value: /online-boutique/_healthz diff --git a/kustomize/components/cymbal-branding/README.md b/kustomize/components/cymbal-branding/README.md new file mode 100644 index 0000000..78c7e4f --- /dev/null +++ b/kustomize/components/cymbal-branding/README.md @@ -0,0 +1,48 @@ +# Change the Online Boutique theme to the Cymbal Shops Branding + +By default, when you deploy this sample app, the "Online Boutique" branding (logo and wording) will be used. +But you may want to use Google Cloud's fictitious company, _Cymbal Shops_, instead. + +To use "Cymbal Shops" branding, set the `CYMBAL_BRANDING` environment variable to `"true"` in the the Kubernetes manifest (`.yaml`) for the `frontend` Deployment. + +```yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: frontend +spec: + ... + template: + ... + spec: + ... + containers: + ... + env: + ... + - name: CYMBAL_BRANDING + value: "true" +``` + +## Deploy Online Boutique with the Cymbal Shops branding via Kustomize + +To automate the deployment of Online Boutique with the Cymbal Shops branding you can leverage the following variation with [Kustomize](../..). + +From the `kustomize/` folder at the root level of this repository, execute this command: + +```bash +kustomize edit add component components/cymbal-branding +``` + +This will update the `kustomize/kustomization.yaml` file which could be similar to: + +```yaml +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: +- base +components: +- components/cymbal-branding +``` + +You can locally render these manifests by running `kubectl kustomize .` as well as deploying them by running `kubectl apply -k .`. diff --git a/kustomize/components/cymbal-branding/kustomization.yaml b/kustomize/components/cymbal-branding/kustomization.yaml new file mode 100644 index 0000000..c2a1f80 --- /dev/null +++ b/kustomize/components/cymbal-branding/kustomization.yaml @@ -0,0 +1,30 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: kustomize.config.k8s.io/v1alpha1 +kind: Component +patches: +- patch: |- + apiVersion: apps/v1 + kind: Deployment + metadata: + name: frontend + spec: + template: + spec: + containers: + - name: server + env: + - name: CYMBAL_BRANDING + value: "true" diff --git a/kustomize/components/google-cloud-operations/README.md b/kustomize/components/google-cloud-operations/README.md new file mode 100644 index 0000000..da26e4f --- /dev/null +++ b/kustomize/components/google-cloud-operations/README.md @@ -0,0 +1,123 @@ +# Integrate Online Boutique with Google Cloud Operations + +By default, [Google Cloud Operations](https://cloud.google.com/products/operations) instrumentation is **turned off** for Online Boutique deployments. This includes Monitoring (Stats), Tracing, and Profiler. This means that even if you're running this app on [GKE](https://cloud.google.com/kubernetes-engine), traces (for example) will not be exported to [Google Cloud Trace](https://cloud.google.com/trace). + +If you want to re-enable Google Cloud Operations instrumentation, the easiest way is to enable the included kustomize module, which enables traces, metrics, and adds a deployment of the [Open Telemetry Collector](https://opentelemetry.io/docs/collector/) to gather the traces and metrics and forward them to the appropriate Google Cloud backend. + +From the `kustomize/` folder at the root level of this repository, execute this command: + +```bash +kustomize edit add component components/google-cloud-operations +``` + +This will update the `kustomize/kustomization.yaml` file which could be similar to: + +```yaml +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: +- base +components: +- components/google-cloud-operations +``` + +You can locally render these manifests by running `kubectl kustomize .` as well as deploying them by running `kubectl apply -k .`. + +You will also need to make sure that you have the associated Google APIs enabled in your Google Cloud project: + +```bash +PROJECT_ID= +gcloud services enable \ + monitoring.googleapis.com \ + cloudtrace.googleapis.com \ + cloudprofiler.googleapis.com \ + --project ${PROJECT_ID} +``` + +In addition to that, you will need to grant the following IAM roles associated to your Google Service Account (GSA): + +```bash +PROJECT_ID= +GSA_NAME= + +gcloud projects add-iam-policy-binding ${PROJECT_ID} \ + --member "serviceAccount:${GSA_NAME}@${PROJECT_ID}.iam.gserviceaccount.com" \ + --role roles/cloudtrace.agent + +gcloud projects add-iam-policy-binding ${PROJECT_ID} \ + --member "serviceAccount:${GSA_NAME}@${PROJECT_ID}.iam.gserviceaccount.com" \ + --role roles/monitoring.metricWriter + +gcloud projects add-iam-policy-binding ${PROJECT_ID} \ + --member "serviceAccount:${GSA_NAME}@${PROJECT_ID}.iam.gserviceaccount.com" \ + --role roles/cloudprofiler.agent +``` + +**Note** +Currently only trace is supported. Support for metrics, and more is coming soon. + +## Changes + +When enabling this kustomize module, most services will be patched with a configuration similar to the following: + +```yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: productcatalogservice +spec: + template: + spec: + containers: + - name: server + env: + - name: COLLECTOR_SERVICE_ADDR + value: "opentelemetrycollector:4317" + - name: ENABLE_STATS + value: "1" + - name: ENABLE_TRACING + value: "1" +``` + +This patch sets environment variables to enable export of stats and tracing, as well as a variable to tell the service how to reach the new collector deployment. + +## OpenTelemetry Collector + +Currently, this component adds a single collector service which collects traces and metrics from individual services and forwards them to the appropriate Google Cloud backend. + +![Collector Architecture Diagram](collector-model.png) + +If you wish to experiment with different backends, you can modify the appropriate lines in [otel-collector.yaml](otel-collector.yaml) to export traces or metrics to a different backend. See the [OpenTelemetry docs](https://opentelemetry.io/docs/collector/configuration/) for more details. + +## Workload Identity + +If you are running this sample on GKE, your GKE cluster may be configured to use [Workload Identity](https://cloud.google.com/kubernetes-engine/docs/how-to/workload-identity) to manage access to Google Cloud APIs (like Cloud Trace). If this is the case, you may not see traces properly exported, or may see an error message like `failed to export to Google Cloud Trace: rpc error: code = PermissionDenied desc = The caller does not have permission` logged by your `opentelemetrycollector` Pod(s). In order to export traces with such a setup, you need to associate the Kubernetes [ServiceAccount](https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/) (`default/default`) with your [default compute service account](https://cloud.google.com/compute/docs/access/service-accounts#default_service_account) on Google Cloud (or a custom Google Cloud service account you may create for this purpose). + +* To get the email address associated with your Google service account, check in the IAM section of the Cloud Console. Or run the following command in your terminal: + +```bash +gcloud iam service-accounts list +``` + +* Then, allow the Kubernetes service account to act as your Google service account with the following command (using your own `PROJECT_ID` and the `GSA_EMAIL` you found in the previous step): + +```bash +gcloud iam service-accounts add-iam-policy-binding ${GSA_EMAIL} \ + --role roles/iam.workloadIdentityUser \ + --member "serviceAccount:${PROJECT_ID}.svc.id.goog[default/default]" +``` + +* Annotate your Kubernetes service account (`default/default` for the `default` namespace) to use the Google IAM service account: + +```bash +kubectl annotate serviceaccount default \ + iam.gke.io/gcp-service-account=${GSA_EMAIL} +``` + +* Finally, restart your `opentelemetrycollector` deployment to reflect the new settings: + +```bash +kubectl rollout restart deployment opentelemetrycollector +``` + +When the new Pod rolls out, you should start to see traces appear in the cloud console. diff --git a/kustomize/components/google-cloud-operations/collector-model.png b/kustomize/components/google-cloud-operations/collector-model.png new file mode 100644 index 0000000..470a93d Binary files /dev/null and b/kustomize/components/google-cloud-operations/collector-model.png differ diff --git a/kustomize/components/google-cloud-operations/kustomization.yaml b/kustomize/components/google-cloud-operations/kustomization.yaml new file mode 100644 index 0000000..e998094 --- /dev/null +++ b/kustomize/components/google-cloud-operations/kustomization.yaml @@ -0,0 +1,174 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: kustomize.config.k8s.io/v1alpha1 +kind: Component +resources: + - otel-collector.yaml +patches: +# adservice - not yet implemented +# checkoutservice - tracing, profiler +- patch: |- + apiVersion: apps/v1 + kind: Deployment + metadata: + name: checkoutservice + spec: + template: + spec: + containers: + - name: server + env: + - name: COLLECTOR_SERVICE_ADDR + value: "opentelemetrycollector:4317" + - name: OTEL_SERVICE_NAME + value: "checkoutservice" + - name: ENABLE_TRACING + value: "1" + - name: ENABLE_PROFILER + value: "1" +# currencyservice - tracing, profiler +- patch: |- + apiVersion: apps/v1 + kind: Deployment + metadata: + name: currencyservice + spec: + template: + spec: + containers: + - name: server + env: + - name: COLLECTOR_SERVICE_ADDR + value: "opentelemetrycollector:4317" + - name: OTEL_SERVICE_NAME + value: "currencyservice" + - name: ENABLE_TRACING + value: "1" + - name: DISABLE_PROFILER + $patch: delete +# emailservice - tracing, profiler +- patch: |- + apiVersion: apps/v1 + kind: Deployment + metadata: + name: emailservice + spec: + template: + spec: + containers: + - name: server + env: + - name: COLLECTOR_SERVICE_ADDR + value: "opentelemetrycollector:4317" + - name: OTEL_SERVICE_NAME + value: "emailservice" + - name: ENABLE_TRACING + value: "1" + - name: DISABLE_PROFILER + $patch: delete +# frontend - tracing, profiler +- patch: |- + apiVersion: apps/v1 + kind: Deployment + metadata: + name: frontend + spec: + template: + spec: + containers: + - name: server + env: + - name: ENABLE_TRACING + value: "1" + - name: COLLECTOR_SERVICE_ADDR + value: "opentelemetrycollector:4317" + - name: OTEL_SERVICE_NAME + value: "frontend" + - name: ENABLE_PROFILER + value: "1" +# paymentservice - tracing, profiler +- patch: |- + apiVersion: apps/v1 + kind: Deployment + metadata: + name: paymentservice + spec: + template: + spec: + containers: + - name: server + env: + - name: COLLECTOR_SERVICE_ADDR + value: "opentelemetrycollector:4317" + - name: OTEL_SERVICE_NAME + value: "paymentservice" + - name: ENABLE_TRACING + value: "1" + - name: DISABLE_PROFILER + $patch: delete +# productcatalogservice - tracing, profiler +- patch: |- + apiVersion: apps/v1 + kind: Deployment + metadata: + name: productcatalogservice + spec: + template: + spec: + containers: + - name: server + env: + - name: COLLECTOR_SERVICE_ADDR + value: "opentelemetrycollector:4317" + - name: OTEL_SERVICE_NAME + value: "productcatalogservice" + - name: ENABLE_TRACING + value: "1" + - name: DISABLE_PROFILER + value: "1" +# recommendationservice - tracing, profiler +- patch: |- + apiVersion: apps/v1 + kind: Deployment + metadata: + name: recommendationservice + spec: + template: + spec: + containers: + - name: server + env: + - name: COLLECTOR_SERVICE_ADDR + value: "opentelemetrycollector:4317" + - name: OTEL_SERVICE_NAME + value: "recommendationservice" + - name: ENABLE_TRACING + value: "1" + - name: DISABLE_PROFILER + $patch: delete +# shippingservice - stats, tracing, profiler +- patch: |- + apiVersion: apps/v1 + kind: Deployment + metadata: + name: shippingservice + spec: + template: + spec: + containers: + - name: server + env: + - name: DISABLE_PROFILER + $patch: delete diff --git a/kustomize/components/google-cloud-operations/otel-collector.yaml b/kustomize/components/google-cloud-operations/otel-collector.yaml new file mode 100644 index 0000000..93ae873 --- /dev/null +++ b/kustomize/components/google-cloud-operations/otel-collector.yaml @@ -0,0 +1,126 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: opentelemetrycollector +spec: + replicas: 1 + selector: + matchLabels: + app: opentelemetrycollector + template: + metadata: + labels: + app: opentelemetrycollector + spec: + securityContext: + fsGroup: 1000 + runAsGroup: 1000 + runAsNonRoot: true + runAsUser: 1000 + # Init container retrieves the current cloud project id from the metadata server + # and inserts it into the collector config template + # https://cloud.google.com/compute/docs/storing-retrieving-metadata + initContainers: + - name: otel-gateway-init + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + privileged: false + readOnlyRootFilesystem: true + image: busybox:latest@sha256:e226d6308690dbe282443c8c7e57365c96b5228f0fe7f40731b5d84d37a06839 + command: + - '/bin/sh' + - '-c' + - | + sed "s/{{PROJECT_ID}}/$(curl -H 'Metadata-Flavor: Google' http://metadata.google.internal/computeMetadata/v1/project/project-id)/" /template/collector-gateway-config-template.yaml >> /conf/collector-gateway-config.yaml + volumeMounts: + - name: collector-gateway-config-template + mountPath: /template + - name: collector-gateway-config + mountPath: /conf + containers: + # This gateway container will receive traces and metrics from each microservice + # and forward it to GCP + - name: otel-gateway + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + privileged: false + readOnlyRootFilesystem: true + args: + - --config=/conf/collector-gateway-config.yaml + image: otel/opentelemetry-collector-contrib:0.144.0@sha256:213886eb6407af91b87fa47551c3632be1a6419ff3a5114ef1e6fc364628496f + volumeMounts: + - name: collector-gateway-config + mountPath: /conf + volumes: + # Simple ConfigMap volume with template file + - name: collector-gateway-config-template + configMap: + items: + - key: collector-gateway-config-template.yaml + path: collector-gateway-config-template.yaml + name: collector-gateway-config-template + # Create a volume to store the expanded template (with correct cloud project ID) + - name: collector-gateway-config + emptyDir: {} +--- +apiVersion: v1 +kind: Service +metadata: + name: opentelemetrycollector +spec: + ports: + - name: grpc-otlp + port: 4317 + protocol: TCP + targetPort: 4317 + selector: + app: opentelemetrycollector + type: ClusterIP +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: collector-gateway-config-template +# Open Telemetry Collector config +# https://opentelemetry.io/docs/collector/configuration/ +data: + collector-gateway-config-template.yaml: | + receivers: + otlp: + protocols: + grpc: + processors: + exporters: + googlecloud: + project: {{PROJECT_ID}} + service: + pipelines: + traces: + receivers: [otlp] # Receive otlp-formatted data from other collector instances + processors: [] + exporters: [googlecloud] # Export traces directly to Google Cloud + metrics: + receivers: [otlp] + processors: [] + exporters: [googlecloud] # Export metrics to Google Cloud \ No newline at end of file diff --git a/kustomize/components/memorystore/README.md b/kustomize/components/memorystore/README.md new file mode 100644 index 0000000..ab31adf --- /dev/null +++ b/kustomize/components/memorystore/README.md @@ -0,0 +1,67 @@ +# Integrate Online Boutique with Memorystore (Redis) + +By default the `cartservice` app is serializing the data in an in-cluster Redis database. Using a database outside your GKE cluster could bring more resiliency and more security with a managed service like Google Cloud Memorystore (Redis). + +![Architecture diagram with Memorystore](/docs/img/memorystore.png) + +## Provision a Memorystore (Redis) instance + +Important notes: + +- You can connect to a Memorystore (Redis) instance from GKE clusters that are in the same region and use the same network as your instance. +- You cannot connect to a Memorystore (Redis) instance from a GKE cluster without VPC-native/IP aliasing enabled. + +To provision a Memorystore (Redis) instance you can follow the following instructions: + +```bash +ZONE="" +REGION="" + +gcloud services enable redis.googleapis.com + +gcloud redis instances create redis-cart \ + --size=1 \ + --region=${REGION} \ + --zone=${ZONE} \ + --redis-version=redis_7_0 +``` + +_Note: You can also find in this repository the Terraform script to provision the Memorystore (Redis) instance alongside the GKE cluster, more information [here](/terraform)._ + +## Deploy Online Boutique connected to a Memorystore (Redis) instance + +To automate the deployment of Online Boutique integrated with Memorystore (Redis) you can leverage the following variation with [Kustomize](../..). + +From the `kustomize/` folder at the root level of this repository, execute this command: + +```bash +kustomize edit add component components/memorystore +``` + +_Note: this Kustomize component will also remove the `redis-cart` `Deployment` and `Service` not used anymore._ + +This will update the `kustomize/kustomization.yaml` file which could be similar to: + +```yaml +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: +- base +components: +- components/memorystore +``` + +Update current Kustomize manifest to target this Memorystore (Redis) instance. + +```bash +REDIS_IP=$(gcloud redis instances describe redis-cart --region=${REGION} --format='get(host)') +REDIS_PORT=$(gcloud redis instances describe redis-cart --region=${REGION} --format='get(port)') +sed -i "s/REDIS_CONNECTION_STRING/${REDIS_IP}:${REDIS_PORT}/g" components/memorystore/kustomization.yaml +``` + +You can locally render these manifests by running `kubectl kustomize .` as well as deploying them by running `kubectl apply -k .`. + +## Resources + +- [Connecting to a Redis instance from a Google Kubernetes Engine cluster](https://cloud.google.com/memorystore/docs/redis/connect-redis-instance-gke) +- [Seamlessly encrypt traffic from any apps in your Mesh to Memorystore (Redis)](https://medium.com/google-cloud/64b71969318d) diff --git a/kustomize/components/memorystore/kustomization.yaml b/kustomize/components/memorystore/kustomization.yaml new file mode 100644 index 0000000..18a09da --- /dev/null +++ b/kustomize/components/memorystore/kustomization.yaml @@ -0,0 +1,45 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: kustomize.config.k8s.io/v1alpha1 +kind: Component +patches: +# cartservice - replace REDIS_ADDR to target new Memorystore (redis) instance +- patch: |- + apiVersion: apps/v1 + kind: Deployment + metadata: + name: cartservice + spec: + template: + spec: + containers: + - name: server + env: + - name: REDIS_ADDR + value: "REDIS_CONNECTION_STRING" +# redis - remove the redis-cart Deployment +- patch: |- + apiVersion: apps/v1 + kind: Deployment + metadata: + name: redis-cart + $patch: delete +# redis - remove the redis-cart Service +- patch: |- + apiVersion: v1 + kind: Service + metadata: + name: redis-cart + $patch: delete diff --git a/kustomize/components/network-policies/README.md b/kustomize/components/network-policies/README.md new file mode 100644 index 0000000..b434f05 --- /dev/null +++ b/kustomize/components/network-policies/README.md @@ -0,0 +1,64 @@ +# Secure Online Boutique with Network Policies + +You can use [Network Policies](https://kubernetes.io/docs/concepts/services-networking/network-policies/) enforcement to control the communication between your cluster's Pods and Services. + +To use `NetworkPolicies` in Google Kubernetes Engine (GKE), you will need a GKE cluster with network policy enforcement enabled, the recommended approach is to use [GKE Dataplane V2](https://cloud.google.com/kubernetes-engine/docs/how-to/dataplane-v2). + +To use `NetworkPolicies` on a local cluster such as [minikube](https://minikube.sigs.k8s.io/docs/start/), you will need to use an alternative CNI that supports `NetworkPolicies` like [Calico](https://projectcalico.docs.tigera.io/getting-started/kubernetes/minikube). To run a minikube cluster with Calico, run `minikube start --cni=calico`. By design, the minikube default CNI [Kindnet](https://github.com/aojea/kindnet) does not support it. + +## Deploy Online Boutique with `NetworkPolicies` via Kustomize + +To automate the deployment of Online Boutique integrated with fine granular `NetworkPolicies` (one per `Deployment`), you can leverage the following variation with [Kustomize](../..). + +From the `kustomize/` folder at the root level of this repository, execute this command: + +```bash +kustomize edit add component components/network-policies +``` + +This will update the `kustomize/kustomization.yaml` file which could be similar to: + +```yaml +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: +- base +components: +- components/network-policies +``` + +You can locally render these manifests by running `kubectl kustomize .` as well as deploying them by running `kubectl apply -k .`. + +Once deployed, you can verify that the `NetworkPolicies` are successfully deployed: + +```bash +kubectl get networkpolicy +``` + +The output could be similar to: + +```output +NAME POD-SELECTOR AGE +adservice app=adservice 2m58s +cartservice app=cartservice 2m58s +checkoutservice app=checkoutservice 2m58s +currencyservice app=currencyservice 2m58s +deny-all 2m58s +emailservice app=emailservice 2m58s +frontend app=frontend 2m58s +loadgenerator app=loadgenerator 2m58s +paymentservice app=paymentservice 2m58s +productcatalogservice app=productcatalogservice 2m58s +recommendationservice app=recommendationservice 2m58s +redis-cart app=redis-cart 2m58s +shippingservice app=shippingservice 2m58s +``` + +_Note: `Egress` is wide open in these `NetworkPolicies` . In our case, we do this is on purpose because there are multiple egress destinations to take into consideration like the Kubernetes DNS, Istio control plane (`istiod`), Cloud Trace API, Cloud Profiler API, etc._ + +## Related Resources + +- [GKE Dataplane V2 announcement](https://cloud.google.com/blog/products/containers-kubernetes/bringing-ebpf-and-cilium-to-google-kubernetes-engine) +- [Kubernetes Network Policies](https://kubernetes.io/docs/concepts/services-networking/network-policies/) +- [Kubernetes Network Policy Recipes](https://github.com/ahmetb/kubernetes-network-policy-recipes) +- [Network policy logging](https://cloud.google.com/kubernetes-engine/docs/how-to/network-policy-logging) diff --git a/kustomize/components/network-policies/kustomization.yaml b/kustomize/components/network-policies/kustomization.yaml new file mode 100644 index 0000000..c08f153 --- /dev/null +++ b/kustomize/components/network-policies/kustomization.yaml @@ -0,0 +1,30 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: kustomize.config.k8s.io/v1alpha1 +kind: Component +resources: +- network-policy-deny-all.yaml +- network-policy-adservice.yaml +- network-policy-cartservice.yaml +- network-policy-checkoutservice.yaml +- network-policy-currencyservice.yaml +- network-policy-emailservice.yaml +- network-policy-frontend.yaml +- network-policy-loadgenerator.yaml +- network-policy-paymentservice.yaml +- network-policy-productcatalogservice.yaml +- network-policy-recommendationservice.yaml +- network-policy-redis.yaml +- network-policy-shippingservice.yaml diff --git a/kustomize/components/network-policies/network-policy-adservice.yaml b/kustomize/components/network-policies/network-policy-adservice.yaml new file mode 100644 index 0000000..0be415e --- /dev/null +++ b/kustomize/components/network-policies/network-policy-adservice.yaml @@ -0,0 +1,35 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: adservice +spec: + podSelector: + matchLabels: + app: adservice + policyTypes: + - Ingress + - Egress + ingress: + - from: + - podSelector: + matchLabels: + app: frontend + ports: + - port: 9555 + protocol: TCP + egress: + - {} \ No newline at end of file diff --git a/kustomize/components/network-policies/network-policy-cartservice.yaml b/kustomize/components/network-policies/network-policy-cartservice.yaml new file mode 100644 index 0000000..a62d346 --- /dev/null +++ b/kustomize/components/network-policies/network-policy-cartservice.yaml @@ -0,0 +1,38 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: cartservice +spec: + podSelector: + matchLabels: + app: cartservice + policyTypes: + - Ingress + - Egress + ingress: + - from: + - podSelector: + matchLabels: + app: frontend + - podSelector: + matchLabels: + app: checkoutservice + ports: + - port: 7070 + protocol: TCP + egress: + - {} \ No newline at end of file diff --git a/kustomize/components/network-policies/network-policy-checkoutservice.yaml b/kustomize/components/network-policies/network-policy-checkoutservice.yaml new file mode 100644 index 0000000..40d9d8c --- /dev/null +++ b/kustomize/components/network-policies/network-policy-checkoutservice.yaml @@ -0,0 +1,35 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: checkoutservice +spec: + podSelector: + matchLabels: + app: checkoutservice + policyTypes: + - Ingress + - Egress + ingress: + - from: + - podSelector: + matchLabels: + app: frontend + ports: + - port: 5050 + protocol: TCP + egress: + - {} \ No newline at end of file diff --git a/kustomize/components/network-policies/network-policy-currencyservice.yaml b/kustomize/components/network-policies/network-policy-currencyservice.yaml new file mode 100644 index 0000000..fc2e6c5 --- /dev/null +++ b/kustomize/components/network-policies/network-policy-currencyservice.yaml @@ -0,0 +1,38 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: currencyservice +spec: + podSelector: + matchLabels: + app: currencyservice + policyTypes: + - Ingress + - Egress + ingress: + - from: + - podSelector: + matchLabels: + app: frontend + - podSelector: + matchLabels: + app: checkoutservice + ports: + - port: 7000 + protocol: TCP + egress: + - {} \ No newline at end of file diff --git a/kustomize/components/network-policies/network-policy-deny-all.yaml b/kustomize/components/network-policies/network-policy-deny-all.yaml new file mode 100644 index 0000000..0b6a47e --- /dev/null +++ b/kustomize/components/network-policies/network-policy-deny-all.yaml @@ -0,0 +1,23 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: deny-all +spec: + podSelector: {} + policyTypes: + - Ingress + - Egress diff --git a/kustomize/components/network-policies/network-policy-emailservice.yaml b/kustomize/components/network-policies/network-policy-emailservice.yaml new file mode 100644 index 0000000..2503cda --- /dev/null +++ b/kustomize/components/network-policies/network-policy-emailservice.yaml @@ -0,0 +1,35 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: emailservice +spec: + podSelector: + matchLabels: + app: emailservice + policyTypes: + - Ingress + - Egress + ingress: + - from: + - podSelector: + matchLabels: + app: checkoutservice + ports: + - port: 8080 + protocol: TCP + egress: + - {} \ No newline at end of file diff --git a/kustomize/components/network-policies/network-policy-frontend.yaml b/kustomize/components/network-policies/network-policy-frontend.yaml new file mode 100644 index 0000000..53b36ed --- /dev/null +++ b/kustomize/components/network-policies/network-policy-frontend.yaml @@ -0,0 +1,29 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: frontend +spec: + podSelector: + matchLabels: + app: frontend + policyTypes: + - Ingress + - Egress + ingress: + - {} + egress: + - {} \ No newline at end of file diff --git a/kustomize/components/network-policies/network-policy-loadgenerator.yaml b/kustomize/components/network-policies/network-policy-loadgenerator.yaml new file mode 100644 index 0000000..1fc7799 --- /dev/null +++ b/kustomize/components/network-policies/network-policy-loadgenerator.yaml @@ -0,0 +1,26 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: loadgenerator +spec: + podSelector: + matchLabels: + app: loadgenerator + policyTypes: + - Egress + egress: + - {} \ No newline at end of file diff --git a/kustomize/components/network-policies/network-policy-paymentservice.yaml b/kustomize/components/network-policies/network-policy-paymentservice.yaml new file mode 100644 index 0000000..e74d73a --- /dev/null +++ b/kustomize/components/network-policies/network-policy-paymentservice.yaml @@ -0,0 +1,35 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: paymentservice +spec: + podSelector: + matchLabels: + app: paymentservice + policyTypes: + - Ingress + - Egress + ingress: + - from: + - podSelector: + matchLabels: + app: checkoutservice + ports: + - port: 50051 + protocol: TCP + egress: + - {} \ No newline at end of file diff --git a/kustomize/components/network-policies/network-policy-productcatalogservice.yaml b/kustomize/components/network-policies/network-policy-productcatalogservice.yaml new file mode 100644 index 0000000..d24f8ba --- /dev/null +++ b/kustomize/components/network-policies/network-policy-productcatalogservice.yaml @@ -0,0 +1,41 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: productcatalogservice +spec: + podSelector: + matchLabels: + app: productcatalogservice + policyTypes: + - Ingress + - Egress + ingress: + - from: + - podSelector: + matchLabels: + app: frontend + - podSelector: + matchLabels: + app: checkoutservice + - podSelector: + matchLabels: + app: recommendationservice + ports: + - port: 3550 + protocol: TCP + egress: + - {} \ No newline at end of file diff --git a/kustomize/components/network-policies/network-policy-recommendationservice.yaml b/kustomize/components/network-policies/network-policy-recommendationservice.yaml new file mode 100644 index 0000000..973dc60 --- /dev/null +++ b/kustomize/components/network-policies/network-policy-recommendationservice.yaml @@ -0,0 +1,35 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: recommendationservice +spec: + podSelector: + matchLabels: + app: recommendationservice + policyTypes: + - Ingress + - Egress + ingress: + - from: + - podSelector: + matchLabels: + app: frontend + ports: + - port: 8080 + protocol: TCP + egress: + - {} \ No newline at end of file diff --git a/kustomize/components/network-policies/network-policy-redis.yaml b/kustomize/components/network-policies/network-policy-redis.yaml new file mode 100644 index 0000000..a73f0ac --- /dev/null +++ b/kustomize/components/network-policies/network-policy-redis.yaml @@ -0,0 +1,35 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: redis-cart +spec: + podSelector: + matchLabels: + app: redis-cart + policyTypes: + - Ingress + - Egress + ingress: + - from: + - podSelector: + matchLabels: + app: cartservice + ports: + - port: 6379 + protocol: TCP + egress: + - {} diff --git a/kustomize/components/network-policies/network-policy-shippingservice.yaml b/kustomize/components/network-policies/network-policy-shippingservice.yaml new file mode 100644 index 0000000..a85ebab --- /dev/null +++ b/kustomize/components/network-policies/network-policy-shippingservice.yaml @@ -0,0 +1,38 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: shippingservice +spec: + podSelector: + matchLabels: + app: shippingservice + policyTypes: + - Ingress + - Egress + ingress: + - from: + - podSelector: + matchLabels: + app: frontend + - podSelector: + matchLabels: + app: checkoutservice + ports: + - port: 50051 + protocol: TCP + egress: + - {} \ No newline at end of file diff --git a/kustomize/components/non-public-frontend/README.md b/kustomize/components/non-public-frontend/README.md new file mode 100644 index 0000000..1cd74ee --- /dev/null +++ b/kustomize/components/non-public-frontend/README.md @@ -0,0 +1,27 @@ +# Remove the public exposure of Online Boutique's frontend + +By default, when you deploy Online Boutique, a `Service` (named `frontend-external`) of type `LoadBalancer` is deployed with a publicly accessible IP address. +But you may not want to expose this sample app publicly. + +## Deploy Online Boutique without the default public endpoint + +To automate the deployment of Online Boutique without the default public endpoint you can leverage the following variation with [Kustomize](../..). + +From the `kustomize/` folder at the root level of this repository, execute this command: + +```bash +kustomize edit add component components/non-public-frontend +``` + +This will update the `kustomize/kustomization.yaml` file which could be similar to: + +```yaml +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: +- base +components: +- components/non-public-frontend +``` + +You can locally render these manifests by running `kubectl kustomize .` as well as deploying them by running `kubectl apply -k .`. diff --git a/kustomize/components/non-public-frontend/kustomization.yaml b/kustomize/components/non-public-frontend/kustomization.yaml new file mode 100644 index 0000000..ca35bcb --- /dev/null +++ b/kustomize/components/non-public-frontend/kustomization.yaml @@ -0,0 +1,24 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: kustomize.config.k8s.io/v1alpha1 +kind: Component +patches: +# frontend - delete frontend-external service +- patch: |- + apiVersion: v1 + kind: Service + metadata: + name: frontend-external + $patch: delete diff --git a/kustomize/components/service-mesh-istio/README.md b/kustomize/components/service-mesh-istio/README.md new file mode 100644 index 0000000..b2a1b5b --- /dev/null +++ b/kustomize/components/service-mesh-istio/README.md @@ -0,0 +1,297 @@ +# Service mesh with Istio + +You can use [Istio](https://istio.io) to enable [service mesh features](https://cloud.google.com/service-mesh/docs/overview) such as traffic management, observability, and security. Istio can be provisioned using Cloud Service Mesh (CSM), the Open Source Software (OSS) istioctl tool, or via other Istio providers. You can then label individual namespaces for sidecar injection and configure an Istio gateway to replace the frontend-external load balancer. + +# Setup + +The following CLI tools needs to be installed and in the PATH: + +- `gcloud` +- `kubectl` +- `kustomize` +- `istioctl` (optional) + +1. Set-up some default environment variables. + + ```sh + PROJECT_ID="" + REGION=" 9555/TCP 49s + service/cartservice ClusterIP 10.68.184.25 7070/TCP 49s + service/checkoutservice ClusterIP 10.68.177.213 5050/TCP 49s + service/currencyservice ClusterIP 10.68.249.87 7000/TCP 49s + service/emailservice ClusterIP 10.68.205.123 5000/TCP 49s + service/frontend ClusterIP 10.68.94.203 80/TCP 48s + service/istio-gateway-istio LoadBalancer 10.68.147.158 35.247.123.146 15021:30376/TCP,80:30332/TCP 45s + service/kubernetes ClusterIP 10.68.0.1 443/TCP 65m + service/paymentservice ClusterIP 10.68.114.19 50051/TCP 48s + service/productcatalogservice ClusterIP 10.68.240.153 3550/TCP 48s + service/recommendationservice ClusterIP 10.68.117.97 8080/TCP 48s + service/redis-cart ClusterIP 10.68.189.126 6379/TCP 48s + service/shippingservice ClusterIP 10.68.221.62 50051/TCP 48s + ``` + +1. Find the external IP address of your Istio gateway. + + ```sh + INGRESS_HOST="$(kubectl get gateway istio-gateway \ + -o jsonpath='{.status.addresses[*].value}')" + ``` + +1. Navigate to the frontend in a web browser. + + ``` + http://$INGRESS_HOST + ``` + +# Additional service mesh demos using Online Boutique + +- [Canary deployment](https://github.com/GoogleCloudPlatform/istio-samples/tree/master/istio-canary-gke) +- [Security (mTLS, JWT, Authorization)](https://github.com/GoogleCloudPlatform/istio-samples/tree/master/security-intro) +- [Cloud Operations (Stackdriver)](https://github.com/GoogleCloudPlatform/istio-samples/tree/master/istio-stackdriver) +- [Stackdriver metrics (Open source Istio)](https://github.com/GoogleCloudPlatform/istio-samples/tree/master/stackdriver-metrics) + +# Related resources + +- [Deploying classic istio-ingressgateway in ASM](https://cloud.google.com/service-mesh/docs/gateways#deploy_gateways) +- [Uninstall Istio via istioctl](https://istio.io/latest/docs/setup/install/istioctl/#uninstall-istio) +- [Uninstall Cloud Service Mesh](https://cloud.google.com/service-mesh/docs/uninstall) diff --git a/kustomize/components/service-mesh-istio/allow-egress-googleapis.yaml b/kustomize/components/service-mesh-istio/allow-egress-googleapis.yaml new file mode 100644 index 0000000..50410b2 --- /dev/null +++ b/kustomize/components/service-mesh-istio/allow-egress-googleapis.yaml @@ -0,0 +1,46 @@ +# Copyright 2018 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: networking.istio.io/v1alpha3 +kind: ServiceEntry +metadata: + name: allow-egress-googleapis +spec: + hosts: + - "accounts.google.com" # Used to get token + - "*.googleapis.com" + ports: + - number: 80 + protocol: HTTP + name: http + - number: 443 + protocol: HTTPS + name: https +--- +apiVersion: networking.istio.io/v1alpha3 +kind: ServiceEntry +metadata: + name: allow-egress-google-metadata +spec: + hosts: + - metadata.google.internal + addresses: + - 169.254.169.254 # GCE metadata server + ports: + - number: 80 + name: http + protocol: HTTP + - number: 443 + name: https + protocol: HTTPS diff --git a/kustomize/components/service-mesh-istio/frontend-gateway.yaml b/kustomize/components/service-mesh-istio/frontend-gateway.yaml new file mode 100644 index 0000000..4f62844 --- /dev/null +++ b/kustomize/components/service-mesh-istio/frontend-gateway.yaml @@ -0,0 +1,42 @@ +# Copyright 2018 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: gateway.networking.k8s.io/v1beta1 +kind: Gateway +metadata: + name: istio-gateway +spec: + gatewayClassName: istio + listeners: + - name: http + port: 80 + protocol: HTTP + allowedRoutes: + namespaces: + from: Same +--- +apiVersion: gateway.networking.k8s.io/v1beta1 +kind: HTTPRoute +metadata: + name: frontend-route +spec: + parentRefs: + - name: istio-gateway + rules: + - matches: + - path: + value: / + backendRefs: + - name: frontend + port: 80 diff --git a/kustomize/components/service-mesh-istio/frontend.yaml b/kustomize/components/service-mesh-istio/frontend.yaml new file mode 100644 index 0000000..23cd648 --- /dev/null +++ b/kustomize/components/service-mesh-istio/frontend.yaml @@ -0,0 +1,27 @@ +# Copyright 2018 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: networking.istio.io/v1alpha3 +kind: VirtualService +metadata: + name: frontend +spec: + hosts: + - "frontend.default.svc.cluster.local" + http: + - route: + - destination: + host: frontend + port: + number: 80 diff --git a/kustomize/components/service-mesh-istio/kustomization.yaml b/kustomize/components/service-mesh-istio/kustomization.yaml new file mode 100644 index 0000000..2399e65 --- /dev/null +++ b/kustomize/components/service-mesh-istio/kustomization.yaml @@ -0,0 +1,28 @@ +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: kustomize.config.k8s.io/v1alpha1 +kind: Component +resources: + - allow-egress-googleapis.yaml + - frontend-gateway.yaml + - frontend.yaml +patches: +# frontend - delete frontend-external service (same as non-public-frontend component) +- patch: |- + apiVersion: v1 + kind: Service + metadata: + name: frontend-external + $patch: delete diff --git a/kustomize/components/shopping-assistant/README.md b/kustomize/components/shopping-assistant/README.md new file mode 100644 index 0000000..3b57821 --- /dev/null +++ b/kustomize/components/shopping-assistant/README.md @@ -0,0 +1,145 @@ +# Shopping Assistant with RAG & AlloyDB + +This demo adds a new service to Online Boutique called `shoppingassistantservice` which, alongside an Alloy-DB backed products catalog, adds a RAG-featured AI assistant to the frontend experience, helping users suggest products matching their home decor. + +## Setup instructions + +**Note:** This demo requires a Google Cloud project where you to have the `owner` role, else you may be unable to enable APIs or modify VPC rules that are needed for this demo. + +1. Set some environment variables. + ```sh + export PROJECT_ID= + export PROJECT_NUMBER= + export PGPASSWORD= + ``` + + **Note**: The project ID and project number of your Google Cloud project can be found in the Console. The PostgreSQL password can be set to anything you want, but make sure to note it down. + +1. Change your default Google Cloud project. + ```sh + gcloud auth login + gcloud config set project $PROJECT_ID + ``` + +1. Enable the Google Kubernetes Engine (GKE) and Artifact Registry (AR) APIs. + ```sh + gcloud services enable container.googleapis.com + gcloud services enable artifactregistry.googleapis.com + ``` + +1. Create a GKE Autopilot cluster. This may take a few minutes. + ```sh + gcloud container clusters create-auto cymbal-shops \ + --region=us-central1 + ``` + +1. Change your Kubernetes context to your newly created GKE cluster. + ```sh + gcloud container clusters get-credentials cymbal-shops \ + --region us-central1 + ``` + +1. Create an Artifact Registry container image repository. + ```sh + gcloud artifacts repositories create images \ + --repository-format=docker \ + --location=us-central1 + ``` + +1. Clone the `microservices-demo` repository locally. + ```sh + git clone https://github.com/GoogleCloudPlatform/microservices-demo \ + && cd microservices-demo/ + ``` + +1. Run script #1. If it asks about policy bindings, select the option `None`. This may take a few minutes. + ```sh + ./kustomize/components/shopping-assistant/scripts/1_deploy_alloydb_infra.sh + ``` + + **Note**: If you are on macOS and use a non-GNU version of `sed`, you may have to tweak the script to use `gsed` instead. + +1. Create a Linux VM in Compute Engine (GCE). + ```sh + gcloud compute instances create gce-linux \ + --zone=us-central1-a \ + --machine-type=e2-micro \ + --image-family=debian-12 \ + --image-project=debian-cloud + ``` + +1. SSH into the VM. From here until we exit, all steps happen in the VM. + ```sh + gcloud compute ssh gce-linux \ + --zone "us-central1-a" + ``` + +1. Install the Postgres client and set your default Google Cloud project. + ```sh + sudo apt-get install -y postgresql-client + gcloud auth login + gcloud config set project + ``` + +1. Copy script #2, the python script, and the products.json to the VM. Make sure the scripts are executable. + ```sh + nano 2_create_populate_alloydb_tables.sh # paste content + nano generate_sql_from_products.py # paste content + nano products.json # paste content + chmod +x 2_create_populate_alloydb_tables.sh + chmod +x generate_sql_from_products.py + ``` + + **Note:** You can find the files at the following places: + - `kustomize/components/shopping-assistant/scripts/2_create_populate_alloydb_tables.sh` + - `kustomize/components/shopping-assistant/scripts/generate_sql_from_products.py` + - `src/productcatalogservice/products.json` + +1. Run script #2 in the VM. If it asks for a postgres password, it should be the same that you set in script #1 earlier. This may take a few minutes. + ```sh + ./2_create_populate_alloydb_tables.sh + ``` + +1. Exit SSH. + ```sh + exit + ``` + +1. Create an API key in the [Credentials page](https://pantheon.corp.google.com/apis/credentials) with permissions for "Generative Language API", and make note of the secret key. + +1. Replace the Google API key placeholder in the shoppingassistant service. + ```sh + export GOOGLE_API_KEY= + sed -i "s/GOOGLE_API_KEY_VAL/${GOOGLE_API_KEY}/g" kustomize/components/shopping-assistant/shoppingassistantservice.yaml + ``` + +1. Edit the root Kustomize file to enable the `alloydb` and `shopping-assistant` components. + ```sh + nano kubernetes-manifests/kustomization.yaml # make the modifications below + ``` + + ```yaml + # ...head of the file + components: # remove this comment + # - ../kustomize/components/cymbal-branding + # - ../kustomize/components/google-cloud-operations + # - ../kustomize/components/memorystore + # - ../kustomize/components/network-policies + - ../kustomize/components/alloydb # remove this comment + - ../kustomize/components/shopping-assistant # remove this comment + # - ../kustomize/components/spanner + # - ../kustomize/components/container-images-tag + # - ../kustomize/components/container-images-tag-suffix + # - ../kustomize/components/container-images-registry + ``` + +1. Deploy to the GKE cluster. + ```sh + skaffold run --default-repo=us-central1-docker.pkg.dev/$PROJECT_ID/images + ``` + +1. Wait for all the pods to be up and running. You can then find the external IP and navigate to it. + ```sh + kubectl get pods + kubectl get services + ``` diff --git a/kustomize/components/shopping-assistant/kustomization.yaml b/kustomize/components/shopping-assistant/kustomization.yaml new file mode 100644 index 0000000..90ec786 --- /dev/null +++ b/kustomize/components/shopping-assistant/kustomization.yaml @@ -0,0 +1,32 @@ +# Copyright 2024 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: kustomize.config.k8s.io/v1alpha1 +kind: Component +resources: +- shoppingassistantservice.yaml +patches: +- patch: |- + apiVersion: apps/v1 + kind: Deployment + metadata: + name: frontend + spec: + template: + spec: + containers: + - name: server + env: + - name: ENABLE_ASSISTANT + value: "true" diff --git a/kustomize/components/shopping-assistant/scripts/1_deploy_alloydb_infra.sh b/kustomize/components/shopping-assistant/scripts/1_deploy_alloydb_infra.sh new file mode 100755 index 0000000..446df8b --- /dev/null +++ b/kustomize/components/shopping-assistant/scripts/1_deploy_alloydb_infra.sh @@ -0,0 +1,135 @@ +#!/bin/sh +# +# Copyright 2024 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set -e +set -x + +# Replace me +PROJECT_ID=$PROJECT_ID +PROJECT_NUMBER=$PROJECT_NUMBER +PGPASSWORD=$PGPASSWORD + +# Set sensible defaults +REGION=us-central1 +USE_GKE_GCLOUD_AUTH_PLUGIN=True +ALLOYDB_NETWORK=default +ALLOYDB_SERVICE_NAME=onlineboutique-network-range +ALLOYDB_CLUSTER_NAME=onlineboutique-cluster +ALLOYDB_INSTANCE_NAME=onlineboutique-instance +ALLOYDB_CARTS_DATABASE_NAME=carts +ALLOYDB_CARTS_TABLE_NAME=cart_items +ALLOYDB_PRODUCTS_DATABASE_NAME=products +ALLOYDB_PRODUCTS_TABLE_NAME=catalog_items +ALLOYDB_USER_GSA_NAME=alloydb-user-sa +ALLOYDB_USER_GSA_ID=${ALLOYDB_USER_GSA_NAME}@${PROJECT_ID}.iam.gserviceaccount.com +ALLOYDB_SECRET_NAME=alloydb-secret + +# Enable services +gcloud services enable alloydb.googleapis.com +gcloud services enable servicenetworking.googleapis.com +gcloud services enable secretmanager.googleapis.com +gcloud services enable aiplatform.googleapis.com +gcloud services enable generativelanguage.googleapis.com + +# Set our DB credentials behind the secret +echo $PGPASSWORD | gcloud secrets create ${ALLOYDB_SECRET_NAME} --data-file=- + +# Set up needed service connection +gcloud compute addresses create ${ALLOYDB_SERVICE_NAME} \ + --global \ + --purpose=VPC_PEERING \ + --prefix-length=16 \ + --description="Online Boutique Private Services" \ + --network=${ALLOYDB_NETWORK} + +gcloud services vpc-peerings connect \ + --service=servicenetworking.googleapis.com \ + --ranges=${ALLOYDB_SERVICE_NAME} \ + --network=${ALLOYDB_NETWORK} + +gcloud alloydb clusters create ${ALLOYDB_CLUSTER_NAME} \ + --region=${REGION} \ + --password=${PGPASSWORD} \ + --disable-automated-backup \ + --network=${ALLOYDB_NETWORK} + +gcloud alloydb instances create ${ALLOYDB_INSTANCE_NAME} \ + --cluster=${ALLOYDB_CLUSTER_NAME} \ + --region=${REGION} \ + --cpu-count=4 \ + --instance-type=PRIMARY + +gcloud alloydb instances create ${ALLOYDB_INSTANCE_NAME}-replica \ + --cluster=${ALLOYDB_CLUSTER_NAME} \ + --region=${REGION} \ + --cpu-count=4 \ + --instance-type=READ_POOL \ + --read-pool-node-count=2 + +gcloud beta alloydb instances update ${ALLOYDB_INSTANCE_NAME} \ + --cluster=${ALLOYDB_CLUSTER_NAME} \ + --region=${REGION} \ + --assign-inbound-public-ip=ASSIGN_IPV4 \ + --database-flags password.enforce_complexity=on + +# Fetch the primary and read IPs +ALLOYDB_PRIMARY_IP=`gcloud alloydb instances list --region=${REGION} --cluster=${ALLOYDB_CLUSTER_NAME} --filter="INSTANCE_TYPE:PRIMARY" --format=flattened | sed -nE "s/ipAddress:\s*(.*)/\1/p"` +ALLOYDB_READ_IP=`gcloud alloydb instances list --region=${REGION} --cluster=${ALLOYDB_CLUSTER_NAME} --filter="INSTANCE_TYPE:READ_POOL" --format=flattened | sed -nE "s/ipAddress:\s*(.*)/\1/p"` + +# Substitute environment values (alloydb/kustomization.yaml) +sed -i "s/PROJECT_ID_VAL/${PROJECT_ID}/g" kustomize/components/alloydb/kustomization.yaml +sed -i "s/REGION_VAL/${REGION}/g" kustomize/components/alloydb/kustomization.yaml +sed -i "s/ALLOYDB_PRIMARY_IP_VAL/${ALLOYDB_PRIMARY_IP}/g" kustomize/components/alloydb/kustomization.yaml +sed -i "s/ALLOYDB_USER_GSA_ID/${ALLOYDB_USER_GSA_ID}/g" kustomize/components/alloydb/kustomization.yaml +sed -i "s/ALLOYDB_CLUSTER_NAME_VAL/${ALLOYDB_CLUSTER_NAME}/g" kustomize/components/alloydb/kustomization.yaml +sed -i "s/ALLOYDB_INSTANCE_NAME_VAL/${ALLOYDB_INSTANCE_NAME}/g" kustomize/components/alloydb/kustomization.yaml +sed -i "s/ALLOYDB_CARTS_DATABASE_NAME_VAL/${ALLOYDB_CARTS_DATABASE_NAME}/g" kustomize/components/alloydb/kustomization.yaml +sed -i "s/ALLOYDB_CARTS_TABLE_NAME_VAL/${ALLOYDB_CARTS_TABLE_NAME}/g" kustomize/components/alloydb/kustomization.yaml +sed -i "s/ALLOYDB_PRODUCTS_DATABASE_NAME_VAL/${ALLOYDB_PRODUCTS_DATABASE_NAME}/g" kustomize/components/alloydb/kustomization.yaml +sed -i "s/ALLOYDB_PRODUCTS_TABLE_NAME_VAL/${ALLOYDB_PRODUCTS_TABLE_NAME}/g" kustomize/components/alloydb/kustomization.yaml +sed -i "s/ALLOYDB_SECRET_NAME_VAL/${ALLOYDB_SECRET_NAME}/g" kustomize/components/alloydb/kustomization.yaml + +# Substitute environment values (kustomize/components/shopping-assistant/shoppingassistantservice.yaml) +sed -i "s/PROJECT_ID_VAL/${PROJECT_ID}/g" kustomize/components/shopping-assistant/shoppingassistantservice.yaml +sed -i "s/REGION_VAL/${REGION}/g" kustomize/components/shopping-assistant/shoppingassistantservice.yaml +sed -i "s/ALLOYDB_CLUSTER_NAME_VAL/${ALLOYDB_CLUSTER_NAME}/g" kustomize/components/shopping-assistant/shoppingassistantservice.yaml +sed -i "s/ALLOYDB_INSTANCE_NAME_VAL/${ALLOYDB_INSTANCE_NAME}/g" kustomize/components/shopping-assistant/shoppingassistantservice.yaml +sed -i "s/ALLOYDB_DATABASE_NAME_VAL/${ALLOYDB_PRODUCTS_DATABASE_NAME}/g" kustomize/components/shopping-assistant/shoppingassistantservice.yaml +sed -i "s/ALLOYDB_TABLE_NAME_VAL/${ALLOYDB_PRODUCTS_TABLE_NAME}/g" kustomize/components/shopping-assistant/shoppingassistantservice.yaml +sed -i "s/ALLOYDB_SECRET_NAME_VAL/${ALLOYDB_SECRET_NAME}/g" kustomize/components/shopping-assistant/shoppingassistantservice.yaml +sed -i "s/ALLOYDB_USER_GSA_ID/${ALLOYDB_USER_GSA_ID}/g" kustomize/components/shopping-assistant/shoppingassistantservice.yaml + +# Create service account for the cart and shopping assistant services +gcloud iam service-accounts create ${ALLOYDB_USER_GSA_NAME} \ + --display-name=${ALLOYDB_USER_GSA_NAME} + +gcloud projects add-iam-policy-binding ${PROJECT_ID} --member=serviceAccount:${ALLOYDB_USER_GSA_ID} --role=roles/alloydb.client +gcloud projects add-iam-policy-binding ${PROJECT_ID} --member=serviceAccount:${ALLOYDB_USER_GSA_ID} --role=roles/alloydb.databaseUser +gcloud projects add-iam-policy-binding ${PROJECT_ID} --member=serviceAccount:${ALLOYDB_USER_GSA_ID} --role=roles/secretmanager.secretAccessor +gcloud projects add-iam-policy-binding ${PROJECT_ID} --member=serviceAccount:${ALLOYDB_USER_GSA_ID} --role=roles/serviceusage.serviceUsageConsumer +gcloud projects add-iam-policy-binding ${PROJECT_ID} --member=serviceAccount:service-${PROJECT_NUMBER}@gcp-sa-alloydb.iam.gserviceaccount.com --role=roles/aiplatform.user + +gcloud iam service-accounts add-iam-policy-binding ${ALLOYDB_USER_GSA_ID} \ + --member "serviceAccount:${PROJECT_ID}.svc.id.goog[default/cartservice]" \ + --role roles/iam.workloadIdentityUser + +gcloud iam service-accounts add-iam-policy-binding ${ALLOYDB_USER_GSA_ID} \ + --member "serviceAccount:${PROJECT_ID}.svc.id.goog[default/shoppingassistantservice]" \ + --role roles/iam.workloadIdentityUser + +gcloud iam service-accounts add-iam-policy-binding ${ALLOYDB_USER_GSA_ID} \ + --member "serviceAccount:${PROJECT_ID}.svc.id.goog[default/productcatalogservice]" \ + --role roles/iam.workloadIdentityUser diff --git a/kustomize/components/shopping-assistant/scripts/2_create_populate_alloydb_tables.sh b/kustomize/components/shopping-assistant/scripts/2_create_populate_alloydb_tables.sh new file mode 100755 index 0000000..77de831 --- /dev/null +++ b/kustomize/components/shopping-assistant/scripts/2_create_populate_alloydb_tables.sh @@ -0,0 +1,49 @@ +#!/bin/sh +# +# Copyright 2024 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set -e +set -x + +# Set sensible defaults +REGION=us-central1 +ALLOYDB_CLUSTER_NAME=onlineboutique-cluster +ALLOYDB_CARTS_DATABASE_NAME=carts +ALLOYDB_CARTS_TABLE_NAME=cart_items +ALLOYDB_PRODUCTS_DATABASE_NAME=products +ALLOYDB_PRODUCTS_TABLE_NAME=catalog_items + +# Fetch the primary and read IPs +ALLOYDB_PRIMARY_IP=`gcloud alloydb instances list --region=${REGION} --cluster=${ALLOYDB_CLUSTER_NAME} --filter="INSTANCE_TYPE:PRIMARY" --format=flattened | sed -nE "s/ipAddress:\s*(.*)/\1/p"` + +# Create carts database and table +psql -h ${ALLOYDB_PRIMARY_IP} -U postgres -c "CREATE DATABASE ${ALLOYDB_CARTS_DATABASE_NAME}" +psql -h ${ALLOYDB_PRIMARY_IP} -U postgres -d ${ALLOYDB_CARTS_DATABASE_NAME} -c "CREATE TABLE ${ALLOYDB_CARTS_TABLE_NAME} (userId text, productId text, quantity int, PRIMARY KEY(userId, productId))" +psql -h ${ALLOYDB_PRIMARY_IP} -U postgres -d ${ALLOYDB_CARTS_DATABASE_NAME} -c "CREATE INDEX cartItemsByUserId ON ${ALLOYDB_CARTS_TABLE_NAME}(userId)" + +# Create products database, table, and extensions +psql -h ${ALLOYDB_PRIMARY_IP} -U postgres -c "CREATE DATABASE ${ALLOYDB_PRODUCTS_DATABASE_NAME}" +psql -h ${ALLOYDB_PRIMARY_IP} -U postgres -d ${ALLOYDB_PRODUCTS_DATABASE_NAME} -c "CREATE EXTENSION IF NOT EXISTS vector" +psql -h ${ALLOYDB_PRIMARY_IP} -U postgres -d ${ALLOYDB_PRODUCTS_DATABASE_NAME} -c "CREATE EXTENSION IF NOT EXISTS google_ml_integration CASCADE;" +psql -h ${ALLOYDB_PRIMARY_IP} -U postgres -d ${ALLOYDB_PRODUCTS_DATABASE_NAME} -c "GRANT EXECUTE ON FUNCTION embedding TO postgres;" +psql -h ${ALLOYDB_PRIMARY_IP} -U postgres -d ${ALLOYDB_PRODUCTS_DATABASE_NAME} -c "CREATE TABLE ${ALLOYDB_PRODUCTS_TABLE_NAME} (id TEXT PRIMARY KEY, name TEXT, description TEXT, picture TEXT, price_usd_currency_code TEXT, price_usd_units INTEGER, price_usd_nanos BIGINT, categories TEXT, product_embedding VECTOR(768), embed_model TEXT)" + +# Generate and insert products table entries +python3 ./generate_sql_from_products.py > products.sql +psql -h ${ALLOYDB_PRIMARY_IP} -U postgres -d ${ALLOYDB_PRODUCTS_DATABASE_NAME} -f products.sql +rm products.sql + +# Generate vector embeddings +psql -h ${ALLOYDB_PRIMARY_IP} -U postgres -d ${ALLOYDB_PRODUCTS_DATABASE_NAME} -c "UPDATE ${ALLOYDB_PRODUCTS_TABLE_NAME} SET product_embedding = embedding('textembedding-gecko@003', description), embed_model='textembedding-gecko@003';" diff --git a/kustomize/components/shopping-assistant/scripts/generate_sql_from_products.py b/kustomize/components/shopping-assistant/scripts/generate_sql_from_products.py new file mode 100755 index 0000000..3ef940d --- /dev/null +++ b/kustomize/components/shopping-assistant/scripts/generate_sql_from_products.py @@ -0,0 +1,50 @@ +# Copyright 2024 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import json + +table_name = "catalog_items" +fields = [ + 'id', 'name', 'description', 'picture', + 'price_usd_currency_code', 'price_usd_units', 'price_usd_nanos', + 'categories' +] + +# Load the produts JSON +with open("products.json", 'r') as f: + data = json.load(f) + +# Generate SQL INSERT statements +for product in data['products']: + columns = ', '.join(fields) + placeholders = ', '.join(['{}'] * len(fields)) + sql = f"INSERT INTO {table_name} ({columns}) VALUES ({placeholders});" + + # Escape single quotes within product data + product['name'] = product['name'].replace("'", "") + product['description'] = product['description'].replace("'", "") + + escaped_values = ( + f"'{product['id']}'", + f"'{product['name']}'", + f"'{product['description']}'", + f"'{product['picture']}'", + f"'{product['priceUsd']['currencyCode']}'", + product['priceUsd']['units'], + product['priceUsd']['nanos'], + f"'{','.join(product['categories'])}'" + ) + + # Render the formatted SQL query + print(sql.format(*escaped_values)) diff --git a/kustomize/components/shopping-assistant/shoppingassistantservice.yaml b/kustomize/components/shopping-assistant/shoppingassistantservice.yaml new file mode 100644 index 0000000..d4c180f --- /dev/null +++ b/kustomize/components/shopping-assistant/shoppingassistantservice.yaml @@ -0,0 +1,95 @@ +# Copyright 2024 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: apps/v1 +kind: Deployment +metadata: + name: shoppingassistantservice + labels: + app: shoppingassistantservice +spec: + selector: + matchLabels: + app: shoppingassistantservice + template: + metadata: + labels: + app: shoppingassistantservice + spec: + serviceAccountName: shoppingassistantservice + terminationGracePeriodSeconds: 5 + securityContext: + fsGroup: 1000 + runAsGroup: 1000 + runAsNonRoot: true + runAsUser: 1000 + containers: + - name: server + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + privileged: false + readOnlyRootFilesystem: false + image: shoppingassistantservice + ports: + - name: http + containerPort: 8080 + env: + - name: GOOGLE_API_KEY + value: GOOGLE_API_KEY_VAL + - name: ALLOYDB_CLUSTER_NAME + value: ALLOYDB_CLUSTER_NAME_VAL + - name: ALLOYDB_INSTANCE_NAME + value: ALLOYDB_INSTANCE_NAME_VAL + - name: ALLOYDB_DATABASE_NAME + value: ALLOYDB_DATABASE_NAME_VAL + - name: ALLOYDB_TABLE_NAME + value: ALLOYDB_TABLE_NAME_VAL + - name: ALLOYDB_SECRET_NAME + value: ALLOYDB_SECRET_NAME_VAL + - name: PROJECT_ID + value: PROJECT_ID_VAL + - name: REGION + value: REGION_VAL + resources: + requests: + cpu: 100m + memory: 64Mi + limits: + cpu: 200m + memory: 128Mi +--- +apiVersion: v1 +kind: Service +metadata: + name: shoppingassistantservice + labels: + app: shoppingassistantservice +spec: + type: ClusterIP + selector: + app: shoppingassistantservice + ports: + - name: http + port: 80 + targetPort: 8080 +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: shoppingassistantservice + annotations: + iam.gke.io/gcp-service-account: ALLOYDB_USER_GSA_ID diff --git a/kustomize/components/single-shared-session/README.md b/kustomize/components/single-shared-session/README.md new file mode 100644 index 0000000..d674b25 --- /dev/null +++ b/kustomize/components/single-shared-session/README.md @@ -0,0 +1,28 @@ +# Manage a single shared session for the Online Boutique apps + +By default, when you deploy this sample app, the Online Boutique's `frontend` generates a `shop_session-id` cookie per browser session. +But you may want to share one unique `shop_session-id` cookie across all browser sessions. +This is useful for multi-cluster environments. + +## Deploy Online Boutique to generate a single shared session + +To automate the deployment of Online Boutique to manage a single shared session you can leverage the following variation with [Kustomize](../..). + +From the `kustomize/` folder at the root level of this repository, execute this command: + +```bash +kustomize edit add component components/single-shared-session +``` + +This will update the `kustomize/kustomization.yaml` file which could be similar to: + +```yaml +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: +- base +components: +- components/single-shared-session +``` + +You can locally render these manifests by running `kubectl kustomize .` as well as deploying them by running `kubectl apply -k .`. diff --git a/kustomize/components/single-shared-session/kustomization.yaml b/kustomize/components/single-shared-session/kustomization.yaml new file mode 100644 index 0000000..93c3b6a --- /dev/null +++ b/kustomize/components/single-shared-session/kustomization.yaml @@ -0,0 +1,31 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: kustomize.config.k8s.io/v1alpha1 +kind: Component +patches: +# frontend - ENABLE_SINGLE_SHARED_SESSION +- patch: |- + apiVersion: apps/v1 + kind: Deployment + metadata: + name: frontend + spec: + template: + spec: + containers: + - name: server + env: + - name: ENABLE_SINGLE_SHARED_SESSION + value: "true" diff --git a/kustomize/components/spanner/README.md b/kustomize/components/spanner/README.md new file mode 100644 index 0000000..6878cea --- /dev/null +++ b/kustomize/components/spanner/README.md @@ -0,0 +1,104 @@ +# Integrate Online Boutique with Spanner + +By default the `cartservice` stores its data in an in-cluster Redis database. +Using a fully managed database service outside your GKE cluster (such as [Google Cloud Spanner](https://cloud.google.com/spanner)) could bring more resiliency and more security. + +## Provision a Spanner database + +To provision a Spanner instance you can follow the following instructions: + +```bash +gcloud services enable spanner.googleapis.com + +SPANNER_REGION_CONFIG="" # e.g. "regional-us-east5" +SPANNER_INSTANCE_NAME=onlineboutique + +gcloud spanner instances create ${SPANNER_INSTANCE_NAME} \ + --description="online boutique shopping cart" \ + --config ${SPANNER_REGION_CONFIG} \ + --instance-type free-instance +``` + +_Note: With latest version of `gcloud` we are creating a free Spanner instance._ + +To provision a Spanner database you can follow the following instructions: + +```bash +SPANNER_DATABASE_NAME=carts + +gcloud spanner databases create ${SPANNER_DATABASE_NAME} \ + --instance ${SPANNER_INSTANCE_NAME} \ + --database-dialect GOOGLE_STANDARD_SQL \ + --ddl "CREATE TABLE CartItems (userId STRING(1024), productId STRING(1024), quantity INT64) PRIMARY KEY (userId, productId); CREATE INDEX CartItemsByUserId ON CartItems(userId);" +``` + +## Grant the `cartservice`'s service account access to the Spanner database + +**Important note:** Your GKE cluster should have [Workload Identity enabled](https://cloud.google.com/kubernetes-engine/docs/how-to/workload-identity#enable). + +As a good practice, let's create a dedicated least privilege Google Service Account to allow the `cartservice` to communicate with the Spanner database: + +```bash +PROJECT_ID= +SPANNER_DB_USER_GSA_NAME=spanner-db-user-sa +SPANNER_DB_USER_GSA_ID=${SPANNER_DB_USER_GSA_NAME}@${PROJECT_ID}.iam.gserviceaccount.com +ONLINEBOUTIQUE_NAMESPACE=default +CARTSERVICE_KSA_NAME=cartservice + +gcloud iam service-accounts create ${SPANNER_DB_USER_GSA_NAME} \ + --display-name=${SPANNER_DB_USER_GSA_NAME} + +gcloud spanner databases add-iam-policy-binding ${SPANNER_DATABASE_NAME} \ + --member "serviceAccount:${SPANNER_DB_USER_GSA_ID}" \ + --role roles/spanner.databaseUser + +gcloud iam service-accounts add-iam-policy-binding ${SPANNER_DB_USER_GSA_ID} \ + --member "serviceAccount:${PROJECT_ID}.svc.id.goog[${ONLINEBOUTIQUE_NAMESPACE}/${CARTSERVICE_KSA_NAME}]" \ + --role roles/iam.workloadIdentityUser +``` + +## Deploy Online Boutique connected to a Spanner database + +To automate the deployment of Online Boutique integrated with Spanner you can leverage the following variation with [Kustomize](../..). + +From the `kustomize/` folder at the root level of this repository, execute these commands: + +```bash +kustomize edit add component components/spanner +``` + +_Note: this Kustomize component will also remove the `redis-cart` `Deployment` and `Service` not used anymore._ + +This will update the `kustomize/kustomization.yaml` file which could be similar to: + +```yaml +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: +- base +components: +- components/spanner +``` + +Update current Kustomize manifest to target this Spanner database. + +```bash +sed -i "s/SPANNER_PROJECT/${PROJECT_ID}/g" components/spanner/kustomization.yaml +sed -i "s/SPANNER_INSTANCE/${SPANNER_INSTANCE_NAME}/g" components/spanner/kustomization.yaml +sed -i "s/SPANNER_DATABASE/${SPANNER_DATABASE_NAME}/g" components/spanner/kustomization.yaml +sed -i "s/SPANNER_DB_USER_GSA_ID/${SPANNER_DB_USER_GSA_ID}/g" components/spanner/kustomization.yaml +``` + +You can locally render these manifests by running `kubectl kustomize .` as well as deploying them by running `kubectl apply -k .`. + +## Note on Spanner connection environment variables + +The following environment variables will be used by the `cartservice`, if present: + +- `SPANNER_INSTANCE`: defaults to `onlineboutique`, unless specified. +- `SPANNER_DATABASE`: defaults to `carts`, unless specified. +- `SPANNER_CONNECTION_STRING`: defaults to `projects/${SPANNER_PROJECT}/instances/${SPANNER_INSTANCE}/databases/${SPANNER_DATABASE}`. If this variable is defined explicitly, all other environment variables will be ignored. + +## Resources + +- [Use Google Cloud Spanner with the Online Boutique sample apps](https://medium.com/google-cloud/f7248e077339) diff --git a/kustomize/components/spanner/kustomization.yaml b/kustomize/components/spanner/kustomization.yaml new file mode 100644 index 0000000..298e0d1 --- /dev/null +++ b/kustomize/components/spanner/kustomization.yaml @@ -0,0 +1,55 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: kustomize.config.k8s.io/v1alpha1 +kind: Component +patches: +# cartservice - replace REDIS_ADDR by SPANNER_CONNECTION_STRING for the cartservice Deployment +- patch: |- + apiVersion: apps/v1 + kind: Deployment + metadata: + name: cartservice + spec: + template: + spec: + containers: + - name: server + env: + - name: REDIS_ADDR + $patch: delete + - name: SPANNER_CONNECTION_STRING + value: projects/SPANNER_PROJECT/instances/SPANNER_INSTANCE/databases/SPANNER_DATABASE +# cartservice - add the GSA annotation for the cartservice KSA +- patch: |- + apiVersion: v1 + kind: ServiceAccount + metadata: + name: cartservice + annotations: + iam.gke.io/gcp-service-account: SPANNER_DB_USER_GSA_ID +# redis - remove the redis-cart Deployment +- patch: |- + apiVersion: apps/v1 + kind: Deployment + metadata: + name: redis-cart + $patch: delete +# redis - remove the redis-cart Service +- patch: |- + apiVersion: v1 + kind: Service + metadata: + name: redis-cart + $patch: delete diff --git a/kustomize/components/without-loadgenerator/README.md b/kustomize/components/without-loadgenerator/README.md new file mode 100644 index 0000000..4cb99b1 --- /dev/null +++ b/kustomize/components/without-loadgenerator/README.md @@ -0,0 +1,30 @@ +# Exclude the loadgenerator + +By default, when you deploy Online Boutique, its [loadgenerator](/src/loadgenerator/) will also be deployed. + +You can use this Kustomize component to exclude the loadgenerator. + +Note: This Kustomize component has not been tested with [other Kustomize Components](/kustomize/components/) that rely on the loadgenerator. + +## Use this component + +From the `kustomize/` folder at the root level of this repository, execute this command: + +```bash +kustomize edit add component components/without-loadgenerator +``` + +This will update the `kustomize/kustomization.yaml` file which could be similar to: + +```yaml +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: +- base +components: +- components/without-loadgenerator +``` + +You can then deploy Online Boutique and this component to your cluster using `kubectl apply -k .`. If you just want to render the YAML manifest (without deploying to your cluster), run `kubectl kustomize .`. + +Learn more about Online Boutique's kustomize components at [/kustomize](/kustomize#readme). diff --git a/kustomize/components/without-loadgenerator/delete-loadgenerator.patch.yaml b/kustomize/components/without-loadgenerator/delete-loadgenerator.patch.yaml new file mode 100644 index 0000000..8e5c77f --- /dev/null +++ b/kustomize/components/without-loadgenerator/delete-loadgenerator.patch.yaml @@ -0,0 +1,5 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: loadgenerator +$patch: delete diff --git a/kustomize/components/without-loadgenerator/kustomization.yaml b/kustomize/components/without-loadgenerator/kustomization.yaml new file mode 100644 index 0000000..f866136 --- /dev/null +++ b/kustomize/components/without-loadgenerator/kustomization.yaml @@ -0,0 +1,18 @@ +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: kustomize.config.k8s.io/v1alpha1 +kind: Component +patches: +- path: delete-loadgenerator.patch.yaml diff --git a/kustomize/kustomization.yaml b/kustomize/kustomization.yaml new file mode 100644 index 0000000..c89a816 --- /dev/null +++ b/kustomize/kustomization.yaml @@ -0,0 +1,34 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: +- base +components: +# - components/cymbal-branding +# - components/google-cloud-operations +# - components/memorystore +# - components/network-policies +# - components/non-public-frontend +# - components/service-accounts +# - components/alloydb +# - components/single-shared-session +# - components/spanner +# - components/service-mesh-istio +# - components/without-loadgenerator +# These must be run last and in this order +# - components/container-images-tag +# - components/container-images-tag-suffix +# - components/container-images-registry diff --git a/kustomize/tests/README.md b/kustomize/tests/README.md new file mode 100644 index 0000000..6b160cf --- /dev/null +++ b/kustomize/tests/README.md @@ -0,0 +1,2 @@ +This directory contains a list of scenarios (different combinations of Kustomize Components) used for testing. +See [/.github/workflows/kustomize-build-ci.yaml](../../.github/workflows/kustomize-build-ci.yaml). diff --git a/kustomize/tests/memorystore-with-all-components/kustomization.yaml b/kustomize/tests/memorystore-with-all-components/kustomization.yaml new file mode 100644 index 0000000..05e6d41 --- /dev/null +++ b/kustomize/tests/memorystore-with-all-components/kustomization.yaml @@ -0,0 +1,23 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: +- ../../base +components: +- ../../components/cymbal-branding +- ../../components/google-cloud-operations +- ../../components/network-policies +- ../../components/memorystore diff --git a/kustomize/tests/service-mesh-istio-with-all-components/kustomization.yaml b/kustomize/tests/service-mesh-istio-with-all-components/kustomization.yaml new file mode 100644 index 0000000..edb8980 --- /dev/null +++ b/kustomize/tests/service-mesh-istio-with-all-components/kustomization.yaml @@ -0,0 +1,23 @@ +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: +- ../../base +components: +- ../../components/cymbal-branding +- ../../components/google-cloud-operations +- ../../components/network-policies +- ../../components/service-mesh-istio diff --git a/kustomize/tests/spanner-with-all-components/kustomization.yaml b/kustomize/tests/spanner-with-all-components/kustomization.yaml new file mode 100644 index 0000000..5d7ba0d --- /dev/null +++ b/kustomize/tests/spanner-with-all-components/kustomization.yaml @@ -0,0 +1,23 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: +- ../../base +components: +- ../../components/cymbal-branding +- ../../components/google-cloud-operations +- ../../components/network-policies +- ../../components/spanner diff --git a/protos/demo.proto b/protos/demo.proto new file mode 100644 index 0000000..af32097 --- /dev/null +++ b/protos/demo.proto @@ -0,0 +1,262 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package hipstershop; + +option go_package = "github.com/GoogleCloudPlatform/microservices-demo/hipstershop"; + +// -----------------Cart service----------------- + +service CartService { + rpc AddItem(AddItemRequest) returns (Empty) {} + rpc GetCart(GetCartRequest) returns (Cart) {} + rpc EmptyCart(EmptyCartRequest) returns (Empty) {} +} + +message CartItem { + string product_id = 1; + int32 quantity = 2; +} + +message AddItemRequest { + string user_id = 1; + CartItem item = 2; +} + +message EmptyCartRequest { + string user_id = 1; +} + +message GetCartRequest { + string user_id = 1; +} + +message Cart { + string user_id = 1; + repeated CartItem items = 2; +} + +message Empty {} + +// ---------------Recommendation service---------- + +service RecommendationService { + rpc ListRecommendations(ListRecommendationsRequest) returns (ListRecommendationsResponse){} +} + +message ListRecommendationsRequest { + string user_id = 1; + repeated string product_ids = 2; +} + +message ListRecommendationsResponse { + repeated string product_ids = 1; +} + +// ---------------Product Catalog---------------- + +service ProductCatalogService { + rpc ListProducts(Empty) returns (ListProductsResponse) {} + rpc GetProduct(GetProductRequest) returns (Product) {} + rpc SearchProducts(SearchProductsRequest) returns (SearchProductsResponse) {} +} + +message Product { + string id = 1; + string name = 2; + string description = 3; + string picture = 4; + Money price_usd = 5; + + // Categories such as "clothing" or "kitchen" that can be used to look up + // other related products. + repeated string categories = 6; +} + +message ListProductsResponse { + repeated Product products = 1; +} + +message GetProductRequest { + string id = 1; +} + +message SearchProductsRequest { + string query = 1; +} + +message SearchProductsResponse { + repeated Product results = 1; +} + +// ---------------Shipping Service---------- + +service ShippingService { + rpc GetQuote(GetQuoteRequest) returns (GetQuoteResponse) {} + rpc ShipOrder(ShipOrderRequest) returns (ShipOrderResponse) {} +} + +message GetQuoteRequest { + Address address = 1; + repeated CartItem items = 2; +} + +message GetQuoteResponse { + Money cost_usd = 1; +} + +message ShipOrderRequest { + Address address = 1; + repeated CartItem items = 2; +} + +message ShipOrderResponse { + string tracking_id = 1; +} + +message Address { + string street_address = 1; + string city = 2; + string state = 3; + string country = 4; + int32 zip_code = 5; +} + +// -----------------Currency service----------------- + +service CurrencyService { + rpc GetSupportedCurrencies(Empty) returns (GetSupportedCurrenciesResponse) {} + rpc Convert(CurrencyConversionRequest) returns (Money) {} +} + +// Represents an amount of money with its currency type. +message Money { + // The 3-letter currency code defined in ISO 4217. + string currency_code = 1; + + // The whole units of the amount. + // For example if `currencyCode` is `"USD"`, then 1 unit is one US dollar. + int64 units = 2; + + // Number of nano (10^-9) units of the amount. + // The value must be between -999,999,999 and +999,999,999 inclusive. + // If `units` is positive, `nanos` must be positive or zero. + // If `units` is zero, `nanos` can be positive, zero, or negative. + // If `units` is negative, `nanos` must be negative or zero. + // For example $-1.75 is represented as `units`=-1 and `nanos`=-750,000,000. + int32 nanos = 3; +} + +message GetSupportedCurrenciesResponse { + // The 3-letter currency code defined in ISO 4217. + repeated string currency_codes = 1; +} + +message CurrencyConversionRequest { + Money from = 1; + + // The 3-letter currency code defined in ISO 4217. + string to_code = 2; +} + +// -------------Payment service----------------- + +service PaymentService { + rpc Charge(ChargeRequest) returns (ChargeResponse) {} +} + +message CreditCardInfo { + string credit_card_number = 1; + int32 credit_card_cvv = 2; + int32 credit_card_expiration_year = 3; + int32 credit_card_expiration_month = 4; +} + +message ChargeRequest { + Money amount = 1; + CreditCardInfo credit_card = 2; +} + +message ChargeResponse { + string transaction_id = 1; +} + +// -------------Email service----------------- + +service EmailService { + rpc SendOrderConfirmation(SendOrderConfirmationRequest) returns (Empty) {} +} + +message OrderItem { + CartItem item = 1; + Money cost = 2; +} + +message OrderResult { + string order_id = 1; + string shipping_tracking_id = 2; + Money shipping_cost = 3; + Address shipping_address = 4; + repeated OrderItem items = 5; +} + +message SendOrderConfirmationRequest { + string email = 1; + OrderResult order = 2; +} + + +// -------------Checkout service----------------- + +service CheckoutService { + rpc PlaceOrder(PlaceOrderRequest) returns (PlaceOrderResponse) {} +} + +message PlaceOrderRequest { + string user_id = 1; + string user_currency = 2; + + Address address = 3; + string email = 5; + CreditCardInfo credit_card = 6; +} + +message PlaceOrderResponse { + OrderResult order = 1; +} + +// ------------Ad service------------------ + +service AdService { + rpc GetAds(AdRequest) returns (AdResponse) {} +} + +message AdRequest { + // List of important key words from the current page describing the context. + repeated string context_keys = 1; +} + +message AdResponse { + repeated Ad ads = 1; +} + +message Ad { + // url to redirect to when an ad is clicked. + string redirect_url = 1; + + // short advertisement text to display. + string text = 2; +} diff --git a/protos/grpc/health/v1/health.proto b/protos/grpc/health/v1/health.proto new file mode 100644 index 0000000..4b4677b --- /dev/null +++ b/protos/grpc/health/v1/health.proto @@ -0,0 +1,43 @@ +// Copyright 2015 The gRPC Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// The canonical version of this proto can be found at +// https://github.com/grpc/grpc-proto/blob/master/grpc/health/v1/health.proto + +syntax = "proto3"; + +package grpc.health.v1; + +option csharp_namespace = "Grpc.Health.V1"; +option go_package = "google.golang.org/grpc/health/grpc_health_v1"; +option java_multiple_files = true; +option java_outer_classname = "HealthProto"; +option java_package = "io.grpc.health.v1"; + +message HealthCheckRequest { + string service = 1; +} + +message HealthCheckResponse { + enum ServingStatus { + UNKNOWN = 0; + SERVING = 1; + NOT_SERVING = 2; + } + ServingStatus status = 1; +} + +service Health { + rpc Check(HealthCheckRequest) returns (HealthCheckResponse); +} diff --git a/release/istio-manifests.yaml b/release/istio-manifests.yaml new file mode 100644 index 0000000..374a4ad --- /dev/null +++ b/release/istio-manifests.yaml @@ -0,0 +1,96 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# ---------------------------------------------------------- +# WARNING: This file is autogenerated. Do not manually edit. +# ---------------------------------------------------------- + +# [START servicemesh_release_istio_manifests_microservices_demo] +--- +apiVersion: gateway.networking.k8s.io/v1beta1 +kind: Gateway +metadata: + name: istio-gateway +spec: + gatewayClassName: istio + listeners: + - name: http + port: 80 + protocol: HTTP + allowedRoutes: + namespaces: + from: Same +--- +apiVersion: gateway.networking.k8s.io/v1beta1 +kind: HTTPRoute +metadata: + name: frontend-route +spec: + parentRefs: + - name: istio-gateway + rules: + - matches: + - path: + value: / + backendRefs: + - name: frontend + port: 80 +--- +apiVersion: networking.istio.io/v1alpha3 +kind: ServiceEntry +metadata: + name: allow-egress-googleapis +spec: + hosts: + - "accounts.google.com" # Used to get token + - "*.googleapis.com" + ports: + - number: 80 + protocol: HTTP + name: http + - number: 443 + protocol: HTTPS + name: https +--- +apiVersion: networking.istio.io/v1alpha3 +kind: ServiceEntry +metadata: + name: allow-egress-google-metadata +spec: + hosts: + - metadata.google.internal + addresses: + - 169.254.169.254 # GCE metadata server + ports: + - number: 80 + name: http + protocol: HTTP + - number: 443 + name: https + protocol: HTTPS +--- +apiVersion: networking.istio.io/v1alpha3 +kind: VirtualService +metadata: + name: frontend +spec: + hosts: + - "frontend.default.svc.cluster.local" + http: + - route: + - destination: + host: frontend + port: + number: 80 +# [END servicemesh_release_istio_manifests_microservices_demo] diff --git a/release/kubernetes-manifests.yaml b/release/kubernetes-manifests.yaml new file mode 100644 index 0000000..54ab4f4 --- /dev/null +++ b/release/kubernetes-manifests.yaml @@ -0,0 +1,980 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# ---------------------------------------------------------- +# WARNING: This file is autogenerated. Do not manually edit. +# ---------------------------------------------------------- + +# [START gke_release_kubernetes_manifests_microservices_demo] +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: currencyservice + labels: + app: currencyservice +spec: + selector: + matchLabels: + app: currencyservice + template: + metadata: + labels: + app: currencyservice + spec: + serviceAccountName: currencyservice + terminationGracePeriodSeconds: 5 + securityContext: + fsGroup: 1000 + runAsGroup: 1000 + runAsNonRoot: true + runAsUser: 1000 + containers: + - name: server + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + privileged: false + readOnlyRootFilesystem: true + image: us-central1-docker.pkg.dev/google-samples/microservices-demo/currencyservice:v0.10.4 + ports: + - name: grpc + containerPort: 7000 + env: + - name: PORT + value: "7000" + - name: DISABLE_PROFILER + value: "1" + readinessProbe: + grpc: + port: 7000 + livenessProbe: + grpc: + port: 7000 + resources: + requests: + cpu: 100m + memory: 64Mi + limits: + cpu: 200m + memory: 128Mi +--- +apiVersion: v1 +kind: Service +metadata: + name: currencyservice + labels: + app: currencyservice +spec: + type: ClusterIP + selector: + app: currencyservice + ports: + - name: grpc + port: 7000 + targetPort: 7000 +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: currencyservice +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: loadgenerator + labels: + app: loadgenerator +spec: + selector: + matchLabels: + app: loadgenerator + replicas: 1 + template: + metadata: + labels: + app: loadgenerator + annotations: + sidecar.istio.io/rewriteAppHTTPProbers: "true" + spec: + serviceAccountName: loadgenerator + terminationGracePeriodSeconds: 5 + restartPolicy: Always + securityContext: + fsGroup: 1000 + runAsGroup: 1000 + runAsNonRoot: true + runAsUser: 1000 + initContainers: + - command: + - /bin/sh + - -exc + - | + MAX_RETRIES=12 + RETRY_INTERVAL=10 + for i in $(seq 1 $MAX_RETRIES); do + echo "Attempt $i: Pinging frontend: ${FRONTEND_ADDR}..." + STATUSCODE=$(wget --server-response http://${FRONTEND_ADDR} 2>&1 | awk '/^ HTTP/{print $2}') + if [ $STATUSCODE -eq 200 ]; then + echo "Frontend is reachable." + exit 0 + fi + echo "Error: Could not reach frontend - Status code: ${STATUSCODE}" + sleep $RETRY_INTERVAL + done + echo "Failed to reach frontend after $MAX_RETRIES attempts." + exit 1 + name: frontend-check + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + privileged: false + readOnlyRootFilesystem: true + image: busybox:latest + env: + - name: FRONTEND_ADDR + value: "frontend:80" + containers: + - name: main + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + privileged: false + readOnlyRootFilesystem: true + image: us-central1-docker.pkg.dev/google-samples/microservices-demo/loadgenerator:v0.10.4 + env: + - name: FRONTEND_ADDR + value: "frontend:80" + - name: USERS + value: "10" + - name: RATE + value: "1" + resources: + requests: + cpu: 300m + memory: 256Mi + limits: + cpu: 500m + memory: 512Mi +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: loadgenerator +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: productcatalogservice + labels: + app: productcatalogservice +spec: + selector: + matchLabels: + app: productcatalogservice + template: + metadata: + labels: + app: productcatalogservice + spec: + serviceAccountName: productcatalogservice + terminationGracePeriodSeconds: 5 + securityContext: + fsGroup: 1000 + runAsGroup: 1000 + runAsNonRoot: true + runAsUser: 1000 + containers: + - name: server + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + privileged: false + readOnlyRootFilesystem: true + image: us-central1-docker.pkg.dev/google-samples/microservices-demo/productcatalogservice:v0.10.4 + ports: + - containerPort: 3550 + env: + - name: PORT + value: "3550" + - name: DISABLE_PROFILER + value: "1" + readinessProbe: + grpc: + port: 3550 + livenessProbe: + grpc: + port: 3550 + resources: + requests: + cpu: 100m + memory: 64Mi + limits: + cpu: 200m + memory: 128Mi +--- +apiVersion: v1 +kind: Service +metadata: + name: productcatalogservice + labels: + app: productcatalogservice +spec: + type: ClusterIP + selector: + app: productcatalogservice + ports: + - name: grpc + port: 3550 + targetPort: 3550 +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: productcatalogservice +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: checkoutservice + labels: + app: checkoutservice +spec: + selector: + matchLabels: + app: checkoutservice + template: + metadata: + labels: + app: checkoutservice + spec: + serviceAccountName: checkoutservice + securityContext: + fsGroup: 1000 + runAsGroup: 1000 + runAsNonRoot: true + runAsUser: 1000 + containers: + - name: server + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + privileged: false + readOnlyRootFilesystem: true + image: us-central1-docker.pkg.dev/google-samples/microservices-demo/checkoutservice:v0.10.4 + ports: + - containerPort: 5050 + readinessProbe: + grpc: + port: 5050 + livenessProbe: + grpc: + port: 5050 + env: + - name: PORT + value: "5050" + - name: PRODUCT_CATALOG_SERVICE_ADDR + value: "productcatalogservice:3550" + - name: SHIPPING_SERVICE_ADDR + value: "shippingservice:50051" + - name: PAYMENT_SERVICE_ADDR + value: "paymentservice:50051" + - name: EMAIL_SERVICE_ADDR + value: "emailservice:5000" + - name: CURRENCY_SERVICE_ADDR + value: "currencyservice:7000" + - name: CART_SERVICE_ADDR + value: "cartservice:7070" + resources: + requests: + cpu: 100m + memory: 64Mi + limits: + cpu: 200m + memory: 128Mi +--- +apiVersion: v1 +kind: Service +metadata: + name: checkoutservice + labels: + app: checkoutservice +spec: + type: ClusterIP + selector: + app: checkoutservice + ports: + - name: grpc + port: 5050 + targetPort: 5050 +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: checkoutservice +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: shippingservice + labels: + app: shippingservice +spec: + selector: + matchLabels: + app: shippingservice + template: + metadata: + labels: + app: shippingservice + spec: + serviceAccountName: shippingservice + securityContext: + fsGroup: 1000 + runAsGroup: 1000 + runAsNonRoot: true + runAsUser: 1000 + containers: + - name: server + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + privileged: false + readOnlyRootFilesystem: true + image: us-central1-docker.pkg.dev/google-samples/microservices-demo/shippingservice:v0.10.4 + ports: + - containerPort: 50051 + env: + - name: PORT + value: "50051" + - name: DISABLE_PROFILER + value: "1" + readinessProbe: + periodSeconds: 5 + grpc: + port: 50051 + livenessProbe: + grpc: + port: 50051 + resources: + requests: + cpu: 100m + memory: 64Mi + limits: + cpu: 200m + memory: 128Mi +--- +apiVersion: v1 +kind: Service +metadata: + name: shippingservice + labels: + app: shippingservice +spec: + type: ClusterIP + selector: + app: shippingservice + ports: + - name: grpc + port: 50051 + targetPort: 50051 +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: shippingservice +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: cartservice + labels: + app: cartservice +spec: + selector: + matchLabels: + app: cartservice + template: + metadata: + labels: + app: cartservice + spec: + serviceAccountName: cartservice + terminationGracePeriodSeconds: 5 + securityContext: + fsGroup: 1000 + runAsGroup: 1000 + runAsNonRoot: true + runAsUser: 1000 + containers: + - name: server + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + privileged: false + readOnlyRootFilesystem: true + image: us-central1-docker.pkg.dev/google-samples/microservices-demo/cartservice:v0.10.4 + ports: + - containerPort: 7070 + env: + - name: REDIS_ADDR + value: "redis-cart:6379" + resources: + requests: + cpu: 200m + memory: 64Mi + limits: + cpu: 300m + memory: 128Mi + readinessProbe: + initialDelaySeconds: 15 + grpc: + port: 7070 + livenessProbe: + initialDelaySeconds: 15 + periodSeconds: 10 + grpc: + port: 7070 +--- +apiVersion: v1 +kind: Service +metadata: + name: cartservice + labels: + app: cartservice +spec: + type: ClusterIP + selector: + app: cartservice + ports: + - name: grpc + port: 7070 + targetPort: 7070 +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: cartservice +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: redis-cart + labels: + app: redis-cart +spec: + selector: + matchLabels: + app: redis-cart + template: + metadata: + labels: + app: redis-cart + spec: + securityContext: + fsGroup: 1000 + runAsGroup: 1000 + runAsNonRoot: true + runAsUser: 1000 + containers: + - name: redis + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + privileged: false + readOnlyRootFilesystem: true + image: redis:alpine + ports: + - containerPort: 6379 + readinessProbe: + periodSeconds: 5 + tcpSocket: + port: 6379 + livenessProbe: + periodSeconds: 5 + tcpSocket: + port: 6379 + volumeMounts: + - mountPath: /data + name: redis-data + resources: + limits: + memory: 256Mi + cpu: 125m + requests: + cpu: 70m + memory: 200Mi + volumes: + - name: redis-data + emptyDir: {} +--- +apiVersion: v1 +kind: Service +metadata: + name: redis-cart + labels: + app: redis-cart +spec: + type: ClusterIP + selector: + app: redis-cart + ports: + - name: tcp-redis + port: 6379 + targetPort: 6379 +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: emailservice + labels: + app: emailservice +spec: + selector: + matchLabels: + app: emailservice + template: + metadata: + labels: + app: emailservice + spec: + serviceAccountName: emailservice + terminationGracePeriodSeconds: 5 + securityContext: + fsGroup: 1000 + runAsGroup: 1000 + runAsNonRoot: true + runAsUser: 1000 + containers: + - name: server + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + privileged: false + readOnlyRootFilesystem: true + image: us-central1-docker.pkg.dev/google-samples/microservices-demo/emailservice:v0.10.4 + ports: + - containerPort: 8080 + env: + - name: PORT + value: "8080" + - name: DISABLE_PROFILER + value: "1" + readinessProbe: + periodSeconds: 5 + grpc: + port: 8080 + livenessProbe: + periodSeconds: 5 + grpc: + port: 8080 + resources: + requests: + cpu: 100m + memory: 64Mi + limits: + cpu: 200m + memory: 128Mi +--- +apiVersion: v1 +kind: Service +metadata: + name: emailservice + labels: + app: emailservice +spec: + type: ClusterIP + selector: + app: emailservice + ports: + - name: grpc + port: 5000 + targetPort: 8080 +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: emailservice +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: paymentservice + labels: + app: paymentservice +spec: + selector: + matchLabels: + app: paymentservice + template: + metadata: + labels: + app: paymentservice + spec: + serviceAccountName: paymentservice + terminationGracePeriodSeconds: 5 + securityContext: + fsGroup: 1000 + runAsGroup: 1000 + runAsNonRoot: true + runAsUser: 1000 + containers: + - name: server + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + privileged: false + readOnlyRootFilesystem: true + image: us-central1-docker.pkg.dev/google-samples/microservices-demo/paymentservice:v0.10.4 + ports: + - containerPort: 50051 + env: + - name: PORT + value: "50051" + - name: DISABLE_PROFILER + value: "1" + readinessProbe: + grpc: + port: 50051 + livenessProbe: + grpc: + port: 50051 + resources: + requests: + cpu: 100m + memory: 64Mi + limits: + cpu: 200m + memory: 128Mi +--- +apiVersion: v1 +kind: Service +metadata: + name: paymentservice + labels: + app: paymentservice +spec: + type: ClusterIP + selector: + app: paymentservice + ports: + - name: grpc + port: 50051 + targetPort: 50051 +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: paymentservice +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: frontend + labels: + app: frontend +spec: + selector: + matchLabels: + app: frontend + template: + metadata: + labels: + app: frontend + annotations: + sidecar.istio.io/rewriteAppHTTPProbers: "true" + spec: + serviceAccountName: frontend + securityContext: + fsGroup: 1000 + runAsGroup: 1000 + runAsNonRoot: true + runAsUser: 1000 + containers: + - name: server + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + privileged: false + readOnlyRootFilesystem: true + image: us-central1-docker.pkg.dev/google-samples/microservices-demo/frontend:v0.10.4 + ports: + - containerPort: 8080 + readinessProbe: + initialDelaySeconds: 10 + httpGet: + path: "/_healthz" + port: 8080 + httpHeaders: + - name: "Cookie" + value: "shop_session-id=x-readiness-probe" + livenessProbe: + initialDelaySeconds: 10 + httpGet: + path: "/_healthz" + port: 8080 + httpHeaders: + - name: "Cookie" + value: "shop_session-id=x-liveness-probe" + env: + - name: PORT + value: "8080" + - name: PRODUCT_CATALOG_SERVICE_ADDR + value: "productcatalogservice:3550" + - name: CURRENCY_SERVICE_ADDR + value: "currencyservice:7000" + - name: CART_SERVICE_ADDR + value: "cartservice:7070" + - name: RECOMMENDATION_SERVICE_ADDR + value: "recommendationservice:8080" + - name: SHIPPING_SERVICE_ADDR + value: "shippingservice:50051" + - name: CHECKOUT_SERVICE_ADDR + value: "checkoutservice:5050" + - name: AD_SERVICE_ADDR + value: "adservice:9555" + - name: SHOPPING_ASSISTANT_SERVICE_ADDR + value: "shoppingassistantservice:80" + # # ENV_PLATFORM: One of: local, gcp, aws, azure, onprem, alibaba + # # When not set, defaults to "local" unless running in GKE, otherwies auto-sets to gcp + # - name: ENV_PLATFORM + # value: "aws" + - name: ENABLE_PROFILER + value: "0" + # - name: CYMBAL_BRANDING + # value: "true" + # - name: ENABLE_ASSISTANT + # value: "true" + # - name: FRONTEND_MESSAGE + # value: "Replace this with a message you want to display on all pages." + # As part of an optional Google Cloud demo, you can run an optional microservice called the "packaging service". + # - name: PACKAGING_SERVICE_URL + # value: "" # This value would look like "http://123.123.123" + resources: + requests: + cpu: 100m + memory: 64Mi + limits: + cpu: 200m + memory: 128Mi +--- +apiVersion: v1 +kind: Service +metadata: + name: frontend + labels: + app: frontend +spec: + type: ClusterIP + selector: + app: frontend + ports: + - name: http + port: 80 + targetPort: 8080 +--- +apiVersion: v1 +kind: Service +metadata: + name: frontend-external + labels: + app: frontend +spec: + type: LoadBalancer + selector: + app: frontend + ports: + - name: http + port: 80 + targetPort: 8080 +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: frontend +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: recommendationservice + labels: + app: recommendationservice +spec: + selector: + matchLabels: + app: recommendationservice + template: + metadata: + labels: + app: recommendationservice + spec: + serviceAccountName: recommendationservice + terminationGracePeriodSeconds: 5 + securityContext: + fsGroup: 1000 + runAsGroup: 1000 + runAsNonRoot: true + runAsUser: 1000 + containers: + - name: server + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + privileged: false + readOnlyRootFilesystem: true + image: us-central1-docker.pkg.dev/google-samples/microservices-demo/recommendationservice:v0.10.4 + ports: + - containerPort: 8080 + readinessProbe: + periodSeconds: 5 + grpc: + port: 8080 + livenessProbe: + periodSeconds: 5 + grpc: + port: 8080 + env: + - name: PORT + value: "8080" + - name: PRODUCT_CATALOG_SERVICE_ADDR + value: "productcatalogservice:3550" + - name: DISABLE_PROFILER + value: "1" + resources: + requests: + cpu: 100m + memory: 220Mi + limits: + cpu: 200m + memory: 450Mi +--- +apiVersion: v1 +kind: Service +metadata: + name: recommendationservice + labels: + app: recommendationservice +spec: + type: ClusterIP + selector: + app: recommendationservice + ports: + - name: grpc + port: 8080 + targetPort: 8080 +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: recommendationservice +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: adservice + labels: + app: adservice +spec: + selector: + matchLabels: + app: adservice + template: + metadata: + labels: + app: adservice + spec: + serviceAccountName: adservice + terminationGracePeriodSeconds: 5 + securityContext: + fsGroup: 1000 + runAsGroup: 1000 + runAsNonRoot: true + runAsUser: 1000 + containers: + - name: server + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + privileged: false + readOnlyRootFilesystem: true + image: us-central1-docker.pkg.dev/google-samples/microservices-demo/adservice:v0.10.4 + ports: + - containerPort: 9555 + env: + - name: PORT + value: "9555" + resources: + requests: + cpu: 200m + memory: 180Mi + limits: + cpu: 300m + memory: 300Mi + readinessProbe: + initialDelaySeconds: 20 + periodSeconds: 15 + grpc: + port: 9555 + livenessProbe: + initialDelaySeconds: 20 + periodSeconds: 15 + grpc: + port: 9555 +--- +apiVersion: v1 +kind: Service +metadata: + name: adservice + labels: + app: adservice +spec: + type: ClusterIP + selector: + app: adservice + ports: + - name: grpc + port: 9555 + targetPort: 9555 +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: adservice +# [END gke_release_kubernetes_manifests_microservices_demo] diff --git a/skaffold.yaml b/skaffold.yaml new file mode 100644 index 0000000..9a13f5f --- /dev/null +++ b/skaffold.yaml @@ -0,0 +1,122 @@ +# Copyright 2021 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: skaffold/v3 +kind: Config +metadata: + name: app +build: + platforms: ["linux/amd64", "linux/arm64"] + artifacts: + # image tags are relative; to specify an image repo (e.g. GCR), you + # must provide a "default repo" using one of the methods described + # here: + # https://skaffold.dev/docs/concepts/#image-repository-handling + - image: emailservice + context: src/emailservice + - image: productcatalogservice + context: src/productcatalogservice + - image: recommendationservice + context: src/recommendationservice + - image: shoppingassistantservice + context: src/shoppingassistantservice + - image: shippingservice + context: src/shippingservice + - image: checkoutservice + context: src/checkoutservice + - image: paymentservice + context: src/paymentservice + - image: currencyservice + context: src/currencyservice + - image: cartservice + context: src/cartservice/src + docker: + dockerfile: Dockerfile + - image: frontend + context: src/frontend + - image: adservice + context: src/adservice + tagPolicy: + gitCommit: {} + local: + useDockerCLI: true + useBuildkit: true +manifests: + kustomize: + paths: + - kubernetes-manifests +deploy: + kubectl: {} +# "gcb" profile allows building and pushing the images +# on Google Container Builder without requiring docker +# installed on the developer machine. However, note that +# since GCB does not cache the builds, each build will +# start from scratch and therefore take a long time. +# +# This is not used by default. To use it, run: +# skaffold run -p gcb +profiles: +- name: gcb + build: + googleCloudBuild: + diskSizeGb: 300 + machineType: N1_HIGHCPU_32 + timeout: 4000s +# "debug" profile replaces the default Dockerfile in cartservice with Dockerfile.debug, +# which enables debugging via skaffold. +# +# This profile is used by default when running skaffold debug. +- name: debug + activation: + - command: debug + patches: + - op: replace + path: /build/artifacts/7/docker/dockerfile + value: Dockerfile.debug +# The "network-policies" profile is not used by default. +# You can use it in isolation or in combination with other profiles: +# skaffold run -p network-policies, debug +- name: network-policies + patches: + - op: add + path: /manifests/kustomize/paths/1 + value: kustomize/components/network-policies +--- +apiVersion: skaffold/v3 +kind: Config +metadata: + name: loadgenerator +requires: +- configs: + - app +build: + platforms: ["linux/amd64", "linux/arm64"] + artifacts: + - image: loadgenerator + context: src/loadgenerator + local: + useDockerCLI: true + useBuildkit: true +manifests: + rawYaml: + - ./kubernetes-manifests/loadgenerator.yaml +deploy: + kubectl: {} +profiles: +- name: gcb + build: + googleCloudBuild: + diskSizeGb: 300 + machineType: N1_HIGHCPU_32 + timeout: 4000s diff --git a/src/adservice/Dockerfile b/src/adservice/Dockerfile new file mode 100644 index 0000000..8e133dc --- /dev/null +++ b/src/adservice/Dockerfile @@ -0,0 +1,41 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +FROM --platform=$BUILDPLATFORM eclipse-temurin:24.0.2_12-jdk-noble@sha256:dacac8e9a0df0d2bd24e702b4431132875c249930b70555ebd7ca285b5bee684 AS builder + +WORKDIR /app + +COPY ["build.gradle", "gradlew", "./"] +COPY gradle gradle +RUN chmod +x gradlew +RUN ./gradlew downloadRepos + +COPY . . +RUN chmod +x gradlew +RUN ./gradlew installDist + +FROM eclipse-temurin:25.0.1_8-jre-alpine@sha256:9c65fe190cb22ba92f50b8d29a749d0f1cfb2437e09fe5fbf9ff927c45fc6e99 + +# @TODO: https://github.com/GoogleCloudPlatform/microservices-demo/issues/2517 +# Download Stackdriver Profiler Java agent +# RUN mkdir -p /opt/cprof && \ +# wget -q -O- https://storage.googleapis.com/cloud-profiler/java/latest/profiler_java_agent_alpine.tar.gz \ +# | tar xzv -C /opt/cprof && \ +# rm -rf profiler_java_agent.tar.gz + +WORKDIR /app +COPY --from=builder /app . + +EXPOSE 9555 +ENTRYPOINT ["/app/build/install/hipstershop/bin/AdService"] diff --git a/src/adservice/README.md b/src/adservice/README.md new file mode 100644 index 0000000..599dbb7 --- /dev/null +++ b/src/adservice/README.md @@ -0,0 +1,28 @@ +# Ad Service + +The Ad service provides advertisement based on context keys. If no context keys are provided then it returns random ads. + +## Building locally + +The Ad service uses gradlew to compile/install/distribute. Gradle wrapper is already part of the source code. To build Ad Service, run: + +``` +./gradlew installDist +``` +It will create executable script src/adservice/build/install/hipstershop/bin/AdService + +### Upgrading gradle version +If you need to upgrade the version of gradle then run + +``` +./gradlew wrapper --gradle-version +``` + +## Building docker image + +From `src/adservice/`, run: + +``` +docker build ./ +``` + diff --git a/src/adservice/build.gradle b/src/adservice/build.gradle new file mode 100644 index 0000000..06ceff0 --- /dev/null +++ b/src/adservice/build.gradle @@ -0,0 +1,119 @@ +plugins { + id 'com.google.protobuf' version '0.9.6' + id 'com.github.sherter.google-java-format' version '0.9' + id 'idea' + id 'application' +} + +repositories { + mavenCentral() + mavenLocal() +} + +description = 'Ad Service' +group = "adservice" +version = "0.1.0-SNAPSHOT" + +def grpcVersion = "1.78.0" +def jacksonCoreVersion = "2.21.0" +def jacksonDatabindVersion = "2.21.0" +def protocVersion = "4.33.4" + +tasks.withType(JavaCompile) { + sourceCompatibility = JavaVersion.VERSION_21 + targetCompatibility = JavaVersion.VERSION_21 +} + +ext { + speed = project.hasProperty('speed') ? project.getProperty('speed') : false + offlineCompile = new File("$buildDir/output/lib") +} + +dependencies { + if (speed) { + implementation fileTree(dir: offlineCompile, include: '*.jar') + } else { + implementation "com.google.api.grpc:proto-google-common-protos:2.65.0", + "javax.annotation:javax.annotation-api:1.3.2", + "io.grpc:grpc-protobuf:${grpcVersion}", + "io.grpc:grpc-stub:${grpcVersion}", + "io.grpc:grpc-netty:${grpcVersion}", + "io.grpc:grpc-services:${grpcVersion}", + "io.grpc:grpc-census:${grpcVersion}", + "org.apache.logging.log4j:log4j-core:2.25.3", + "com.google.protobuf:protobuf-java:${protocVersion}" + + runtimeOnly "com.fasterxml.jackson.core:jackson-core:${jacksonCoreVersion}", + "com.fasterxml.jackson.core:jackson-databind:${jacksonDatabindVersion}", + "io.netty:netty-tcnative-boringssl-static:2.0.74.Final" + } +} + +protobuf { + protoc { + artifact = "com.google.protobuf:protoc:${protocVersion}" + } + plugins { + grpc { + artifact = "io.grpc:protoc-gen-grpc-java:${grpcVersion}" + } + } + generateProtoTasks { + all()*.plugins { + grpc {} + } + ofSourceSet('main') + } +} + +googleJavaFormat { + toolVersion '1.33.0' +} + +// Inform IDEs like IntelliJ IDEA, Eclipse or NetBeans about the generated code. +sourceSets { + main { + java { + srcDirs 'hipstershop' + srcDirs 'build/generated/source/proto/main/java/hipstershop' + srcDirs 'build/generated/source/proto/main/grpc/hipstershop' + } + } +} + +startScripts.enabled = false + +// This to cache dependencies during Docker image building. First build will take time. +// Subsequent build will be incremental. +task downloadRepos(type: Copy) { + from configurations.compileClasspath + into offlineCompile + from configurations.compileClasspath + into offlineCompile +} + +task adService(type: CreateStartScripts) { + mainClass.set('hipstershop.AdService') + applicationName = 'AdService' + outputDir = new File(project.buildDir, 'tmp') + classpath = startScripts.classpath + // @TODO: https://github.com/GoogleCloudPlatform/microservices-demo/issues/2517 + // defaultJvmOpts = + // ["-agentpath:/opt/cprof/profiler_java_agent.so=-cprof_service=adservice,-cprof_service_version=1.0.0"] +} + +task adServiceClient(type: CreateStartScripts) { + mainClass.set('hipstershop.AdServiceClient') + applicationName = 'AdServiceClient' + outputDir = new File(project.buildDir, 'tmp') + classpath = startScripts.classpath + // @TODO: https://github.com/GoogleCloudPlatform/microservices-demo/issues/2517 + // defaultJvmOpts = + // ["-agentpath:/opt/cprof/profiler_java_agent.so=-cprof_service=adserviceclient,-cprof_service_version=1.0.0"] +} + +applicationDistribution.into('bin') { + from(adService) + from(adServiceClient) + fileMode = 0755 +} diff --git a/src/adservice/genproto.sh b/src/adservice/genproto.sh new file mode 100755 index 0000000..09bf1d1 --- /dev/null +++ b/src/adservice/genproto.sh @@ -0,0 +1,23 @@ +#!/bin/bash -eu +# +# Copyright 2018 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# [START gke_adservice_genproto] +# protos are needed in adservice folder for compiling during Docker build. + +mkdir -p proto && \ +cp ../../protos/demo.proto src/main/proto + +# [END gke_adservice_genproto] \ No newline at end of file diff --git a/src/adservice/gradle/wrapper/gradle-wrapper.jar b/src/adservice/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..1b33c55 Binary files /dev/null and b/src/adservice/gradle/wrapper/gradle-wrapper.jar differ diff --git a/src/adservice/gradle/wrapper/gradle-wrapper.properties b/src/adservice/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..aaaabb3 --- /dev/null +++ b/src/adservice/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.4-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/src/adservice/gradlew b/src/adservice/gradlew new file mode 100644 index 0000000..23d15a9 --- /dev/null +++ b/src/adservice/gradlew @@ -0,0 +1,251 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH="\\\"\\\"" + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/src/adservice/gradlew.bat b/src/adservice/gradlew.bat new file mode 100644 index 0000000..db3a6ac --- /dev/null +++ b/src/adservice/gradlew.bat @@ -0,0 +1,94 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH= + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/src/adservice/settings.gradle b/src/adservice/settings.gradle new file mode 100644 index 0000000..0bbe011 --- /dev/null +++ b/src/adservice/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'hipstershop' diff --git a/src/adservice/src/main/java/hipstershop/AdService.java b/src/adservice/src/main/java/hipstershop/AdService.java new file mode 100644 index 0000000..1b4f98c --- /dev/null +++ b/src/adservice/src/main/java/hipstershop/AdService.java @@ -0,0 +1,238 @@ +/* + * Copyright 2018, Google LLC. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package hipstershop; + +import com.google.common.collect.ImmutableListMultimap; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Iterables; +import hipstershop.Demo.Ad; +import hipstershop.Demo.AdRequest; +import hipstershop.Demo.AdResponse; +import io.grpc.Server; +import io.grpc.ServerBuilder; +import io.grpc.StatusRuntimeException; +import io.grpc.health.v1.HealthCheckResponse.ServingStatus; +import io.grpc.services.*; +import io.grpc.stub.StreamObserver; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Random; +import java.util.concurrent.TimeUnit; +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +public final class AdService { + + private static final Logger logger = LogManager.getLogger(AdService.class); + + @SuppressWarnings("FieldCanBeLocal") + private static int MAX_ADS_TO_SERVE = 2; + + private Server server; + private HealthStatusManager healthMgr; + + private static final AdService service = new AdService(); + + private void start() throws IOException { + int port = Integer.parseInt(System.getenv().getOrDefault("PORT", "9555")); + healthMgr = new HealthStatusManager(); + + server = + ServerBuilder.forPort(port) + .addService(new AdServiceImpl()) + .addService(healthMgr.getHealthService()) + .build() + .start(); + logger.info("Ad Service started, listening on " + port); + Runtime.getRuntime() + .addShutdownHook( + new Thread( + () -> { + // Use stderr here since the logger may have been reset by its JVM shutdown hook. + System.err.println( + "*** shutting down gRPC ads server since JVM is shutting down"); + AdService.this.stop(); + System.err.println("*** server shut down"); + })); + healthMgr.setStatus("", ServingStatus.SERVING); + } + + private void stop() { + if (server != null) { + healthMgr.clearStatus(""); + server.shutdown(); + } + } + + private static class AdServiceImpl extends hipstershop.AdServiceGrpc.AdServiceImplBase { + + /** + * Retrieves ads based on context provided in the request {@code AdRequest}. + * + * @param req the request containing context. + * @param responseObserver the stream observer which gets notified with the value of {@code + * AdResponse} + */ + @Override + public void getAds(AdRequest req, StreamObserver responseObserver) { + AdService service = AdService.getInstance(); + try { + List allAds = new ArrayList<>(); + logger.info("received ad request (context_words=" + req.getContextKeysList() + ")"); + if (req.getContextKeysCount() > 0) { + for (int i = 0; i < req.getContextKeysCount(); i++) { + Collection ads = service.getAdsByCategory(req.getContextKeys(i)); + allAds.addAll(ads); + } + } else { + allAds = service.getRandomAds(); + } + if (allAds.isEmpty()) { + // Serve random ads. + allAds = service.getRandomAds(); + } + AdResponse reply = AdResponse.newBuilder().addAllAds(allAds).build(); + responseObserver.onNext(reply); + responseObserver.onCompleted(); + } catch (StatusRuntimeException e) { + logger.log(Level.WARN, "GetAds Failed with status {}", e.getStatus()); + responseObserver.onError(e); + } + } + } + + private static final ImmutableListMultimap adsMap = createAdsMap(); + + private Collection getAdsByCategory(String category) { + return adsMap.get(category); + } + + private static final Random random = new Random(); + + private List getRandomAds() { + List ads = new ArrayList<>(MAX_ADS_TO_SERVE); + Collection allAds = adsMap.values(); + for (int i = 0; i < MAX_ADS_TO_SERVE; i++) { + ads.add(Iterables.get(allAds, random.nextInt(allAds.size()))); + } + return ads; + } + + private static AdService getInstance() { + return service; + } + + /** Await termination on the main thread since the grpc library uses daemon threads. */ + private void blockUntilShutdown() throws InterruptedException { + if (server != null) { + server.awaitTermination(); + } + } + + private static ImmutableListMultimap createAdsMap() { + Ad hairdryer = + Ad.newBuilder() + .setRedirectUrl("/product/2ZYFJ3GM2N") + .setText("Hairdryer for sale. 50% off.") + .build(); + Ad tankTop = + Ad.newBuilder() + .setRedirectUrl("/product/66VCHSJNUP") + .setText("Tank top for sale. 20% off.") + .build(); + Ad candleHolder = + Ad.newBuilder() + .setRedirectUrl("/product/0PUK6V6EV0") + .setText("Candle holder for sale. 30% off.") + .build(); + Ad bambooGlassJar = + Ad.newBuilder() + .setRedirectUrl("/product/9SIQT8TOJO") + .setText("Bamboo glass jar for sale. 10% off.") + .build(); + Ad watch = + Ad.newBuilder() + .setRedirectUrl("/product/1YMWWN1N4O") + .setText("Watch for sale. Buy one, get second kit for free") + .build(); + Ad mug = + Ad.newBuilder() + .setRedirectUrl("/product/6E92ZMYYFZ") + .setText("Mug for sale. Buy two, get third one for free") + .build(); + Ad loafers = + Ad.newBuilder() + .setRedirectUrl("/product/L9ECAV7KIM") + .setText("Loafers for sale. Buy one, get second one for free") + .build(); + return ImmutableListMultimap.builder() + .putAll("clothing", tankTop) + .putAll("accessories", watch) + .putAll("footwear", loafers) + .putAll("hair", hairdryer) + .putAll("decor", candleHolder) + .putAll("kitchen", bambooGlassJar, mug) + .build(); + } + + private static void initStats() { + if (System.getenv("DISABLE_STATS") != null) { + logger.info("Stats disabled."); + return; + } + logger.info("Stats enabled, but temporarily unavailable"); + + long sleepTime = 10; /* seconds */ + int maxAttempts = 5; + + // TODO(arbrown) Implement OpenTelemetry stats + + } + + private static void initTracing() { + if (System.getenv("DISABLE_TRACING") != null) { + logger.info("Tracing disabled."); + return; + } + logger.info("Tracing enabled but temporarily unavailable"); + logger.info("See https://github.com/GoogleCloudPlatform/microservices-demo/issues/422 for more info."); + + // TODO(arbrown) Implement OpenTelemetry tracing + + logger.info("Tracing enabled - Stackdriver exporter initialized."); + } + + /** Main launches the server from the command line. */ + public static void main(String[] args) throws IOException, InterruptedException { + + new Thread( + () -> { + initStats(); + initTracing(); + }) + .start(); + + // Start the RPC server. You shouldn't see any output from gRPC before this. + logger.info("AdService starting."); + final AdService service = AdService.getInstance(); + service.start(); + service.blockUntilShutdown(); + } +} diff --git a/src/adservice/src/main/java/hipstershop/AdServiceClient.java b/src/adservice/src/main/java/hipstershop/AdServiceClient.java new file mode 100644 index 0000000..1393b7f --- /dev/null +++ b/src/adservice/src/main/java/hipstershop/AdServiceClient.java @@ -0,0 +1,116 @@ +/* + * Copyright 2018, Google LLC. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package hipstershop; + +import hipstershop.Demo.Ad; +import hipstershop.Demo.AdRequest; +import hipstershop.Demo.AdResponse; +import io.grpc.ManagedChannel; +import io.grpc.ManagedChannelBuilder; +import io.grpc.StatusRuntimeException; +import java.util.concurrent.TimeUnit; +import javax.annotation.Nullable; +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +/** A simple client that requests ads from the Ads Service. */ +public class AdServiceClient { + + private static final Logger logger = LogManager.getLogger(AdServiceClient.class); + + private final ManagedChannel channel; + private final hipstershop.AdServiceGrpc.AdServiceBlockingStub blockingStub; + + /** Construct client connecting to Ad Service at {@code host:port}. */ + private AdServiceClient(String host, int port) { + this( + ManagedChannelBuilder.forAddress(host, port) + // Channels are secure by default (via SSL/TLS). For the example we disable TLS to avoid + // needing certificates. + .usePlaintext() + .build()); + } + + /** Construct client for accessing RouteGuide server using the existing channel. */ + private AdServiceClient(ManagedChannel channel) { + this.channel = channel; + blockingStub = hipstershop.AdServiceGrpc.newBlockingStub(channel); + } + + private void shutdown() throws InterruptedException { + channel.shutdown().awaitTermination(5, TimeUnit.SECONDS); + } + + /** Get Ads from Server. */ + public void getAds(String contextKey) { + logger.info("Get Ads with context " + contextKey + " ..."); + AdRequest request = AdRequest.newBuilder().addContextKeys(contextKey).build(); + AdResponse response; + + try { + response = blockingStub.getAds(request); + } catch (StatusRuntimeException e) { + logger.log(Level.WARN, "RPC failed: " + e.getStatus()); + return; + } + for (Ad ads : response.getAdsList()) { + logger.info("Ads: " + ads.getText()); + } + } + + private static int getPortOrDefaultFromArgs(String[] args) { + int portNumber = 9555; + if (2 < args.length) { + try { + portNumber = Integer.parseInt(args[2]); + } catch (NumberFormatException e) { + logger.warn(String.format("Port %s is invalid, use default port %d.", args[2], 9555)); + } + } + return portNumber; + } + + private static String getStringOrDefaultFromArgs( + String[] args, int index, @Nullable String defaultString) { + String s = defaultString; + if (index < args.length) { + s = args[index]; + } + return s; + } + + /** + * Ads Service Client main. If provided, the first element of {@code args} is the context key to + * get the ads from the Ads Service + */ + public static void main(String[] args) throws InterruptedException { + // Add final keyword to pass checkStyle. + final String contextKeys = getStringOrDefaultFromArgs(args, 0, "camera"); + final String host = getStringOrDefaultFromArgs(args, 1, "localhost"); + final int serverPort = getPortOrDefaultFromArgs(args); + + AdServiceClient client = new AdServiceClient(host, serverPort); + try { + client.getAds(contextKeys); + } finally { + client.shutdown(); + } + + logger.info("Exiting AdServiceClient..."); + } +} diff --git a/src/adservice/src/main/proto/demo.proto b/src/adservice/src/main/proto/demo.proto new file mode 100644 index 0000000..9693928 --- /dev/null +++ b/src/adservice/src/main/proto/demo.proto @@ -0,0 +1,260 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package hipstershop; + +// -----------------Cart service----------------- + +service CartService { + rpc AddItem(AddItemRequest) returns (Empty) {} + rpc GetCart(GetCartRequest) returns (Cart) {} + rpc EmptyCart(EmptyCartRequest) returns (Empty) {} +} + +message CartItem { + string product_id = 1; + int32 quantity = 2; +} + +message AddItemRequest { + string user_id = 1; + CartItem item = 2; +} + +message EmptyCartRequest { + string user_id = 1; +} + +message GetCartRequest { + string user_id = 1; +} + +message Cart { + string user_id = 1; + repeated CartItem items = 2; +} + +message Empty {} + +// ---------------Recommendation service---------- + +service RecommendationService { + rpc ListRecommendations(ListRecommendationsRequest) returns (ListRecommendationsResponse){} +} + +message ListRecommendationsRequest { + string user_id = 1; + repeated string product_ids = 2; +} + +message ListRecommendationsResponse { + repeated string product_ids = 1; +} + +// ---------------Product Catalog---------------- + +service ProductCatalogService { + rpc ListProducts(Empty) returns (ListProductsResponse) {} + rpc GetProduct(GetProductRequest) returns (Product) {} + rpc SearchProducts(SearchProductsRequest) returns (SearchProductsResponse) {} +} + +message Product { + string id = 1; + string name = 2; + string description = 3; + string picture = 4; + Money price_usd = 5; + + // Categories such as "clothing" or "kitchen" that can be used to look up + // other related products. + repeated string categories = 6; +} + +message ListProductsResponse { + repeated Product products = 1; +} + +message GetProductRequest { + string id = 1; +} + +message SearchProductsRequest { + string query = 1; +} + +message SearchProductsResponse { + repeated Product results = 1; +} + +// ---------------Shipping Service---------- + +service ShippingService { + rpc GetQuote(GetQuoteRequest) returns (GetQuoteResponse) {} + rpc ShipOrder(ShipOrderRequest) returns (ShipOrderResponse) {} +} + +message GetQuoteRequest { + Address address = 1; + repeated CartItem items = 2; +} + +message GetQuoteResponse { + Money cost_usd = 1; +} + +message ShipOrderRequest { + Address address = 1; + repeated CartItem items = 2; +} + +message ShipOrderResponse { + string tracking_id = 1; +} + +message Address { + string street_address = 1; + string city = 2; + string state = 3; + string country = 4; + int32 zip_code = 5; +} + +// -----------------Currency service----------------- + +service CurrencyService { + rpc GetSupportedCurrencies(Empty) returns (GetSupportedCurrenciesResponse) {} + rpc Convert(CurrencyConversionRequest) returns (Money) {} +} + +// Represents an amount of money with its currency type. +message Money { + // The 3-letter currency code defined in ISO 4217. + string currency_code = 1; + + // The whole units of the amount. + // For example if `currencyCode` is `"USD"`, then 1 unit is one US dollar. + int64 units = 2; + + // Number of nano (10^-9) units of the amount. + // The value must be between -999,999,999 and +999,999,999 inclusive. + // If `units` is positive, `nanos` must be positive or zero. + // If `units` is zero, `nanos` can be positive, zero, or negative. + // If `units` is negative, `nanos` must be negative or zero. + // For example $-1.75 is represented as `units`=-1 and `nanos`=-750,000,000. + int32 nanos = 3; +} + +message GetSupportedCurrenciesResponse { + // The 3-letter currency code defined in ISO 4217. + repeated string currency_codes = 1; +} + +message CurrencyConversionRequest { + Money from = 1; + + // The 3-letter currency code defined in ISO 4217. + string to_code = 2; +} + +// -------------Payment service----------------- + +service PaymentService { + rpc Charge(ChargeRequest) returns (ChargeResponse) {} +} + +message CreditCardInfo { + string credit_card_number = 1; + int32 credit_card_cvv = 2; + int32 credit_card_expiration_year = 3; + int32 credit_card_expiration_month = 4; +} + +message ChargeRequest { + Money amount = 1; + CreditCardInfo credit_card = 2; +} + +message ChargeResponse { + string transaction_id = 1; +} + +// -------------Email service----------------- + +service EmailService { + rpc SendOrderConfirmation(SendOrderConfirmationRequest) returns (Empty) {} +} + +message OrderItem { + CartItem item = 1; + Money cost = 2; +} + +message OrderResult { + string order_id = 1; + string shipping_tracking_id = 2; + Money shipping_cost = 3; + Address shipping_address = 4; + repeated OrderItem items = 5; +} + +message SendOrderConfirmationRequest { + string email = 1; + OrderResult order = 2; +} + + +// -------------Checkout service----------------- + +service CheckoutService { + rpc PlaceOrder(PlaceOrderRequest) returns (PlaceOrderResponse) {} +} + +message PlaceOrderRequest { + string user_id = 1; + string user_currency = 2; + + Address address = 3; + string email = 5; + CreditCardInfo credit_card = 6; +} + +message PlaceOrderResponse { + OrderResult order = 1; +} + +// ------------Ad service------------------ + +service AdService { + rpc GetAds(AdRequest) returns (AdResponse) {} +} + +message AdRequest { + // List of important key words from the current page describing the context. + repeated string context_keys = 1; +} + +message AdResponse { + repeated Ad ads = 1; +} + +message Ad { + // url to redirect to when an ad is clicked. + string redirect_url = 1; + + // short advertisement text to display. + string text = 2; +} diff --git a/src/adservice/src/main/resources/log4j2.xml b/src/adservice/src/main/resources/log4j2.xml new file mode 100644 index 0000000..2a87a5c --- /dev/null +++ b/src/adservice/src/main/resources/log4j2.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/cartservice/cartservice.sln b/src/cartservice/cartservice.sln new file mode 100644 index 0000000..8d32082 --- /dev/null +++ b/src/cartservice/cartservice.sln @@ -0,0 +1,48 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.26124.0 +MinimumVisualStudioVersion = 15.0.26124.0 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "cartservice", "src\cartservice.csproj", "{2348C29F-E8D3-4955-916D-D609CBC97FCB}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "cartservice.tests", "tests\cartservice.tests.csproj", "{59825342-CE64-4AFA-8744-781692C0811B}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {2348C29F-E8D3-4955-916D-D609CBC97FCB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2348C29F-E8D3-4955-916D-D609CBC97FCB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2348C29F-E8D3-4955-916D-D609CBC97FCB}.Debug|x64.ActiveCfg = Debug|Any CPU + {2348C29F-E8D3-4955-916D-D609CBC97FCB}.Debug|x64.Build.0 = Debug|Any CPU + {2348C29F-E8D3-4955-916D-D609CBC97FCB}.Debug|x86.ActiveCfg = Debug|Any CPU + {2348C29F-E8D3-4955-916D-D609CBC97FCB}.Debug|x86.Build.0 = Debug|Any CPU + {2348C29F-E8D3-4955-916D-D609CBC97FCB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2348C29F-E8D3-4955-916D-D609CBC97FCB}.Release|Any CPU.Build.0 = Release|Any CPU + {2348C29F-E8D3-4955-916D-D609CBC97FCB}.Release|x64.ActiveCfg = Release|Any CPU + {2348C29F-E8D3-4955-916D-D609CBC97FCB}.Release|x64.Build.0 = Release|Any CPU + {2348C29F-E8D3-4955-916D-D609CBC97FCB}.Release|x86.ActiveCfg = Release|Any CPU + {2348C29F-E8D3-4955-916D-D609CBC97FCB}.Release|x86.Build.0 = Release|Any CPU + {59825342-CE64-4AFA-8744-781692C0811B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {59825342-CE64-4AFA-8744-781692C0811B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {59825342-CE64-4AFA-8744-781692C0811B}.Debug|x64.ActiveCfg = Debug|Any CPU + {59825342-CE64-4AFA-8744-781692C0811B}.Debug|x64.Build.0 = Debug|Any CPU + {59825342-CE64-4AFA-8744-781692C0811B}.Debug|x86.ActiveCfg = Debug|Any CPU + {59825342-CE64-4AFA-8744-781692C0811B}.Debug|x86.Build.0 = Debug|Any CPU + {59825342-CE64-4AFA-8744-781692C0811B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {59825342-CE64-4AFA-8744-781692C0811B}.Release|Any CPU.Build.0 = Release|Any CPU + {59825342-CE64-4AFA-8744-781692C0811B}.Release|x64.ActiveCfg = Release|Any CPU + {59825342-CE64-4AFA-8744-781692C0811B}.Release|x64.Build.0 = Release|Any CPU + {59825342-CE64-4AFA-8744-781692C0811B}.Release|x86.ActiveCfg = Release|Any CPU + {59825342-CE64-4AFA-8744-781692C0811B}.Release|x86.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/src/cartservice/src/.dockerignore b/src/cartservice/src/.dockerignore new file mode 100644 index 0000000..0224086 --- /dev/null +++ b/src/cartservice/src/.dockerignore @@ -0,0 +1,6 @@ +**/*.sh +**/*.bat +**/bin/ +**/obj/ +**/out/ +Dockerfile* \ No newline at end of file diff --git a/src/cartservice/src/Dockerfile b/src/cartservice/src/Dockerfile new file mode 100644 index 0000000..24f37a4 --- /dev/null +++ b/src/cartservice/src/Dockerfile @@ -0,0 +1,41 @@ +# Copyright 2021 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# https://mcr.microsoft.com/product/dotnet/sdk +FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:10.0.100-noble@sha256:c7445f141c04f1a6b454181bd098dcfa606c61ba0bd213d0a702489e5bd4cd71 AS builder +ARG TARGETARCH +WORKDIR /app +COPY cartservice.csproj . +RUN dotnet restore cartservice.csproj \ + -a $TARGETARCH +COPY . . +RUN dotnet publish cartservice.csproj \ + -p:PublishSingleFile=true \ + -a $TARGETARCH \ + --self-contained true \ + -p:PublishTrimmed=true \ + -p:TrimMode=full \ + -c release \ + -o /cartservice + +# https://mcr.microsoft.com/product/dotnet/runtime-deps +FROM mcr.microsoft.com/dotnet/runtime-deps:10.0.0-noble-chiseled@sha256:b857c8cb8d929183cfe4c6dd9994abba92a2639dd2dbaf06005379f815991604 + +WORKDIR /app +COPY --from=builder /cartservice . +EXPOSE 7070 +ENV DOTNET_EnableDiagnostics=0 \ + ASPNETCORE_HTTP_PORTS=7070 +USER 1000 +ENTRYPOINT ["/app/cartservice"] diff --git a/src/cartservice/src/Dockerfile.debug b/src/cartservice/src/Dockerfile.debug new file mode 100644 index 0000000..b244fba --- /dev/null +++ b/src/cartservice/src/Dockerfile.debug @@ -0,0 +1,33 @@ +# Copyright 2021 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +FROM mcr.microsoft.com/dotnet/sdk:10.0@sha256:25d14b400b75fa4e89d5bd4487a92a604a4e409ab65becb91821e7dc4ac7f81f AS build +WORKDIR /app +COPY . . +RUN dotnet restore cartservice.csproj +RUN dotnet build "./cartservice.csproj" -c Debug -o /out + +FROM build AS publish +RUN dotnet publish cartservice.csproj -c Debug -o /out + +# Building final image used in running container +FROM mcr.microsoft.com/dotnet/aspnet:10.0@sha256:1aacc8154bc3071349907dae26849df301188be1a2e1f4560b903fb6275e481a AS final +# Installing procps on the container to enable debugging of .NET Core +RUN apt-get update \ + && apt-get install -y unzip procps wget +WORKDIR /app +COPY --from=publish /out . +ENV ASPNETCORE_HTTP_PORTS=7070 + +ENTRYPOINT ["dotnet", "cartservice.dll"] diff --git a/src/cartservice/src/Program.cs b/src/cartservice/src/Program.cs new file mode 100644 index 0000000..beb7e79 --- /dev/null +++ b/src/cartservice/src/Program.cs @@ -0,0 +1,26 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Hosting; +using cartservice; + +CreateHostBuilder(args).Build().Run(); + +static IHostBuilder CreateHostBuilder(string[] args) => + Host.CreateDefaultBuilder(args) + .ConfigureWebHostDefaults(webBuilder => + { + webBuilder.UseStartup(); + }); \ No newline at end of file diff --git a/src/cartservice/src/Startup.cs b/src/cartservice/src/Startup.cs new file mode 100644 index 0000000..136d303 --- /dev/null +++ b/src/cartservice/src/Startup.cs @@ -0,0 +1,84 @@ +using System; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Diagnostics.HealthChecks; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Diagnostics.HealthChecks; +using Microsoft.Extensions.Hosting; +using cartservice.cartstore; +using cartservice.services; +using Microsoft.Extensions.Caching.StackExchangeRedis; + +namespace cartservice +{ + public class Startup + { + public Startup(IConfiguration configuration) + { + Configuration = configuration; + } + + public IConfiguration Configuration { get; } + + // This method gets called by the runtime. Use this method to add services to the container. + // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940 + public void ConfigureServices(IServiceCollection services) + { + string redisAddress = Configuration["REDIS_ADDR"]; + string spannerProjectId = Configuration["SPANNER_PROJECT"]; + string spannerConnectionString = Configuration["SPANNER_CONNECTION_STRING"]; + string alloyDBConnectionString = Configuration["ALLOYDB_PRIMARY_IP"]; + + if (!string.IsNullOrEmpty(redisAddress)) + { + services.AddStackExchangeRedisCache(options => + { + options.Configuration = redisAddress; + }); + services.AddSingleton(); + } + else if (!string.IsNullOrEmpty(spannerProjectId) || !string.IsNullOrEmpty(spannerConnectionString)) + { + services.AddSingleton(); + } + else if (!string.IsNullOrEmpty(alloyDBConnectionString)) + { + Console.WriteLine("Creating AlloyDB cart store"); + services.AddSingleton(); + } + else + { + Console.WriteLine("Redis cache host(hostname+port) was not specified. Starting a cart service using in memory store"); + services.AddDistributedMemoryCache(); + services.AddSingleton(); + } + + + services.AddGrpc(); + } + + // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + public void Configure(IApplicationBuilder app, IWebHostEnvironment env) + { + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + } + + app.UseRouting(); + + app.UseEndpoints(endpoints => + { + endpoints.MapGrpcService(); + endpoints.MapGrpcService(); + + endpoints.MapGet("/", async context => + { + await context.Response.WriteAsync("Communication with gRPC endpoints must be made through a gRPC client. To learn how to create a client, visit: https://go.microsoft.com/fwlink/?linkid=2086909"); + }); + }); + } + } +} diff --git a/src/cartservice/src/appsettings.json b/src/cartservice/src/appsettings.json new file mode 100644 index 0000000..02e5e60 --- /dev/null +++ b/src/cartservice/src/appsettings.json @@ -0,0 +1,15 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information" + } + }, + "AllowedHosts": "*", + "Kestrel": { + "EndpointDefaults": { + "Protocols": "Http2" + } + } + } \ No newline at end of file diff --git a/src/cartservice/src/cartservice.csproj b/src/cartservice/src/cartservice.csproj new file mode 100644 index 0000000..87c36b0 --- /dev/null +++ b/src/cartservice/src/cartservice.csproj @@ -0,0 +1,19 @@ + + + + net10.0 + + + + + + + + + + + + + + + diff --git a/src/cartservice/src/cartstore/AlloyDBCartStore.cs b/src/cartservice/src/cartstore/AlloyDBCartStore.cs new file mode 100644 index 0000000..61b139d --- /dev/null +++ b/src/cartservice/src/cartstore/AlloyDBCartStore.cs @@ -0,0 +1,177 @@ +// Copyright 2021 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; +using Grpc.Core; +using Npgsql; +using Microsoft.Extensions.Configuration; +using System.Threading.Tasks; +using Google.Api.Gax.ResourceNames; +using Google.Cloud.SecretManager.V1; + +namespace cartservice.cartstore +{ + public class AlloyDBCartStore : ICartStore + { + private readonly string tableName; + private readonly string connectionString; + + public AlloyDBCartStore(IConfiguration configuration) + { + // Create a Cloud Secrets client. + SecretManagerServiceClient client = SecretManagerServiceClient.Create(); + var projectId = configuration["PROJECT_ID"]; + var secretId = configuration["ALLOYDB_SECRET_NAME"]; + SecretVersionName secretVersionName = new SecretVersionName(projectId, secretId, "latest"); + + AccessSecretVersionResponse result = client.AccessSecretVersion(secretVersionName); + // Convert the payload to a string. Payloads are bytes by default. + string alloyDBPassword = result.Payload.Data.ToStringUtf8().TrimEnd('\r', '\n'); + + // TODO: Create a separate user for connecting within the application + // rather than using our superuser + string alloyDBUser = "postgres"; + string databaseName = configuration["ALLOYDB_DATABASE_NAME"]; + // TODO: Consider splitting workloads into read vs. write and take + // advantage of the AlloyDB read pools + string primaryIPAddress = configuration["ALLOYDB_PRIMARY_IP"]; + connectionString = "Host=" + + primaryIPAddress + + ";Username=" + + alloyDBUser + + ";Password=" + + alloyDBPassword + + ";Database=" + + databaseName; + + tableName = configuration["ALLOYDB_TABLE_NAME"]; + } + + + public async Task AddItemAsync(string userId, string productId, int quantity) + { + Console.WriteLine($"AddItemAsync for {userId} called"); + try + { + await using var dataSource = NpgsqlDataSource.Create(connectionString); + + var fetchCmd = $"SELECT quantity FROM {tableName} WHERE userID='{userId}' AND productID='{productId}'"; + var currentQuantity = 0; + var cmdRead = dataSource.CreateCommand(fetchCmd); + await using (var reader = await cmdRead.ExecuteReaderAsync()) + { + while (await reader.ReadAsync()) + currentQuantity += reader.GetInt32(0); + } + + var totalQuantity = quantity + currentQuantity; + + // Use INSERT ... ON CONFLICT to prevent duplicate key error + var insertCmd = $@" + INSERT INTO {tableName} (userId, productId, quantity) + VALUES ('{userId}', '{productId}', {totalQuantity}) + ON CONFLICT (userId, productId) + DO UPDATE SET quantity = {totalQuantity}; + "; + + await using (var cmdInsert = dataSource.CreateCommand(insertCmd)) + { + await Task.Run(() => + { + return cmdInsert.ExecuteNonQueryAsync(); + }); + } + } + catch (Exception ex) + { + throw new RpcException( + new Status(StatusCode.FailedPrecondition, $"Unable to access cart storage due to an internal error. {ex}")); + } + } + + + public async Task GetCartAsync(string userId) + { + Console.WriteLine($"GetCartAsync called for userId={userId}"); + Hipstershop.Cart cart = new(); + cart.UserId = userId; + try + { + await using var dataSource = NpgsqlDataSource.Create(connectionString); + + var cartFetchCmd = $"SELECT productId, quantity FROM {tableName} WHERE userId = '{userId}'"; + var cmd = dataSource.CreateCommand(cartFetchCmd); + await using (var reader = await cmd.ExecuteReaderAsync()) + { + while (await reader.ReadAsync()) + { + Hipstershop.CartItem item = new() + { + ProductId = reader.GetString(0), + Quantity = reader.GetInt32(1) + }; + cart.Items.Add(item); + } + } + await Task.Run(() => + { + return cart; + }); + } + catch (Exception ex) + { + throw new RpcException( + new Status(StatusCode.FailedPrecondition, $"Unable to access cart storage due to an internal error. {ex}")); + } + return cart; + } + + + public async Task EmptyCartAsync(string userId) + { + Console.WriteLine($"EmptyCartAsync called for userId={userId}"); + + try + { + await using var dataSource = NpgsqlDataSource.Create(connectionString); + var deleteCmd = $"DELETE FROM {tableName} WHERE userID = '{userId}'"; + await using (var cmd = dataSource.CreateCommand(deleteCmd)) + { + await Task.Run(() => + { + return cmd.ExecuteNonQueryAsync(); + }); + } + } + catch (Exception ex) + { + throw new RpcException( + new Status(StatusCode.FailedPrecondition, $"Unable to access cart storage due to an internal error. {ex}")); + } + } + + public bool Ping() + { + try + { + return true; + } + catch (Exception) + { + return false; + } + } + } +} + diff --git a/src/cartservice/src/cartstore/ICartStore.cs b/src/cartservice/src/cartstore/ICartStore.cs new file mode 100644 index 0000000..ccf01e2 --- /dev/null +++ b/src/cartservice/src/cartstore/ICartStore.cs @@ -0,0 +1,26 @@ +// Copyright 2018 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System.Threading.Tasks; + +namespace cartservice.cartstore +{ + public interface ICartStore + { + Task AddItemAsync(string userId, string productId, int quantity); + Task EmptyCartAsync(string userId); + Task GetCartAsync(string userId); + bool Ping(); + } +} \ No newline at end of file diff --git a/src/cartservice/src/cartstore/RedisCartStore.cs b/src/cartservice/src/cartstore/RedisCartStore.cs new file mode 100644 index 0000000..3c835b2 --- /dev/null +++ b/src/cartservice/src/cartstore/RedisCartStore.cs @@ -0,0 +1,118 @@ +// Copyright 2018 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; +using System.Linq; +using System.Threading.Tasks; +using Grpc.Core; +using Microsoft.Extensions.Caching.Distributed; +using Google.Protobuf; + +namespace cartservice.cartstore +{ + public class RedisCartStore : ICartStore + { + private readonly IDistributedCache _cache; + + public RedisCartStore(IDistributedCache cache) + { + _cache = cache; + } + + public async Task AddItemAsync(string userId, string productId, int quantity) + { + Console.WriteLine($"AddItemAsync called with userId={userId}, productId={productId}, quantity={quantity}"); + + try + { + Hipstershop.Cart cart; + var value = await _cache.GetAsync(userId); + if (value == null) + { + cart = new Hipstershop.Cart(); + cart.UserId = userId; + cart.Items.Add(new Hipstershop.CartItem { ProductId = productId, Quantity = quantity }); + } + else + { + cart = Hipstershop.Cart.Parser.ParseFrom(value); + var existingItem = cart.Items.SingleOrDefault(i => i.ProductId == productId); + if (existingItem == null) + { + cart.Items.Add(new Hipstershop.CartItem { ProductId = productId, Quantity = quantity }); + } + else + { + existingItem.Quantity += quantity; + } + } + await _cache.SetAsync(userId, cart.ToByteArray()); + } + catch (Exception ex) + { + throw new RpcException(new Status(StatusCode.FailedPrecondition, $"Can't access cart storage. {ex}")); + } + } + + public async Task EmptyCartAsync(string userId) + { + Console.WriteLine($"EmptyCartAsync called with userId={userId}"); + + try + { + var cart = new Hipstershop.Cart(); + await _cache.SetAsync(userId, cart.ToByteArray()); + } + catch (Exception ex) + { + throw new RpcException(new Status(StatusCode.FailedPrecondition, $"Can't access cart storage. {ex}")); + } + } + + public async Task GetCartAsync(string userId) + { + Console.WriteLine($"GetCartAsync called with userId={userId}"); + + try + { + // Access the cart from the cache + var value = await _cache.GetAsync(userId); + + if (value != null) + { + return Hipstershop.Cart.Parser.ParseFrom(value); + } + + // We decided to return empty cart in cases when user wasn't in the cache before + return new Hipstershop.Cart(); + } + catch (Exception ex) + { + throw new RpcException(new Status(StatusCode.FailedPrecondition, $"Can't access cart storage. {ex}")); + } + } + + public bool Ping() + { + try + { + return true; + } + catch (Exception) + { + return false; + } + } + } +} diff --git a/src/cartservice/src/cartstore/SpannerCartStore.cs b/src/cartservice/src/cartstore/SpannerCartStore.cs new file mode 100644 index 0000000..a97e53c --- /dev/null +++ b/src/cartservice/src/cartstore/SpannerCartStore.cs @@ -0,0 +1,185 @@ +// Copyright 2021 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; +using Google.Cloud.Spanner.Data; +using Grpc.Core; +using Microsoft.Extensions.Configuration; +using System.Threading.Tasks; + +namespace cartservice.cartstore +{ + public class SpannerCartStore : ICartStore + { + private static readonly string TableName = "CartItems"; + private static readonly string DefaultInstanceName = "onlineboutique"; + private static readonly string DefaultDatabaseName = "carts"; + private readonly string databaseString; + + public SpannerCartStore(IConfiguration configuration) + { + string spannerProjectId = configuration["SPANNER_PROJECT"]; + string spannerInstanceId = configuration["SPANNER_INSTANCE"]; + string spannerDatabaseId = configuration["SPANNER_DATABASE"]; + string spannerConnectionString = configuration["SPANNER_CONNECTION_STRING"]; + SpannerConnectionStringBuilder builder = new(); + if (!string.IsNullOrEmpty(spannerConnectionString)) { + builder.DataSource = spannerConnectionString; + databaseString = builder.ToString(); + Console.WriteLine($"Spanner connection string: ${databaseString}"); + return; + } + if (string.IsNullOrEmpty(spannerInstanceId)) + spannerInstanceId = DefaultInstanceName; + if (string.IsNullOrEmpty(spannerDatabaseId)) + spannerDatabaseId = DefaultDatabaseName; + builder.DataSource = + $"projects/{spannerProjectId}/instances/{spannerInstanceId}/databases/{spannerDatabaseId}"; + databaseString = builder.ToString(); + Console.WriteLine($"Built Spanner connection string: '{databaseString}'"); + } + + + public async Task AddItemAsync(string userId, string productId, int quantity) + { + Console.WriteLine($"AddItemAsync for {userId} called"); + try + { + using SpannerConnection spannerConnection = new(databaseString); + await spannerConnection.RunWithRetriableTransactionAsync(async transaction => + { + int currentQuantity = 0; + var quantityLookup = spannerConnection.CreateSelectCommand( + $"SELECT * FROM {TableName} WHERE userId = @userId AND productId = @productId", + new SpannerParameterCollection + { + { "userId", SpannerDbType.String }, + { "productId", SpannerDbType.String } + }); + quantityLookup.Parameters["userId"].Value = userId; + quantityLookup.Parameters["productId"].Value = productId; + quantityLookup.Transaction = transaction; + using (var reader = await quantityLookup.ExecuteReaderAsync()) + { + while (await reader.ReadAsync()) { + currentQuantity += reader.GetFieldValue("quantity"); + } + } + + var cmd = spannerConnection.CreateInsertOrUpdateCommand(TableName, + new SpannerParameterCollection + { + { "userId", SpannerDbType.String }, + { "productId", SpannerDbType.String }, + { "quantity", SpannerDbType.Int64 } + }); + cmd.Parameters["userId"].Value = userId; + cmd.Parameters["productId"].Value = productId; + cmd.Parameters["quantity"].Value = currentQuantity + quantity; + cmd.Transaction = transaction; + await Task.Run(() => + { + return cmd.ExecuteNonQueryAsync(); + }); + }); + } + catch (Exception ex) + { + throw new RpcException( + new Status(StatusCode.FailedPrecondition, $"Can't access cart storage at {databaseString}. {ex}")); + } + } + + + public async Task GetCartAsync(string userId) + { + Console.WriteLine($"GetCartAsync called for userId={userId}"); + Hipstershop.Cart cart = new(); + try + { + using SpannerConnection spannerConnection = new(databaseString); + var cmd = spannerConnection.CreateSelectCommand( + $"SELECT * FROM {TableName} WHERE userId = @userId", + new SpannerParameterCollection { + { "userId", SpannerDbType.String } + } + ); + cmd.Parameters["userId"].Value = userId; + using var reader = await cmd.ExecuteReaderAsync(); + while (await reader.ReadAsync()) + { + // Only add the userId if something is in the cart. + // This is based on how the cartservice example behaves. + // An empty cart has no userId attached. + cart.UserId = userId; + + Hipstershop.CartItem item = new() + { + ProductId = reader.GetFieldValue("productId"), + Quantity = reader.GetFieldValue("quantity") + }; + cart.Items.Add(item); + } + + return cart; + } + catch (Exception ex) + { + throw new RpcException( + new Status(StatusCode.FailedPrecondition, $"Can't access cart storage at {databaseString}. {ex}")); + } + } + + + public async Task EmptyCartAsync(string userId) + { + Console.WriteLine($"EmptyCartAsync called for userId={userId}"); + + try + { + using SpannerConnection spannerConnection = new(databaseString); + await Task.Run(() => + { + var cmd = spannerConnection.CreateDmlCommand( + $"DELETE FROM {TableName} WHERE userId = @userId", + new SpannerParameterCollection + { + { "userId", SpannerDbType.String } + }); + cmd.Parameters["userId"].Value = userId; + return cmd.ExecuteNonQueryAsync(); + }); + } + + catch (Exception ex) + { + throw new RpcException( + new Status(StatusCode.FailedPrecondition, $"Can't access cart storage at {databaseString}. {ex}")); + } + } + + public bool Ping() + { + try + { + return true; + } + catch (Exception) + { + return false; + } + } + } +} + diff --git a/src/cartservice/src/protos/Cart.proto b/src/cartservice/src/protos/Cart.proto new file mode 100644 index 0000000..b2974cb --- /dev/null +++ b/src/cartservice/src/protos/Cart.proto @@ -0,0 +1,50 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package hipstershop; + +// -----------------Cart service----------------- + +service CartService { + rpc AddItem(AddItemRequest) returns (Empty) {} + rpc GetCart(GetCartRequest) returns (Cart) {} + rpc EmptyCart(EmptyCartRequest) returns (Empty) {} +} + +message CartItem { + string product_id = 1; + int32 quantity = 2; +} + +message AddItemRequest { + string user_id = 1; + CartItem item = 2; +} + +message EmptyCartRequest { + string user_id = 1; +} + +message GetCartRequest { + string user_id = 1; +} + +message Cart { + string user_id = 1; + repeated CartItem items = 2; +} + +message Empty {} diff --git a/src/cartservice/src/services/CartService.cs b/src/cartservice/src/services/CartService.cs new file mode 100644 index 0000000..7edff1c --- /dev/null +++ b/src/cartservice/src/services/CartService.cs @@ -0,0 +1,51 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; +using System.Threading.Tasks; +using Grpc.Core; +using Microsoft.Extensions.Logging; +using cartservice.cartstore; +using Hipstershop; + +namespace cartservice.services +{ + public class CartService : Hipstershop.CartService.CartServiceBase + { + private readonly static Empty Empty = new Empty(); + private readonly ICartStore _cartStore; + + public CartService(ICartStore cartStore) + { + _cartStore = cartStore; + } + + public async override Task AddItem(AddItemRequest request, ServerCallContext context) + { + await _cartStore.AddItemAsync(request.UserId, request.Item.ProductId, request.Item.Quantity); + return Empty; + } + + public override Task GetCart(GetCartRequest request, ServerCallContext context) + { + return _cartStore.GetCartAsync(request.UserId); + } + + public async override Task EmptyCart(EmptyCartRequest request, ServerCallContext context) + { + await _cartStore.EmptyCartAsync(request.UserId); + return Empty; + } + } +} \ No newline at end of file diff --git a/src/cartservice/src/services/HealthCheckService.cs b/src/cartservice/src/services/HealthCheckService.cs new file mode 100644 index 0000000..289b0e8 --- /dev/null +++ b/src/cartservice/src/services/HealthCheckService.cs @@ -0,0 +1,41 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; +using System.Threading.Tasks; +using Grpc.Core; +using Grpc.Health.V1; +using static Grpc.Health.V1.Health; +using cartservice.cartstore; + +namespace cartservice.services +{ + internal class HealthCheckService : HealthBase + { + private ICartStore _cartStore { get; } + + public HealthCheckService (ICartStore cartStore) + { + _cartStore = cartStore; + } + + public override Task Check(HealthCheckRequest request, ServerCallContext context) + { + Console.WriteLine ("Checking CartService Health"); + return Task.FromResult(new HealthCheckResponse { + Status = _cartStore.Ping() ? HealthCheckResponse.Types.ServingStatus.Serving : HealthCheckResponse.Types.ServingStatus.NotServing + }); + } + } +} \ No newline at end of file diff --git a/src/cartservice/tests/CartServiceTests.cs b/src/cartservice/tests/CartServiceTests.cs new file mode 100644 index 0000000..ee1c7a1 --- /dev/null +++ b/src/cartservice/tests/CartServiceTests.cs @@ -0,0 +1,160 @@ +// Copyright 2018 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; +using System.Threading.Tasks; +using Grpc.Net.Client; +using Hipstershop; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.TestHost; +using Microsoft.Extensions.Hosting; +using Xunit; +using static Hipstershop.CartService; + +namespace cartservice.tests +{ + public class CartServiceTests + { + private readonly IHostBuilder _host; + + public CartServiceTests() + { + _host = new HostBuilder().ConfigureWebHost(webBuilder => + { + webBuilder + .UseStartup() + .UseTestServer(); + }); + } + + [Fact] + public async Task GetItem_NoAddItemBefore_EmptyCartReturned() + { + // Setup test server and client + using var server = await _host.StartAsync(); + var httpClient = server.GetTestClient(); + + string userId = Guid.NewGuid().ToString(); + + // Create a GRPC communication channel between the client and the server + var channel = GrpcChannel.ForAddress(httpClient.BaseAddress, new GrpcChannelOptions + { + HttpClient = httpClient + }); + + var cartClient = new CartServiceClient(channel); + + var request = new GetCartRequest + { + UserId = userId, + }; + + var cart = await cartClient.GetCartAsync(request); + Assert.NotNull(cart); + + // All grpc objects implement IEquitable, so we can compare equality with by-value semantics + Assert.Equal(new Cart(), cart); + } + + [Fact] + public async Task AddItem_ItemExists_Updated() + { + // Setup test server and client + using var server = await _host.StartAsync(); + var httpClient = server.GetTestClient(); + + string userId = Guid.NewGuid().ToString(); + + // Create a GRPC communication channel between the client and the server + var channel = GrpcChannel.ForAddress(httpClient.BaseAddress, new GrpcChannelOptions + { + HttpClient = httpClient + }); + + var client = new CartServiceClient(channel); + var request = new AddItemRequest + { + UserId = userId, + Item = new CartItem + { + ProductId = "1", + Quantity = 1 + } + }; + + // First add - nothing should fail + await client.AddItemAsync(request); + + // Second add of existing product - quantity should be updated + await client.AddItemAsync(request); + + var getCartRequest = new GetCartRequest + { + UserId = userId + }; + var cart = await client.GetCartAsync(getCartRequest); + Assert.NotNull(cart); + Assert.Equal(userId, cart.UserId); + Assert.Single(cart.Items); + Assert.Equal(2, cart.Items[0].Quantity); + + // Cleanup + await client.EmptyCartAsync(new EmptyCartRequest { UserId = userId }); + } + + [Fact] + public async Task AddItem_New_Inserted() + { + // Setup test server and client + using var server = await _host.StartAsync(); + var httpClient = server.GetTestClient(); + + string userId = Guid.NewGuid().ToString(); + + // Create a GRPC communication channel between the client and the server + var channel = GrpcChannel.ForAddress(httpClient.BaseAddress, new GrpcChannelOptions + { + HttpClient = httpClient + }); + + // Create a proxy object to work with the server + var client = new CartServiceClient(channel); + + var request = new AddItemRequest + { + UserId = userId, + Item = new CartItem + { + ProductId = "1", + Quantity = 1 + } + }; + + await client.AddItemAsync(request); + + var getCartRequest = new GetCartRequest + { + UserId = userId + }; + var cart = await client.GetCartAsync(getCartRequest); + Assert.NotNull(cart); + Assert.Equal(userId, cart.UserId); + Assert.Single(cart.Items); + + await client.EmptyCartAsync(new EmptyCartRequest { UserId = userId }); + cart = await client.GetCartAsync(getCartRequest); + Assert.Empty(cart.Items); + } + } +} diff --git a/src/cartservice/tests/cartservice.tests.csproj b/src/cartservice/tests/cartservice.tests.csproj new file mode 100644 index 0000000..38c4ce2 --- /dev/null +++ b/src/cartservice/tests/cartservice.tests.csproj @@ -0,0 +1,20 @@ + + + + net10.0 + + false + + + + + + + + + + + + + + diff --git a/src/checkoutservice/.dockerignore b/src/checkoutservice/.dockerignore new file mode 100644 index 0000000..48b8bf9 --- /dev/null +++ b/src/checkoutservice/.dockerignore @@ -0,0 +1 @@ +vendor/ diff --git a/src/checkoutservice/Dockerfile b/src/checkoutservice/Dockerfile new file mode 100644 index 0000000..ba7942f --- /dev/null +++ b/src/checkoutservice/Dockerfile @@ -0,0 +1,41 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +FROM --platform=$BUILDPLATFORM golang:1.25.6-alpine@sha256:98e6cffc31ccc44c7c15d83df1d69891efee8115a5bb7ede2bf30a38af3e3c92 AS builder +ARG TARGETOS +ARG TARGETARCH +WORKDIR /src + +# restore dependencies +COPY go.mod go.sum ./ +RUN go mod download + +COPY . . + +# Skaffold passes in debug-oriented compiler flags +ARG SKAFFOLD_GO_GCFLAGS +RUN GOOS=${TARGETOS} GOARCH=${TARGETARCH} CGO_ENABLED=0 go build -ldflags="-s -w" -gcflags="${SKAFFOLD_GO_GCFLAGS}" -o /checkoutservice . + +FROM gcr.io/distroless/static + +WORKDIR /src +COPY --from=builder /checkoutservice /src/checkoutservice + +# Definition of this variable is used by 'skaffold debug' to identify a golang binary. +# Default behavior - a failure prints a stack trace for the current goroutine. +# See https://golang.org/pkg/runtime/ +ENV GOTRACEBACK=single + +EXPOSE 5050 +ENTRYPOINT ["/src/checkoutservice"] diff --git a/src/checkoutservice/README.md b/src/checkoutservice/README.md new file mode 100644 index 0000000..297f51d --- /dev/null +++ b/src/checkoutservice/README.md @@ -0,0 +1,5 @@ +# checkoutservice + +Run the following command to restore dependencies to `vendor/` directory: + + dep ensure --vendor-only diff --git a/src/checkoutservice/genproto.sh b/src/checkoutservice/genproto.sh new file mode 100755 index 0000000..2f85540 --- /dev/null +++ b/src/checkoutservice/genproto.sh @@ -0,0 +1,25 @@ +#!/bin/bash -eu +# +# Copyright 2018 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# [START gke_checkoutservice_genproto] + +PATH=$PATH:$(go env GOPATH)/bin +protodir=../../protos +outdir=./genproto + +protoc --proto_path=$protodir --go_out=./$outdir --go_opt=paths=source_relative --go-grpc_out=./$outdir --go-grpc_opt=paths=source_relative $protodir/demo.proto + +# [END gke_checkoutservice_genproto] \ No newline at end of file diff --git a/src/checkoutservice/genproto/demo.pb.go b/src/checkoutservice/genproto/demo.pb.go new file mode 100644 index 0000000..332cb9c --- /dev/null +++ b/src/checkoutservice/genproto/demo.pb.go @@ -0,0 +1,2610 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.34.2 +// protoc v3.6.1 +// source: demo.proto + +package hipstershop + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type CartItem struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ProductId string `protobuf:"bytes,1,opt,name=product_id,json=productId,proto3" json:"product_id,omitempty"` + Quantity int32 `protobuf:"varint,2,opt,name=quantity,proto3" json:"quantity,omitempty"` +} + +func (x *CartItem) Reset() { + *x = CartItem{} + if protoimpl.UnsafeEnabled { + mi := &file_demo_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CartItem) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CartItem) ProtoMessage() {} + +func (x *CartItem) ProtoReflect() protoreflect.Message { + mi := &file_demo_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CartItem.ProtoReflect.Descriptor instead. +func (*CartItem) Descriptor() ([]byte, []int) { + return file_demo_proto_rawDescGZIP(), []int{0} +} + +func (x *CartItem) GetProductId() string { + if x != nil { + return x.ProductId + } + return "" +} + +func (x *CartItem) GetQuantity() int32 { + if x != nil { + return x.Quantity + } + return 0 +} + +type AddItemRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + UserId string `protobuf:"bytes,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` + Item *CartItem `protobuf:"bytes,2,opt,name=item,proto3" json:"item,omitempty"` +} + +func (x *AddItemRequest) Reset() { + *x = AddItemRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_demo_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *AddItemRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AddItemRequest) ProtoMessage() {} + +func (x *AddItemRequest) ProtoReflect() protoreflect.Message { + mi := &file_demo_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AddItemRequest.ProtoReflect.Descriptor instead. +func (*AddItemRequest) Descriptor() ([]byte, []int) { + return file_demo_proto_rawDescGZIP(), []int{1} +} + +func (x *AddItemRequest) GetUserId() string { + if x != nil { + return x.UserId + } + return "" +} + +func (x *AddItemRequest) GetItem() *CartItem { + if x != nil { + return x.Item + } + return nil +} + +type EmptyCartRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + UserId string `protobuf:"bytes,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` +} + +func (x *EmptyCartRequest) Reset() { + *x = EmptyCartRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_demo_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *EmptyCartRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*EmptyCartRequest) ProtoMessage() {} + +func (x *EmptyCartRequest) ProtoReflect() protoreflect.Message { + mi := &file_demo_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use EmptyCartRequest.ProtoReflect.Descriptor instead. +func (*EmptyCartRequest) Descriptor() ([]byte, []int) { + return file_demo_proto_rawDescGZIP(), []int{2} +} + +func (x *EmptyCartRequest) GetUserId() string { + if x != nil { + return x.UserId + } + return "" +} + +type GetCartRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + UserId string `protobuf:"bytes,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` +} + +func (x *GetCartRequest) Reset() { + *x = GetCartRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_demo_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetCartRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetCartRequest) ProtoMessage() {} + +func (x *GetCartRequest) ProtoReflect() protoreflect.Message { + mi := &file_demo_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetCartRequest.ProtoReflect.Descriptor instead. +func (*GetCartRequest) Descriptor() ([]byte, []int) { + return file_demo_proto_rawDescGZIP(), []int{3} +} + +func (x *GetCartRequest) GetUserId() string { + if x != nil { + return x.UserId + } + return "" +} + +type Cart struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + UserId string `protobuf:"bytes,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` + Items []*CartItem `protobuf:"bytes,2,rep,name=items,proto3" json:"items,omitempty"` +} + +func (x *Cart) Reset() { + *x = Cart{} + if protoimpl.UnsafeEnabled { + mi := &file_demo_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Cart) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Cart) ProtoMessage() {} + +func (x *Cart) ProtoReflect() protoreflect.Message { + mi := &file_demo_proto_msgTypes[4] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Cart.ProtoReflect.Descriptor instead. +func (*Cart) Descriptor() ([]byte, []int) { + return file_demo_proto_rawDescGZIP(), []int{4} +} + +func (x *Cart) GetUserId() string { + if x != nil { + return x.UserId + } + return "" +} + +func (x *Cart) GetItems() []*CartItem { + if x != nil { + return x.Items + } + return nil +} + +type Empty struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *Empty) Reset() { + *x = Empty{} + if protoimpl.UnsafeEnabled { + mi := &file_demo_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Empty) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Empty) ProtoMessage() {} + +func (x *Empty) ProtoReflect() protoreflect.Message { + mi := &file_demo_proto_msgTypes[5] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Empty.ProtoReflect.Descriptor instead. +func (*Empty) Descriptor() ([]byte, []int) { + return file_demo_proto_rawDescGZIP(), []int{5} +} + +type ListRecommendationsRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + UserId string `protobuf:"bytes,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` + ProductIds []string `protobuf:"bytes,2,rep,name=product_ids,json=productIds,proto3" json:"product_ids,omitempty"` +} + +func (x *ListRecommendationsRequest) Reset() { + *x = ListRecommendationsRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_demo_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListRecommendationsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListRecommendationsRequest) ProtoMessage() {} + +func (x *ListRecommendationsRequest) ProtoReflect() protoreflect.Message { + mi := &file_demo_proto_msgTypes[6] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListRecommendationsRequest.ProtoReflect.Descriptor instead. +func (*ListRecommendationsRequest) Descriptor() ([]byte, []int) { + return file_demo_proto_rawDescGZIP(), []int{6} +} + +func (x *ListRecommendationsRequest) GetUserId() string { + if x != nil { + return x.UserId + } + return "" +} + +func (x *ListRecommendationsRequest) GetProductIds() []string { + if x != nil { + return x.ProductIds + } + return nil +} + +type ListRecommendationsResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ProductIds []string `protobuf:"bytes,1,rep,name=product_ids,json=productIds,proto3" json:"product_ids,omitempty"` +} + +func (x *ListRecommendationsResponse) Reset() { + *x = ListRecommendationsResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_demo_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListRecommendationsResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListRecommendationsResponse) ProtoMessage() {} + +func (x *ListRecommendationsResponse) ProtoReflect() protoreflect.Message { + mi := &file_demo_proto_msgTypes[7] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListRecommendationsResponse.ProtoReflect.Descriptor instead. +func (*ListRecommendationsResponse) Descriptor() ([]byte, []int) { + return file_demo_proto_rawDescGZIP(), []int{7} +} + +func (x *ListRecommendationsResponse) GetProductIds() []string { + if x != nil { + return x.ProductIds + } + return nil +} + +type Product struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` + Description string `protobuf:"bytes,3,opt,name=description,proto3" json:"description,omitempty"` + Picture string `protobuf:"bytes,4,opt,name=picture,proto3" json:"picture,omitempty"` + PriceUsd *Money `protobuf:"bytes,5,opt,name=price_usd,json=priceUsd,proto3" json:"price_usd,omitempty"` + // Categories such as "clothing" or "kitchen" that can be used to look up + // other related products. + Categories []string `protobuf:"bytes,6,rep,name=categories,proto3" json:"categories,omitempty"` +} + +func (x *Product) Reset() { + *x = Product{} + if protoimpl.UnsafeEnabled { + mi := &file_demo_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Product) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Product) ProtoMessage() {} + +func (x *Product) ProtoReflect() protoreflect.Message { + mi := &file_demo_proto_msgTypes[8] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Product.ProtoReflect.Descriptor instead. +func (*Product) Descriptor() ([]byte, []int) { + return file_demo_proto_rawDescGZIP(), []int{8} +} + +func (x *Product) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *Product) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *Product) GetDescription() string { + if x != nil { + return x.Description + } + return "" +} + +func (x *Product) GetPicture() string { + if x != nil { + return x.Picture + } + return "" +} + +func (x *Product) GetPriceUsd() *Money { + if x != nil { + return x.PriceUsd + } + return nil +} + +func (x *Product) GetCategories() []string { + if x != nil { + return x.Categories + } + return nil +} + +type ListProductsResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Products []*Product `protobuf:"bytes,1,rep,name=products,proto3" json:"products,omitempty"` +} + +func (x *ListProductsResponse) Reset() { + *x = ListProductsResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_demo_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListProductsResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListProductsResponse) ProtoMessage() {} + +func (x *ListProductsResponse) ProtoReflect() protoreflect.Message { + mi := &file_demo_proto_msgTypes[9] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListProductsResponse.ProtoReflect.Descriptor instead. +func (*ListProductsResponse) Descriptor() ([]byte, []int) { + return file_demo_proto_rawDescGZIP(), []int{9} +} + +func (x *ListProductsResponse) GetProducts() []*Product { + if x != nil { + return x.Products + } + return nil +} + +type GetProductRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` +} + +func (x *GetProductRequest) Reset() { + *x = GetProductRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_demo_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetProductRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetProductRequest) ProtoMessage() {} + +func (x *GetProductRequest) ProtoReflect() protoreflect.Message { + mi := &file_demo_proto_msgTypes[10] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetProductRequest.ProtoReflect.Descriptor instead. +func (*GetProductRequest) Descriptor() ([]byte, []int) { + return file_demo_proto_rawDescGZIP(), []int{10} +} + +func (x *GetProductRequest) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +type SearchProductsRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Query string `protobuf:"bytes,1,opt,name=query,proto3" json:"query,omitempty"` +} + +func (x *SearchProductsRequest) Reset() { + *x = SearchProductsRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_demo_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SearchProductsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SearchProductsRequest) ProtoMessage() {} + +func (x *SearchProductsRequest) ProtoReflect() protoreflect.Message { + mi := &file_demo_proto_msgTypes[11] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SearchProductsRequest.ProtoReflect.Descriptor instead. +func (*SearchProductsRequest) Descriptor() ([]byte, []int) { + return file_demo_proto_rawDescGZIP(), []int{11} +} + +func (x *SearchProductsRequest) GetQuery() string { + if x != nil { + return x.Query + } + return "" +} + +type SearchProductsResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Results []*Product `protobuf:"bytes,1,rep,name=results,proto3" json:"results,omitempty"` +} + +func (x *SearchProductsResponse) Reset() { + *x = SearchProductsResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_demo_proto_msgTypes[12] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SearchProductsResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SearchProductsResponse) ProtoMessage() {} + +func (x *SearchProductsResponse) ProtoReflect() protoreflect.Message { + mi := &file_demo_proto_msgTypes[12] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SearchProductsResponse.ProtoReflect.Descriptor instead. +func (*SearchProductsResponse) Descriptor() ([]byte, []int) { + return file_demo_proto_rawDescGZIP(), []int{12} +} + +func (x *SearchProductsResponse) GetResults() []*Product { + if x != nil { + return x.Results + } + return nil +} + +type GetQuoteRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Address *Address `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"` + Items []*CartItem `protobuf:"bytes,2,rep,name=items,proto3" json:"items,omitempty"` +} + +func (x *GetQuoteRequest) Reset() { + *x = GetQuoteRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_demo_proto_msgTypes[13] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetQuoteRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetQuoteRequest) ProtoMessage() {} + +func (x *GetQuoteRequest) ProtoReflect() protoreflect.Message { + mi := &file_demo_proto_msgTypes[13] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetQuoteRequest.ProtoReflect.Descriptor instead. +func (*GetQuoteRequest) Descriptor() ([]byte, []int) { + return file_demo_proto_rawDescGZIP(), []int{13} +} + +func (x *GetQuoteRequest) GetAddress() *Address { + if x != nil { + return x.Address + } + return nil +} + +func (x *GetQuoteRequest) GetItems() []*CartItem { + if x != nil { + return x.Items + } + return nil +} + +type GetQuoteResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + CostUsd *Money `protobuf:"bytes,1,opt,name=cost_usd,json=costUsd,proto3" json:"cost_usd,omitempty"` +} + +func (x *GetQuoteResponse) Reset() { + *x = GetQuoteResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_demo_proto_msgTypes[14] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetQuoteResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetQuoteResponse) ProtoMessage() {} + +func (x *GetQuoteResponse) ProtoReflect() protoreflect.Message { + mi := &file_demo_proto_msgTypes[14] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetQuoteResponse.ProtoReflect.Descriptor instead. +func (*GetQuoteResponse) Descriptor() ([]byte, []int) { + return file_demo_proto_rawDescGZIP(), []int{14} +} + +func (x *GetQuoteResponse) GetCostUsd() *Money { + if x != nil { + return x.CostUsd + } + return nil +} + +type ShipOrderRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Address *Address `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"` + Items []*CartItem `protobuf:"bytes,2,rep,name=items,proto3" json:"items,omitempty"` +} + +func (x *ShipOrderRequest) Reset() { + *x = ShipOrderRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_demo_proto_msgTypes[15] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ShipOrderRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ShipOrderRequest) ProtoMessage() {} + +func (x *ShipOrderRequest) ProtoReflect() protoreflect.Message { + mi := &file_demo_proto_msgTypes[15] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ShipOrderRequest.ProtoReflect.Descriptor instead. +func (*ShipOrderRequest) Descriptor() ([]byte, []int) { + return file_demo_proto_rawDescGZIP(), []int{15} +} + +func (x *ShipOrderRequest) GetAddress() *Address { + if x != nil { + return x.Address + } + return nil +} + +func (x *ShipOrderRequest) GetItems() []*CartItem { + if x != nil { + return x.Items + } + return nil +} + +type ShipOrderResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + TrackingId string `protobuf:"bytes,1,opt,name=tracking_id,json=trackingId,proto3" json:"tracking_id,omitempty"` +} + +func (x *ShipOrderResponse) Reset() { + *x = ShipOrderResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_demo_proto_msgTypes[16] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ShipOrderResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ShipOrderResponse) ProtoMessage() {} + +func (x *ShipOrderResponse) ProtoReflect() protoreflect.Message { + mi := &file_demo_proto_msgTypes[16] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ShipOrderResponse.ProtoReflect.Descriptor instead. +func (*ShipOrderResponse) Descriptor() ([]byte, []int) { + return file_demo_proto_rawDescGZIP(), []int{16} +} + +func (x *ShipOrderResponse) GetTrackingId() string { + if x != nil { + return x.TrackingId + } + return "" +} + +type Address struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + StreetAddress string `protobuf:"bytes,1,opt,name=street_address,json=streetAddress,proto3" json:"street_address,omitempty"` + City string `protobuf:"bytes,2,opt,name=city,proto3" json:"city,omitempty"` + State string `protobuf:"bytes,3,opt,name=state,proto3" json:"state,omitempty"` + Country string `protobuf:"bytes,4,opt,name=country,proto3" json:"country,omitempty"` + ZipCode int32 `protobuf:"varint,5,opt,name=zip_code,json=zipCode,proto3" json:"zip_code,omitempty"` +} + +func (x *Address) Reset() { + *x = Address{} + if protoimpl.UnsafeEnabled { + mi := &file_demo_proto_msgTypes[17] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Address) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Address) ProtoMessage() {} + +func (x *Address) ProtoReflect() protoreflect.Message { + mi := &file_demo_proto_msgTypes[17] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Address.ProtoReflect.Descriptor instead. +func (*Address) Descriptor() ([]byte, []int) { + return file_demo_proto_rawDescGZIP(), []int{17} +} + +func (x *Address) GetStreetAddress() string { + if x != nil { + return x.StreetAddress + } + return "" +} + +func (x *Address) GetCity() string { + if x != nil { + return x.City + } + return "" +} + +func (x *Address) GetState() string { + if x != nil { + return x.State + } + return "" +} + +func (x *Address) GetCountry() string { + if x != nil { + return x.Country + } + return "" +} + +func (x *Address) GetZipCode() int32 { + if x != nil { + return x.ZipCode + } + return 0 +} + +// Represents an amount of money with its currency type. +type Money struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The 3-letter currency code defined in ISO 4217. + CurrencyCode string `protobuf:"bytes,1,opt,name=currency_code,json=currencyCode,proto3" json:"currency_code,omitempty"` + // The whole units of the amount. + // For example if `currencyCode` is `"USD"`, then 1 unit is one US dollar. + Units int64 `protobuf:"varint,2,opt,name=units,proto3" json:"units,omitempty"` + // Number of nano (10^-9) units of the amount. + // The value must be between -999,999,999 and +999,999,999 inclusive. + // If `units` is positive, `nanos` must be positive or zero. + // If `units` is zero, `nanos` can be positive, zero, or negative. + // If `units` is negative, `nanos` must be negative or zero. + // For example $-1.75 is represented as `units`=-1 and `nanos`=-750,000,000. + Nanos int32 `protobuf:"varint,3,opt,name=nanos,proto3" json:"nanos,omitempty"` +} + +func (x *Money) Reset() { + *x = Money{} + if protoimpl.UnsafeEnabled { + mi := &file_demo_proto_msgTypes[18] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Money) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Money) ProtoMessage() {} + +func (x *Money) ProtoReflect() protoreflect.Message { + mi := &file_demo_proto_msgTypes[18] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Money.ProtoReflect.Descriptor instead. +func (*Money) Descriptor() ([]byte, []int) { + return file_demo_proto_rawDescGZIP(), []int{18} +} + +func (x *Money) GetCurrencyCode() string { + if x != nil { + return x.CurrencyCode + } + return "" +} + +func (x *Money) GetUnits() int64 { + if x != nil { + return x.Units + } + return 0 +} + +func (x *Money) GetNanos() int32 { + if x != nil { + return x.Nanos + } + return 0 +} + +type GetSupportedCurrenciesResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The 3-letter currency code defined in ISO 4217. + CurrencyCodes []string `protobuf:"bytes,1,rep,name=currency_codes,json=currencyCodes,proto3" json:"currency_codes,omitempty"` +} + +func (x *GetSupportedCurrenciesResponse) Reset() { + *x = GetSupportedCurrenciesResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_demo_proto_msgTypes[19] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetSupportedCurrenciesResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetSupportedCurrenciesResponse) ProtoMessage() {} + +func (x *GetSupportedCurrenciesResponse) ProtoReflect() protoreflect.Message { + mi := &file_demo_proto_msgTypes[19] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetSupportedCurrenciesResponse.ProtoReflect.Descriptor instead. +func (*GetSupportedCurrenciesResponse) Descriptor() ([]byte, []int) { + return file_demo_proto_rawDescGZIP(), []int{19} +} + +func (x *GetSupportedCurrenciesResponse) GetCurrencyCodes() []string { + if x != nil { + return x.CurrencyCodes + } + return nil +} + +type CurrencyConversionRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + From *Money `protobuf:"bytes,1,opt,name=from,proto3" json:"from,omitempty"` + // The 3-letter currency code defined in ISO 4217. + ToCode string `protobuf:"bytes,2,opt,name=to_code,json=toCode,proto3" json:"to_code,omitempty"` +} + +func (x *CurrencyConversionRequest) Reset() { + *x = CurrencyConversionRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_demo_proto_msgTypes[20] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CurrencyConversionRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CurrencyConversionRequest) ProtoMessage() {} + +func (x *CurrencyConversionRequest) ProtoReflect() protoreflect.Message { + mi := &file_demo_proto_msgTypes[20] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CurrencyConversionRequest.ProtoReflect.Descriptor instead. +func (*CurrencyConversionRequest) Descriptor() ([]byte, []int) { + return file_demo_proto_rawDescGZIP(), []int{20} +} + +func (x *CurrencyConversionRequest) GetFrom() *Money { + if x != nil { + return x.From + } + return nil +} + +func (x *CurrencyConversionRequest) GetToCode() string { + if x != nil { + return x.ToCode + } + return "" +} + +type CreditCardInfo struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + CreditCardNumber string `protobuf:"bytes,1,opt,name=credit_card_number,json=creditCardNumber,proto3" json:"credit_card_number,omitempty"` + CreditCardCvv int32 `protobuf:"varint,2,opt,name=credit_card_cvv,json=creditCardCvv,proto3" json:"credit_card_cvv,omitempty"` + CreditCardExpirationYear int32 `protobuf:"varint,3,opt,name=credit_card_expiration_year,json=creditCardExpirationYear,proto3" json:"credit_card_expiration_year,omitempty"` + CreditCardExpirationMonth int32 `protobuf:"varint,4,opt,name=credit_card_expiration_month,json=creditCardExpirationMonth,proto3" json:"credit_card_expiration_month,omitempty"` +} + +func (x *CreditCardInfo) Reset() { + *x = CreditCardInfo{} + if protoimpl.UnsafeEnabled { + mi := &file_demo_proto_msgTypes[21] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CreditCardInfo) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreditCardInfo) ProtoMessage() {} + +func (x *CreditCardInfo) ProtoReflect() protoreflect.Message { + mi := &file_demo_proto_msgTypes[21] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CreditCardInfo.ProtoReflect.Descriptor instead. +func (*CreditCardInfo) Descriptor() ([]byte, []int) { + return file_demo_proto_rawDescGZIP(), []int{21} +} + +func (x *CreditCardInfo) GetCreditCardNumber() string { + if x != nil { + return x.CreditCardNumber + } + return "" +} + +func (x *CreditCardInfo) GetCreditCardCvv() int32 { + if x != nil { + return x.CreditCardCvv + } + return 0 +} + +func (x *CreditCardInfo) GetCreditCardExpirationYear() int32 { + if x != nil { + return x.CreditCardExpirationYear + } + return 0 +} + +func (x *CreditCardInfo) GetCreditCardExpirationMonth() int32 { + if x != nil { + return x.CreditCardExpirationMonth + } + return 0 +} + +type ChargeRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Amount *Money `protobuf:"bytes,1,opt,name=amount,proto3" json:"amount,omitempty"` + CreditCard *CreditCardInfo `protobuf:"bytes,2,opt,name=credit_card,json=creditCard,proto3" json:"credit_card,omitempty"` +} + +func (x *ChargeRequest) Reset() { + *x = ChargeRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_demo_proto_msgTypes[22] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ChargeRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ChargeRequest) ProtoMessage() {} + +func (x *ChargeRequest) ProtoReflect() protoreflect.Message { + mi := &file_demo_proto_msgTypes[22] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ChargeRequest.ProtoReflect.Descriptor instead. +func (*ChargeRequest) Descriptor() ([]byte, []int) { + return file_demo_proto_rawDescGZIP(), []int{22} +} + +func (x *ChargeRequest) GetAmount() *Money { + if x != nil { + return x.Amount + } + return nil +} + +func (x *ChargeRequest) GetCreditCard() *CreditCardInfo { + if x != nil { + return x.CreditCard + } + return nil +} + +type ChargeResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + TransactionId string `protobuf:"bytes,1,opt,name=transaction_id,json=transactionId,proto3" json:"transaction_id,omitempty"` +} + +func (x *ChargeResponse) Reset() { + *x = ChargeResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_demo_proto_msgTypes[23] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ChargeResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ChargeResponse) ProtoMessage() {} + +func (x *ChargeResponse) ProtoReflect() protoreflect.Message { + mi := &file_demo_proto_msgTypes[23] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ChargeResponse.ProtoReflect.Descriptor instead. +func (*ChargeResponse) Descriptor() ([]byte, []int) { + return file_demo_proto_rawDescGZIP(), []int{23} +} + +func (x *ChargeResponse) GetTransactionId() string { + if x != nil { + return x.TransactionId + } + return "" +} + +type OrderItem struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Item *CartItem `protobuf:"bytes,1,opt,name=item,proto3" json:"item,omitempty"` + Cost *Money `protobuf:"bytes,2,opt,name=cost,proto3" json:"cost,omitempty"` +} + +func (x *OrderItem) Reset() { + *x = OrderItem{} + if protoimpl.UnsafeEnabled { + mi := &file_demo_proto_msgTypes[24] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *OrderItem) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*OrderItem) ProtoMessage() {} + +func (x *OrderItem) ProtoReflect() protoreflect.Message { + mi := &file_demo_proto_msgTypes[24] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use OrderItem.ProtoReflect.Descriptor instead. +func (*OrderItem) Descriptor() ([]byte, []int) { + return file_demo_proto_rawDescGZIP(), []int{24} +} + +func (x *OrderItem) GetItem() *CartItem { + if x != nil { + return x.Item + } + return nil +} + +func (x *OrderItem) GetCost() *Money { + if x != nil { + return x.Cost + } + return nil +} + +type OrderResult struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + OrderId string `protobuf:"bytes,1,opt,name=order_id,json=orderId,proto3" json:"order_id,omitempty"` + ShippingTrackingId string `protobuf:"bytes,2,opt,name=shipping_tracking_id,json=shippingTrackingId,proto3" json:"shipping_tracking_id,omitempty"` + ShippingCost *Money `protobuf:"bytes,3,opt,name=shipping_cost,json=shippingCost,proto3" json:"shipping_cost,omitempty"` + ShippingAddress *Address `protobuf:"bytes,4,opt,name=shipping_address,json=shippingAddress,proto3" json:"shipping_address,omitempty"` + Items []*OrderItem `protobuf:"bytes,5,rep,name=items,proto3" json:"items,omitempty"` +} + +func (x *OrderResult) Reset() { + *x = OrderResult{} + if protoimpl.UnsafeEnabled { + mi := &file_demo_proto_msgTypes[25] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *OrderResult) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*OrderResult) ProtoMessage() {} + +func (x *OrderResult) ProtoReflect() protoreflect.Message { + mi := &file_demo_proto_msgTypes[25] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use OrderResult.ProtoReflect.Descriptor instead. +func (*OrderResult) Descriptor() ([]byte, []int) { + return file_demo_proto_rawDescGZIP(), []int{25} +} + +func (x *OrderResult) GetOrderId() string { + if x != nil { + return x.OrderId + } + return "" +} + +func (x *OrderResult) GetShippingTrackingId() string { + if x != nil { + return x.ShippingTrackingId + } + return "" +} + +func (x *OrderResult) GetShippingCost() *Money { + if x != nil { + return x.ShippingCost + } + return nil +} + +func (x *OrderResult) GetShippingAddress() *Address { + if x != nil { + return x.ShippingAddress + } + return nil +} + +func (x *OrderResult) GetItems() []*OrderItem { + if x != nil { + return x.Items + } + return nil +} + +type SendOrderConfirmationRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Email string `protobuf:"bytes,1,opt,name=email,proto3" json:"email,omitempty"` + Order *OrderResult `protobuf:"bytes,2,opt,name=order,proto3" json:"order,omitempty"` +} + +func (x *SendOrderConfirmationRequest) Reset() { + *x = SendOrderConfirmationRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_demo_proto_msgTypes[26] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SendOrderConfirmationRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SendOrderConfirmationRequest) ProtoMessage() {} + +func (x *SendOrderConfirmationRequest) ProtoReflect() protoreflect.Message { + mi := &file_demo_proto_msgTypes[26] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SendOrderConfirmationRequest.ProtoReflect.Descriptor instead. +func (*SendOrderConfirmationRequest) Descriptor() ([]byte, []int) { + return file_demo_proto_rawDescGZIP(), []int{26} +} + +func (x *SendOrderConfirmationRequest) GetEmail() string { + if x != nil { + return x.Email + } + return "" +} + +func (x *SendOrderConfirmationRequest) GetOrder() *OrderResult { + if x != nil { + return x.Order + } + return nil +} + +type PlaceOrderRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + UserId string `protobuf:"bytes,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` + UserCurrency string `protobuf:"bytes,2,opt,name=user_currency,json=userCurrency,proto3" json:"user_currency,omitempty"` + Address *Address `protobuf:"bytes,3,opt,name=address,proto3" json:"address,omitempty"` + Email string `protobuf:"bytes,5,opt,name=email,proto3" json:"email,omitempty"` + CreditCard *CreditCardInfo `protobuf:"bytes,6,opt,name=credit_card,json=creditCard,proto3" json:"credit_card,omitempty"` +} + +func (x *PlaceOrderRequest) Reset() { + *x = PlaceOrderRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_demo_proto_msgTypes[27] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *PlaceOrderRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PlaceOrderRequest) ProtoMessage() {} + +func (x *PlaceOrderRequest) ProtoReflect() protoreflect.Message { + mi := &file_demo_proto_msgTypes[27] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PlaceOrderRequest.ProtoReflect.Descriptor instead. +func (*PlaceOrderRequest) Descriptor() ([]byte, []int) { + return file_demo_proto_rawDescGZIP(), []int{27} +} + +func (x *PlaceOrderRequest) GetUserId() string { + if x != nil { + return x.UserId + } + return "" +} + +func (x *PlaceOrderRequest) GetUserCurrency() string { + if x != nil { + return x.UserCurrency + } + return "" +} + +func (x *PlaceOrderRequest) GetAddress() *Address { + if x != nil { + return x.Address + } + return nil +} + +func (x *PlaceOrderRequest) GetEmail() string { + if x != nil { + return x.Email + } + return "" +} + +func (x *PlaceOrderRequest) GetCreditCard() *CreditCardInfo { + if x != nil { + return x.CreditCard + } + return nil +} + +type PlaceOrderResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Order *OrderResult `protobuf:"bytes,1,opt,name=order,proto3" json:"order,omitempty"` +} + +func (x *PlaceOrderResponse) Reset() { + *x = PlaceOrderResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_demo_proto_msgTypes[28] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *PlaceOrderResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PlaceOrderResponse) ProtoMessage() {} + +func (x *PlaceOrderResponse) ProtoReflect() protoreflect.Message { + mi := &file_demo_proto_msgTypes[28] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PlaceOrderResponse.ProtoReflect.Descriptor instead. +func (*PlaceOrderResponse) Descriptor() ([]byte, []int) { + return file_demo_proto_rawDescGZIP(), []int{28} +} + +func (x *PlaceOrderResponse) GetOrder() *OrderResult { + if x != nil { + return x.Order + } + return nil +} + +type AdRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // List of important key words from the current page describing the context. + ContextKeys []string `protobuf:"bytes,1,rep,name=context_keys,json=contextKeys,proto3" json:"context_keys,omitempty"` +} + +func (x *AdRequest) Reset() { + *x = AdRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_demo_proto_msgTypes[29] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *AdRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AdRequest) ProtoMessage() {} + +func (x *AdRequest) ProtoReflect() protoreflect.Message { + mi := &file_demo_proto_msgTypes[29] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AdRequest.ProtoReflect.Descriptor instead. +func (*AdRequest) Descriptor() ([]byte, []int) { + return file_demo_proto_rawDescGZIP(), []int{29} +} + +func (x *AdRequest) GetContextKeys() []string { + if x != nil { + return x.ContextKeys + } + return nil +} + +type AdResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Ads []*Ad `protobuf:"bytes,1,rep,name=ads,proto3" json:"ads,omitempty"` +} + +func (x *AdResponse) Reset() { + *x = AdResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_demo_proto_msgTypes[30] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *AdResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AdResponse) ProtoMessage() {} + +func (x *AdResponse) ProtoReflect() protoreflect.Message { + mi := &file_demo_proto_msgTypes[30] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AdResponse.ProtoReflect.Descriptor instead. +func (*AdResponse) Descriptor() ([]byte, []int) { + return file_demo_proto_rawDescGZIP(), []int{30} +} + +func (x *AdResponse) GetAds() []*Ad { + if x != nil { + return x.Ads + } + return nil +} + +type Ad struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // url to redirect to when an ad is clicked. + RedirectUrl string `protobuf:"bytes,1,opt,name=redirect_url,json=redirectUrl,proto3" json:"redirect_url,omitempty"` + // short advertisement text to display. + Text string `protobuf:"bytes,2,opt,name=text,proto3" json:"text,omitempty"` +} + +func (x *Ad) Reset() { + *x = Ad{} + if protoimpl.UnsafeEnabled { + mi := &file_demo_proto_msgTypes[31] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Ad) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Ad) ProtoMessage() {} + +func (x *Ad) ProtoReflect() protoreflect.Message { + mi := &file_demo_proto_msgTypes[31] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Ad.ProtoReflect.Descriptor instead. +func (*Ad) Descriptor() ([]byte, []int) { + return file_demo_proto_rawDescGZIP(), []int{31} +} + +func (x *Ad) GetRedirectUrl() string { + if x != nil { + return x.RedirectUrl + } + return "" +} + +func (x *Ad) GetText() string { + if x != nil { + return x.Text + } + return "" +} + +var File_demo_proto protoreflect.FileDescriptor + +var file_demo_proto_rawDesc = []byte{ + 0x0a, 0x0a, 0x64, 0x65, 0x6d, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0b, 0x68, 0x69, + 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x22, 0x45, 0x0a, 0x08, 0x43, 0x61, 0x72, + 0x74, 0x49, 0x74, 0x65, 0x6d, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, + 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x70, 0x72, 0x6f, 0x64, 0x75, + 0x63, 0x74, 0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x71, 0x75, 0x61, 0x6e, 0x74, 0x69, 0x74, 0x79, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x71, 0x75, 0x61, 0x6e, 0x74, 0x69, 0x74, 0x79, + 0x22, 0x54, 0x0a, 0x0e, 0x41, 0x64, 0x64, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x29, 0x0a, 0x04, 0x69, + 0x74, 0x65, 0x6d, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x68, 0x69, 0x70, 0x73, + 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x43, 0x61, 0x72, 0x74, 0x49, 0x74, 0x65, 0x6d, + 0x52, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x22, 0x2b, 0x0a, 0x10, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x43, + 0x61, 0x72, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, + 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, + 0x72, 0x49, 0x64, 0x22, 0x29, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x43, 0x61, 0x72, 0x74, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0x4c, + 0x0a, 0x04, 0x43, 0x61, 0x72, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, + 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, + 0x2b, 0x0a, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, + 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x43, 0x61, 0x72, + 0x74, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x22, 0x07, 0x0a, 0x05, + 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x56, 0x0a, 0x1a, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x63, + 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b, + 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, + 0x09, 0x52, 0x0a, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x49, 0x64, 0x73, 0x22, 0x3e, 0x0a, + 0x1b, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x64, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1f, 0x0a, 0x0b, + 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, + 0x09, 0x52, 0x0a, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x49, 0x64, 0x73, 0x22, 0xba, 0x01, + 0x0a, 0x07, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, + 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x20, 0x0a, + 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, + 0x18, 0x0a, 0x07, 0x70, 0x69, 0x63, 0x74, 0x75, 0x72, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x07, 0x70, 0x69, 0x63, 0x74, 0x75, 0x72, 0x65, 0x12, 0x2f, 0x0a, 0x09, 0x70, 0x72, 0x69, + 0x63, 0x65, 0x5f, 0x75, 0x73, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x68, + 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x4d, 0x6f, 0x6e, 0x65, 0x79, + 0x52, 0x08, 0x70, 0x72, 0x69, 0x63, 0x65, 0x55, 0x73, 0x64, 0x12, 0x1e, 0x0a, 0x0a, 0x63, 0x61, + 0x74, 0x65, 0x67, 0x6f, 0x72, 0x69, 0x65, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, + 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x69, 0x65, 0x73, 0x22, 0x48, 0x0a, 0x14, 0x4c, 0x69, + 0x73, 0x74, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x30, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x73, 0x18, 0x01, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, + 0x6f, 0x70, 0x2e, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x64, + 0x75, 0x63, 0x74, 0x73, 0x22, 0x23, 0x0a, 0x11, 0x47, 0x65, 0x74, 0x50, 0x72, 0x6f, 0x64, 0x75, + 0x63, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x22, 0x2d, 0x0a, 0x15, 0x53, 0x65, 0x61, + 0x72, 0x63, 0x68, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x22, 0x48, 0x0a, 0x16, 0x53, 0x65, 0x61, 0x72, + 0x63, 0x68, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x2e, 0x0a, 0x07, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x18, 0x01, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, + 0x70, 0x2e, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x52, 0x07, 0x72, 0x65, 0x73, 0x75, 0x6c, + 0x74, 0x73, 0x22, 0x6e, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x51, 0x75, 0x6f, 0x74, 0x65, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, + 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, 0x07, 0x61, 0x64, + 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x2b, 0x0a, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x18, 0x02, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, + 0x6f, 0x70, 0x2e, 0x43, 0x61, 0x72, 0x74, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x05, 0x69, 0x74, 0x65, + 0x6d, 0x73, 0x22, 0x41, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x51, 0x75, 0x6f, 0x74, 0x65, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2d, 0x0a, 0x08, 0x63, 0x6f, 0x73, 0x74, 0x5f, 0x75, + 0x73, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, + 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x4d, 0x6f, 0x6e, 0x65, 0x79, 0x52, 0x07, 0x63, 0x6f, + 0x73, 0x74, 0x55, 0x73, 0x64, 0x22, 0x6f, 0x0a, 0x10, 0x53, 0x68, 0x69, 0x70, 0x4f, 0x72, 0x64, + 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x07, 0x61, 0x64, 0x64, + 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x68, 0x69, 0x70, + 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, + 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x2b, 0x0a, 0x05, 0x69, 0x74, 0x65, + 0x6d, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, + 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x43, 0x61, 0x72, 0x74, 0x49, 0x74, 0x65, 0x6d, 0x52, + 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x22, 0x34, 0x0a, 0x11, 0x53, 0x68, 0x69, 0x70, 0x4f, 0x72, + 0x64, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x74, + 0x72, 0x61, 0x63, 0x6b, 0x69, 0x6e, 0x67, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0a, 0x74, 0x72, 0x61, 0x63, 0x6b, 0x69, 0x6e, 0x67, 0x49, 0x64, 0x22, 0x8f, 0x01, 0x0a, + 0x07, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x25, 0x0a, 0x0e, 0x73, 0x74, 0x72, 0x65, + 0x65, 0x74, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0d, 0x73, 0x74, 0x72, 0x65, 0x65, 0x74, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, + 0x12, 0x0a, 0x04, 0x63, 0x69, 0x74, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x63, + 0x69, 0x74, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x75, + 0x6e, 0x74, 0x72, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x6f, 0x75, 0x6e, + 0x74, 0x72, 0x79, 0x12, 0x19, 0x0a, 0x08, 0x7a, 0x69, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, + 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x07, 0x7a, 0x69, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x22, 0x58, + 0x0a, 0x05, 0x4d, 0x6f, 0x6e, 0x65, 0x79, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x75, 0x72, 0x72, 0x65, + 0x6e, 0x63, 0x79, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, + 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x14, 0x0a, 0x05, + 0x75, 0x6e, 0x69, 0x74, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x75, 0x6e, 0x69, + 0x74, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x6e, 0x61, 0x6e, 0x6f, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x05, 0x52, 0x05, 0x6e, 0x61, 0x6e, 0x6f, 0x73, 0x22, 0x47, 0x0a, 0x1e, 0x47, 0x65, 0x74, 0x53, + 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x69, + 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x63, 0x75, + 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, + 0x28, 0x09, 0x52, 0x0d, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x43, 0x6f, 0x64, 0x65, + 0x73, 0x22, 0x5c, 0x0a, 0x19, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x43, 0x6f, 0x6e, + 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x26, + 0x0a, 0x04, 0x66, 0x72, 0x6f, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x68, + 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x4d, 0x6f, 0x6e, 0x65, 0x79, + 0x52, 0x04, 0x66, 0x72, 0x6f, 0x6d, 0x12, 0x17, 0x0a, 0x07, 0x74, 0x6f, 0x5f, 0x63, 0x6f, 0x64, + 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x74, 0x6f, 0x43, 0x6f, 0x64, 0x65, 0x22, + 0xe6, 0x01, 0x0a, 0x0e, 0x43, 0x72, 0x65, 0x64, 0x69, 0x74, 0x43, 0x61, 0x72, 0x64, 0x49, 0x6e, + 0x66, 0x6f, 0x12, 0x2c, 0x0a, 0x12, 0x63, 0x72, 0x65, 0x64, 0x69, 0x74, 0x5f, 0x63, 0x61, 0x72, + 0x64, 0x5f, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, + 0x63, 0x72, 0x65, 0x64, 0x69, 0x74, 0x43, 0x61, 0x72, 0x64, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, + 0x12, 0x26, 0x0a, 0x0f, 0x63, 0x72, 0x65, 0x64, 0x69, 0x74, 0x5f, 0x63, 0x61, 0x72, 0x64, 0x5f, + 0x63, 0x76, 0x76, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0d, 0x63, 0x72, 0x65, 0x64, 0x69, + 0x74, 0x43, 0x61, 0x72, 0x64, 0x43, 0x76, 0x76, 0x12, 0x3d, 0x0a, 0x1b, 0x63, 0x72, 0x65, 0x64, + 0x69, 0x74, 0x5f, 0x63, 0x61, 0x72, 0x64, 0x5f, 0x65, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x5f, 0x79, 0x65, 0x61, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x18, 0x63, + 0x72, 0x65, 0x64, 0x69, 0x74, 0x43, 0x61, 0x72, 0x64, 0x45, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x59, 0x65, 0x61, 0x72, 0x12, 0x3f, 0x0a, 0x1c, 0x63, 0x72, 0x65, 0x64, 0x69, + 0x74, 0x5f, 0x63, 0x61, 0x72, 0x64, 0x5f, 0x65, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x5f, 0x6d, 0x6f, 0x6e, 0x74, 0x68, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x19, 0x63, + 0x72, 0x65, 0x64, 0x69, 0x74, 0x43, 0x61, 0x72, 0x64, 0x45, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x4d, 0x6f, 0x6e, 0x74, 0x68, 0x22, 0x79, 0x0a, 0x0d, 0x43, 0x68, 0x61, 0x72, + 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2a, 0x0a, 0x06, 0x61, 0x6d, 0x6f, + 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x68, 0x69, 0x70, 0x73, + 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x4d, 0x6f, 0x6e, 0x65, 0x79, 0x52, 0x06, 0x61, + 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x3c, 0x0a, 0x0b, 0x63, 0x72, 0x65, 0x64, 0x69, 0x74, 0x5f, + 0x63, 0x61, 0x72, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x68, 0x69, 0x70, + 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x43, 0x72, 0x65, 0x64, 0x69, 0x74, 0x43, + 0x61, 0x72, 0x64, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x0a, 0x63, 0x72, 0x65, 0x64, 0x69, 0x74, 0x43, + 0x61, 0x72, 0x64, 0x22, 0x37, 0x0a, 0x0e, 0x43, 0x68, 0x61, 0x72, 0x67, 0x65, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, + 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x74, + 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x22, 0x5e, 0x0a, 0x09, + 0x4f, 0x72, 0x64, 0x65, 0x72, 0x49, 0x74, 0x65, 0x6d, 0x12, 0x29, 0x0a, 0x04, 0x69, 0x74, 0x65, + 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, + 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x43, 0x61, 0x72, 0x74, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x04, + 0x69, 0x74, 0x65, 0x6d, 0x12, 0x26, 0x0a, 0x04, 0x63, 0x6f, 0x73, 0x74, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, + 0x2e, 0x4d, 0x6f, 0x6e, 0x65, 0x79, 0x52, 0x04, 0x63, 0x6f, 0x73, 0x74, 0x22, 0x82, 0x02, 0x0a, + 0x0b, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x19, 0x0a, 0x08, + 0x6f, 0x72, 0x64, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, + 0x6f, 0x72, 0x64, 0x65, 0x72, 0x49, 0x64, 0x12, 0x30, 0x0a, 0x14, 0x73, 0x68, 0x69, 0x70, 0x70, + 0x69, 0x6e, 0x67, 0x5f, 0x74, 0x72, 0x61, 0x63, 0x6b, 0x69, 0x6e, 0x67, 0x5f, 0x69, 0x64, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x12, 0x73, 0x68, 0x69, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x54, + 0x72, 0x61, 0x63, 0x6b, 0x69, 0x6e, 0x67, 0x49, 0x64, 0x12, 0x37, 0x0a, 0x0d, 0x73, 0x68, 0x69, + 0x70, 0x70, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x6f, 0x73, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x12, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x4d, + 0x6f, 0x6e, 0x65, 0x79, 0x52, 0x0c, 0x73, 0x68, 0x69, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x43, 0x6f, + 0x73, 0x74, 0x12, 0x3f, 0x0a, 0x10, 0x73, 0x68, 0x69, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x5f, 0x61, + 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x68, + 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x41, 0x64, 0x64, 0x72, 0x65, + 0x73, 0x73, 0x52, 0x0f, 0x73, 0x68, 0x69, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x41, 0x64, 0x64, 0x72, + 0x65, 0x73, 0x73, 0x12, 0x2c, 0x0a, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x18, 0x05, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, + 0x2e, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d, + 0x73, 0x22, 0x64, 0x0a, 0x1c, 0x53, 0x65, 0x6e, 0x64, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x43, 0x6f, + 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x12, 0x2e, 0x0a, 0x05, 0x6f, 0x72, 0x64, 0x65, 0x72, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, + 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, + 0x52, 0x05, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x22, 0xd5, 0x01, 0x0a, 0x11, 0x50, 0x6c, 0x61, 0x63, + 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x17, 0x0a, + 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, + 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x63, + 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x75, + 0x73, 0x65, 0x72, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x12, 0x2e, 0x0a, 0x07, 0x61, + 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x68, + 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x41, 0x64, 0x64, 0x72, 0x65, + 0x73, 0x73, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x65, + 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x6d, 0x61, 0x69, + 0x6c, 0x12, 0x3c, 0x0a, 0x0b, 0x63, 0x72, 0x65, 0x64, 0x69, 0x74, 0x5f, 0x63, 0x61, 0x72, 0x64, + 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, + 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x43, 0x72, 0x65, 0x64, 0x69, 0x74, 0x43, 0x61, 0x72, 0x64, 0x49, + 0x6e, 0x66, 0x6f, 0x52, 0x0a, 0x63, 0x72, 0x65, 0x64, 0x69, 0x74, 0x43, 0x61, 0x72, 0x64, 0x22, + 0x44, 0x0a, 0x12, 0x50, 0x6c, 0x61, 0x63, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2e, 0x0a, 0x05, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, + 0x6f, 0x70, 0x2e, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x05, + 0x6f, 0x72, 0x64, 0x65, 0x72, 0x22, 0x2e, 0x0a, 0x09, 0x41, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x5f, 0x6b, 0x65, + 0x79, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, + 0x74, 0x4b, 0x65, 0x79, 0x73, 0x22, 0x2f, 0x0a, 0x0a, 0x41, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x21, 0x0a, 0x03, 0x61, 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x0f, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x41, + 0x64, 0x52, 0x03, 0x61, 0x64, 0x73, 0x22, 0x3b, 0x0a, 0x02, 0x41, 0x64, 0x12, 0x21, 0x0a, 0x0c, + 0x72, 0x65, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0b, 0x72, 0x65, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x55, 0x72, 0x6c, 0x12, + 0x12, 0x0a, 0x04, 0x74, 0x65, 0x78, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, + 0x65, 0x78, 0x74, 0x32, 0xca, 0x01, 0x0a, 0x0b, 0x43, 0x61, 0x72, 0x74, 0x53, 0x65, 0x72, 0x76, + 0x69, 0x63, 0x65, 0x12, 0x3c, 0x0a, 0x07, 0x41, 0x64, 0x64, 0x49, 0x74, 0x65, 0x6d, 0x12, 0x1b, + 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x41, 0x64, 0x64, + 0x49, 0x74, 0x65, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x68, 0x69, + 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, + 0x00, 0x12, 0x3b, 0x0a, 0x07, 0x47, 0x65, 0x74, 0x43, 0x61, 0x72, 0x74, 0x12, 0x1b, 0x2e, 0x68, + 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x61, + 0x72, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x11, 0x2e, 0x68, 0x69, 0x70, 0x73, + 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x43, 0x61, 0x72, 0x74, 0x22, 0x00, 0x12, 0x40, + 0x0a, 0x09, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x43, 0x61, 0x72, 0x74, 0x12, 0x1d, 0x2e, 0x68, 0x69, + 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x43, + 0x61, 0x72, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x68, 0x69, 0x70, + 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, + 0x32, 0x83, 0x01, 0x0a, 0x15, 0x52, 0x65, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x64, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x6a, 0x0a, 0x13, 0x4c, 0x69, + 0x73, 0x74, 0x52, 0x65, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x73, 0x12, 0x27, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, + 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x64, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, 0x68, 0x69, 0x70, + 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x63, + 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x32, 0x83, 0x02, 0x0a, 0x15, 0x50, 0x72, 0x6f, 0x64, 0x75, + 0x63, 0x74, 0x43, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, + 0x12, 0x47, 0x0a, 0x0c, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x73, + 0x12, 0x12, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x45, + 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x21, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, + 0x6f, 0x70, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x73, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x44, 0x0a, 0x0a, 0x47, 0x65, 0x74, + 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x12, 0x1e, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, + 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x47, 0x65, 0x74, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, + 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x22, 0x00, 0x12, + 0x5b, 0x0a, 0x0e, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, + 0x73, 0x12, 0x22, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, + 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x73, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, + 0x68, 0x6f, 0x70, 0x2e, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, + 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x32, 0xaa, 0x01, 0x0a, + 0x0f, 0x53, 0x68, 0x69, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, + 0x12, 0x49, 0x0a, 0x08, 0x47, 0x65, 0x74, 0x51, 0x75, 0x6f, 0x74, 0x65, 0x12, 0x1c, 0x2e, 0x68, + 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x47, 0x65, 0x74, 0x51, 0x75, + 0x6f, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x68, 0x69, 0x70, + 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x47, 0x65, 0x74, 0x51, 0x75, 0x6f, 0x74, + 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x4c, 0x0a, 0x09, 0x53, + 0x68, 0x69, 0x70, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x1d, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, + 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x53, 0x68, 0x69, 0x70, 0x4f, 0x72, 0x64, 0x65, 0x72, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, + 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x53, 0x68, 0x69, 0x70, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x32, 0xb7, 0x01, 0x0a, 0x0f, 0x43, 0x75, + 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x5b, 0x0a, + 0x16, 0x47, 0x65, 0x74, 0x53, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x43, 0x75, 0x72, + 0x72, 0x65, 0x6e, 0x63, 0x69, 0x65, 0x73, 0x12, 0x12, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, + 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x2b, 0x2e, 0x68, 0x69, + 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x75, 0x70, + 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x69, 0x65, 0x73, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x47, 0x0a, 0x07, 0x43, 0x6f, + 0x6e, 0x76, 0x65, 0x72, 0x74, 0x12, 0x26, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, + 0x68, 0x6f, 0x70, 0x2e, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x43, 0x6f, 0x6e, 0x76, + 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, + 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x4d, 0x6f, 0x6e, 0x65, + 0x79, 0x22, 0x00, 0x32, 0x55, 0x0a, 0x0e, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x65, + 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x43, 0x0a, 0x06, 0x43, 0x68, 0x61, 0x72, 0x67, 0x65, 0x12, + 0x1a, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x43, 0x68, + 0x61, 0x72, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x68, 0x69, + 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x43, 0x68, 0x61, 0x72, 0x67, 0x65, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x32, 0x68, 0x0a, 0x0c, 0x45, 0x6d, + 0x61, 0x69, 0x6c, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x58, 0x0a, 0x15, 0x53, 0x65, + 0x6e, 0x64, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x12, 0x29, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, + 0x70, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, + 0x72, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, + 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x45, 0x6d, 0x70, + 0x74, 0x79, 0x22, 0x00, 0x32, 0x62, 0x0a, 0x0f, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x6f, 0x75, 0x74, + 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x4f, 0x0a, 0x0a, 0x50, 0x6c, 0x61, 0x63, 0x65, + 0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x1e, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, + 0x68, 0x6f, 0x70, 0x2e, 0x50, 0x6c, 0x61, 0x63, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, + 0x68, 0x6f, 0x70, 0x2e, 0x50, 0x6c, 0x61, 0x63, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x32, 0x48, 0x0a, 0x09, 0x41, 0x64, 0x53, 0x65, + 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x3b, 0x0a, 0x06, 0x47, 0x65, 0x74, 0x41, 0x64, 0x73, 0x12, + 0x16, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x41, 0x64, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, + 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x41, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x22, 0x00, 0x42, 0x40, 0x5a, 0x3e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, + 0x2f, 0x47, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x43, 0x6c, 0x6f, 0x75, 0x64, 0x50, 0x6c, 0x61, 0x74, + 0x66, 0x6f, 0x72, 0x6d, 0x2f, 0x6d, 0x69, 0x73, 0x63, 0x72, 0x6f, 0x73, 0x65, 0x72, 0x76, 0x69, + 0x63, 0x65, 0x73, 0x2d, 0x64, 0x65, 0x6d, 0x6f, 0x2f, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, + 0x73, 0x68, 0x6f, 0x70, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_demo_proto_rawDescOnce sync.Once + file_demo_proto_rawDescData = file_demo_proto_rawDesc +) + +func file_demo_proto_rawDescGZIP() []byte { + file_demo_proto_rawDescOnce.Do(func() { + file_demo_proto_rawDescData = protoimpl.X.CompressGZIP(file_demo_proto_rawDescData) + }) + return file_demo_proto_rawDescData +} + +var file_demo_proto_msgTypes = make([]protoimpl.MessageInfo, 32) +var file_demo_proto_goTypes = []any{ + (*CartItem)(nil), // 0: hipstershop.CartItem + (*AddItemRequest)(nil), // 1: hipstershop.AddItemRequest + (*EmptyCartRequest)(nil), // 2: hipstershop.EmptyCartRequest + (*GetCartRequest)(nil), // 3: hipstershop.GetCartRequest + (*Cart)(nil), // 4: hipstershop.Cart + (*Empty)(nil), // 5: hipstershop.Empty + (*ListRecommendationsRequest)(nil), // 6: hipstershop.ListRecommendationsRequest + (*ListRecommendationsResponse)(nil), // 7: hipstershop.ListRecommendationsResponse + (*Product)(nil), // 8: hipstershop.Product + (*ListProductsResponse)(nil), // 9: hipstershop.ListProductsResponse + (*GetProductRequest)(nil), // 10: hipstershop.GetProductRequest + (*SearchProductsRequest)(nil), // 11: hipstershop.SearchProductsRequest + (*SearchProductsResponse)(nil), // 12: hipstershop.SearchProductsResponse + (*GetQuoteRequest)(nil), // 13: hipstershop.GetQuoteRequest + (*GetQuoteResponse)(nil), // 14: hipstershop.GetQuoteResponse + (*ShipOrderRequest)(nil), // 15: hipstershop.ShipOrderRequest + (*ShipOrderResponse)(nil), // 16: hipstershop.ShipOrderResponse + (*Address)(nil), // 17: hipstershop.Address + (*Money)(nil), // 18: hipstershop.Money + (*GetSupportedCurrenciesResponse)(nil), // 19: hipstershop.GetSupportedCurrenciesResponse + (*CurrencyConversionRequest)(nil), // 20: hipstershop.CurrencyConversionRequest + (*CreditCardInfo)(nil), // 21: hipstershop.CreditCardInfo + (*ChargeRequest)(nil), // 22: hipstershop.ChargeRequest + (*ChargeResponse)(nil), // 23: hipstershop.ChargeResponse + (*OrderItem)(nil), // 24: hipstershop.OrderItem + (*OrderResult)(nil), // 25: hipstershop.OrderResult + (*SendOrderConfirmationRequest)(nil), // 26: hipstershop.SendOrderConfirmationRequest + (*PlaceOrderRequest)(nil), // 27: hipstershop.PlaceOrderRequest + (*PlaceOrderResponse)(nil), // 28: hipstershop.PlaceOrderResponse + (*AdRequest)(nil), // 29: hipstershop.AdRequest + (*AdResponse)(nil), // 30: hipstershop.AdResponse + (*Ad)(nil), // 31: hipstershop.Ad +} +var file_demo_proto_depIdxs = []int32{ + 0, // 0: hipstershop.AddItemRequest.item:type_name -> hipstershop.CartItem + 0, // 1: hipstershop.Cart.items:type_name -> hipstershop.CartItem + 18, // 2: hipstershop.Product.price_usd:type_name -> hipstershop.Money + 8, // 3: hipstershop.ListProductsResponse.products:type_name -> hipstershop.Product + 8, // 4: hipstershop.SearchProductsResponse.results:type_name -> hipstershop.Product + 17, // 5: hipstershop.GetQuoteRequest.address:type_name -> hipstershop.Address + 0, // 6: hipstershop.GetQuoteRequest.items:type_name -> hipstershop.CartItem + 18, // 7: hipstershop.GetQuoteResponse.cost_usd:type_name -> hipstershop.Money + 17, // 8: hipstershop.ShipOrderRequest.address:type_name -> hipstershop.Address + 0, // 9: hipstershop.ShipOrderRequest.items:type_name -> hipstershop.CartItem + 18, // 10: hipstershop.CurrencyConversionRequest.from:type_name -> hipstershop.Money + 18, // 11: hipstershop.ChargeRequest.amount:type_name -> hipstershop.Money + 21, // 12: hipstershop.ChargeRequest.credit_card:type_name -> hipstershop.CreditCardInfo + 0, // 13: hipstershop.OrderItem.item:type_name -> hipstershop.CartItem + 18, // 14: hipstershop.OrderItem.cost:type_name -> hipstershop.Money + 18, // 15: hipstershop.OrderResult.shipping_cost:type_name -> hipstershop.Money + 17, // 16: hipstershop.OrderResult.shipping_address:type_name -> hipstershop.Address + 24, // 17: hipstershop.OrderResult.items:type_name -> hipstershop.OrderItem + 25, // 18: hipstershop.SendOrderConfirmationRequest.order:type_name -> hipstershop.OrderResult + 17, // 19: hipstershop.PlaceOrderRequest.address:type_name -> hipstershop.Address + 21, // 20: hipstershop.PlaceOrderRequest.credit_card:type_name -> hipstershop.CreditCardInfo + 25, // 21: hipstershop.PlaceOrderResponse.order:type_name -> hipstershop.OrderResult + 31, // 22: hipstershop.AdResponse.ads:type_name -> hipstershop.Ad + 1, // 23: hipstershop.CartService.AddItem:input_type -> hipstershop.AddItemRequest + 3, // 24: hipstershop.CartService.GetCart:input_type -> hipstershop.GetCartRequest + 2, // 25: hipstershop.CartService.EmptyCart:input_type -> hipstershop.EmptyCartRequest + 6, // 26: hipstershop.RecommendationService.ListRecommendations:input_type -> hipstershop.ListRecommendationsRequest + 5, // 27: hipstershop.ProductCatalogService.ListProducts:input_type -> hipstershop.Empty + 10, // 28: hipstershop.ProductCatalogService.GetProduct:input_type -> hipstershop.GetProductRequest + 11, // 29: hipstershop.ProductCatalogService.SearchProducts:input_type -> hipstershop.SearchProductsRequest + 13, // 30: hipstershop.ShippingService.GetQuote:input_type -> hipstershop.GetQuoteRequest + 15, // 31: hipstershop.ShippingService.ShipOrder:input_type -> hipstershop.ShipOrderRequest + 5, // 32: hipstershop.CurrencyService.GetSupportedCurrencies:input_type -> hipstershop.Empty + 20, // 33: hipstershop.CurrencyService.Convert:input_type -> hipstershop.CurrencyConversionRequest + 22, // 34: hipstershop.PaymentService.Charge:input_type -> hipstershop.ChargeRequest + 26, // 35: hipstershop.EmailService.SendOrderConfirmation:input_type -> hipstershop.SendOrderConfirmationRequest + 27, // 36: hipstershop.CheckoutService.PlaceOrder:input_type -> hipstershop.PlaceOrderRequest + 29, // 37: hipstershop.AdService.GetAds:input_type -> hipstershop.AdRequest + 5, // 38: hipstershop.CartService.AddItem:output_type -> hipstershop.Empty + 4, // 39: hipstershop.CartService.GetCart:output_type -> hipstershop.Cart + 5, // 40: hipstershop.CartService.EmptyCart:output_type -> hipstershop.Empty + 7, // 41: hipstershop.RecommendationService.ListRecommendations:output_type -> hipstershop.ListRecommendationsResponse + 9, // 42: hipstershop.ProductCatalogService.ListProducts:output_type -> hipstershop.ListProductsResponse + 8, // 43: hipstershop.ProductCatalogService.GetProduct:output_type -> hipstershop.Product + 12, // 44: hipstershop.ProductCatalogService.SearchProducts:output_type -> hipstershop.SearchProductsResponse + 14, // 45: hipstershop.ShippingService.GetQuote:output_type -> hipstershop.GetQuoteResponse + 16, // 46: hipstershop.ShippingService.ShipOrder:output_type -> hipstershop.ShipOrderResponse + 19, // 47: hipstershop.CurrencyService.GetSupportedCurrencies:output_type -> hipstershop.GetSupportedCurrenciesResponse + 18, // 48: hipstershop.CurrencyService.Convert:output_type -> hipstershop.Money + 23, // 49: hipstershop.PaymentService.Charge:output_type -> hipstershop.ChargeResponse + 5, // 50: hipstershop.EmailService.SendOrderConfirmation:output_type -> hipstershop.Empty + 28, // 51: hipstershop.CheckoutService.PlaceOrder:output_type -> hipstershop.PlaceOrderResponse + 30, // 52: hipstershop.AdService.GetAds:output_type -> hipstershop.AdResponse + 38, // [38:53] is the sub-list for method output_type + 23, // [23:38] is the sub-list for method input_type + 23, // [23:23] is the sub-list for extension type_name + 23, // [23:23] is the sub-list for extension extendee + 0, // [0:23] is the sub-list for field type_name +} + +func init() { file_demo_proto_init() } +func file_demo_proto_init() { + if File_demo_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_demo_proto_msgTypes[0].Exporter = func(v any, i int) any { + switch v := v.(*CartItem); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_demo_proto_msgTypes[1].Exporter = func(v any, i int) any { + switch v := v.(*AddItemRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_demo_proto_msgTypes[2].Exporter = func(v any, i int) any { + switch v := v.(*EmptyCartRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_demo_proto_msgTypes[3].Exporter = func(v any, i int) any { + switch v := v.(*GetCartRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_demo_proto_msgTypes[4].Exporter = func(v any, i int) any { + switch v := v.(*Cart); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_demo_proto_msgTypes[5].Exporter = func(v any, i int) any { + switch v := v.(*Empty); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_demo_proto_msgTypes[6].Exporter = func(v any, i int) any { + switch v := v.(*ListRecommendationsRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_demo_proto_msgTypes[7].Exporter = func(v any, i int) any { + switch v := v.(*ListRecommendationsResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_demo_proto_msgTypes[8].Exporter = func(v any, i int) any { + switch v := v.(*Product); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_demo_proto_msgTypes[9].Exporter = func(v any, i int) any { + switch v := v.(*ListProductsResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_demo_proto_msgTypes[10].Exporter = func(v any, i int) any { + switch v := v.(*GetProductRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_demo_proto_msgTypes[11].Exporter = func(v any, i int) any { + switch v := v.(*SearchProductsRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_demo_proto_msgTypes[12].Exporter = func(v any, i int) any { + switch v := v.(*SearchProductsResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_demo_proto_msgTypes[13].Exporter = func(v any, i int) any { + switch v := v.(*GetQuoteRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_demo_proto_msgTypes[14].Exporter = func(v any, i int) any { + switch v := v.(*GetQuoteResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_demo_proto_msgTypes[15].Exporter = func(v any, i int) any { + switch v := v.(*ShipOrderRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_demo_proto_msgTypes[16].Exporter = func(v any, i int) any { + switch v := v.(*ShipOrderResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_demo_proto_msgTypes[17].Exporter = func(v any, i int) any { + switch v := v.(*Address); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_demo_proto_msgTypes[18].Exporter = func(v any, i int) any { + switch v := v.(*Money); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_demo_proto_msgTypes[19].Exporter = func(v any, i int) any { + switch v := v.(*GetSupportedCurrenciesResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_demo_proto_msgTypes[20].Exporter = func(v any, i int) any { + switch v := v.(*CurrencyConversionRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_demo_proto_msgTypes[21].Exporter = func(v any, i int) any { + switch v := v.(*CreditCardInfo); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_demo_proto_msgTypes[22].Exporter = func(v any, i int) any { + switch v := v.(*ChargeRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_demo_proto_msgTypes[23].Exporter = func(v any, i int) any { + switch v := v.(*ChargeResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_demo_proto_msgTypes[24].Exporter = func(v any, i int) any { + switch v := v.(*OrderItem); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_demo_proto_msgTypes[25].Exporter = func(v any, i int) any { + switch v := v.(*OrderResult); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_demo_proto_msgTypes[26].Exporter = func(v any, i int) any { + switch v := v.(*SendOrderConfirmationRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_demo_proto_msgTypes[27].Exporter = func(v any, i int) any { + switch v := v.(*PlaceOrderRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_demo_proto_msgTypes[28].Exporter = func(v any, i int) any { + switch v := v.(*PlaceOrderResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_demo_proto_msgTypes[29].Exporter = func(v any, i int) any { + switch v := v.(*AdRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_demo_proto_msgTypes[30].Exporter = func(v any, i int) any { + switch v := v.(*AdResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_demo_proto_msgTypes[31].Exporter = func(v any, i int) any { + switch v := v.(*Ad); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_demo_proto_rawDesc, + NumEnums: 0, + NumMessages: 32, + NumExtensions: 0, + NumServices: 9, + }, + GoTypes: file_demo_proto_goTypes, + DependencyIndexes: file_demo_proto_depIdxs, + MessageInfos: file_demo_proto_msgTypes, + }.Build() + File_demo_proto = out.File + file_demo_proto_rawDesc = nil + file_demo_proto_goTypes = nil + file_demo_proto_depIdxs = nil +} diff --git a/src/checkoutservice/genproto/demo_grpc.pb.go b/src/checkoutservice/genproto/demo_grpc.pb.go new file mode 100644 index 0000000..e99e6d6 --- /dev/null +++ b/src/checkoutservice/genproto/demo_grpc.pb.go @@ -0,0 +1,1179 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.5.1 +// - protoc v3.6.1 +// source: demo.proto + +package hipstershop + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.64.0 or later. +const _ = grpc.SupportPackageIsVersion9 + +const ( + CartService_AddItem_FullMethodName = "/hipstershop.CartService/AddItem" + CartService_GetCart_FullMethodName = "/hipstershop.CartService/GetCart" + CartService_EmptyCart_FullMethodName = "/hipstershop.CartService/EmptyCart" +) + +// CartServiceClient is the client API for CartService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type CartServiceClient interface { + AddItem(ctx context.Context, in *AddItemRequest, opts ...grpc.CallOption) (*Empty, error) + GetCart(ctx context.Context, in *GetCartRequest, opts ...grpc.CallOption) (*Cart, error) + EmptyCart(ctx context.Context, in *EmptyCartRequest, opts ...grpc.CallOption) (*Empty, error) +} + +type cartServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewCartServiceClient(cc grpc.ClientConnInterface) CartServiceClient { + return &cartServiceClient{cc} +} + +func (c *cartServiceClient) AddItem(ctx context.Context, in *AddItemRequest, opts ...grpc.CallOption) (*Empty, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(Empty) + err := c.cc.Invoke(ctx, CartService_AddItem_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *cartServiceClient) GetCart(ctx context.Context, in *GetCartRequest, opts ...grpc.CallOption) (*Cart, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(Cart) + err := c.cc.Invoke(ctx, CartService_GetCart_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *cartServiceClient) EmptyCart(ctx context.Context, in *EmptyCartRequest, opts ...grpc.CallOption) (*Empty, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(Empty) + err := c.cc.Invoke(ctx, CartService_EmptyCart_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +// CartServiceServer is the server API for CartService service. +// All implementations must embed UnimplementedCartServiceServer +// for forward compatibility. +type CartServiceServer interface { + AddItem(context.Context, *AddItemRequest) (*Empty, error) + GetCart(context.Context, *GetCartRequest) (*Cart, error) + EmptyCart(context.Context, *EmptyCartRequest) (*Empty, error) + mustEmbedUnimplementedCartServiceServer() +} + +// UnimplementedCartServiceServer must be embedded to have +// forward compatible implementations. +// +// NOTE: this should be embedded by value instead of pointer to avoid a nil +// pointer dereference when methods are called. +type UnimplementedCartServiceServer struct{} + +func (UnimplementedCartServiceServer) AddItem(context.Context, *AddItemRequest) (*Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method AddItem not implemented") +} +func (UnimplementedCartServiceServer) GetCart(context.Context, *GetCartRequest) (*Cart, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetCart not implemented") +} +func (UnimplementedCartServiceServer) EmptyCart(context.Context, *EmptyCartRequest) (*Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method EmptyCart not implemented") +} +func (UnimplementedCartServiceServer) mustEmbedUnimplementedCartServiceServer() {} +func (UnimplementedCartServiceServer) testEmbeddedByValue() {} + +// UnsafeCartServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to CartServiceServer will +// result in compilation errors. +type UnsafeCartServiceServer interface { + mustEmbedUnimplementedCartServiceServer() +} + +func RegisterCartServiceServer(s grpc.ServiceRegistrar, srv CartServiceServer) { + // If the following call pancis, it indicates UnimplementedCartServiceServer was + // embedded by pointer and is nil. This will cause panics if an + // unimplemented method is ever invoked, so we test this at initialization + // time to prevent it from happening at runtime later due to I/O. + if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { + t.testEmbeddedByValue() + } + s.RegisterService(&CartService_ServiceDesc, srv) +} + +func _CartService_AddItem_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(AddItemRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(CartServiceServer).AddItem(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: CartService_AddItem_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(CartServiceServer).AddItem(ctx, req.(*AddItemRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _CartService_GetCart_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetCartRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(CartServiceServer).GetCart(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: CartService_GetCart_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(CartServiceServer).GetCart(ctx, req.(*GetCartRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _CartService_EmptyCart_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(EmptyCartRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(CartServiceServer).EmptyCart(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: CartService_EmptyCart_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(CartServiceServer).EmptyCart(ctx, req.(*EmptyCartRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// CartService_ServiceDesc is the grpc.ServiceDesc for CartService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var CartService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "hipstershop.CartService", + HandlerType: (*CartServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "AddItem", + Handler: _CartService_AddItem_Handler, + }, + { + MethodName: "GetCart", + Handler: _CartService_GetCart_Handler, + }, + { + MethodName: "EmptyCart", + Handler: _CartService_EmptyCart_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "demo.proto", +} + +const ( + RecommendationService_ListRecommendations_FullMethodName = "/hipstershop.RecommendationService/ListRecommendations" +) + +// RecommendationServiceClient is the client API for RecommendationService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type RecommendationServiceClient interface { + ListRecommendations(ctx context.Context, in *ListRecommendationsRequest, opts ...grpc.CallOption) (*ListRecommendationsResponse, error) +} + +type recommendationServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewRecommendationServiceClient(cc grpc.ClientConnInterface) RecommendationServiceClient { + return &recommendationServiceClient{cc} +} + +func (c *recommendationServiceClient) ListRecommendations(ctx context.Context, in *ListRecommendationsRequest, opts ...grpc.CallOption) (*ListRecommendationsResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ListRecommendationsResponse) + err := c.cc.Invoke(ctx, RecommendationService_ListRecommendations_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +// RecommendationServiceServer is the server API for RecommendationService service. +// All implementations must embed UnimplementedRecommendationServiceServer +// for forward compatibility. +type RecommendationServiceServer interface { + ListRecommendations(context.Context, *ListRecommendationsRequest) (*ListRecommendationsResponse, error) + mustEmbedUnimplementedRecommendationServiceServer() +} + +// UnimplementedRecommendationServiceServer must be embedded to have +// forward compatible implementations. +// +// NOTE: this should be embedded by value instead of pointer to avoid a nil +// pointer dereference when methods are called. +type UnimplementedRecommendationServiceServer struct{} + +func (UnimplementedRecommendationServiceServer) ListRecommendations(context.Context, *ListRecommendationsRequest) (*ListRecommendationsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method ListRecommendations not implemented") +} +func (UnimplementedRecommendationServiceServer) mustEmbedUnimplementedRecommendationServiceServer() {} +func (UnimplementedRecommendationServiceServer) testEmbeddedByValue() {} + +// UnsafeRecommendationServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to RecommendationServiceServer will +// result in compilation errors. +type UnsafeRecommendationServiceServer interface { + mustEmbedUnimplementedRecommendationServiceServer() +} + +func RegisterRecommendationServiceServer(s grpc.ServiceRegistrar, srv RecommendationServiceServer) { + // If the following call pancis, it indicates UnimplementedRecommendationServiceServer was + // embedded by pointer and is nil. This will cause panics if an + // unimplemented method is ever invoked, so we test this at initialization + // time to prevent it from happening at runtime later due to I/O. + if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { + t.testEmbeddedByValue() + } + s.RegisterService(&RecommendationService_ServiceDesc, srv) +} + +func _RecommendationService_ListRecommendations_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ListRecommendationsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(RecommendationServiceServer).ListRecommendations(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: RecommendationService_ListRecommendations_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(RecommendationServiceServer).ListRecommendations(ctx, req.(*ListRecommendationsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// RecommendationService_ServiceDesc is the grpc.ServiceDesc for RecommendationService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var RecommendationService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "hipstershop.RecommendationService", + HandlerType: (*RecommendationServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "ListRecommendations", + Handler: _RecommendationService_ListRecommendations_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "demo.proto", +} + +const ( + ProductCatalogService_ListProducts_FullMethodName = "/hipstershop.ProductCatalogService/ListProducts" + ProductCatalogService_GetProduct_FullMethodName = "/hipstershop.ProductCatalogService/GetProduct" + ProductCatalogService_SearchProducts_FullMethodName = "/hipstershop.ProductCatalogService/SearchProducts" +) + +// ProductCatalogServiceClient is the client API for ProductCatalogService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type ProductCatalogServiceClient interface { + ListProducts(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*ListProductsResponse, error) + GetProduct(ctx context.Context, in *GetProductRequest, opts ...grpc.CallOption) (*Product, error) + SearchProducts(ctx context.Context, in *SearchProductsRequest, opts ...grpc.CallOption) (*SearchProductsResponse, error) +} + +type productCatalogServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewProductCatalogServiceClient(cc grpc.ClientConnInterface) ProductCatalogServiceClient { + return &productCatalogServiceClient{cc} +} + +func (c *productCatalogServiceClient) ListProducts(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*ListProductsResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ListProductsResponse) + err := c.cc.Invoke(ctx, ProductCatalogService_ListProducts_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *productCatalogServiceClient) GetProduct(ctx context.Context, in *GetProductRequest, opts ...grpc.CallOption) (*Product, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(Product) + err := c.cc.Invoke(ctx, ProductCatalogService_GetProduct_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *productCatalogServiceClient) SearchProducts(ctx context.Context, in *SearchProductsRequest, opts ...grpc.CallOption) (*SearchProductsResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(SearchProductsResponse) + err := c.cc.Invoke(ctx, ProductCatalogService_SearchProducts_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +// ProductCatalogServiceServer is the server API for ProductCatalogService service. +// All implementations must embed UnimplementedProductCatalogServiceServer +// for forward compatibility. +type ProductCatalogServiceServer interface { + ListProducts(context.Context, *Empty) (*ListProductsResponse, error) + GetProduct(context.Context, *GetProductRequest) (*Product, error) + SearchProducts(context.Context, *SearchProductsRequest) (*SearchProductsResponse, error) + mustEmbedUnimplementedProductCatalogServiceServer() +} + +// UnimplementedProductCatalogServiceServer must be embedded to have +// forward compatible implementations. +// +// NOTE: this should be embedded by value instead of pointer to avoid a nil +// pointer dereference when methods are called. +type UnimplementedProductCatalogServiceServer struct{} + +func (UnimplementedProductCatalogServiceServer) ListProducts(context.Context, *Empty) (*ListProductsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method ListProducts not implemented") +} +func (UnimplementedProductCatalogServiceServer) GetProduct(context.Context, *GetProductRequest) (*Product, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetProduct not implemented") +} +func (UnimplementedProductCatalogServiceServer) SearchProducts(context.Context, *SearchProductsRequest) (*SearchProductsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method SearchProducts not implemented") +} +func (UnimplementedProductCatalogServiceServer) mustEmbedUnimplementedProductCatalogServiceServer() {} +func (UnimplementedProductCatalogServiceServer) testEmbeddedByValue() {} + +// UnsafeProductCatalogServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to ProductCatalogServiceServer will +// result in compilation errors. +type UnsafeProductCatalogServiceServer interface { + mustEmbedUnimplementedProductCatalogServiceServer() +} + +func RegisterProductCatalogServiceServer(s grpc.ServiceRegistrar, srv ProductCatalogServiceServer) { + // If the following call pancis, it indicates UnimplementedProductCatalogServiceServer was + // embedded by pointer and is nil. This will cause panics if an + // unimplemented method is ever invoked, so we test this at initialization + // time to prevent it from happening at runtime later due to I/O. + if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { + t.testEmbeddedByValue() + } + s.RegisterService(&ProductCatalogService_ServiceDesc, srv) +} + +func _ProductCatalogService_ListProducts_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(Empty) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ProductCatalogServiceServer).ListProducts(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: ProductCatalogService_ListProducts_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ProductCatalogServiceServer).ListProducts(ctx, req.(*Empty)) + } + return interceptor(ctx, in, info, handler) +} + +func _ProductCatalogService_GetProduct_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetProductRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ProductCatalogServiceServer).GetProduct(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: ProductCatalogService_GetProduct_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ProductCatalogServiceServer).GetProduct(ctx, req.(*GetProductRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _ProductCatalogService_SearchProducts_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(SearchProductsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ProductCatalogServiceServer).SearchProducts(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: ProductCatalogService_SearchProducts_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ProductCatalogServiceServer).SearchProducts(ctx, req.(*SearchProductsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// ProductCatalogService_ServiceDesc is the grpc.ServiceDesc for ProductCatalogService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var ProductCatalogService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "hipstershop.ProductCatalogService", + HandlerType: (*ProductCatalogServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "ListProducts", + Handler: _ProductCatalogService_ListProducts_Handler, + }, + { + MethodName: "GetProduct", + Handler: _ProductCatalogService_GetProduct_Handler, + }, + { + MethodName: "SearchProducts", + Handler: _ProductCatalogService_SearchProducts_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "demo.proto", +} + +const ( + ShippingService_GetQuote_FullMethodName = "/hipstershop.ShippingService/GetQuote" + ShippingService_ShipOrder_FullMethodName = "/hipstershop.ShippingService/ShipOrder" +) + +// ShippingServiceClient is the client API for ShippingService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type ShippingServiceClient interface { + GetQuote(ctx context.Context, in *GetQuoteRequest, opts ...grpc.CallOption) (*GetQuoteResponse, error) + ShipOrder(ctx context.Context, in *ShipOrderRequest, opts ...grpc.CallOption) (*ShipOrderResponse, error) +} + +type shippingServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewShippingServiceClient(cc grpc.ClientConnInterface) ShippingServiceClient { + return &shippingServiceClient{cc} +} + +func (c *shippingServiceClient) GetQuote(ctx context.Context, in *GetQuoteRequest, opts ...grpc.CallOption) (*GetQuoteResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(GetQuoteResponse) + err := c.cc.Invoke(ctx, ShippingService_GetQuote_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *shippingServiceClient) ShipOrder(ctx context.Context, in *ShipOrderRequest, opts ...grpc.CallOption) (*ShipOrderResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ShipOrderResponse) + err := c.cc.Invoke(ctx, ShippingService_ShipOrder_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +// ShippingServiceServer is the server API for ShippingService service. +// All implementations must embed UnimplementedShippingServiceServer +// for forward compatibility. +type ShippingServiceServer interface { + GetQuote(context.Context, *GetQuoteRequest) (*GetQuoteResponse, error) + ShipOrder(context.Context, *ShipOrderRequest) (*ShipOrderResponse, error) + mustEmbedUnimplementedShippingServiceServer() +} + +// UnimplementedShippingServiceServer must be embedded to have +// forward compatible implementations. +// +// NOTE: this should be embedded by value instead of pointer to avoid a nil +// pointer dereference when methods are called. +type UnimplementedShippingServiceServer struct{} + +func (UnimplementedShippingServiceServer) GetQuote(context.Context, *GetQuoteRequest) (*GetQuoteResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetQuote not implemented") +} +func (UnimplementedShippingServiceServer) ShipOrder(context.Context, *ShipOrderRequest) (*ShipOrderResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method ShipOrder not implemented") +} +func (UnimplementedShippingServiceServer) mustEmbedUnimplementedShippingServiceServer() {} +func (UnimplementedShippingServiceServer) testEmbeddedByValue() {} + +// UnsafeShippingServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to ShippingServiceServer will +// result in compilation errors. +type UnsafeShippingServiceServer interface { + mustEmbedUnimplementedShippingServiceServer() +} + +func RegisterShippingServiceServer(s grpc.ServiceRegistrar, srv ShippingServiceServer) { + // If the following call pancis, it indicates UnimplementedShippingServiceServer was + // embedded by pointer and is nil. This will cause panics if an + // unimplemented method is ever invoked, so we test this at initialization + // time to prevent it from happening at runtime later due to I/O. + if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { + t.testEmbeddedByValue() + } + s.RegisterService(&ShippingService_ServiceDesc, srv) +} + +func _ShippingService_GetQuote_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetQuoteRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ShippingServiceServer).GetQuote(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: ShippingService_GetQuote_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ShippingServiceServer).GetQuote(ctx, req.(*GetQuoteRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _ShippingService_ShipOrder_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ShipOrderRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ShippingServiceServer).ShipOrder(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: ShippingService_ShipOrder_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ShippingServiceServer).ShipOrder(ctx, req.(*ShipOrderRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// ShippingService_ServiceDesc is the grpc.ServiceDesc for ShippingService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var ShippingService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "hipstershop.ShippingService", + HandlerType: (*ShippingServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "GetQuote", + Handler: _ShippingService_GetQuote_Handler, + }, + { + MethodName: "ShipOrder", + Handler: _ShippingService_ShipOrder_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "demo.proto", +} + +const ( + CurrencyService_GetSupportedCurrencies_FullMethodName = "/hipstershop.CurrencyService/GetSupportedCurrencies" + CurrencyService_Convert_FullMethodName = "/hipstershop.CurrencyService/Convert" +) + +// CurrencyServiceClient is the client API for CurrencyService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type CurrencyServiceClient interface { + GetSupportedCurrencies(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*GetSupportedCurrenciesResponse, error) + Convert(ctx context.Context, in *CurrencyConversionRequest, opts ...grpc.CallOption) (*Money, error) +} + +type currencyServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewCurrencyServiceClient(cc grpc.ClientConnInterface) CurrencyServiceClient { + return ¤cyServiceClient{cc} +} + +func (c *currencyServiceClient) GetSupportedCurrencies(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*GetSupportedCurrenciesResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(GetSupportedCurrenciesResponse) + err := c.cc.Invoke(ctx, CurrencyService_GetSupportedCurrencies_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *currencyServiceClient) Convert(ctx context.Context, in *CurrencyConversionRequest, opts ...grpc.CallOption) (*Money, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(Money) + err := c.cc.Invoke(ctx, CurrencyService_Convert_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +// CurrencyServiceServer is the server API for CurrencyService service. +// All implementations must embed UnimplementedCurrencyServiceServer +// for forward compatibility. +type CurrencyServiceServer interface { + GetSupportedCurrencies(context.Context, *Empty) (*GetSupportedCurrenciesResponse, error) + Convert(context.Context, *CurrencyConversionRequest) (*Money, error) + mustEmbedUnimplementedCurrencyServiceServer() +} + +// UnimplementedCurrencyServiceServer must be embedded to have +// forward compatible implementations. +// +// NOTE: this should be embedded by value instead of pointer to avoid a nil +// pointer dereference when methods are called. +type UnimplementedCurrencyServiceServer struct{} + +func (UnimplementedCurrencyServiceServer) GetSupportedCurrencies(context.Context, *Empty) (*GetSupportedCurrenciesResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetSupportedCurrencies not implemented") +} +func (UnimplementedCurrencyServiceServer) Convert(context.Context, *CurrencyConversionRequest) (*Money, error) { + return nil, status.Errorf(codes.Unimplemented, "method Convert not implemented") +} +func (UnimplementedCurrencyServiceServer) mustEmbedUnimplementedCurrencyServiceServer() {} +func (UnimplementedCurrencyServiceServer) testEmbeddedByValue() {} + +// UnsafeCurrencyServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to CurrencyServiceServer will +// result in compilation errors. +type UnsafeCurrencyServiceServer interface { + mustEmbedUnimplementedCurrencyServiceServer() +} + +func RegisterCurrencyServiceServer(s grpc.ServiceRegistrar, srv CurrencyServiceServer) { + // If the following call pancis, it indicates UnimplementedCurrencyServiceServer was + // embedded by pointer and is nil. This will cause panics if an + // unimplemented method is ever invoked, so we test this at initialization + // time to prevent it from happening at runtime later due to I/O. + if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { + t.testEmbeddedByValue() + } + s.RegisterService(&CurrencyService_ServiceDesc, srv) +} + +func _CurrencyService_GetSupportedCurrencies_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(Empty) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(CurrencyServiceServer).GetSupportedCurrencies(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: CurrencyService_GetSupportedCurrencies_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(CurrencyServiceServer).GetSupportedCurrencies(ctx, req.(*Empty)) + } + return interceptor(ctx, in, info, handler) +} + +func _CurrencyService_Convert_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(CurrencyConversionRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(CurrencyServiceServer).Convert(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: CurrencyService_Convert_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(CurrencyServiceServer).Convert(ctx, req.(*CurrencyConversionRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// CurrencyService_ServiceDesc is the grpc.ServiceDesc for CurrencyService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var CurrencyService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "hipstershop.CurrencyService", + HandlerType: (*CurrencyServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "GetSupportedCurrencies", + Handler: _CurrencyService_GetSupportedCurrencies_Handler, + }, + { + MethodName: "Convert", + Handler: _CurrencyService_Convert_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "demo.proto", +} + +const ( + PaymentService_Charge_FullMethodName = "/hipstershop.PaymentService/Charge" +) + +// PaymentServiceClient is the client API for PaymentService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type PaymentServiceClient interface { + Charge(ctx context.Context, in *ChargeRequest, opts ...grpc.CallOption) (*ChargeResponse, error) +} + +type paymentServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewPaymentServiceClient(cc grpc.ClientConnInterface) PaymentServiceClient { + return &paymentServiceClient{cc} +} + +func (c *paymentServiceClient) Charge(ctx context.Context, in *ChargeRequest, opts ...grpc.CallOption) (*ChargeResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ChargeResponse) + err := c.cc.Invoke(ctx, PaymentService_Charge_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +// PaymentServiceServer is the server API for PaymentService service. +// All implementations must embed UnimplementedPaymentServiceServer +// for forward compatibility. +type PaymentServiceServer interface { + Charge(context.Context, *ChargeRequest) (*ChargeResponse, error) + mustEmbedUnimplementedPaymentServiceServer() +} + +// UnimplementedPaymentServiceServer must be embedded to have +// forward compatible implementations. +// +// NOTE: this should be embedded by value instead of pointer to avoid a nil +// pointer dereference when methods are called. +type UnimplementedPaymentServiceServer struct{} + +func (UnimplementedPaymentServiceServer) Charge(context.Context, *ChargeRequest) (*ChargeResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method Charge not implemented") +} +func (UnimplementedPaymentServiceServer) mustEmbedUnimplementedPaymentServiceServer() {} +func (UnimplementedPaymentServiceServer) testEmbeddedByValue() {} + +// UnsafePaymentServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to PaymentServiceServer will +// result in compilation errors. +type UnsafePaymentServiceServer interface { + mustEmbedUnimplementedPaymentServiceServer() +} + +func RegisterPaymentServiceServer(s grpc.ServiceRegistrar, srv PaymentServiceServer) { + // If the following call pancis, it indicates UnimplementedPaymentServiceServer was + // embedded by pointer and is nil. This will cause panics if an + // unimplemented method is ever invoked, so we test this at initialization + // time to prevent it from happening at runtime later due to I/O. + if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { + t.testEmbeddedByValue() + } + s.RegisterService(&PaymentService_ServiceDesc, srv) +} + +func _PaymentService_Charge_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ChargeRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(PaymentServiceServer).Charge(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: PaymentService_Charge_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(PaymentServiceServer).Charge(ctx, req.(*ChargeRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// PaymentService_ServiceDesc is the grpc.ServiceDesc for PaymentService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var PaymentService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "hipstershop.PaymentService", + HandlerType: (*PaymentServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "Charge", + Handler: _PaymentService_Charge_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "demo.proto", +} + +const ( + EmailService_SendOrderConfirmation_FullMethodName = "/hipstershop.EmailService/SendOrderConfirmation" +) + +// EmailServiceClient is the client API for EmailService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type EmailServiceClient interface { + SendOrderConfirmation(ctx context.Context, in *SendOrderConfirmationRequest, opts ...grpc.CallOption) (*Empty, error) +} + +type emailServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewEmailServiceClient(cc grpc.ClientConnInterface) EmailServiceClient { + return &emailServiceClient{cc} +} + +func (c *emailServiceClient) SendOrderConfirmation(ctx context.Context, in *SendOrderConfirmationRequest, opts ...grpc.CallOption) (*Empty, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(Empty) + err := c.cc.Invoke(ctx, EmailService_SendOrderConfirmation_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +// EmailServiceServer is the server API for EmailService service. +// All implementations must embed UnimplementedEmailServiceServer +// for forward compatibility. +type EmailServiceServer interface { + SendOrderConfirmation(context.Context, *SendOrderConfirmationRequest) (*Empty, error) + mustEmbedUnimplementedEmailServiceServer() +} + +// UnimplementedEmailServiceServer must be embedded to have +// forward compatible implementations. +// +// NOTE: this should be embedded by value instead of pointer to avoid a nil +// pointer dereference when methods are called. +type UnimplementedEmailServiceServer struct{} + +func (UnimplementedEmailServiceServer) SendOrderConfirmation(context.Context, *SendOrderConfirmationRequest) (*Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method SendOrderConfirmation not implemented") +} +func (UnimplementedEmailServiceServer) mustEmbedUnimplementedEmailServiceServer() {} +func (UnimplementedEmailServiceServer) testEmbeddedByValue() {} + +// UnsafeEmailServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to EmailServiceServer will +// result in compilation errors. +type UnsafeEmailServiceServer interface { + mustEmbedUnimplementedEmailServiceServer() +} + +func RegisterEmailServiceServer(s grpc.ServiceRegistrar, srv EmailServiceServer) { + // If the following call pancis, it indicates UnimplementedEmailServiceServer was + // embedded by pointer and is nil. This will cause panics if an + // unimplemented method is ever invoked, so we test this at initialization + // time to prevent it from happening at runtime later due to I/O. + if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { + t.testEmbeddedByValue() + } + s.RegisterService(&EmailService_ServiceDesc, srv) +} + +func _EmailService_SendOrderConfirmation_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(SendOrderConfirmationRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(EmailServiceServer).SendOrderConfirmation(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: EmailService_SendOrderConfirmation_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(EmailServiceServer).SendOrderConfirmation(ctx, req.(*SendOrderConfirmationRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// EmailService_ServiceDesc is the grpc.ServiceDesc for EmailService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var EmailService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "hipstershop.EmailService", + HandlerType: (*EmailServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "SendOrderConfirmation", + Handler: _EmailService_SendOrderConfirmation_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "demo.proto", +} + +const ( + CheckoutService_PlaceOrder_FullMethodName = "/hipstershop.CheckoutService/PlaceOrder" +) + +// CheckoutServiceClient is the client API for CheckoutService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type CheckoutServiceClient interface { + PlaceOrder(ctx context.Context, in *PlaceOrderRequest, opts ...grpc.CallOption) (*PlaceOrderResponse, error) +} + +type checkoutServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewCheckoutServiceClient(cc grpc.ClientConnInterface) CheckoutServiceClient { + return &checkoutServiceClient{cc} +} + +func (c *checkoutServiceClient) PlaceOrder(ctx context.Context, in *PlaceOrderRequest, opts ...grpc.CallOption) (*PlaceOrderResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(PlaceOrderResponse) + err := c.cc.Invoke(ctx, CheckoutService_PlaceOrder_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +// CheckoutServiceServer is the server API for CheckoutService service. +// All implementations must embed UnimplementedCheckoutServiceServer +// for forward compatibility. +type CheckoutServiceServer interface { + PlaceOrder(context.Context, *PlaceOrderRequest) (*PlaceOrderResponse, error) + mustEmbedUnimplementedCheckoutServiceServer() +} + +// UnimplementedCheckoutServiceServer must be embedded to have +// forward compatible implementations. +// +// NOTE: this should be embedded by value instead of pointer to avoid a nil +// pointer dereference when methods are called. +type UnimplementedCheckoutServiceServer struct{} + +func (UnimplementedCheckoutServiceServer) PlaceOrder(context.Context, *PlaceOrderRequest) (*PlaceOrderResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method PlaceOrder not implemented") +} +func (UnimplementedCheckoutServiceServer) mustEmbedUnimplementedCheckoutServiceServer() {} +func (UnimplementedCheckoutServiceServer) testEmbeddedByValue() {} + +// UnsafeCheckoutServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to CheckoutServiceServer will +// result in compilation errors. +type UnsafeCheckoutServiceServer interface { + mustEmbedUnimplementedCheckoutServiceServer() +} + +func RegisterCheckoutServiceServer(s grpc.ServiceRegistrar, srv CheckoutServiceServer) { + // If the following call pancis, it indicates UnimplementedCheckoutServiceServer was + // embedded by pointer and is nil. This will cause panics if an + // unimplemented method is ever invoked, so we test this at initialization + // time to prevent it from happening at runtime later due to I/O. + if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { + t.testEmbeddedByValue() + } + s.RegisterService(&CheckoutService_ServiceDesc, srv) +} + +func _CheckoutService_PlaceOrder_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(PlaceOrderRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(CheckoutServiceServer).PlaceOrder(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: CheckoutService_PlaceOrder_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(CheckoutServiceServer).PlaceOrder(ctx, req.(*PlaceOrderRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// CheckoutService_ServiceDesc is the grpc.ServiceDesc for CheckoutService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var CheckoutService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "hipstershop.CheckoutService", + HandlerType: (*CheckoutServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "PlaceOrder", + Handler: _CheckoutService_PlaceOrder_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "demo.proto", +} + +const ( + AdService_GetAds_FullMethodName = "/hipstershop.AdService/GetAds" +) + +// AdServiceClient is the client API for AdService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type AdServiceClient interface { + GetAds(ctx context.Context, in *AdRequest, opts ...grpc.CallOption) (*AdResponse, error) +} + +type adServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewAdServiceClient(cc grpc.ClientConnInterface) AdServiceClient { + return &adServiceClient{cc} +} + +func (c *adServiceClient) GetAds(ctx context.Context, in *AdRequest, opts ...grpc.CallOption) (*AdResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(AdResponse) + err := c.cc.Invoke(ctx, AdService_GetAds_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +// AdServiceServer is the server API for AdService service. +// All implementations must embed UnimplementedAdServiceServer +// for forward compatibility. +type AdServiceServer interface { + GetAds(context.Context, *AdRequest) (*AdResponse, error) + mustEmbedUnimplementedAdServiceServer() +} + +// UnimplementedAdServiceServer must be embedded to have +// forward compatible implementations. +// +// NOTE: this should be embedded by value instead of pointer to avoid a nil +// pointer dereference when methods are called. +type UnimplementedAdServiceServer struct{} + +func (UnimplementedAdServiceServer) GetAds(context.Context, *AdRequest) (*AdResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetAds not implemented") +} +func (UnimplementedAdServiceServer) mustEmbedUnimplementedAdServiceServer() {} +func (UnimplementedAdServiceServer) testEmbeddedByValue() {} + +// UnsafeAdServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to AdServiceServer will +// result in compilation errors. +type UnsafeAdServiceServer interface { + mustEmbedUnimplementedAdServiceServer() +} + +func RegisterAdServiceServer(s grpc.ServiceRegistrar, srv AdServiceServer) { + // If the following call pancis, it indicates UnimplementedAdServiceServer was + // embedded by pointer and is nil. This will cause panics if an + // unimplemented method is ever invoked, so we test this at initialization + // time to prevent it from happening at runtime later due to I/O. + if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { + t.testEmbeddedByValue() + } + s.RegisterService(&AdService_ServiceDesc, srv) +} + +func _AdService_GetAds_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(AdRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AdServiceServer).GetAds(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: AdService_GetAds_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AdServiceServer).GetAds(ctx, req.(*AdRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// AdService_ServiceDesc is the grpc.ServiceDesc for AdService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var AdService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "hipstershop.AdService", + HandlerType: (*AdServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "GetAds", + Handler: _AdService_GetAds_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "demo.proto", +} diff --git a/src/checkoutservice/go.mod b/src/checkoutservice/go.mod new file mode 100644 index 0000000..71fe456 --- /dev/null +++ b/src/checkoutservice/go.mod @@ -0,0 +1,50 @@ +module github.com/GoogleCloudPlatform/microservices-demo/src/checkoutservice + +go 1.25 + +toolchain go1.25.6 + +require ( + cloud.google.com/go/profiler v0.4.3 + github.com/google/uuid v1.6.0 + github.com/pkg/errors v0.9.1 + github.com/sirupsen/logrus v1.9.4 + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.64.0 + go.opentelemetry.io/otel v1.39.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0 + go.opentelemetry.io/otel/sdk v1.39.0 + google.golang.org/grpc v1.78.0 + google.golang.org/protobuf v1.36.11 +) + +require ( + cloud.google.com/go v0.123.0 // indirect + cloud.google.com/go/auth v0.17.0 // indirect + cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect + cloud.google.com/go/compute/metadata v0.9.0 // indirect + github.com/cenkalti/backoff/v5 v5.0.3 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/go-logr/logr v1.4.3 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/google/pprof v0.0.0-20251114195745-4902fdda35c8 // indirect + github.com/google/s2a-go v0.1.9 // indirect + github.com/googleapis/enterprise-certificate-proxy v0.3.7 // indirect + github.com/googleapis/gax-go/v2 v2.15.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 // indirect + go.opentelemetry.io/auto/sdk v1.2.1 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0 // indirect + go.opentelemetry.io/otel/metric v1.39.0 // indirect + go.opentelemetry.io/otel/trace v1.39.0 // indirect + go.opentelemetry.io/proto/otlp v1.9.0 // indirect + golang.org/x/crypto v0.45.0 // indirect + golang.org/x/net v0.47.0 // indirect + golang.org/x/oauth2 v0.33.0 // indirect + golang.org/x/sync v0.18.0 // indirect + golang.org/x/sys v0.39.0 // indirect + golang.org/x/text v0.31.0 // indirect + golang.org/x/time v0.14.0 // indirect + google.golang.org/api v0.256.0 // indirect + google.golang.org/genproto v0.0.0-20251124214823-79d6a2a48846 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect +) diff --git a/src/checkoutservice/go.sum b/src/checkoutservice/go.sum new file mode 100644 index 0000000..7ea7c45 --- /dev/null +++ b/src/checkoutservice/go.sum @@ -0,0 +1,164 @@ +cel.dev/expr v0.24.0 h1:56OvJKSH3hDGL0ml5uSxZmz3/3Pq4tJ+fb1unVLAFcY= +cel.dev/expr v0.24.0/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw= +cloud.google.com/go v0.123.0 h1:2NAUJwPR47q+E35uaJeYoNhuNEM9kM8SjgRgdeOJUSE= +cloud.google.com/go v0.123.0/go.mod h1:xBoMV08QcqUGuPW65Qfm1o9Y4zKZBpGS+7bImXLTAZU= +cloud.google.com/go/auth v0.17.0 h1:74yCm7hCj2rUyyAocqnFzsAYXgJhrG26XCFimrc/Kz4= +cloud.google.com/go/auth v0.17.0/go.mod h1:6wv/t5/6rOPAX4fJiRjKkJCvswLwdet7G8+UGXt7nCQ= +cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc= +cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c= +cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs= +cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10= +cloud.google.com/go/iam v1.5.3 h1:+vMINPiDF2ognBJ97ABAYYwRgsaqxPbQDlMnbHMjolc= +cloud.google.com/go/iam v1.5.3/go.mod h1:MR3v9oLkZCTlaqljW6Eb2d3HGDGK5/bDv93jhfISFvU= +cloud.google.com/go/monitoring v1.24.3 h1:dde+gMNc0UhPZD1Azu6at2e79bfdztVDS5lvhOdsgaE= +cloud.google.com/go/monitoring v1.24.3/go.mod h1:nYP6W0tm3N9H/bOw8am7t62YTzZY+zUeQ+Bi6+2eonI= +cloud.google.com/go/profiler v0.4.3 h1:IY3QNKlr8VbXwGWHcZbJQsMA/83ZTH6uAHf8jYyj7OI= +cloud.google.com/go/profiler v0.4.3/go.mod h1:3xFodugWfPIQZWFcXdUmfa+yTiiyQ8fWrdT+d2Sg4J0= +cloud.google.com/go/storage v1.56.0 h1:iixmq2Fse2tqxMbWhLWC9HfBj1qdxqAmiK8/eqtsLxI= +cloud.google.com/go/storage v1.56.0/go.mod h1:Tpuj6t4NweCLzlNbw9Z9iwxEkrSem20AetIeH/shgVU= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0 h1:sBEjpZlNHzK1voKq9695PJSX2o5NEXl7/OL3coiIY0c= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0/go.mod h1:P4WPRUkOhJC13W//jWpyfJNDAIpvRbAUIYLX/4jtlE0= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.53.0 h1:owcC2UnmsZycprQ5RfRgjydWhuoxg71LUfyiQdijZuM= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.53.0/go.mod h1:ZPpqegjbE99EPKsu3iUWV22A04wzGPcAY/ziSIQEEgs= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.53.0 h1:Ron4zCA/yk6U7WOBXhTJcDpsUBG9npumK6xw2auFltQ= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.53.0/go.mod h1:cSgYe11MCNYunTnRXrKiR/tHc0eoKjICUuWpNZoVCOo= +github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= +github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cncf/xds/go v0.0.0-20251022180443-0feb69152e9f h1:Y8xYupdHxryycyPlc9Y+bSQAYZnetRJ70VMVKm5CKI0= +github.com/cncf/xds/go v0.0.0-20251022180443-0feb69152e9f/go.mod h1:HlzOvOjVBOfTGSRXRyY0OiCS/3J1akRGQQpRO/7zyF4= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/envoyproxy/go-control-plane v0.13.5-0.20251024222203-75eaa193e329 h1:K+fnvUM0VZ7ZFJf0n4L/BRlnsb9pL/GuDG6FqaH+PwM= +github.com/envoyproxy/go-control-plane/envoy v1.35.0 h1:ixjkELDE+ru6idPxcHLj8LBVc2bFP7iBytj353BoHUo= +github.com/envoyproxy/go-control-plane/envoy v1.35.0/go.mod h1:09qwbGVuSWWAyN5t/b3iyVfz5+z8QWGrzkoqm/8SbEs= +github.com/envoyproxy/protoc-gen-validate v1.2.1 h1:DEo3O99U8j4hBFwbJfrz9VtgcDfUKS7KJ7spH3d86P8= +github.com/envoyproxy/protoc-gen-validate v1.2.1/go.mod h1:d/C80l/jxXLdfEIhX1W2TmLfsJ31lvEjwamM4DxlWXU= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/go-jose/go-jose/v4 v4.1.3 h1:CVLmWDhDVRa6Mi/IgCgaopNosCaHz7zrMeF9MlZRkrs= +github.com/go-jose/go-jose/v4 v4.1.3/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/golang/mock v1.7.0-rc.1 h1:YojYx61/OLFsiv6Rw1Z96LpldJIy31o+UHmwAUMJ6/U= +github.com/golang/mock v1.7.0-rc.1/go.mod h1:s42URUywIqd+OcERslBJvOjepvNymP31m3q8d/GkuRs= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/pprof v0.0.0-20251114195745-4902fdda35c8 h1:3DsUAV+VNEQa2CUVLxCY3f87278uWfIDhJnbdvDjvmE= +github.com/google/pprof v0.0.0-20251114195745-4902fdda35c8/go.mod h1:I6V7YzU0XDpsHqbsyrghnFZLO1gwK6NPTNvmetQIk9U= +github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0= +github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/enterprise-certificate-proxy v0.3.7 h1:zrn2Ee/nWmHulBx5sAVrGgAa0f2/R35S4DJwfFaUPFQ= +github.com/googleapis/enterprise-certificate-proxy v0.3.7/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA= +github.com/googleapis/gax-go/v2 v2.15.0 h1:SyjDc1mGgZU5LncH8gimWo9lW1DtIfPibOG81vgd/bo= +github.com/googleapis/gax-go/v2 v2.15.0/go.mod h1:zVVkkxAQHa1RQpg9z2AUCMnKhi0Qld9rcmyfL1OZhoc= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 h1:NmZ1PKzSTQbuGHw9DGPFomqkkLWMC+vZCkfs+FHv1Vg= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3/go.mod h1:zQrxl1YP88HQlA6i9c63DSVPFklWpGX4OWAc9bFuaH4= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo= +github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w= +github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g= +github.com/spiffe/go-spiffe/v2 v2.6.0 h1:l+DolpxNWYgruGQVV0xsfeya3CsC7m8iBzDnMpsbLuo= +github.com/spiffe/go-spiffe/v2 v2.6.0/go.mod h1:gm2SeUoMZEtpnzPNs2Csc0D/gX33k1xIx7lEzqblHEs= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= +go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= +go.opentelemetry.io/contrib/detectors/gcp v1.38.0 h1:ZoYbqX7OaA/TAikspPl3ozPI6iY6LiIY9I8cUfm+pJs= +go.opentelemetry.io/contrib/detectors/gcp v1.38.0/go.mod h1:SU+iU7nu5ud4oCb3LQOhIZ3nRLj6FNVrKgtflbaf2ts= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0 h1:YH4g8lQroajqUwWbq/tr2QX1JFmEXaDLgG+ew9bLMWo= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0/go.mod h1:fvPi2qXDqFs8M4B4fmJhE92TyQs9Ydjlg3RvfUp+NbQ= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.64.0 h1:RN3ifU8y4prNWeEnQp2kRRHz8UwonAEYZl8tUzHEXAk= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.64.0/go.mod h1:habDz3tEWiFANTo6oUE99EmaFUrCNYAAg3wiVmusm70= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q= +go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= +go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= +go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= +go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 h1:GqRJVj7UmLjCVyVJ3ZFLdPRmhDUp2zFmQe3RHIOsw24= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0/go.mod h1:ri3aaHSmCTVYu2AWv44YMauwAQc0aqI9gHKIcSbI1pU= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0 h1:f0cb2XPmrqn4XMy9PNliTgRKJgS5WcL/u0/WRYGz4t0= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0/go.mod h1:vnakAaFckOMiMtOIhFI2MNH4FYrZzXCYxmb1LlhoGz8= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 h1:lwI4Dc5leUqENgGuQImwLo4WnuXFPetmPpkLi2IrX54= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0/go.mod h1:Kz/oCE7z5wuyhPxsXDuaPteSWqjSBD5YaSdbxZYGbGk= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0 h1:in9O8ESIOlwJAEGTkkf34DesGRAc/Pn8qJ7k3r/42LM= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0/go.mod h1:Rp0EXBm5tfnv0WL+ARyO/PHBEaEAT8UUHQ6AGJcSq6c= +go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= +go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= +go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= +go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= +go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E= +go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg= +go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= +go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= +go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM= +go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= +go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= +go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= +go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= +go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= +go.opentelemetry.io/proto/otlp v1.9.0 h1:l706jCMITVouPOqEnii2fIAuO3IVGBRPV5ICjceRb/A= +go.opentelemetry.io/proto/otlp v1.9.0/go.mod h1:xE+Cx5E/eEHw+ISFkwPLwCZefwVjY+pqKg1qcK03+/4= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q= +golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4= +golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= +golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= +golang.org/x/oauth2 v0.33.0 h1:4Q+qn+E5z8gPRJfmRy7C2gGG3T4jIprK6aSYgTXGRpo= +golang.org/x/oauth2 v0.33.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= +golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= +golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= +golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= +golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= +golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= +golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= +golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= +gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= +gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= +google.golang.org/api v0.256.0 h1:u6Khm8+F9sxbCTYNoBHg6/Hwv0N/i+V94MvkOSor6oI= +google.golang.org/api v0.256.0/go.mod h1:KIgPhksXADEKJlnEoRa9qAII4rXcy40vfI8HRqcU964= +google.golang.org/genproto v0.0.0-20251124214823-79d6a2a48846 h1:dDbsTLIK7EzwUq36kCSAsk0slouq/S0tWHeeGi97cD8= +google.golang.org/genproto v0.0.0-20251124214823-79d6a2a48846/go.mod h1:PP0g88Dz3C7hRAfbQCQggeWAXjuqGsNPLE4s7jh0RGU= +google.golang.org/genproto/googleapis/api v0.0.0-20251124214823-79d6a2a48846 h1:ZdyUkS9po3H7G0tuh955QVyyotWvOD4W0aEapeGeUYk= +google.golang.org/genproto/googleapis/api v0.0.0-20251124214823-79d6a2a48846/go.mod h1:Fk4kyraUvqD7i5H6S43sj2W98fbZa75lpZz/eUyhfO0= +google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 h1:fCvbg86sFXwdrl5LgVcTEvNC+2txB5mgROGmRL5mrls= +google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:+rXWjjaukWZun3mLfjmVnQi18E1AsFbDN9QdJ5YXLto= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251124214823-79d6a2a48846 h1:Wgl1rcDNThT+Zn47YyCXOXyX/COgMTIdhJ717F0l4xk= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251124214823-79d6a2a48846/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 h1:gRkg/vSppuSQoDjxyiGfN4Upv/h/DQmIR10ZU8dh4Ww= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= +google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM= +google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig= +google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc= +google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U= +google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= +google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= +google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/src/checkoutservice/main.go b/src/checkoutservice/main.go new file mode 100644 index 0000000..7f97142 --- /dev/null +++ b/src/checkoutservice/main.go @@ -0,0 +1,394 @@ +// Copyright 2018 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +import ( + "context" + "fmt" + "net" + "os" + "time" + + "cloud.google.com/go/profiler" + "github.com/google/uuid" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/credentials/insecure" + "google.golang.org/grpc/health" + "google.golang.org/grpc/status" + + pb "github.com/GoogleCloudPlatform/microservices-demo/src/checkoutservice/genproto" + money "github.com/GoogleCloudPlatform/microservices-demo/src/checkoutservice/money" + healthpb "google.golang.org/grpc/health/grpc_health_v1" + + "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc" + "go.opentelemetry.io/otel/propagation" + sdktrace "go.opentelemetry.io/otel/sdk/trace" +) + +const ( + listenPort = "5050" + usdCurrency = "USD" +) + +var log *logrus.Logger + +func init() { + log = logrus.New() + log.Level = logrus.DebugLevel + log.Formatter = &logrus.JSONFormatter{ + FieldMap: logrus.FieldMap{ + logrus.FieldKeyTime: "timestamp", + logrus.FieldKeyLevel: "severity", + logrus.FieldKeyMsg: "message", + }, + TimestampFormat: time.RFC3339Nano, + } + log.Out = os.Stdout +} + +type checkoutService struct { + pb.UnimplementedCheckoutServiceServer + + productCatalogSvcAddr string + productCatalogSvcConn *grpc.ClientConn + + cartSvcAddr string + cartSvcConn *grpc.ClientConn + + currencySvcAddr string + currencySvcConn *grpc.ClientConn + + shippingSvcAddr string + shippingSvcConn *grpc.ClientConn + + emailSvcAddr string + emailSvcConn *grpc.ClientConn + + paymentSvcAddr string + paymentSvcConn *grpc.ClientConn +} + +func main() { + ctx := context.Background() + if os.Getenv("ENABLE_TRACING") == "1" { + log.Info("Tracing enabled.") + initTracing() + + } else { + log.Info("Tracing disabled.") + } + + if os.Getenv("ENABLE_PROFILER") == "1" { + log.Info("Profiling enabled.") + go initProfiling("checkoutservice", "1.0.0") + } else { + log.Info("Profiling disabled.") + } + + port := listenPort + if os.Getenv("PORT") != "" { + port = os.Getenv("PORT") + } + + svc := new(checkoutService) + mustMapEnv(&svc.shippingSvcAddr, "SHIPPING_SERVICE_ADDR") + mustMapEnv(&svc.productCatalogSvcAddr, "PRODUCT_CATALOG_SERVICE_ADDR") + mustMapEnv(&svc.cartSvcAddr, "CART_SERVICE_ADDR") + mustMapEnv(&svc.currencySvcAddr, "CURRENCY_SERVICE_ADDR") + mustMapEnv(&svc.emailSvcAddr, "EMAIL_SERVICE_ADDR") + mustMapEnv(&svc.paymentSvcAddr, "PAYMENT_SERVICE_ADDR") + + mustConnGRPC(ctx, &svc.shippingSvcConn, svc.shippingSvcAddr) + mustConnGRPC(ctx, &svc.productCatalogSvcConn, svc.productCatalogSvcAddr) + mustConnGRPC(ctx, &svc.cartSvcConn, svc.cartSvcAddr) + mustConnGRPC(ctx, &svc.currencySvcConn, svc.currencySvcAddr) + mustConnGRPC(ctx, &svc.emailSvcConn, svc.emailSvcAddr) + mustConnGRPC(ctx, &svc.paymentSvcConn, svc.paymentSvcAddr) + + log.Infof("service config: %+v", svc) + + lis, err := net.Listen("tcp", fmt.Sprintf(":%s", port)) + if err != nil { + log.Fatal(err) + } + + var srv *grpc.Server + + // Propagate trace context always + otel.SetTextMapPropagator( + propagation.NewCompositeTextMapPropagator( + propagation.TraceContext{}, propagation.Baggage{})) + srv = grpc.NewServer( + grpc.StatsHandler(otelgrpc.NewServerHandler()), + ) + + pb.RegisterCheckoutServiceServer(srv, svc) + healthcheck := health.NewServer() + healthpb.RegisterHealthServer(srv, healthcheck) + log.Infof("starting to listen on tcp: %q", lis.Addr().String()) + err = srv.Serve(lis) + log.Fatal(err) +} + +func initStats() { + //TODO(arbrown) Implement OpenTelemetry stats +} + +func initTracing() { + var ( + collectorAddr string + collectorConn *grpc.ClientConn + ) + + ctx := context.Background() + ctx, cancel := context.WithTimeout(ctx, time.Second*3) + defer cancel() + + mustMapEnv(&collectorAddr, "COLLECTOR_SERVICE_ADDR") + mustConnGRPC(ctx, &collectorConn, collectorAddr) + + exporter, err := otlptracegrpc.New( + ctx, + otlptracegrpc.WithGRPCConn(collectorConn)) + if err != nil { + log.Warnf("warn: Failed to create trace exporter: %v", err) + } + tp := sdktrace.NewTracerProvider( + sdktrace.WithBatcher(exporter), + sdktrace.WithSampler(sdktrace.AlwaysSample())) + otel.SetTracerProvider(tp) + +} + +func initProfiling(service, version string) { + // TODO(ahmetb) this method is duplicated in other microservices using Go + // since they are not sharing packages. + for i := 1; i <= 3; i++ { + if err := profiler.Start(profiler.Config{ + Service: service, + ServiceVersion: version, + // ProjectID must be set if not running on GCP. + // ProjectID: "my-project", + }); err != nil { + log.Warnf("failed to start profiler: %+v", err) + } else { + log.Info("started Stackdriver profiler") + return + } + d := time.Second * 10 * time.Duration(i) + log.Infof("sleeping %v to retry initializing Stackdriver profiler", d) + time.Sleep(d) + } + log.Warn("could not initialize Stackdriver profiler after retrying, giving up") +} + +func mustMapEnv(target *string, envKey string) { + v := os.Getenv(envKey) + if v == "" { + panic(fmt.Sprintf("environment variable %q not set", envKey)) + } + *target = v +} + +func mustConnGRPC(ctx context.Context, conn **grpc.ClientConn, addr string) { + var err error + _, cancel := context.WithTimeout(ctx, time.Second*3) + defer cancel() + *conn, err = grpc.NewClient(addr, + grpc.WithTransportCredentials(insecure.NewCredentials()), + grpc.WithStatsHandler(otelgrpc.NewClientHandler())) + if err != nil { + panic(errors.Wrapf(err, "grpc: failed to connect %s", addr)) + } +} + +func (cs *checkoutService) Check(ctx context.Context, req *healthpb.HealthCheckRequest) (*healthpb.HealthCheckResponse, error) { + return &healthpb.HealthCheckResponse{Status: healthpb.HealthCheckResponse_SERVING}, nil +} + +func (cs *checkoutService) Watch(req *healthpb.HealthCheckRequest, ws healthpb.Health_WatchServer) error { + return status.Errorf(codes.Unimplemented, "health check via Watch not implemented") +} + +func (cs *checkoutService) PlaceOrder(ctx context.Context, req *pb.PlaceOrderRequest) (*pb.PlaceOrderResponse, error) { + log.Infof("[PlaceOrder] user_id=%q user_currency=%q", req.UserId, req.UserCurrency) + + orderID, err := uuid.NewUUID() + if err != nil { + return nil, status.Errorf(codes.Internal, "failed to generate order uuid") + } + + prep, err := cs.prepareOrderItemsAndShippingQuoteFromCart(ctx, req.UserId, req.UserCurrency, req.Address) + if err != nil { + return nil, status.Errorf(codes.Internal, err.Error()) + } + + total := pb.Money{CurrencyCode: req.UserCurrency, + Units: 0, + Nanos: 0} + total = money.Must(money.Sum(total, *prep.shippingCostLocalized)) + for _, it := range prep.orderItems { + multPrice := money.MultiplySlow(*it.Cost, uint32(it.GetItem().GetQuantity())) + total = money.Must(money.Sum(total, multPrice)) + } + + txID, err := cs.chargeCard(ctx, &total, req.CreditCard) + if err != nil { + return nil, status.Errorf(codes.Internal, "failed to charge card: %+v", err) + } + log.Infof("payment went through (transaction_id: %s)", txID) + + shippingTrackingID, err := cs.shipOrder(ctx, req.Address, prep.cartItems) + if err != nil { + return nil, status.Errorf(codes.Unavailable, "shipping error: %+v", err) + } + + _ = cs.emptyUserCart(ctx, req.UserId) + + orderResult := &pb.OrderResult{ + OrderId: orderID.String(), + ShippingTrackingId: shippingTrackingID, + ShippingCost: prep.shippingCostLocalized, + ShippingAddress: req.Address, + Items: prep.orderItems, + } + + if err := cs.sendOrderConfirmation(ctx, req.Email, orderResult); err != nil { + log.Warnf("failed to send order confirmation to %q: %+v", req.Email, err) + } else { + log.Infof("order confirmation email sent to %q", req.Email) + } + resp := &pb.PlaceOrderResponse{Order: orderResult} + return resp, nil +} + +type orderPrep struct { + orderItems []*pb.OrderItem + cartItems []*pb.CartItem + shippingCostLocalized *pb.Money +} + +func (cs *checkoutService) prepareOrderItemsAndShippingQuoteFromCart(ctx context.Context, userID, userCurrency string, address *pb.Address) (orderPrep, error) { + var out orderPrep + cartItems, err := cs.getUserCart(ctx, userID) + if err != nil { + return out, fmt.Errorf("cart failure: %+v", err) + } + orderItems, err := cs.prepOrderItems(ctx, cartItems, userCurrency) + if err != nil { + return out, fmt.Errorf("failed to prepare order: %+v", err) + } + shippingUSD, err := cs.quoteShipping(ctx, address, cartItems) + if err != nil { + return out, fmt.Errorf("shipping quote failure: %+v", err) + } + shippingPrice, err := cs.convertCurrency(ctx, shippingUSD, userCurrency) + if err != nil { + return out, fmt.Errorf("failed to convert shipping cost to currency: %+v", err) + } + + out.shippingCostLocalized = shippingPrice + out.cartItems = cartItems + out.orderItems = orderItems + return out, nil +} + +func (cs *checkoutService) quoteShipping(ctx context.Context, address *pb.Address, items []*pb.CartItem) (*pb.Money, error) { + shippingQuote, err := pb.NewShippingServiceClient(cs.shippingSvcConn). + GetQuote(ctx, &pb.GetQuoteRequest{ + Address: address, + Items: items}) + if err != nil { + return nil, fmt.Errorf("failed to get shipping quote: %+v", err) + } + return shippingQuote.GetCostUsd(), nil +} + +func (cs *checkoutService) getUserCart(ctx context.Context, userID string) ([]*pb.CartItem, error) { + cart, err := pb.NewCartServiceClient(cs.cartSvcConn).GetCart(ctx, &pb.GetCartRequest{UserId: userID}) + if err != nil { + return nil, fmt.Errorf("failed to get user cart during checkout: %+v", err) + } + return cart.GetItems(), nil +} + +func (cs *checkoutService) emptyUserCart(ctx context.Context, userID string) error { + if _, err := pb.NewCartServiceClient(cs.cartSvcConn).EmptyCart(ctx, &pb.EmptyCartRequest{UserId: userID}); err != nil { + return fmt.Errorf("failed to empty user cart during checkout: %+v", err) + } + return nil +} + +func (cs *checkoutService) prepOrderItems(ctx context.Context, items []*pb.CartItem, userCurrency string) ([]*pb.OrderItem, error) { + out := make([]*pb.OrderItem, len(items)) + cl := pb.NewProductCatalogServiceClient(cs.productCatalogSvcConn) + + for i, item := range items { + product, err := cl.GetProduct(ctx, &pb.GetProductRequest{Id: item.GetProductId()}) + if err != nil { + return nil, fmt.Errorf("failed to get product #%q", item.GetProductId()) + } + price, err := cs.convertCurrency(ctx, product.GetPriceUsd(), userCurrency) + if err != nil { + return nil, fmt.Errorf("failed to convert price of %q to %s", item.GetProductId(), userCurrency) + } + out[i] = &pb.OrderItem{ + Item: item, + Cost: price} + } + return out, nil +} + +func (cs *checkoutService) convertCurrency(ctx context.Context, from *pb.Money, toCurrency string) (*pb.Money, error) { + result, err := pb.NewCurrencyServiceClient(cs.currencySvcConn).Convert(context.TODO(), &pb.CurrencyConversionRequest{ + From: from, + ToCode: toCurrency}) + if err != nil { + return nil, fmt.Errorf("failed to convert currency: %+v", err) + } + return result, err +} + +func (cs *checkoutService) chargeCard(ctx context.Context, amount *pb.Money, paymentInfo *pb.CreditCardInfo) (string, error) { + paymentResp, err := pb.NewPaymentServiceClient(cs.paymentSvcConn).Charge(ctx, &pb.ChargeRequest{ + Amount: amount, + CreditCard: paymentInfo}) + if err != nil { + return "", fmt.Errorf("could not charge the card: %+v", err) + } + return paymentResp.GetTransactionId(), nil +} + +func (cs *checkoutService) sendOrderConfirmation(ctx context.Context, email string, order *pb.OrderResult) error { + _, err := pb.NewEmailServiceClient(cs.emailSvcConn).SendOrderConfirmation(ctx, &pb.SendOrderConfirmationRequest{ + Email: email, + Order: order}) + return err +} + +func (cs *checkoutService) shipOrder(ctx context.Context, address *pb.Address, items []*pb.CartItem) (string, error) { + resp, err := pb.NewShippingServiceClient(cs.shippingSvcConn).ShipOrder(ctx, &pb.ShipOrderRequest{ + Address: address, + Items: items}) + if err != nil { + return "", fmt.Errorf("shipment failed: %+v", err) + } + return resp.GetTrackingId(), nil +} diff --git a/src/checkoutservice/money/money.go b/src/checkoutservice/money/money.go new file mode 100644 index 0000000..fd11ed6 --- /dev/null +++ b/src/checkoutservice/money/money.go @@ -0,0 +1,132 @@ +// Copyright 2018 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package money + +import ( + "errors" + + pb "github.com/GoogleCloudPlatform/microservices-demo/src/checkoutservice/genproto" +) + +const ( + nanosMin = -999999999 + nanosMax = +999999999 + nanosMod = 1000000000 +) + +var ( + ErrInvalidValue = errors.New("one of the specified money values is invalid") + ErrMismatchingCurrency = errors.New("mismatching currency codes") +) + +// IsValid checks if specified value has a valid units/nanos signs and ranges. +func IsValid(m pb.Money) bool { + return signMatches(m) && validNanos(m.GetNanos()) +} + +func signMatches(m pb.Money) bool { + return m.GetNanos() == 0 || m.GetUnits() == 0 || (m.GetNanos() < 0) == (m.GetUnits() < 0) +} + +func validNanos(nanos int32) bool { return nanosMin <= nanos && nanos <= nanosMax } + +// IsZero returns true if the specified money value is equal to zero. +func IsZero(m pb.Money) bool { return m.GetUnits() == 0 && m.GetNanos() == 0 } + +// IsPositive returns true if the specified money value is valid and is +// positive. +func IsPositive(m pb.Money) bool { + return IsValid(m) && m.GetUnits() > 0 || (m.GetUnits() == 0 && m.GetNanos() > 0) +} + +// IsNegative returns true if the specified money value is valid and is +// negative. +func IsNegative(m pb.Money) bool { + return IsValid(m) && m.GetUnits() < 0 || (m.GetUnits() == 0 && m.GetNanos() < 0) +} + +// AreSameCurrency returns true if values l and r have a currency code and +// they are the same values. +func AreSameCurrency(l, r pb.Money) bool { + return l.GetCurrencyCode() == r.GetCurrencyCode() && l.GetCurrencyCode() != "" +} + +// AreEquals returns true if values l and r are the equal, including the +// currency. This does not check validity of the provided values. +func AreEquals(l, r pb.Money) bool { + return l.GetCurrencyCode() == r.GetCurrencyCode() && + l.GetUnits() == r.GetUnits() && l.GetNanos() == r.GetNanos() +} + +// Negate returns the same amount with the sign negated. +func Negate(m pb.Money) pb.Money { + return pb.Money{ + Units: -m.GetUnits(), + Nanos: -m.GetNanos(), + CurrencyCode: m.GetCurrencyCode()} +} + +// Must panics if the given error is not nil. This can be used with other +// functions like: "m := Must(Sum(a,b))". +func Must(v pb.Money, err error) pb.Money { + if err != nil { + panic(err) + } + return v +} + +// Sum adds two values. Returns an error if one of the values are invalid or +// currency codes are not matching (unless currency code is unspecified for +// both). +func Sum(l, r pb.Money) (pb.Money, error) { + if !IsValid(l) || !IsValid(r) { + return pb.Money{}, ErrInvalidValue + } else if l.GetCurrencyCode() != r.GetCurrencyCode() { + return pb.Money{}, ErrMismatchingCurrency + } + units := l.GetUnits() + r.GetUnits() + nanos := l.GetNanos() + r.GetNanos() + + if (units == 0 && nanos == 0) || (units > 0 && nanos >= 0) || (units < 0 && nanos <= 0) { + // same sign + units += int64(nanos / nanosMod) + nanos = nanos % nanosMod + } else { + // different sign. nanos guaranteed to not to go over the limit + if units > 0 { + units-- + nanos += nanosMod + } else { + units++ + nanos -= nanosMod + } + } + + return pb.Money{ + Units: units, + Nanos: nanos, + CurrencyCode: l.GetCurrencyCode()}, nil +} + +// MultiplySlow is a slow multiplication operation done through adding the value +// to itself n-1 times. +func MultiplySlow(m pb.Money, n uint32) pb.Money { + out := m + for n > 1 { + out = Must(Sum(out, m)) + n-- + } + return out +} diff --git a/src/checkoutservice/money/money_test.go b/src/checkoutservice/money/money_test.go new file mode 100644 index 0000000..b9d8b70 --- /dev/null +++ b/src/checkoutservice/money/money_test.go @@ -0,0 +1,245 @@ +// Copyright 2018 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package money + +import ( + "fmt" + "reflect" + "testing" + + pb "github.com/GoogleCloudPlatform/microservices-demo/src/checkoutservice/genproto" +) + +func mmc(u int64, n int32, c string) pb.Money { return pb.Money{Units: u, Nanos: n, CurrencyCode: c} } +func mm(u int64, n int32) pb.Money { return mmc(u, n, "") } + +func TestIsValid(t *testing.T) { + tests := []struct { + name string + in pb.Money + want bool + }{ + {"valid -/-", mm(-981273891273, -999999999), true}, + {"invalid -/+", mm(-981273891273, +999999999), false}, + {"valid +/+", mm(981273891273, 999999999), true}, + {"invalid +/-", mm(981273891273, -999999999), false}, + {"invalid +/+overflow", mm(3, 1000000000), false}, + {"invalid +/-overflow", mm(3, -1000000000), false}, + {"invalid -/+overflow", mm(-3, 1000000000), false}, + {"invalid -/-overflow", mm(-3, -1000000000), false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := IsValid(tt.in); got != tt.want { + t.Errorf("IsValid(%v) = %v, want %v", tt.in, got, tt.want) + } + }) + } +} + +func TestIsZero(t *testing.T) { + tests := []struct { + name string + in pb.Money + want bool + }{ + {"zero", mm(0, 0), true}, + {"not-zero (-/+)", mm(-1, +1), false}, + {"not-zero (-/-)", mm(-1, -1), false}, + {"not-zero (+/+)", mm(+1, +1), false}, + {"not-zero (+/-)", mm(+1, -1), false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := IsZero(tt.in); got != tt.want { + t.Errorf("IsZero(%v) = %v, want %v", tt.in, got, tt.want) + } + }) + } +} + +func TestIsPositive(t *testing.T) { + tests := []struct { + name string + in pb.Money + want bool + }{ + {"zero", mm(0, 0), false}, + {"positive (+/+)", mm(+1, +1), true}, + {"invalid (-/+)", mm(-1, +1), false}, + {"negative (-/-)", mm(-1, -1), false}, + {"invalid (+/-)", mm(+1, -1), false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := IsPositive(tt.in); got != tt.want { + t.Errorf("IsPositive(%v) = %v, want %v", tt.in, got, tt.want) + } + }) + } +} + +func TestIsNegative(t *testing.T) { + tests := []struct { + name string + in pb.Money + want bool + }{ + {"zero", mm(0, 0), false}, + {"positive (+/+)", mm(+1, +1), false}, + {"invalid (-/+)", mm(-1, +1), false}, + {"negative (-/-)", mm(-1, -1), true}, + {"invalid (+/-)", mm(+1, -1), false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := IsNegative(tt.in); got != tt.want { + t.Errorf("IsNegative(%v) = %v, want %v", tt.in, got, tt.want) + } + }) + } +} + +func TestAreSameCurrency(t *testing.T) { + type args struct { + l pb.Money + r pb.Money + } + tests := []struct { + name string + args args + want bool + }{ + {"both empty currency", args{mmc(1, 0, ""), mmc(2, 0, "")}, false}, + {"left empty currency", args{mmc(1, 0, ""), mmc(2, 0, "USD")}, false}, + {"right empty currency", args{mmc(1, 0, "USD"), mmc(2, 0, "")}, false}, + {"mismatching", args{mmc(1, 0, "USD"), mmc(2, 0, "CAD")}, false}, + {"matching", args{mmc(1, 0, "USD"), mmc(2, 0, "USD")}, true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := AreSameCurrency(tt.args.l, tt.args.r); got != tt.want { + t.Errorf("AreSameCurrency([%v],[%v]) = %v, want %v", tt.args.l, tt.args.r, got, tt.want) + } + }) + } +} + +func TestAreEquals(t *testing.T) { + type args struct { + l pb.Money + r pb.Money + } + tests := []struct { + name string + args args + want bool + }{ + {"equals", args{mmc(1, 2, "USD"), mmc(1, 2, "USD")}, true}, + {"mismatching currency", args{mmc(1, 2, "USD"), mmc(1, 2, "CAD")}, false}, + {"mismatching units", args{mmc(10, 20, "USD"), mmc(1, 20, "USD")}, false}, + {"mismatching nanos", args{mmc(1, 2, "USD"), mmc(1, 20, "USD")}, false}, + {"negated", args{mmc(1, 2, "USD"), mmc(-1, -2, "USD")}, false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := AreEquals(tt.args.l, tt.args.r); got != tt.want { + t.Errorf("AreEquals([%v],[%v]) = %v, want %v", tt.args.l, tt.args.r, got, tt.want) + } + }) + } +} + +func TestNegate(t *testing.T) { + tests := []struct { + name string + in pb.Money + want pb.Money + }{ + {"zero", mm(0, 0), mm(0, 0)}, + {"negative", mm(-1, -200), mm(1, 200)}, + {"positive", mm(1, 200), mm(-1, -200)}, + {"carries currency code", mmc(0, 0, "XXX"), mmc(0, 0, "XXX")}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := Negate(tt.in); !AreEquals(got, tt.want) { + t.Errorf("Negate([%v]) = %v, want %v", tt.in, got, tt.want) + } + }) + } +} + +func TestMust_pass(t *testing.T) { + v := Must(mm(2, 3), nil) + if !AreEquals(v, mm(2, 3)) { + t.Errorf("returned the wrong value: %v", v) + } +} + +func TestMust_panic(t *testing.T) { + defer func() { + if r := recover(); r != nil { + t.Logf("panic captured: %v", r) + } + }() + Must(mm(2, 3), fmt.Errorf("some error")) + t.Fatal("this should not have executed due to the panic above") +} + +func TestSum(t *testing.T) { + type args struct { + l pb.Money + r pb.Money + } + tests := []struct { + name string + args args + want pb.Money + wantErr error + }{ + {"0+0=0", args{mm(0, 0), mm(0, 0)}, mm(0, 0), nil}, + {"Error: currency code on left", args{mmc(0, 0, "XXX"), mm(0, 0)}, mm(0, 0), ErrMismatchingCurrency}, + {"Error: currency code on right", args{mm(0, 0), mmc(0, 0, "YYY")}, mm(0, 0), ErrMismatchingCurrency}, + {"Error: currency code mismatch", args{mmc(0, 0, "AAA"), mmc(0, 0, "BBB")}, mm(0, 0), ErrMismatchingCurrency}, + {"Error: invalid +/-", args{mm(+1, -1), mm(0, 0)}, mm(0, 0), ErrInvalidValue}, + {"Error: invalid -/+", args{mm(0, 0), mm(-1, +2)}, mm(0, 0), ErrInvalidValue}, + {"Error: invalid nanos", args{mm(0, 1000000000), mm(1, 0)}, mm(0, 0), ErrInvalidValue}, + {"both positive (no carry)", args{mm(2, 200000000), mm(2, 200000000)}, mm(4, 400000000), nil}, + {"both positive (nanos=max)", args{mm(2, 111111111), mm(2, 888888888)}, mm(4, 999999999), nil}, + {"both positive (carry)", args{mm(2, 200000000), mm(2, 900000000)}, mm(5, 100000000), nil}, + {"both negative (no carry)", args{mm(-2, -200000000), mm(-2, -200000000)}, mm(-4, -400000000), nil}, + {"both negative (carry)", args{mm(-2, -200000000), mm(-2, -900000000)}, mm(-5, -100000000), nil}, + {"mixed (larger positive, just decimals)", args{mm(11, 0), mm(-2, 0)}, mm(9, 0), nil}, + {"mixed (larger negative, just decimals)", args{mm(-11, 0), mm(2, 0)}, mm(-9, 0), nil}, + {"mixed (larger positive, no borrow)", args{mm(11, 100000000), mm(-2, -100000000)}, mm(9, 0), nil}, + {"mixed (larger positive, with borrow)", args{mm(11, 100000000), mm(-2, -9000000 /*.09*/)}, mm(9, 91000000 /*.091*/), nil}, + {"mixed (larger negative, no borrow)", args{mm(-11, -100000000), mm(2, 100000000)}, mm(-9, 0), nil}, + {"mixed (larger negative, with borrow)", args{mm(-11, -100000000), mm(2, 9000000 /*.09*/)}, mm(-9, -91000000 /*.091*/), nil}, + {"0+negative", args{mm(0, 0), mm(-2, -100000000)}, mm(-2, -100000000), nil}, + {"negative+0", args{mm(-2, -100000000), mm(0, 0)}, mm(-2, -100000000), nil}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := Sum(tt.args.l, tt.args.r) + if err != tt.wantErr { + t.Errorf("Sum([%v],[%v]): expected err=\"%v\" got=\"%v\"", tt.args.l, tt.args.r, tt.wantErr, err) + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("Sum([%v],[%v]) = %v, want %v", tt.args.l, tt.args.r, got, tt.want) + } + }) + } +} diff --git a/src/currencyservice/.dockerignore b/src/currencyservice/.dockerignore new file mode 100644 index 0000000..4d92be1 --- /dev/null +++ b/src/currencyservice/.dockerignore @@ -0,0 +1,2 @@ +client.js +node_modules/ diff --git a/src/currencyservice/.gitignore b/src/currencyservice/.gitignore new file mode 100644 index 0000000..c2658d7 --- /dev/null +++ b/src/currencyservice/.gitignore @@ -0,0 +1 @@ +node_modules/ diff --git a/src/currencyservice/Dockerfile b/src/currencyservice/Dockerfile new file mode 100644 index 0000000..bc7cc15 --- /dev/null +++ b/src/currencyservice/Dockerfile @@ -0,0 +1,42 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +FROM --platform=$BUILDPLATFORM node:20.20.0-alpine@sha256:09e2b3d9726018aecf269bd35325f46bf75046a643a66d28360ec71132750ec8 AS builder + +# Some packages (e.g. @google-cloud/profiler) require additional +# deps for post-install scripts +RUN apk add --update --no-cache \ + python3 \ + make \ + g++ + +WORKDIR /usr/src/app + +COPY package*.json ./ + +RUN npm install --only=production + +FROM alpine:3.23.2@sha256:865b95f46d98cf867a156fe4a135ad3fe50d2056aa3f25ed31662dff6da4eb62 + +RUN apk add --no-cache nodejs + +WORKDIR /usr/src/app + +COPY --from=builder /usr/src/app/node_modules ./node_modules + +COPY . . + +EXPOSE 7000 + +ENTRYPOINT [ "node", "server.js" ] diff --git a/src/currencyservice/client.js b/src/currencyservice/client.js new file mode 100644 index 0000000..64c7c7c --- /dev/null +++ b/src/currencyservice/client.js @@ -0,0 +1,68 @@ +/* + * + * Copyright 2015 gRPC authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +require('@google-cloud/trace-agent').start(); + +const path = require('path'); +const grpc = require('grpc'); +const pino = require('pino'); + +const PROTO_PATH = path.join(__dirname, './proto/demo.proto'); +const PORT = 7000; + +const shopProto = grpc.load(PROTO_PATH).hipstershop; +const client = new shopProto.CurrencyService(`localhost:${PORT}`, + grpc.credentials.createInsecure()); + +const logger = pino({ + name: 'currencyservice-client', + messageKey: 'message', + formatters: { + level (logLevelString, logLevelNum) { + return { severity: logLevelString } + } + } +}); + +const request = { + from: { + currency_code: 'CHF', + units: 300, + nanos: 0 + }, + to_code: 'EUR' +}; + +function _moneyToString (m) { + return `${m.units}.${m.nanos.toString().padStart(9,'0')} ${m.currency_code}`; +} + +client.getSupportedCurrencies({}, (err, response) => { + if (err) { + logger.error(`Error in getSupportedCurrencies: ${err}`); + } else { + logger.info(`Currency codes: ${response.currency_codes}`); + } +}); + +client.convert(request, (err, response) => { + if (err) { + logger.error(`Error in convert: ${err}`); + } else { + logger.log(`Convert: ${_moneyToString(request.from)} to ${_moneyToString(response)}`); + } +}); diff --git a/src/currencyservice/data/currency_conversion.json b/src/currencyservice/data/currency_conversion.json new file mode 100644 index 0000000..bd28709 --- /dev/null +++ b/src/currencyservice/data/currency_conversion.json @@ -0,0 +1,35 @@ +{ + "EUR": "1.0", + "USD": "1.1305", + "JPY": "126.40", + "BGN": "1.9558", + "CZK": "25.592", + "DKK": "7.4609", + "GBP": "0.85970", + "HUF": "315.51", + "PLN": "4.2996", + "RON": "4.7463", + "SEK": "10.5375", + "CHF": "1.1360", + "ISK": "136.80", + "NOK": "9.8040", + "HRK": "7.4210", + "RUB": "74.4208", + "TRY": "6.1247", + "AUD": "1.6072", + "BRL": "4.2682", + "CAD": "1.5128", + "CNY": "7.5857", + "HKD": "8.8743", + "IDR": "15999.40", + "ILS": "4.0875", + "INR": "79.4320", + "KRW": "1275.05", + "MXN": "21.7999", + "MYR": "4.6289", + "NZD": "1.6679", + "PHP": "59.083", + "SGD": "1.5349", + "THB": "36.012", + "ZAR": "16.0583" +} \ No newline at end of file diff --git a/src/currencyservice/genproto.sh b/src/currencyservice/genproto.sh new file mode 100755 index 0000000..34c0fb6 --- /dev/null +++ b/src/currencyservice/genproto.sh @@ -0,0 +1,23 @@ +#!/bin/bash -eu +# +# Copyright 2018 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# [START gke_currencyservice_genproto] + +# protos are loaded dynamically for node, simply copies over the proto. +mkdir -p proto +cp -r ../../protos/* ./proto + +# [END gke_currencyservice_genproto] \ No newline at end of file diff --git a/src/currencyservice/package-lock.json b/src/currencyservice/package-lock.json new file mode 100644 index 0000000..66aedaf --- /dev/null +++ b/src/currencyservice/package-lock.json @@ -0,0 +1,3589 @@ +{ + "name": "grpc-currency-service", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "grpc-currency-service", + "version": "0.1.0", + "license": "Apache-2.0", + "dependencies": { + "@google-cloud/profiler": "6.0.3", + "@google-cloud/trace-agent": "8.0.0", + "@grpc/grpc-js": "1.14.3", + "@grpc/proto-loader": "0.8.0", + "@opentelemetry/api": "1.9.0", + "@opentelemetry/exporter-otlp-grpc": "0.26.0", + "@opentelemetry/instrumentation-grpc": "0.211.0", + "@opentelemetry/resources": "2.5.0", + "@opentelemetry/sdk-node": "0.211.0", + "@opentelemetry/sdk-trace-base": "2.5.0", + "@opentelemetry/semantic-conventions": "1.39.0", + "async": "3.2.6", + "google-protobuf": "4.0.1", + "pino": "10.3.0", + "xml2js": "0.6.2" + } + }, + "node_modules/@google-cloud/common": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@google-cloud/common/-/common-5.0.2.tgz", + "integrity": "sha512-V7bmBKYQyu0eVG2BFejuUjlBt+zrya6vtsKdY+JxMM/dNntPF41vZ9+LhOshEUH01zOHEqBSvI7Dad7ZS6aUeA==", + "license": "Apache-2.0", + "dependencies": { + "@google-cloud/projectify": "^4.0.0", + "@google-cloud/promisify": "^4.0.0", + "arrify": "^2.0.1", + "duplexify": "^4.1.1", + "extend": "^3.0.2", + "google-auth-library": "^9.0.0", + "html-entities": "^2.5.2", + "retry-request": "^7.0.0", + "teeny-request": "^9.0.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@google-cloud/logging-min": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/@google-cloud/logging-min/-/logging-min-11.2.0.tgz", + "integrity": "sha512-o1mwzi1+9NMEjwYZJ0X3tK64obf9PzPVBAhzEJv65L0h7jVl3Fw7GswtsMUkdUvZexf96vAvlZZMvXB9jAIW2Q==", + "license": "Apache-2.0", + "dependencies": { + "@google-cloud/common": "^5.0.0", + "@google-cloud/paginator": "^5.0.0", + "@google-cloud/projectify": "^4.0.0", + "@google-cloud/promisify": "^4.0.0", + "@opentelemetry/api": "^1.7.0", + "arrify": "^2.0.1", + "dot-prop": "^6.0.0", + "eventid": "^2.0.0", + "extend": "^3.0.2", + "gcp-metadata": "^6.0.0", + "google-auth-library": "^9.0.0", + "google-gax": "^4.0.3", + "on-finished": "^2.3.0", + "pumpify": "^2.0.1", + "stream-events": "^1.0.5", + "uuid": "^9.0.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@google-cloud/paginator": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@google-cloud/paginator/-/paginator-5.0.2.tgz", + "integrity": "sha512-DJS3s0OVH4zFDB1PzjxAsHqJT6sKVbRwwML0ZBP9PbU7Yebtu/7SWMRzvO2J3nUi9pRNITCfu4LJeooM2w4pjg==", + "license": "Apache-2.0", + "dependencies": { + "arrify": "^2.0.0", + "extend": "^3.0.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@google-cloud/profiler": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@google-cloud/profiler/-/profiler-6.0.3.tgz", + "integrity": "sha512-Ey8li6Vc2CbfEzOTSZaqKolxPMGacxVUQuhChNT0Wi55a3nfImMiiuDgqYw1In/a9Q3Z62O7jUg2L8f1XwMN7Q==", + "license": "Apache-2.0", + "dependencies": { + "@google-cloud/common": "^5.0.0", + "@google-cloud/logging-min": "^11.0.0", + "@google-cloud/promisify": "~4.0.0", + "@types/console-log-level": "^1.4.0", + "@types/semver": "^7.0.0", + "console-log-level": "^1.4.0", + "delay": "^5.0.0", + "extend": "^3.0.2", + "gcp-metadata": "^6.0.0", + "ms": "^2.1.3", + "pprof": "4.0.0", + "pretty-ms": "^7.0.0", + "protobufjs": "~7.4.0", + "semver": "^7.0.0", + "teeny-request": "^9.0.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@google-cloud/projectify": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@google-cloud/projectify/-/projectify-4.0.0.tgz", + "integrity": "sha512-MmaX6HeSvyPbWGwFq7mXdo0uQZLGBYCwziiLIGq5JVX+/bdI3SAq6bP98trV5eTWfLuvsMcIC1YJOF2vfteLFA==", + "license": "Apache-2.0", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@google-cloud/promisify": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@google-cloud/promisify/-/promisify-4.0.0.tgz", + "integrity": "sha512-Orxzlfb9c67A15cq2JQEyVc7wEsmFBmHjZWZYQMUyJ1qivXyMwdyNOs9odi79hze+2zqdTtu1E19IM/FtqZ10g==", + "license": "Apache-2.0", + "engines": { + "node": ">=14" + } + }, + "node_modules/@google-cloud/trace-agent": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@google-cloud/trace-agent/-/trace-agent-8.0.0.tgz", + "integrity": "sha512-Q9oLnemR7RCZZ5DGZHAtxIRvO+2B9gnP+2HcvBbDIXBCQAMt4EU4jySeI5UpiIHy6KRcL1YWisnsiBAj8zTfMg==", + "dependencies": { + "@google-cloud/common": "^5.0.0", + "@opencensus/propagation-stackdriver": "0.1.0", + "builtin-modules": "^3.0.0", + "console-log-level": "^1.4.0", + "continuation-local-storage": "^3.2.1", + "extend": "^3.0.2", + "gcp-metadata": "^6.0.0", + "google-auth-library": "^9.0.0", + "hex2dec": "^1.0.1", + "is": "^3.2.0", + "methods": "^1.1.1", + "require-in-the-middle": "^7.0.0", + "semver": "^7.0.0", + "shimmer": "^1.2.0", + "source-map-support": "^0.5.16", + "uuid": "^9.0.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@grpc/grpc-js": { + "version": "1.14.3", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.14.3.tgz", + "integrity": "sha512-Iq8QQQ/7X3Sac15oB6p0FmUg/klxQvXLeileoqrTRGJYLV+/9tubbr9ipz0GKHjmXVsgFPo/+W+2cA8eNcR+XA==", + "license": "Apache-2.0", + "dependencies": { + "@grpc/proto-loader": "^0.8.0", + "@js-sdsl/ordered-map": "^4.4.2" + }, + "engines": { + "node": ">=12.10.0" + } + }, + "node_modules/@grpc/proto-loader": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.8.0.tgz", + "integrity": "sha512-rc1hOQtjIWGxcxpb9aHAfLpIctjEnsDehj0DAiVfBlmT84uvR0uUtN2hEi/ecvWVjXUGf5qPF4qEgiLOx1YIMQ==", + "license": "Apache-2.0", + "dependencies": { + "lodash.camelcase": "^4.3.0", + "long": "^5.0.0", + "protobufjs": "^7.5.3", + "yargs": "^17.7.2" + }, + "bin": { + "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@grpc/proto-loader/node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@grpc/proto-loader/node_modules/long": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", + "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", + "license": "Apache-2.0" + }, + "node_modules/@grpc/proto-loader/node_modules/protobufjs": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.4.tgz", + "integrity": "sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg==", + "hasInstallScript": true, + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/node": ">=13.7.0", + "long": "^5.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@grpc/proto-loader/node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@grpc/proto-loader/node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/@js-sdsl/ordered-map": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/@js-sdsl/ordered-map/-/ordered-map-4.4.2.tgz", + "integrity": "sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/js-sdsl" + } + }, + "node_modules/@mapbox/node-pre-gyp": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.10.tgz", + "integrity": "sha512-4ySo4CjzStuprMwk35H5pPbkymjv1SF3jGLj6rAHp/xT/RF7TL7bd9CTm1xDY49K2qF7jmR/g7k+SkLETP6opA==", + "dependencies": { + "detect-libc": "^2.0.0", + "https-proxy-agent": "^5.0.0", + "make-dir": "^3.1.0", + "node-fetch": "^2.6.7", + "nopt": "^5.0.0", + "npmlog": "^5.0.1", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "tar": "^6.1.11" + }, + "bin": { + "node-pre-gyp": "bin/node-pre-gyp" + } + }, + "node_modules/@opencensus/core": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@opencensus/core/-/core-0.1.0.tgz", + "integrity": "sha512-Bdbi5vi44a1fwyHNyKh6bwzuFZJeZJPhzdwogk/Kw5juoEeRGPworK1sgtB3loeR8cqLyi5us0mz9h0xqINiSQ==", + "dependencies": { + "continuation-local-storage": "^3.2.1", + "log-driver": "^1.2.7", + "semver": "^7.0.0", + "shimmer": "^1.2.0", + "uuid": "^8.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@opencensus/core/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/@opencensus/propagation-stackdriver": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@opencensus/propagation-stackdriver/-/propagation-stackdriver-0.1.0.tgz", + "integrity": "sha512-YLklu8jnnYKaJ8gUFz3rM0FVdsWXEJAMLzeeU4JRac6LI34raENy4kvRezZtNEFS5KthaJUsYg04sPc/Ag0w4w==", + "dependencies": { + "@opencensus/core": "^0.1.0", + "hex2dec": "^1.0.1", + "uuid": "^8.0.0" + } + }, + "node_modules/@opencensus/propagation-stackdriver/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/@opentelemetry/api": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", + "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==", + "license": "Apache-2.0", + "peer": true, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@opentelemetry/api-logs": { + "version": "0.211.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.211.0.tgz", + "integrity": "sha512-swFdZq8MCdmdR22jTVGQDhwqDzcI4M10nhjXkLr1EsIzXgZBqm4ZlmmcWsg3TSNf+3mzgOiqveXmBLZuDi2Lgg==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api": "^1.3.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@opentelemetry/api-metrics": { + "version": "0.26.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-metrics/-/api-metrics-0.26.0.tgz", + "integrity": "sha512-idDSUTx+LRwJiHhVHhdh45SWow5u9lKNDROKu5AMzsIVPI29utH5FfT9vor8qMM6blxWWvlT22HUNdNMWqUQfQ==", + "deprecated": "Please use @opentelemetry/api >= 1.3.0", + "engines": { + "node": ">=8.0.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.0.2" + } + }, + "node_modules/@opentelemetry/configuration": { + "version": "0.211.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/configuration/-/configuration-0.211.0.tgz", + "integrity": "sha512-PNsCkzsYQKyv8wiUIsH+loC4RYyblOaDnVASBtKS22hK55ToWs2UP6IsrcfSWWn54wWTvVe2gnfwz67Pvrxf2Q==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.5.0", + "yaml": "^2.0.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.9.0" + } + }, + "node_modules/@opentelemetry/configuration/node_modules/@opentelemetry/core": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.5.0.tgz", + "integrity": "sha512-ka4H8OM6+DlUhSAZpONu0cPBtPPTQKxbxVzC4CzVx5+K4JnroJVBtDzLAMx4/3CDTJXRvVFhpFjtl4SaiTNoyQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/context-async-hooks": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/context-async-hooks/-/context-async-hooks-2.5.0.tgz", + "integrity": "sha512-uOXpVX0ZjO7heSVjhheW2XEPrhQAWr2BScDPoZ9UDycl5iuHG+Usyc3AIfG6kZeC1GyLpMInpQ6X5+9n69yOFw==", + "license": "Apache-2.0", + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/core": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-1.0.0.tgz", + "integrity": "sha512-1+qvKilADnSFW4PiXy+f7D22pvfGVxepZ69GcbF8cTcbQTUt7w63xEBWn5f5j92x9I3c0sqbW1RUx5/a4wgzxA==", + "dependencies": { + "@opentelemetry/semantic-conventions": "1.0.0", + "semver": "^7.3.5" + }, + "engines": { + "node": ">=8.5.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.0.2" + } + }, + "node_modules/@opentelemetry/core/node_modules/@opentelemetry/semantic-conventions": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.0.0.tgz", + "integrity": "sha512-XCZ6ZSmc8FOspxKUU+Ow9UtJeSSRcS5rFBYGpjzix02U2v+X9ofjOjgNRnpvxlSvkccYIhdTuwcvNskmZ46SeA==", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@opentelemetry/exporter-logs-otlp-grpc": { + "version": "0.211.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-logs-otlp-grpc/-/exporter-logs-otlp-grpc-0.211.0.tgz", + "integrity": "sha512-UhOoWENNqyaAMP/dL1YXLkXt6ZBtovkDDs1p4rxto9YwJX1+wMjwg+Obfyg2kwpcMoaiIFT3KQIcLNW8nNGNfQ==", + "license": "Apache-2.0", + "dependencies": { + "@grpc/grpc-js": "^1.7.1", + "@opentelemetry/core": "2.5.0", + "@opentelemetry/otlp-exporter-base": "0.211.0", + "@opentelemetry/otlp-grpc-exporter-base": "0.211.0", + "@opentelemetry/otlp-transformer": "0.211.0", + "@opentelemetry/sdk-logs": "0.211.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/exporter-logs-otlp-grpc/node_modules/@opentelemetry/core": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.5.0.tgz", + "integrity": "sha512-ka4H8OM6+DlUhSAZpONu0cPBtPPTQKxbxVzC4CzVx5+K4JnroJVBtDzLAMx4/3CDTJXRvVFhpFjtl4SaiTNoyQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-logs-otlp-http": { + "version": "0.211.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-logs-otlp-http/-/exporter-logs-otlp-http-0.211.0.tgz", + "integrity": "sha512-c118Awf1kZirHkqxdcF+rF5qqWwNjJh+BB1CmQvN9AQHC/DUIldy6dIkJn3EKlQnQ3HmuNRKc/nHHt5IusN7mA==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api-logs": "0.211.0", + "@opentelemetry/core": "2.5.0", + "@opentelemetry/otlp-exporter-base": "0.211.0", + "@opentelemetry/otlp-transformer": "0.211.0", + "@opentelemetry/sdk-logs": "0.211.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/exporter-logs-otlp-http/node_modules/@opentelemetry/core": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.5.0.tgz", + "integrity": "sha512-ka4H8OM6+DlUhSAZpONu0cPBtPPTQKxbxVzC4CzVx5+K4JnroJVBtDzLAMx4/3CDTJXRvVFhpFjtl4SaiTNoyQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-logs-otlp-proto": { + "version": "0.211.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-logs-otlp-proto/-/exporter-logs-otlp-proto-0.211.0.tgz", + "integrity": "sha512-kMvfKMtY5vJDXeLnwhrZMEwhZ2PN8sROXmzacFU/Fnl4Z79CMrOaL7OE+5X3SObRYlDUa7zVqaXp9ZetYCxfDQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api-logs": "0.211.0", + "@opentelemetry/core": "2.5.0", + "@opentelemetry/otlp-exporter-base": "0.211.0", + "@opentelemetry/otlp-transformer": "0.211.0", + "@opentelemetry/resources": "2.5.0", + "@opentelemetry/sdk-logs": "0.211.0", + "@opentelemetry/sdk-trace-base": "2.5.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/exporter-logs-otlp-proto/node_modules/@opentelemetry/core": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.5.0.tgz", + "integrity": "sha512-ka4H8OM6+DlUhSAZpONu0cPBtPPTQKxbxVzC4CzVx5+K4JnroJVBtDzLAMx4/3CDTJXRvVFhpFjtl4SaiTNoyQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-metrics-otlp-grpc": { + "version": "0.211.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-metrics-otlp-grpc/-/exporter-metrics-otlp-grpc-0.211.0.tgz", + "integrity": "sha512-D/U3G8L4PzZp8ot5hX9wpgbTymgtLZCiwR7heMe4LsbGV4OdctS1nfyvaQHLT6CiGZ6FjKc1Vk9s6kbo9SWLXQ==", + "license": "Apache-2.0", + "dependencies": { + "@grpc/grpc-js": "^1.7.1", + "@opentelemetry/core": "2.5.0", + "@opentelemetry/exporter-metrics-otlp-http": "0.211.0", + "@opentelemetry/otlp-exporter-base": "0.211.0", + "@opentelemetry/otlp-grpc-exporter-base": "0.211.0", + "@opentelemetry/otlp-transformer": "0.211.0", + "@opentelemetry/resources": "2.5.0", + "@opentelemetry/sdk-metrics": "2.5.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/exporter-metrics-otlp-grpc/node_modules/@opentelemetry/core": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.5.0.tgz", + "integrity": "sha512-ka4H8OM6+DlUhSAZpONu0cPBtPPTQKxbxVzC4CzVx5+K4JnroJVBtDzLAMx4/3CDTJXRvVFhpFjtl4SaiTNoyQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-metrics-otlp-http": { + "version": "0.211.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-metrics-otlp-http/-/exporter-metrics-otlp-http-0.211.0.tgz", + "integrity": "sha512-lfHXElPAoDSPpPO59DJdN5FLUnwi1wxluLTWQDayqrSPfWRnluzxRhD+g7rF8wbj1qCz0sdqABl//ug1IZyWvA==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.5.0", + "@opentelemetry/otlp-exporter-base": "0.211.0", + "@opentelemetry/otlp-transformer": "0.211.0", + "@opentelemetry/resources": "2.5.0", + "@opentelemetry/sdk-metrics": "2.5.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/exporter-metrics-otlp-http/node_modules/@opentelemetry/core": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.5.0.tgz", + "integrity": "sha512-ka4H8OM6+DlUhSAZpONu0cPBtPPTQKxbxVzC4CzVx5+K4JnroJVBtDzLAMx4/3CDTJXRvVFhpFjtl4SaiTNoyQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-metrics-otlp-proto": { + "version": "0.211.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-metrics-otlp-proto/-/exporter-metrics-otlp-proto-0.211.0.tgz", + "integrity": "sha512-61iNbffEpyZv/abHaz3BQM3zUtA2kVIDBM+0dS9RK68ML0QFLRGYa50xVMn2PYMToyfszEPEgFC3ypGae2z8FA==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.5.0", + "@opentelemetry/exporter-metrics-otlp-http": "0.211.0", + "@opentelemetry/otlp-exporter-base": "0.211.0", + "@opentelemetry/otlp-transformer": "0.211.0", + "@opentelemetry/resources": "2.5.0", + "@opentelemetry/sdk-metrics": "2.5.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/exporter-metrics-otlp-proto/node_modules/@opentelemetry/core": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.5.0.tgz", + "integrity": "sha512-ka4H8OM6+DlUhSAZpONu0cPBtPPTQKxbxVzC4CzVx5+K4JnroJVBtDzLAMx4/3CDTJXRvVFhpFjtl4SaiTNoyQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-otlp-grpc": { + "version": "0.26.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-otlp-grpc/-/exporter-otlp-grpc-0.26.0.tgz", + "integrity": "sha512-64VPck7CbGhI7c2bj54xaGGHCy4mP+rMnrKBAruNBmUUnN9OgihddNcMsIiHyddUcyC1I+hXS3JLW1G6AvlAmg==", + "deprecated": "Please use trace and metric specific exporters @opentelemetry/exporter-trace-otlp-grpc and @opentelemetry/exporter-metrics-otlp-grpc", + "dependencies": { + "@grpc/grpc-js": "^1.3.7", + "@grpc/proto-loader": "^0.6.4", + "@opentelemetry/core": "1.0.0", + "@opentelemetry/exporter-otlp-http": "0.26.0", + "@opentelemetry/resources": "1.0.0", + "@opentelemetry/sdk-metrics-base": "0.26.0", + "@opentelemetry/sdk-trace-base": "1.0.0" + }, + "engines": { + "node": ">=8.0.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.0.2" + } + }, + "node_modules/@opentelemetry/exporter-otlp-grpc/node_modules/@grpc/proto-loader": { + "version": "0.6.13", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.6.13.tgz", + "integrity": "sha512-FjxPYDRTn6Ec3V0arm1FtSpmP6V50wuph2yILpyvTKzjc76oDdoihXqM1DzOW5ubvCC8GivfCnNtfaRE8myJ7g==", + "dependencies": { + "@types/long": "^4.0.1", + "lodash.camelcase": "^4.3.0", + "long": "^4.0.0", + "protobufjs": "^6.11.3", + "yargs": "^16.2.0" + }, + "bin": { + "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@opentelemetry/exporter-otlp-grpc/node_modules/@opentelemetry/resources": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-1.0.0.tgz", + "integrity": "sha512-ORP8F2LLcJEm5M3H24RmdlMdiDc70ySPushpkrAW34KZGdZXwkrFoFXZhhs5MUxPT+fLrTuBafXxZVr8eHtFuQ==", + "dependencies": { + "@opentelemetry/core": "1.0.0", + "@opentelemetry/semantic-conventions": "1.0.0" + }, + "engines": { + "node": ">=8.0.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.0.2" + } + }, + "node_modules/@opentelemetry/exporter-otlp-grpc/node_modules/@opentelemetry/sdk-trace-base": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-1.0.0.tgz", + "integrity": "sha512-/rXoyQlDlJTJ4SOVAbP0Gpj89B8oZ2hJApYG2Dq5klkgFAtDifN8271TIzwtM8/ET8HUhgx9eyoUJi42LhIesg==", + "dependencies": { + "@opentelemetry/core": "1.0.0", + "@opentelemetry/resources": "1.0.0", + "@opentelemetry/semantic-conventions": "1.0.0", + "lodash.merge": "^4.6.2" + }, + "engines": { + "node": ">=8.0.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.0.2" + } + }, + "node_modules/@opentelemetry/exporter-otlp-grpc/node_modules/@opentelemetry/semantic-conventions": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.0.0.tgz", + "integrity": "sha512-XCZ6ZSmc8FOspxKUU+Ow9UtJeSSRcS5rFBYGpjzix02U2v+X9ofjOjgNRnpvxlSvkccYIhdTuwcvNskmZ46SeA==", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@opentelemetry/exporter-otlp-grpc/node_modules/protobufjs": { + "version": "6.11.4", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.4.tgz", + "integrity": "sha512-5kQWPaJHi1WoCpjTGszzQ32PG2F4+wRY6BmAT4Vfw56Q2FZ4YZzK20xUYQH4YkfehY1e6QSICrJquM6xXZNcrw==", + "hasInstallScript": true, + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/long": "^4.0.1", + "@types/node": ">=13.7.0", + "long": "^4.0.0" + }, + "bin": { + "pbjs": "bin/pbjs", + "pbts": "bin/pbts" + } + }, + "node_modules/@opentelemetry/exporter-otlp-http": { + "version": "0.26.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-otlp-http/-/exporter-otlp-http-0.26.0.tgz", + "integrity": "sha512-V3FcUEIVDZ66b3/6vjSBjwwozf/XV5eUXuELNzN8PAvGZH4mw36vaWlaxnGEV8HaZb2hbu2KbRpcOzqxx3tFDA==", + "deprecated": "Please use trace and metric specific exporters @opentelemetry/exporter-trace-otlp-http and @opentelemetry/exporter-metrics-otlp-http", + "dependencies": { + "@opentelemetry/api-metrics": "0.26.0", + "@opentelemetry/core": "1.0.0", + "@opentelemetry/resources": "1.0.0", + "@opentelemetry/sdk-metrics-base": "0.26.0", + "@opentelemetry/sdk-trace-base": "1.0.0" + }, + "engines": { + "node": ">=8.0.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.0.2" + } + }, + "node_modules/@opentelemetry/exporter-otlp-http/node_modules/@opentelemetry/resources": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-1.0.0.tgz", + "integrity": "sha512-ORP8F2LLcJEm5M3H24RmdlMdiDc70ySPushpkrAW34KZGdZXwkrFoFXZhhs5MUxPT+fLrTuBafXxZVr8eHtFuQ==", + "dependencies": { + "@opentelemetry/core": "1.0.0", + "@opentelemetry/semantic-conventions": "1.0.0" + }, + "engines": { + "node": ">=8.0.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.0.2" + } + }, + "node_modules/@opentelemetry/exporter-otlp-http/node_modules/@opentelemetry/sdk-trace-base": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-1.0.0.tgz", + "integrity": "sha512-/rXoyQlDlJTJ4SOVAbP0Gpj89B8oZ2hJApYG2Dq5klkgFAtDifN8271TIzwtM8/ET8HUhgx9eyoUJi42LhIesg==", + "dependencies": { + "@opentelemetry/core": "1.0.0", + "@opentelemetry/resources": "1.0.0", + "@opentelemetry/semantic-conventions": "1.0.0", + "lodash.merge": "^4.6.2" + }, + "engines": { + "node": ">=8.0.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.0.2" + } + }, + "node_modules/@opentelemetry/exporter-otlp-http/node_modules/@opentelemetry/semantic-conventions": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.0.0.tgz", + "integrity": "sha512-XCZ6ZSmc8FOspxKUU+Ow9UtJeSSRcS5rFBYGpjzix02U2v+X9ofjOjgNRnpvxlSvkccYIhdTuwcvNskmZ46SeA==", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@opentelemetry/exporter-prometheus": { + "version": "0.211.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-prometheus/-/exporter-prometheus-0.211.0.tgz", + "integrity": "sha512-cD0WleEL3TPqJbvxwz5MVdVJ82H8jl8mvMad4bNU24cB5SH2mRW5aMLDTuV4614ll46R//R3RMmci26mc2L99g==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.5.0", + "@opentelemetry/resources": "2.5.0", + "@opentelemetry/sdk-metrics": "2.5.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/exporter-prometheus/node_modules/@opentelemetry/core": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.5.0.tgz", + "integrity": "sha512-ka4H8OM6+DlUhSAZpONu0cPBtPPTQKxbxVzC4CzVx5+K4JnroJVBtDzLAMx4/3CDTJXRvVFhpFjtl4SaiTNoyQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-trace-otlp-grpc": { + "version": "0.211.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-trace-otlp-grpc/-/exporter-trace-otlp-grpc-0.211.0.tgz", + "integrity": "sha512-eFwx4Gvu6LaEiE1rOd4ypgAiWEdZu7Qzm2QNN2nJqPW1XDeAVH1eNwVcVQl+QK9HR/JCDZ78PZgD7xD/DBDqbw==", + "license": "Apache-2.0", + "dependencies": { + "@grpc/grpc-js": "^1.7.1", + "@opentelemetry/core": "2.5.0", + "@opentelemetry/otlp-exporter-base": "0.211.0", + "@opentelemetry/otlp-grpc-exporter-base": "0.211.0", + "@opentelemetry/otlp-transformer": "0.211.0", + "@opentelemetry/resources": "2.5.0", + "@opentelemetry/sdk-trace-base": "2.5.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/exporter-trace-otlp-grpc/node_modules/@opentelemetry/core": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.5.0.tgz", + "integrity": "sha512-ka4H8OM6+DlUhSAZpONu0cPBtPPTQKxbxVzC4CzVx5+K4JnroJVBtDzLAMx4/3CDTJXRvVFhpFjtl4SaiTNoyQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-trace-otlp-http": { + "version": "0.211.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-trace-otlp-http/-/exporter-trace-otlp-http-0.211.0.tgz", + "integrity": "sha512-F1Rv3JeMkgS//xdVjbQMrI3+26e5SXC7vXA6trx8SWEA0OUhw4JHB+qeHtH0fJn46eFItrYbL5m8j4qi9Sfaxw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.5.0", + "@opentelemetry/otlp-exporter-base": "0.211.0", + "@opentelemetry/otlp-transformer": "0.211.0", + "@opentelemetry/resources": "2.5.0", + "@opentelemetry/sdk-trace-base": "2.5.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/exporter-trace-otlp-http/node_modules/@opentelemetry/core": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.5.0.tgz", + "integrity": "sha512-ka4H8OM6+DlUhSAZpONu0cPBtPPTQKxbxVzC4CzVx5+K4JnroJVBtDzLAMx4/3CDTJXRvVFhpFjtl4SaiTNoyQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-trace-otlp-proto": { + "version": "0.211.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-trace-otlp-proto/-/exporter-trace-otlp-proto-0.211.0.tgz", + "integrity": "sha512-DkjXwbPiqpcPlycUojzG2RmR0/SIK8Gi9qWO9znNvSqgzrnAIE9x2n6yPfpZ+kWHZGafvsvA1lVXucTyyQa5Kg==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.5.0", + "@opentelemetry/otlp-exporter-base": "0.211.0", + "@opentelemetry/otlp-transformer": "0.211.0", + "@opentelemetry/resources": "2.5.0", + "@opentelemetry/sdk-trace-base": "2.5.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/exporter-trace-otlp-proto/node_modules/@opentelemetry/core": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.5.0.tgz", + "integrity": "sha512-ka4H8OM6+DlUhSAZpONu0cPBtPPTQKxbxVzC4CzVx5+K4JnroJVBtDzLAMx4/3CDTJXRvVFhpFjtl4SaiTNoyQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-zipkin": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-zipkin/-/exporter-zipkin-2.5.0.tgz", + "integrity": "sha512-bk9VJgFgUAzkZzU8ZyXBSWiUGLOM3mZEgKJ1+jsZclhRnAoDNf+YBdq+G9R3cP0+TKjjWad+vVrY/bE/vRR9lA==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.5.0", + "@opentelemetry/resources": "2.5.0", + "@opentelemetry/sdk-trace-base": "2.5.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.0.0" + } + }, + "node_modules/@opentelemetry/exporter-zipkin/node_modules/@opentelemetry/core": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.5.0.tgz", + "integrity": "sha512-ka4H8OM6+DlUhSAZpONu0cPBtPPTQKxbxVzC4CzVx5+K4JnroJVBtDzLAMx4/3CDTJXRvVFhpFjtl4SaiTNoyQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/instrumentation": { + "version": "0.211.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.211.0.tgz", + "integrity": "sha512-h0nrZEC/zvI994nhg7EgQ8URIHt0uDTwN90r3qQUdZORS455bbx+YebnGeEuFghUT0HlJSrLF4iHw67f+odY+Q==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api-logs": "0.211.0", + "import-in-the-middle": "^2.0.0", + "require-in-the-middle": "^8.0.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-grpc": { + "version": "0.211.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-grpc/-/instrumentation-grpc-0.211.0.tgz", + "integrity": "sha512-bshedE3TaD18OE3oPU15j8bn4vz+3X5mvg9jluoSn/ZjlshCb1FrstjNkTYQuRERWzeMl7WcR8sShr91FcUBXA==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/instrumentation": "0.211.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation/node_modules/require-in-the-middle": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/require-in-the-middle/-/require-in-the-middle-8.0.0.tgz", + "integrity": "sha512-9s0pnM5tH8G4dSI3pms2GboYOs25LwOGnRMxN/Hx3TYT1K0rh6OjaWf4dI0DAQnMyaEXWoGVnSTPQasqwzTTAA==", + "license": "MIT", + "dependencies": { + "debug": "^4.3.5", + "module-details-from-path": "^1.0.3" + }, + "engines": { + "node": ">=9.3.0 || >=8.10.0 <9.0.0" + } + }, + "node_modules/@opentelemetry/otlp-exporter-base": { + "version": "0.211.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-exporter-base/-/otlp-exporter-base-0.211.0.tgz", + "integrity": "sha512-bp1+63V8WPV+bRI9EQG6E9YID1LIHYSZVbp7f+44g9tRzCq+rtw/o4fpL5PC31adcUsFiz/oN0MdLISSrZDdrg==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.5.0", + "@opentelemetry/otlp-transformer": "0.211.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/otlp-exporter-base/node_modules/@opentelemetry/core": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.5.0.tgz", + "integrity": "sha512-ka4H8OM6+DlUhSAZpONu0cPBtPPTQKxbxVzC4CzVx5+K4JnroJVBtDzLAMx4/3CDTJXRvVFhpFjtl4SaiTNoyQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/otlp-grpc-exporter-base": { + "version": "0.211.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-grpc-exporter-base/-/otlp-grpc-exporter-base-0.211.0.tgz", + "integrity": "sha512-mR5X+N4SuphJeb7/K7y0JNMC8N1mB6gEtjyTLv+TSAhl0ZxNQzpSKP8S5Opk90fhAqVYD4R0SQSAirEBlH1KSA==", + "license": "Apache-2.0", + "dependencies": { + "@grpc/grpc-js": "^1.7.1", + "@opentelemetry/core": "2.5.0", + "@opentelemetry/otlp-exporter-base": "0.211.0", + "@opentelemetry/otlp-transformer": "0.211.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/otlp-grpc-exporter-base/node_modules/@opentelemetry/core": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.5.0.tgz", + "integrity": "sha512-ka4H8OM6+DlUhSAZpONu0cPBtPPTQKxbxVzC4CzVx5+K4JnroJVBtDzLAMx4/3CDTJXRvVFhpFjtl4SaiTNoyQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/otlp-transformer": { + "version": "0.211.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-transformer/-/otlp-transformer-0.211.0.tgz", + "integrity": "sha512-julhCJ9dXwkOg9svuuYqqjXLhVaUgyUvO2hWbTxwjvLXX2rG3VtAaB0SzxMnGTuoCZizBT7Xqqm2V7+ggrfCXA==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api-logs": "0.211.0", + "@opentelemetry/core": "2.5.0", + "@opentelemetry/resources": "2.5.0", + "@opentelemetry/sdk-logs": "0.211.0", + "@opentelemetry/sdk-metrics": "2.5.0", + "@opentelemetry/sdk-trace-base": "2.5.0", + "protobufjs": "8.0.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/otlp-transformer/node_modules/@opentelemetry/core": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.5.0.tgz", + "integrity": "sha512-ka4H8OM6+DlUhSAZpONu0cPBtPPTQKxbxVzC4CzVx5+K4JnroJVBtDzLAMx4/3CDTJXRvVFhpFjtl4SaiTNoyQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/otlp-transformer/node_modules/long": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", + "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", + "license": "Apache-2.0" + }, + "node_modules/@opentelemetry/otlp-transformer/node_modules/protobufjs": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-8.0.0.tgz", + "integrity": "sha512-jx6+sE9h/UryaCZhsJWbJtTEy47yXoGNYI4z8ZaRncM0zBKeRqjO2JEcOUYwrYGb1WLhXM1FfMzW3annvFv0rw==", + "hasInstallScript": true, + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/node": ">=13.7.0", + "long": "^5.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@opentelemetry/propagator-b3": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/propagator-b3/-/propagator-b3-2.5.0.tgz", + "integrity": "sha512-g10m4KD73RjHrSvUge+sUxUl8m4VlgnGc6OKvo68a4uMfaLjdFU+AULfvMQE/APq38k92oGUxEzBsAZ8RN/YHg==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.5.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/propagator-b3/node_modules/@opentelemetry/core": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.5.0.tgz", + "integrity": "sha512-ka4H8OM6+DlUhSAZpONu0cPBtPPTQKxbxVzC4CzVx5+K4JnroJVBtDzLAMx4/3CDTJXRvVFhpFjtl4SaiTNoyQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/propagator-jaeger": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/propagator-jaeger/-/propagator-jaeger-2.5.0.tgz", + "integrity": "sha512-t70ErZCncAR/zz5AcGkL0TF25mJiK1FfDPEQCgreyAHZ+mRJ/bNUiCnImIBDlP3mSDXy6N09DbUEKq0ktW98Hg==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.5.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/propagator-jaeger/node_modules/@opentelemetry/core": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.5.0.tgz", + "integrity": "sha512-ka4H8OM6+DlUhSAZpONu0cPBtPPTQKxbxVzC4CzVx5+K4JnroJVBtDzLAMx4/3CDTJXRvVFhpFjtl4SaiTNoyQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/resources": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.5.0.tgz", + "integrity": "sha512-F8W52ApePshpoSrfsSk1H2yJn9aKjCrbpQF1M9Qii0GHzbfVeFUB+rc3X4aggyZD8x9Gu3Slua+s6krmq6Dt8g==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.5.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/resources/node_modules/@opentelemetry/core": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.5.0.tgz", + "integrity": "sha512-ka4H8OM6+DlUhSAZpONu0cPBtPPTQKxbxVzC4CzVx5+K4JnroJVBtDzLAMx4/3CDTJXRvVFhpFjtl4SaiTNoyQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-logs": { + "version": "0.211.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-logs/-/sdk-logs-0.211.0.tgz", + "integrity": "sha512-O5nPwzgg2JHzo59kpQTPUOTzFi0Nv5LxryG27QoXBciX3zWM3z83g+SNOHhiQVYRWFSxoWn1JM2TGD5iNjOwdA==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api-logs": "0.211.0", + "@opentelemetry/core": "2.5.0", + "@opentelemetry/resources": "2.5.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.4.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-logs/node_modules/@opentelemetry/core": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.5.0.tgz", + "integrity": "sha512-ka4H8OM6+DlUhSAZpONu0cPBtPPTQKxbxVzC4CzVx5+K4JnroJVBtDzLAMx4/3CDTJXRvVFhpFjtl4SaiTNoyQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-metrics": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-2.5.0.tgz", + "integrity": "sha512-BeJLtU+f5Gf905cJX9vXFQorAr6TAfK3SPvTFqP+scfIpDQEJfRaGJWta7sJgP+m4dNtBf9y3yvBKVAZZtJQVA==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.5.0", + "@opentelemetry/resources": "2.5.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.9.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-metrics-base": { + "version": "0.26.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics-base/-/sdk-metrics-base-0.26.0.tgz", + "integrity": "sha512-PbJsso7Vy/CLATAOyXbt/VP7ZQ2QYnvlq28lhOWaLPw8aqLogMBvidNGRrt7rF4/hfzLT6pMgpAAcit2C/nUMA==", + "deprecated": "Please use @opentelemetry/sdk-metrics", + "dependencies": { + "@opentelemetry/api-metrics": "0.26.0", + "@opentelemetry/core": "1.0.0", + "@opentelemetry/resources": "1.0.0", + "lodash.merge": "^4.6.2" + }, + "engines": { + "node": ">=8.0.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.0.2" + } + }, + "node_modules/@opentelemetry/sdk-metrics-base/node_modules/@opentelemetry/resources": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-1.0.0.tgz", + "integrity": "sha512-ORP8F2LLcJEm5M3H24RmdlMdiDc70ySPushpkrAW34KZGdZXwkrFoFXZhhs5MUxPT+fLrTuBafXxZVr8eHtFuQ==", + "dependencies": { + "@opentelemetry/core": "1.0.0", + "@opentelemetry/semantic-conventions": "1.0.0" + }, + "engines": { + "node": ">=8.0.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.0.2" + } + }, + "node_modules/@opentelemetry/sdk-metrics-base/node_modules/@opentelemetry/semantic-conventions": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.0.0.tgz", + "integrity": "sha512-XCZ6ZSmc8FOspxKUU+Ow9UtJeSSRcS5rFBYGpjzix02U2v+X9ofjOjgNRnpvxlSvkccYIhdTuwcvNskmZ46SeA==", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@opentelemetry/sdk-metrics/node_modules/@opentelemetry/core": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.5.0.tgz", + "integrity": "sha512-ka4H8OM6+DlUhSAZpONu0cPBtPPTQKxbxVzC4CzVx5+K4JnroJVBtDzLAMx4/3CDTJXRvVFhpFjtl4SaiTNoyQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-node": { + "version": "0.211.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-node/-/sdk-node-0.211.0.tgz", + "integrity": "sha512-+s1eGjoqmPCMptNxcJJD4IxbWJKNLOQFNKhpwkzi2gLkEbCj6LzSHJNhPcLeBrBlBLtlSpibM+FuS7fjZ8SSFQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api-logs": "0.211.0", + "@opentelemetry/configuration": "0.211.0", + "@opentelemetry/context-async-hooks": "2.5.0", + "@opentelemetry/core": "2.5.0", + "@opentelemetry/exporter-logs-otlp-grpc": "0.211.0", + "@opentelemetry/exporter-logs-otlp-http": "0.211.0", + "@opentelemetry/exporter-logs-otlp-proto": "0.211.0", + "@opentelemetry/exporter-metrics-otlp-grpc": "0.211.0", + "@opentelemetry/exporter-metrics-otlp-http": "0.211.0", + "@opentelemetry/exporter-metrics-otlp-proto": "0.211.0", + "@opentelemetry/exporter-prometheus": "0.211.0", + "@opentelemetry/exporter-trace-otlp-grpc": "0.211.0", + "@opentelemetry/exporter-trace-otlp-http": "0.211.0", + "@opentelemetry/exporter-trace-otlp-proto": "0.211.0", + "@opentelemetry/exporter-zipkin": "2.5.0", + "@opentelemetry/instrumentation": "0.211.0", + "@opentelemetry/propagator-b3": "2.5.0", + "@opentelemetry/propagator-jaeger": "2.5.0", + "@opentelemetry/resources": "2.5.0", + "@opentelemetry/sdk-logs": "0.211.0", + "@opentelemetry/sdk-metrics": "2.5.0", + "@opentelemetry/sdk-trace-base": "2.5.0", + "@opentelemetry/sdk-trace-node": "2.5.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-node/node_modules/@opentelemetry/core": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.5.0.tgz", + "integrity": "sha512-ka4H8OM6+DlUhSAZpONu0cPBtPPTQKxbxVzC4CzVx5+K4JnroJVBtDzLAMx4/3CDTJXRvVFhpFjtl4SaiTNoyQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-trace-base": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.5.0.tgz", + "integrity": "sha512-VzRf8LzotASEyNDUxTdaJ9IRJ1/h692WyArDBInf5puLCjxbICD6XkHgpuudis56EndyS7LYFmtTMny6UABNdQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.5.0", + "@opentelemetry/resources": "2.5.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-trace-base/node_modules/@opentelemetry/core": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.5.0.tgz", + "integrity": "sha512-ka4H8OM6+DlUhSAZpONu0cPBtPPTQKxbxVzC4CzVx5+K4JnroJVBtDzLAMx4/3CDTJXRvVFhpFjtl4SaiTNoyQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-trace-node": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-node/-/sdk-trace-node-2.5.0.tgz", + "integrity": "sha512-O6N/ejzburFm2C84aKNrwJVPpt6HSTSq8T0ZUMq3xT2XmqT4cwxUItcL5UWGThYuq8RTcbH8u1sfj6dmRci0Ow==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/context-async-hooks": "2.5.0", + "@opentelemetry/core": "2.5.0", + "@opentelemetry/sdk-trace-base": "2.5.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-trace-node/node_modules/@opentelemetry/core": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.5.0.tgz", + "integrity": "sha512-ka4H8OM6+DlUhSAZpONu0cPBtPPTQKxbxVzC4CzVx5+K4JnroJVBtDzLAMx4/3CDTJXRvVFhpFjtl4SaiTNoyQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/semantic-conventions": { + "version": "1.39.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.39.0.tgz", + "integrity": "sha512-R5R9tb2AXs2IRLNKLBJDynhkfmx7mX0vi8NkhZb3gUkPWHn6HXk5J8iQ/dql0U3ApfWym4kXXmBDRGO+oeOfjg==", + "license": "Apache-2.0", + "engines": { + "node": ">=14" + } + }, + "node_modules/@pinojs/redact": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@pinojs/redact/-/redact-0.4.0.tgz", + "integrity": "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==", + "license": "MIT" + }, + "node_modules/@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==" + }, + "node_modules/@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==" + }, + "node_modules/@protobufjs/codegen": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==" + }, + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==" + }, + "node_modules/@protobufjs/fetch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", + "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", + "dependencies": { + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" + } + }, + "node_modules/@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==" + }, + "node_modules/@protobufjs/inquire": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", + "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==" + }, + "node_modules/@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==" + }, + "node_modules/@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==" + }, + "node_modules/@protobufjs/utf8": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", + "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==" + }, + "node_modules/@tootallnate/once": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", + "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", + "engines": { + "node": ">= 10" + } + }, + "node_modules/@types/caseless": { + "version": "0.12.5", + "resolved": "https://registry.npmjs.org/@types/caseless/-/caseless-0.12.5.tgz", + "integrity": "sha512-hWtVTC2q7hc7xZ/RLbxapMvDMgUnDvKvMOpKal4DrMyfGBUfB1oKaZlIRr6mJL+If3bAP6sV/QneGzF6tJjZDg==", + "license": "MIT" + }, + "node_modules/@types/console-log-level": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@types/console-log-level/-/console-log-level-1.4.2.tgz", + "integrity": "sha512-TnhDAntcJthcCMrR3OAKAUjgHyQgoms1yaBJepGv+BtXi8PLf8aX2L/NMCfofRTpVqW0bLklpGTsuqmUSCR2Uw==" + }, + "node_modules/@types/long": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz", + "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==" + }, + "node_modules/@types/node": { + "version": "18.11.9", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.9.tgz", + "integrity": "sha512-CRpX21/kGdzjOpFsZSkcrXMGIBWMGNIHXXBVFSH+ggkftxg+XYP20TESbh+zFvFj3EQOl5byk0HTRn1IL6hbqg==" + }, + "node_modules/@types/request": { + "version": "2.48.12", + "resolved": "https://registry.npmjs.org/@types/request/-/request-2.48.12.tgz", + "integrity": "sha512-G3sY+NpsA9jnwm0ixhAFQSJ3Q9JkpLZpJbI3GMv0mIAT0y3mRabYeINzal5WOChIiaTEGQYlHOKgkaM9EisWHw==", + "license": "MIT", + "dependencies": { + "@types/caseless": "*", + "@types/node": "*", + "@types/tough-cookie": "*", + "form-data": "^2.5.0" + } + }, + "node_modules/@types/semver": { + "version": "7.3.13", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.13.tgz", + "integrity": "sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw==" + }, + "node_modules/@types/tough-cookie": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz", + "integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==", + "license": "MIT" + }, + "node_modules/abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" + }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "license": "MIT", + "peer": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-import-attributes": { + "version": "1.9.5", + "resolved": "https://registry.npmjs.org/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz", + "integrity": "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==", + "license": "MIT", + "peerDependencies": { + "acorn": "^8" + } + }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/aproba": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", + "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==" + }, + "node_modules/are-we-there-yet": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", + "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==", + "dependencies": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/arrify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", + "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==", + "engines": { + "node": ">=8" + } + }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "license": "MIT" + }, + "node_modules/async-listener": { + "version": "0.6.10", + "resolved": "https://registry.npmjs.org/async-listener/-/async-listener-0.6.10.tgz", + "integrity": "sha512-gpuo6xOyF4D5DE5WvyqZdPA3NGhiT6Qf07l7DCB0wwDEsLvDIbCr6j9S5aj5Ch96dLace5tXVzWBZkxU/c5ohw==", + "dependencies": { + "semver": "^5.3.0", + "shimmer": "^1.1.0" + }, + "engines": { + "node": "<=0.11.8 || >0.11.10" + } + }, + "node_modules/async-listener/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/atomic-sleep": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz", + "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/bignumber.js": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.0.tgz", + "integrity": "sha512-4LwHK4nfDOraBCtst+wOWIHbu1vhvAPJK8g8nROd4iuc3PSEjWif/qwbkh8jwCJz6yDBvtU4KPynETgrfh7y3A==", + "engines": { + "node": "*" + } + }, + "node_modules/bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "dependencies": { + "file-uri-to-path": "1.0.0" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "license": "BSD-3-Clause" + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" + }, + "node_modules/builtin-modules": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz", + "integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "engines": { + "node": ">=10" + } + }, + "node_modules/cjs-module-lexer": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz", + "integrity": "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==", + "license": "MIT" + }, + "node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "bin": { + "color-support": "bin.js" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + }, + "node_modules/console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==" + }, + "node_modules/console-log-level": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/console-log-level/-/console-log-level-1.4.1.tgz", + "integrity": "sha512-VZzbIORbP+PPcN/gg3DXClTLPLg5Slwd5fL2MIc+o1qZ4BXBvWyc6QxPk6T/Mkr6IVjRpoAGf32XxP3ZWMVRcQ==" + }, + "node_modules/continuation-local-storage": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/continuation-local-storage/-/continuation-local-storage-3.2.1.tgz", + "integrity": "sha512-jx44cconVqkCEEyLSKWwkvUXwO561jXMa3LPjTPsm5QR22PA0/mhe33FT4Xb5y74JDvt/Cq+5lm8S8rskLv9ZA==", + "dependencies": { + "async-listener": "^0.6.0", + "emitter-listener": "^1.1.1" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/delay": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/delay/-/delay-5.0.0.tgz", + "integrity": "sha512-ReEBKkIfe4ya47wlPYf/gu5ib6yUG0/Aez0JQZQz94kiWtRQvZIQbTiehsnwHvLSWJnQdhVeqYue7Id1dKr0qw==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==" + }, + "node_modules/detect-libc": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.1.tgz", + "integrity": "sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w==", + "engines": { + "node": ">=8" + } + }, + "node_modules/dot-prop": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-6.0.1.tgz", + "integrity": "sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA==", + "dependencies": { + "is-obj": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/duplexify": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.2.tgz", + "integrity": "sha512-fz3OjcNCHmRP12MJoZMPglx8m4rrFP8rovnk4vT8Fs+aonZoCwGg10dSsQsfP/E62eZcPTMSMP6686fu9Qlqtw==", + "dependencies": { + "end-of-stream": "^1.4.1", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1", + "stream-shift": "^1.0.0" + } + }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" + }, + "node_modules/emitter-listener": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/emitter-listener/-/emitter-listener-1.1.2.tgz", + "integrity": "sha512-Bt1sBAGFHY9DKY+4/2cV6izcKJUf5T7/gkdmkxzX/qv9CcGH8xSwVRW5mtX03SWJtRTWSOpzCuWN9rBFYZepZQ==", + "dependencies": { + "shimmer": "^1.2.0" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "engines": { + "node": ">=6" + } + }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "engines": { + "node": ">=6" + } + }, + "node_modules/eventid": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/eventid/-/eventid-2.0.1.tgz", + "integrity": "sha512-sPNTqiMokAvV048P2c9+foqVJzk49o6d4e0D/sq5jog3pw+4kBgyR0gaM1FM7Mx6Kzd9dztesh9oYz1LWWOpzw==", + "dependencies": { + "uuid": "^8.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/eventid/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + }, + "node_modules/file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==" + }, + "node_modules/findit2": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/findit2/-/findit2-2.2.3.tgz", + "integrity": "sha512-lg/Moejf4qXovVutL0Lz4IsaPoNYMuxt4PA0nGqFxnJ1CTTGGlEO2wKgoDpwknhvZ8k4Q2F+eesgkLbG2Mxfog==", + "engines": { + "node": ">=0.8.22" + } + }, + "node_modules/form-data": { + "version": "2.5.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.5.tgz", + "integrity": "sha512-jqdObeR2rxZZbPSGL+3VckHMYtu+f9//KXBsVny6JSX/pa38Fy+bGjuG8eW/H6USNQWhLi8Num++cU2yOCNz4A==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.35", + "safe-buffer": "^5.2.1" + }, + "engines": { + "node": ">= 0.12" + } + }, + "node_modules/fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gauge": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz", + "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==", + "dependencies": { + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.2", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.1", + "object-assign": "^4.1.1", + "signal-exit": "^3.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/gaxios": { + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-6.7.1.tgz", + "integrity": "sha512-LDODD4TMYx7XXdpwxAVRAIAuB0bzv0s+ywFonY46k126qzQHT9ygyoa9tncmOiQmmDrik65UYsEkv3lbfqQ3yQ==", + "license": "Apache-2.0", + "dependencies": { + "extend": "^3.0.2", + "https-proxy-agent": "^7.0.1", + "is-stream": "^2.0.0", + "node-fetch": "^2.6.9", + "uuid": "^9.0.1" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/gaxios/node_modules/agent-base": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", + "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", + "license": "MIT", + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/gaxios/node_modules/https-proxy-agent": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz", + "integrity": "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.0.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/gcp-metadata": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-6.1.0.tgz", + "integrity": "sha512-Jh/AIwwgaxan+7ZUUmRLCjtchyDiqh4KjBJ5tW3plBZb5iL/BPcso8A5DlzeD9qlw0duCamnNdpFjxwaT0KyKg==", + "license": "Apache-2.0", + "dependencies": { + "gaxios": "^6.0.0", + "json-bigint": "^1.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/google-auth-library": { + "version": "9.14.1", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-9.14.1.tgz", + "integrity": "sha512-Rj+PMjoNFGFTmtItH7gHfbHpGVSb3vmnGK3nwNBqxQF9NoBpttSZI/rc0WiM63ma2uGDQtYEkMHkK9U6937NiA==", + "license": "Apache-2.0", + "dependencies": { + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "gaxios": "^6.1.1", + "gcp-metadata": "^6.1.0", + "gtoken": "^7.0.0", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/google-gax": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-4.4.1.tgz", + "integrity": "sha512-Phyp9fMfA00J3sZbJxbbB4jC55b7DBjE3F6poyL3wKMEBVKA79q6BGuHcTiM28yOzVql0NDbRL8MLLh8Iwk9Dg==", + "license": "Apache-2.0", + "dependencies": { + "@grpc/grpc-js": "^1.10.9", + "@grpc/proto-loader": "^0.7.13", + "@types/long": "^4.0.0", + "abort-controller": "^3.0.0", + "duplexify": "^4.0.0", + "google-auth-library": "^9.3.0", + "node-fetch": "^2.7.0", + "object-hash": "^3.0.0", + "proto3-json-serializer": "^2.0.2", + "protobufjs": "^7.3.2", + "retry-request": "^7.0.0", + "uuid": "^9.0.1" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/google-gax/node_modules/@grpc/proto-loader": { + "version": "0.7.15", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.15.tgz", + "integrity": "sha512-tMXdRCfYVixjuFK+Hk0Q1s38gV9zDiDJfWL3h1rv4Qc39oILCu1TRTDt7+fGUI8K4G1Fj125Hx/ru3azECWTyQ==", + "license": "Apache-2.0", + "dependencies": { + "lodash.camelcase": "^4.3.0", + "long": "^5.0.0", + "protobufjs": "^7.2.5", + "yargs": "^17.7.2" + }, + "bin": { + "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/google-gax/node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/google-gax/node_modules/long": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", + "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", + "license": "Apache-2.0" + }, + "node_modules/google-gax/node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/google-gax/node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/google-protobuf": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/google-protobuf/-/google-protobuf-4.0.1.tgz", + "integrity": "sha512-0I4mx3UtAwpcc8l9Ds5gHcwV2FyPdwvxxBcor8LusE4b6PjoFxEngZRBepjvuQ4B0eZ2iu/Vezh8oRsnpJ9UGA==", + "license": "(BSD-3-Clause AND Apache-2.0)" + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gtoken": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-7.1.0.tgz", + "integrity": "sha512-pCcEwRi+TKpMlxAQObHDQ56KawURgyAf6jtIY046fJ5tIv3zDe/LEIubckAO8fj6JnAxLdmWkUfNyulQ2iKdEw==", + "license": "MIT", + "dependencies": { + "gaxios": "^6.0.0", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==" + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hex2dec": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/hex2dec/-/hex2dec-1.1.2.tgz", + "integrity": "sha512-Yu+q/XWr2fFQ11tHxPq4p4EiNkb2y+lAacJNhAdRXVfRIcDH6gi7htWFnnlIzvqHMHoWeIsfXlNAjZInpAOJDA==" + }, + "node_modules/html-entities": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.5.2.tgz", + "integrity": "sha512-K//PSRMQk4FZ78Kyau+mZurHn3FH0Vwr+H36eE0rPbeYkRRi9YxceYPhuN60UwWorxyKHhqoAJl2OFKa4BVtaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/mdevils" + }, + { + "type": "patreon", + "url": "https://patreon.com/mdevils" + } + ], + "license": "MIT" + }, + "node_modules/http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "dependencies": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/import-in-the-middle": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/import-in-the-middle/-/import-in-the-middle-2.0.0.tgz", + "integrity": "sha512-yNZhyQYqXpkT0AKq3F3KLasUSK4fHvebNH5hOsKQw2dhGSALvQ4U0BqUc5suziKvydO5u5hgN2hy1RJaho8U5A==", + "license": "Apache-2.0", + "dependencies": { + "acorn": "^8.14.0", + "acorn-import-attributes": "^1.9.5", + "cjs-module-lexer": "^1.2.2", + "module-details-from-path": "^1.0.3" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/is": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/is/-/is-3.3.0.tgz", + "integrity": "sha512-nW24QBoPcFGGHJGUwnfpI7Yc5CdqWNdsyHQszVE/z2pKHXzh7FZ5GWhJqSyaQ9wMkQnsTx+kAI8bHlCX4tKdbg==", + "engines": { + "node": "*" + } + }, + "node_modules/is-core-module": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz", + "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==", + "dependencies": { + "has": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/json-bigint": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", + "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", + "dependencies": { + "bignumber.js": "^9.0.0" + } + }, + "node_modules/jwa": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", + "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", + "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", + "license": "MIT", + "dependencies": { + "jwa": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==" + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==" + }, + "node_modules/lodash.sortby": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", + "integrity": "sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==" + }, + "node_modules/log-driver": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/log-driver/-/log-driver-1.2.7.tgz", + "integrity": "sha512-U7KCmLdqsGHBLeWqYlFA0V0Sl6P08EE1ZrmA9cxjUE0WVqT9qnyVDPz1kzpFEP0jdJuFnasWIfSd7fsaNXkpbg==", + "engines": { + "node": ">=0.8.6" + } + }, + "node_modules/long": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", + "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" + }, + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minipass": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.4.tgz", + "integrity": "sha512-I9WPbWHCGu8W+6k1ZiGpPu0GkoKBeorkfKNuAFBNS1HNFJvke82sxvI5bzcCNpWPorkOO5QQ+zomzzwRxejXiw==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/module-details-from-path": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/module-details-from-path/-/module-details-from-path-1.0.3.tgz", + "integrity": "sha512-ySViT69/76t8VhE1xXHK6Ch4NcDd26gx0MzKXLO+F7NOtnqH68d9zF94nT8ZWSxXh8ELOERsnJO/sWt1xZYw5A==" + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/nan": { + "version": "2.17.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.17.0.tgz", + "integrity": "sha512-2ZTgtl0nJsO0KQCjEpxcIr5D+Yv90plTitZt9JBfQvVJDS5seMl3FOvsh3+9CoYWXf/1l5OaZzzF6nDm4cagaQ==" + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/nopt": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", + "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", + "dependencies": { + "abbrev": "1" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/npmlog": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", + "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==", + "dependencies": { + "are-we-there-yet": "^2.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^3.0.0", + "set-blocking": "^2.0.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/on-exit-leak-free": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.0.tgz", + "integrity": "sha512-VuCaZZAjReZ3vUwgOB8LxAosIurDiAW0s13rI1YwmaP++jvcxP77AWoQvenZebpCA2m8WC1/EosPYPMjnRAp/w==" + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parse-ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-2.1.0.tgz", + "integrity": "sha512-kHt7kzLoS9VBZfUsiKjv43mr91ea+U05EyKkEtqp7vNbHxmaVuEqN7XxeEVnGrMtYOAxGrDElSi96K7EgO1zCA==", + "engines": { + "node": ">=6" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" + }, + "node_modules/pino": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/pino/-/pino-10.3.0.tgz", + "integrity": "sha512-0GNPNzHXBKw6U/InGe79A3Crzyk9bcSyObF9/Gfo9DLEf5qj5RF50RSjsu0W1rZ6ZqRGdzDFCRBQvi9/rSGPtA==", + "license": "MIT", + "dependencies": { + "@pinojs/redact": "^0.4.0", + "atomic-sleep": "^1.0.0", + "on-exit-leak-free": "^2.1.0", + "pino-abstract-transport": "^3.0.0", + "pino-std-serializers": "^7.0.0", + "process-warning": "^5.0.0", + "quick-format-unescaped": "^4.0.3", + "real-require": "^0.2.0", + "safe-stable-stringify": "^2.3.1", + "sonic-boom": "^4.0.1", + "thread-stream": "^4.0.0" + }, + "bin": { + "pino": "bin.js" + } + }, + "node_modules/pino-abstract-transport": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-3.0.0.tgz", + "integrity": "sha512-wlfUczU+n7Hy/Ha5j9a/gZNy7We5+cXp8YL+X+PG8S0KXxw7n/JXA3c46Y0zQznIJ83URJiwy7Lh56WLokNuxg==", + "license": "MIT", + "dependencies": { + "split2": "^4.0.0" + } + }, + "node_modules/pino-std-serializers": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-7.0.0.tgz", + "integrity": "sha512-e906FRY0+tV27iq4juKzSYPbUj2do2X2JX4EzSca1631EB2QJQUqGbDuERal7LCtOpxl6x3+nvo9NPZcmjkiFA==", + "license": "MIT" + }, + "node_modules/pprof": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/pprof/-/pprof-4.0.0.tgz", + "integrity": "sha512-Yhfk7Y0G1MYsy97oXxmSG5nvbM1sCz9EALiNhW/isAv5Xf7svzP+1RfGeBlS6mLSgRJvgSLh6Mi5DaisQuPttw==", + "hasInstallScript": true, + "dependencies": { + "@mapbox/node-pre-gyp": "^1.0.9", + "bindings": "^1.2.1", + "delay": "^5.0.0", + "findit2": "^2.2.3", + "nan": "^2.17.0", + "p-limit": "^3.0.0", + "protobufjs": "~7.2.4", + "source-map": "~0.8.0-beta.0", + "split": "^1.0.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/pprof/node_modules/long": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz", + "integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==" + }, + "node_modules/pprof/node_modules/protobufjs": { + "version": "7.2.5", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.2.5.tgz", + "integrity": "sha512-gGXRSXvxQ7UiPgfw8gevrfRWcTlSbOFg+p/N+JVJEK5VhueL2miT6qTymqAmjr1Q5WbOCyJbyrk6JfWKwlFn6A==", + "hasInstallScript": true, + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/node": ">=13.7.0", + "long": "^5.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/pprof/node_modules/source-map": { + "version": "0.8.0-beta.0", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.8.0-beta.0.tgz", + "integrity": "sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==", + "dependencies": { + "whatwg-url": "^7.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/pprof/node_modules/tr46": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", + "integrity": "sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/pprof/node_modules/webidl-conversions": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", + "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==" + }, + "node_modules/pprof/node_modules/whatwg-url": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.1.0.tgz", + "integrity": "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==", + "dependencies": { + "lodash.sortby": "^4.7.0", + "tr46": "^1.0.1", + "webidl-conversions": "^4.0.2" + } + }, + "node_modules/pretty-ms": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-7.0.1.tgz", + "integrity": "sha512-973driJZvxiGOQ5ONsFhOF/DtzPMOMtgC11kCpUrPGMTgqp2q/1gwzCquocrN33is0VZ5GFHXZYMM9l6h67v2Q==", + "dependencies": { + "parse-ms": "^2.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/process-warning": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-5.0.0.tgz", + "integrity": "sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" + }, + "node_modules/proto3-json-serializer": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/proto3-json-serializer/-/proto3-json-serializer-2.0.2.tgz", + "integrity": "sha512-SAzp/O4Yh02jGdRc+uIrGoe87dkN/XtwxfZ4ZyafJHymd79ozp5VG5nyZ7ygqPM5+cpLDjjGnYFUkngonyDPOQ==", + "license": "Apache-2.0", + "dependencies": { + "protobufjs": "^7.2.5" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/protobufjs": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.4.0.tgz", + "integrity": "sha512-mRUWCc3KUU4w1jU8sGxICXH/gNS94DvI1gxqDvBzhj1JpcsimQkYiOJfwsPUykUI5ZaspFbSgmBLER8IrQ3tqw==", + "hasInstallScript": true, + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/node": ">=13.7.0", + "long": "^5.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/protobufjs/node_modules/long": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz", + "integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==", + "license": "Apache-2.0" + }, + "node_modules/pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/pumpify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-2.0.1.tgz", + "integrity": "sha512-m7KOje7jZxrmutanlkS1daj1dS6z6BgslzOXmcSEpIlCxM3VJH7lG5QLeck/6hgF6F4crFf01UtQmNsJfweTAw==", + "dependencies": { + "duplexify": "^4.1.1", + "inherits": "^2.0.3", + "pump": "^3.0.0" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "engines": { + "node": ">=6" + } + }, + "node_modules/quick-format-unescaped": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz", + "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==" + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/real-require": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz", + "integrity": "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==", + "engines": { + "node": ">= 12.13.0" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-in-the-middle": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/require-in-the-middle/-/require-in-the-middle-7.2.0.tgz", + "integrity": "sha512-3TLx5TGyAY6AOqLBoXmHkNql0HIf2RGbuMgCDT2WO/uGVAPJs6h7Kl+bN6TIZGd9bWhWPwnDnTHGtW8Iu77sdw==", + "dependencies": { + "debug": "^4.1.1", + "module-details-from-path": "^1.0.3", + "resolve": "^1.22.1" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/resolve": { + "version": "1.22.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", + "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", + "dependencies": { + "is-core-module": "^2.9.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/retry-request": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/retry-request/-/retry-request-7.0.2.tgz", + "integrity": "sha512-dUOvLMJ0/JJYEn8NrpOaGNE7X3vpI5XlZS/u0ANjqtcZVKnIxP7IgCFwrKTxENw29emmwug53awKtaMm4i9g5w==", + "license": "MIT", + "dependencies": { + "@types/request": "^2.48.8", + "extend": "^3.0.2", + "teeny-request": "^9.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/safe-stable-stringify": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.4.1.tgz", + "integrity": "sha512-dVHE6bMtS/bnL2mwualjc6IxEv1F+OCUpA46pKUj6F8uDbUM0jCCulPqRNPSnWwGNKx5etqMjZYdXtrm5KJZGA==", + "engines": { + "node": ">=10" + } + }, + "node_modules/sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" + }, + "node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==" + }, + "node_modules/shimmer": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/shimmer/-/shimmer-1.2.1.tgz", + "integrity": "sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw==" + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" + }, + "node_modules/sonic-boom": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-4.0.1.tgz", + "integrity": "sha512-hTSD/6JMLyT4r9zeof6UtuBDpjJ9sO08/nmS5djaA9eozT9oOlNdpXSnzcgj4FTqpk3nkLrs61l4gip9r1HCrQ==", + "license": "MIT", + "dependencies": { + "atomic-sleep": "^1.0.0" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/split": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz", + "integrity": "sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==", + "dependencies": { + "through": "2" + }, + "engines": { + "node": "*" + } + }, + "node_modules/split2": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.1.0.tgz", + "integrity": "sha512-VBiJxFkxiXRlUIeyMQi8s4hgvKCSjtknJv/LVYbrgALPwf5zSKmEwV9Lst25AkvMDnvxODugjdl6KZgwKM1WYQ==", + "engines": { + "node": ">= 10.x" + } + }, + "node_modules/stream-events": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/stream-events/-/stream-events-1.0.5.tgz", + "integrity": "sha512-E1GUzBSgvct8Jsb3v2X15pjzN1tYebtbLaMg+eBOUOAxgbLoSbT2NS91ckc5lJD1KfLjId+jXJRgo0qnV5Nerg==", + "dependencies": { + "stubs": "^3.0.0" + } + }, + "node_modules/stream-shift": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz", + "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==" + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/stubs": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/stubs/-/stubs-3.0.0.tgz", + "integrity": "sha512-PdHt7hHUJKxvTCgbKX9C1V/ftOcjJQgz8BZwNfV5c4B6dcGqlpelTbJ999jBGZ2jYiPAwcX5dP6oBwVlBlUbxw==" + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tar": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", + "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/tar/node_modules/minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/teeny-request": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-9.0.0.tgz", + "integrity": "sha512-resvxdc6Mgb7YEThw6G6bExlXKkv6+YbuzGg9xuXxSgxJF7Ozs+o8Y9+2R3sArdWdW8nOokoQb1yrpFB0pQK2g==", + "license": "Apache-2.0", + "dependencies": { + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.0", + "node-fetch": "^2.6.9", + "stream-events": "^1.0.5", + "uuid": "^9.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/thread-stream": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-4.0.0.tgz", + "integrity": "sha512-4iMVL6HAINXWf1ZKZjIPcz5wYaOdPhtO8ATvZ+Xqp3BTdaqtAwQkNmKORqcIo5YkQqGXq5cwfswDwMqqQNrpJA==", + "license": "MIT", + "dependencies": { + "real-require": "^0.2.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==" + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + }, + "node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/wide-align": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", + "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", + "dependencies": { + "string-width": "^1.0.2 || 2 || 3 || 4" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + }, + "node_modules/xml2js": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.6.2.tgz", + "integrity": "sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA==", + "dependencies": { + "sax": ">=0.6.0", + "xmlbuilder": "~11.0.0" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/xmlbuilder": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", + "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, + "node_modules/yaml": { + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.2.tgz", + "integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==", + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + }, + "node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "engines": { + "node": ">=10" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/src/currencyservice/package.json b/src/currencyservice/package.json new file mode 100644 index 0000000..b6d5145 --- /dev/null +++ b/src/currencyservice/package.json @@ -0,0 +1,27 @@ +{ + "name": "grpc-currency-service", + "version": "0.1.0", + "description": "A gRPC currency conversion microservice", + "repository": "https://github.com/GoogleCloudPlatform/microservices-demo", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "license": "Apache-2.0", + "dependencies": { + "@google-cloud/profiler": "6.0.3", + "@google-cloud/trace-agent": "8.0.0", + "@grpc/grpc-js": "1.14.3", + "@grpc/proto-loader": "0.8.0", + "async": "3.2.6", + "google-protobuf": "4.0.1", + "@opentelemetry/api": "1.9.0", + "@opentelemetry/exporter-otlp-grpc": "0.26.0", + "@opentelemetry/instrumentation-grpc": "0.211.0", + "@opentelemetry/resources": "2.5.0", + "@opentelemetry/semantic-conventions": "1.39.0", + "@opentelemetry/sdk-trace-base": "2.5.0", + "@opentelemetry/sdk-node": "0.211.0", + "pino": "10.3.0", + "xml2js": "0.6.2" + } +} diff --git a/src/currencyservice/proto/demo.proto b/src/currencyservice/proto/demo.proto new file mode 100644 index 0000000..9693928 --- /dev/null +++ b/src/currencyservice/proto/demo.proto @@ -0,0 +1,260 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package hipstershop; + +// -----------------Cart service----------------- + +service CartService { + rpc AddItem(AddItemRequest) returns (Empty) {} + rpc GetCart(GetCartRequest) returns (Cart) {} + rpc EmptyCart(EmptyCartRequest) returns (Empty) {} +} + +message CartItem { + string product_id = 1; + int32 quantity = 2; +} + +message AddItemRequest { + string user_id = 1; + CartItem item = 2; +} + +message EmptyCartRequest { + string user_id = 1; +} + +message GetCartRequest { + string user_id = 1; +} + +message Cart { + string user_id = 1; + repeated CartItem items = 2; +} + +message Empty {} + +// ---------------Recommendation service---------- + +service RecommendationService { + rpc ListRecommendations(ListRecommendationsRequest) returns (ListRecommendationsResponse){} +} + +message ListRecommendationsRequest { + string user_id = 1; + repeated string product_ids = 2; +} + +message ListRecommendationsResponse { + repeated string product_ids = 1; +} + +// ---------------Product Catalog---------------- + +service ProductCatalogService { + rpc ListProducts(Empty) returns (ListProductsResponse) {} + rpc GetProduct(GetProductRequest) returns (Product) {} + rpc SearchProducts(SearchProductsRequest) returns (SearchProductsResponse) {} +} + +message Product { + string id = 1; + string name = 2; + string description = 3; + string picture = 4; + Money price_usd = 5; + + // Categories such as "clothing" or "kitchen" that can be used to look up + // other related products. + repeated string categories = 6; +} + +message ListProductsResponse { + repeated Product products = 1; +} + +message GetProductRequest { + string id = 1; +} + +message SearchProductsRequest { + string query = 1; +} + +message SearchProductsResponse { + repeated Product results = 1; +} + +// ---------------Shipping Service---------- + +service ShippingService { + rpc GetQuote(GetQuoteRequest) returns (GetQuoteResponse) {} + rpc ShipOrder(ShipOrderRequest) returns (ShipOrderResponse) {} +} + +message GetQuoteRequest { + Address address = 1; + repeated CartItem items = 2; +} + +message GetQuoteResponse { + Money cost_usd = 1; +} + +message ShipOrderRequest { + Address address = 1; + repeated CartItem items = 2; +} + +message ShipOrderResponse { + string tracking_id = 1; +} + +message Address { + string street_address = 1; + string city = 2; + string state = 3; + string country = 4; + int32 zip_code = 5; +} + +// -----------------Currency service----------------- + +service CurrencyService { + rpc GetSupportedCurrencies(Empty) returns (GetSupportedCurrenciesResponse) {} + rpc Convert(CurrencyConversionRequest) returns (Money) {} +} + +// Represents an amount of money with its currency type. +message Money { + // The 3-letter currency code defined in ISO 4217. + string currency_code = 1; + + // The whole units of the amount. + // For example if `currencyCode` is `"USD"`, then 1 unit is one US dollar. + int64 units = 2; + + // Number of nano (10^-9) units of the amount. + // The value must be between -999,999,999 and +999,999,999 inclusive. + // If `units` is positive, `nanos` must be positive or zero. + // If `units` is zero, `nanos` can be positive, zero, or negative. + // If `units` is negative, `nanos` must be negative or zero. + // For example $-1.75 is represented as `units`=-1 and `nanos`=-750,000,000. + int32 nanos = 3; +} + +message GetSupportedCurrenciesResponse { + // The 3-letter currency code defined in ISO 4217. + repeated string currency_codes = 1; +} + +message CurrencyConversionRequest { + Money from = 1; + + // The 3-letter currency code defined in ISO 4217. + string to_code = 2; +} + +// -------------Payment service----------------- + +service PaymentService { + rpc Charge(ChargeRequest) returns (ChargeResponse) {} +} + +message CreditCardInfo { + string credit_card_number = 1; + int32 credit_card_cvv = 2; + int32 credit_card_expiration_year = 3; + int32 credit_card_expiration_month = 4; +} + +message ChargeRequest { + Money amount = 1; + CreditCardInfo credit_card = 2; +} + +message ChargeResponse { + string transaction_id = 1; +} + +// -------------Email service----------------- + +service EmailService { + rpc SendOrderConfirmation(SendOrderConfirmationRequest) returns (Empty) {} +} + +message OrderItem { + CartItem item = 1; + Money cost = 2; +} + +message OrderResult { + string order_id = 1; + string shipping_tracking_id = 2; + Money shipping_cost = 3; + Address shipping_address = 4; + repeated OrderItem items = 5; +} + +message SendOrderConfirmationRequest { + string email = 1; + OrderResult order = 2; +} + + +// -------------Checkout service----------------- + +service CheckoutService { + rpc PlaceOrder(PlaceOrderRequest) returns (PlaceOrderResponse) {} +} + +message PlaceOrderRequest { + string user_id = 1; + string user_currency = 2; + + Address address = 3; + string email = 5; + CreditCardInfo credit_card = 6; +} + +message PlaceOrderResponse { + OrderResult order = 1; +} + +// ------------Ad service------------------ + +service AdService { + rpc GetAds(AdRequest) returns (AdResponse) {} +} + +message AdRequest { + // List of important key words from the current page describing the context. + repeated string context_keys = 1; +} + +message AdResponse { + repeated Ad ads = 1; +} + +message Ad { + // url to redirect to when an ad is clicked. + string redirect_url = 1; + + // short advertisement text to display. + string text = 2; +} diff --git a/src/currencyservice/proto/grpc/health/v1/health.proto b/src/currencyservice/proto/grpc/health/v1/health.proto new file mode 100644 index 0000000..4b4677b --- /dev/null +++ b/src/currencyservice/proto/grpc/health/v1/health.proto @@ -0,0 +1,43 @@ +// Copyright 2015 The gRPC Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// The canonical version of this proto can be found at +// https://github.com/grpc/grpc-proto/blob/master/grpc/health/v1/health.proto + +syntax = "proto3"; + +package grpc.health.v1; + +option csharp_namespace = "Grpc.Health.V1"; +option go_package = "google.golang.org/grpc/health/grpc_health_v1"; +option java_multiple_files = true; +option java_outer_classname = "HealthProto"; +option java_package = "io.grpc.health.v1"; + +message HealthCheckRequest { + string service = 1; +} + +message HealthCheckResponse { + enum ServingStatus { + UNKNOWN = 0; + SERVING = 1; + NOT_SERVING = 2; + } + ServingStatus status = 1; +} + +service Health { + rpc Check(HealthCheckRequest) returns (HealthCheckResponse); +} diff --git a/src/currencyservice/server.js b/src/currencyservice/server.js new file mode 100644 index 0000000..8d46899 --- /dev/null +++ b/src/currencyservice/server.js @@ -0,0 +1,198 @@ +/* + * Copyright 2018 Google LLC. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +const pino = require('pino'); +const logger = pino({ + name: 'currencyservice-server', + messageKey: 'message', + formatters: { + level (logLevelString, logLevelNum) { + return { severity: logLevelString } + } + } +}); + +if(process.env.DISABLE_PROFILER) { + logger.info("Profiler disabled.") +} +else { + logger.info("Profiler enabled.") + require('@google-cloud/profiler').start({ + serviceContext: { + service: 'currencyservice', + version: '1.0.0' + } + }); +} + +// Register GRPC OTel Instrumentation for trace propagation +// regardless of whether tracing is emitted. +const { GrpcInstrumentation } = require('@opentelemetry/instrumentation-grpc'); +const { registerInstrumentations } = require('@opentelemetry/instrumentation'); + +registerInstrumentations({ + instrumentations: [new GrpcInstrumentation()] +}); + +if(process.env.ENABLE_TRACING == "1") { + logger.info("Tracing enabled.") + + const { resourceFromAttributes } = require('@opentelemetry/resources'); + + const { ATTR_SERVICE_NAME } = require('@opentelemetry/semantic-conventions'); + + const opentelemetry = require('@opentelemetry/sdk-node'); + + const { OTLPTraceExporter } = require('@opentelemetry/exporter-otlp-grpc'); + + const collectorUrl = process.env.COLLECTOR_SERVICE_ADDR; + const traceExporter = new OTLPTraceExporter({url: collectorUrl}); + const sdk = new opentelemetry.NodeSDK({ + resource: resourceFromAttributes({ + [ ATTR_SERVICE_NAME ]: process.env.OTEL_SERVICE_NAME || 'currencyservice', + }), + traceExporter: traceExporter, + }); + + sdk.start() +} +else { + logger.info("Tracing disabled.") +} + +const path = require('path'); +const grpc = require('@grpc/grpc-js'); +const protoLoader = require('@grpc/proto-loader'); + +const MAIN_PROTO_PATH = path.join(__dirname, './proto/demo.proto'); +const HEALTH_PROTO_PATH = path.join(__dirname, './proto/grpc/health/v1/health.proto'); + +const PORT = process.env.PORT; + +const shopProto = _loadProto(MAIN_PROTO_PATH).hipstershop; +const healthProto = _loadProto(HEALTH_PROTO_PATH).grpc.health.v1; + +/** + * Helper function that loads a protobuf file. + */ +function _loadProto (path) { + const packageDefinition = protoLoader.loadSync( + path, + { + keepCase: true, + longs: String, + enums: String, + defaults: true, + oneofs: true + } + ); + return grpc.loadPackageDefinition(packageDefinition); +} + +/** + * Helper function that gets currency data from a stored JSON file + * Uses public data from European Central Bank + */ +function _getCurrencyData (callback) { + const data = require('./data/currency_conversion.json'); + callback(data); +} + +/** + * Helper function that handles decimal/fractional carrying + */ +function _carry (amount) { + const fractionSize = Math.pow(10, 9); + amount.nanos += (amount.units % 1) * fractionSize; + amount.units = Math.floor(amount.units) + Math.floor(amount.nanos / fractionSize); + amount.nanos = amount.nanos % fractionSize; + return amount; +} + +/** + * Lists the supported currencies + */ +function getSupportedCurrencies (call, callback) { + logger.info('Getting supported currencies...'); + _getCurrencyData((data) => { + callback(null, {currency_codes: Object.keys(data)}); + }); +} + +/** + * Converts between currencies + */ +function convert (call, callback) { + try { + _getCurrencyData((data) => { + const request = call.request; + + // Convert: from_currency --> EUR + const from = request.from; + const euros = _carry({ + units: from.units / data[from.currency_code], + nanos: from.nanos / data[from.currency_code] + }); + + euros.nanos = Math.round(euros.nanos); + + // Convert: EUR --> to_currency + const result = _carry({ + units: euros.units * data[request.to_code], + nanos: euros.nanos * data[request.to_code] + }); + + result.units = Math.floor(result.units); + result.nanos = Math.floor(result.nanos); + result.currency_code = request.to_code; + + logger.info(`conversion request successful`); + callback(null, result); + }); + } catch (err) { + logger.error(`conversion request failed: ${err}`); + callback(err.message); + } +} + +/** + * Endpoint for health checks + */ +function check (call, callback) { + callback(null, { status: 'SERVING' }); +} + +/** + * Starts an RPC server that receives requests for the + * CurrencyConverter service at the sample server port + */ +function main () { + logger.info(`Starting gRPC server on port ${PORT}...`); + const server = new grpc.Server(); + server.addService(shopProto.CurrencyService.service, {getSupportedCurrencies, convert}); + server.addService(healthProto.Health.service, {check}); + + server.bindAsync( + `[::]:${PORT}`, + grpc.ServerCredentials.createInsecure(), + function() { + logger.info(`CurrencyService gRPC server started on port ${PORT}`); + server.start(); + }, + ); +} + +main(); diff --git a/src/emailservice/Dockerfile b/src/emailservice/Dockerfile new file mode 100644 index 0000000..b68c434 --- /dev/null +++ b/src/emailservice/Dockerfile @@ -0,0 +1,51 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +FROM --platform=$BUILDPLATFORM python:3.14.2-alpine@sha256:31da4cb527055e4e3d7e9e006dffe9329f84ebea79eaca0a1f1c27ce61e40ca5 AS base + +FROM base AS builder + +ENV PYTHONDONTWRITEBYTECODE=1 +ENV PYTHONUNBUFFERED=1 + +RUN apk update \ + && apk add --no-cache g++ linux-headers \ + && rm -rf /var/cache/apk/* + +# get packages +COPY requirements.txt . +RUN pip install -r requirements.txt + +FROM base + +ENV PYTHONDONTWRITEBYTECODE=1 +ENV PYTHONUNBUFFERED=1 + +# Enable Profiler +ENV ENABLE_PROFILER=1 + +RUN apk update \ + && apk add --no-cache libstdc++ \ + && rm -rf /var/cache/apk/* + +WORKDIR /email_server + +# Grab packages from builder +COPY --from=builder /usr/local/lib/python3.14/ /usr/local/lib/python3.14/ + +# Add the application +COPY . . + +EXPOSE 8080 +ENTRYPOINT [ "python", "email_server.py" ] diff --git a/src/emailservice/demo_pb2.py b/src/emailservice/demo_pb2.py new file mode 100755 index 0000000..fa7589f --- /dev/null +++ b/src/emailservice/demo_pb2.py @@ -0,0 +1,121 @@ +#!/usr/bin/python +# +# Copyright 2018 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: demo.proto +"""Generated protocol buffer code.""" +from google.protobuf.internal import builder as _builder +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import symbol_database as _symbol_database +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\ndemo.proto\x12\x0bhipstershop\"0\n\x08\x43\x61rtItem\x12\x12\n\nproduct_id\x18\x01 \x01(\t\x12\x10\n\x08quantity\x18\x02 \x01(\x05\"F\n\x0e\x41\x64\x64ItemRequest\x12\x0f\n\x07user_id\x18\x01 \x01(\t\x12#\n\x04item\x18\x02 \x01(\x0b\x32\x15.hipstershop.CartItem\"#\n\x10\x45mptyCartRequest\x12\x0f\n\x07user_id\x18\x01 \x01(\t\"!\n\x0eGetCartRequest\x12\x0f\n\x07user_id\x18\x01 \x01(\t\"=\n\x04\x43\x61rt\x12\x0f\n\x07user_id\x18\x01 \x01(\t\x12$\n\x05items\x18\x02 \x03(\x0b\x32\x15.hipstershop.CartItem\"\x07\n\x05\x45mpty\"B\n\x1aListRecommendationsRequest\x12\x0f\n\x07user_id\x18\x01 \x01(\t\x12\x13\n\x0bproduct_ids\x18\x02 \x03(\t\"2\n\x1bListRecommendationsResponse\x12\x13\n\x0bproduct_ids\x18\x01 \x03(\t\"\x84\x01\n\x07Product\x12\n\n\x02id\x18\x01 \x01(\t\x12\x0c\n\x04name\x18\x02 \x01(\t\x12\x13\n\x0b\x64\x65scription\x18\x03 \x01(\t\x12\x0f\n\x07picture\x18\x04 \x01(\t\x12%\n\tprice_usd\x18\x05 \x01(\x0b\x32\x12.hipstershop.Money\x12\x12\n\ncategories\x18\x06 \x03(\t\">\n\x14ListProductsResponse\x12&\n\x08products\x18\x01 \x03(\x0b\x32\x14.hipstershop.Product\"\x1f\n\x11GetProductRequest\x12\n\n\x02id\x18\x01 \x01(\t\"&\n\x15SearchProductsRequest\x12\r\n\x05query\x18\x01 \x01(\t\"?\n\x16SearchProductsResponse\x12%\n\x07results\x18\x01 \x03(\x0b\x32\x14.hipstershop.Product\"^\n\x0fGetQuoteRequest\x12%\n\x07\x61\x64\x64ress\x18\x01 \x01(\x0b\x32\x14.hipstershop.Address\x12$\n\x05items\x18\x02 \x03(\x0b\x32\x15.hipstershop.CartItem\"8\n\x10GetQuoteResponse\x12$\n\x08\x63ost_usd\x18\x01 \x01(\x0b\x32\x12.hipstershop.Money\"_\n\x10ShipOrderRequest\x12%\n\x07\x61\x64\x64ress\x18\x01 \x01(\x0b\x32\x14.hipstershop.Address\x12$\n\x05items\x18\x02 \x03(\x0b\x32\x15.hipstershop.CartItem\"(\n\x11ShipOrderResponse\x12\x13\n\x0btracking_id\x18\x01 \x01(\t\"a\n\x07\x41\x64\x64ress\x12\x16\n\x0estreet_address\x18\x01 \x01(\t\x12\x0c\n\x04\x63ity\x18\x02 \x01(\t\x12\r\n\x05state\x18\x03 \x01(\t\x12\x0f\n\x07\x63ountry\x18\x04 \x01(\t\x12\x10\n\x08zip_code\x18\x05 \x01(\x05\"<\n\x05Money\x12\x15\n\rcurrency_code\x18\x01 \x01(\t\x12\r\n\x05units\x18\x02 \x01(\x03\x12\r\n\x05nanos\x18\x03 \x01(\x05\"8\n\x1eGetSupportedCurrenciesResponse\x12\x16\n\x0e\x63urrency_codes\x18\x01 \x03(\t\"N\n\x19\x43urrencyConversionRequest\x12 \n\x04\x66rom\x18\x01 \x01(\x0b\x32\x12.hipstershop.Money\x12\x0f\n\x07to_code\x18\x02 \x01(\t\"\x90\x01\n\x0e\x43reditCardInfo\x12\x1a\n\x12\x63redit_card_number\x18\x01 \x01(\t\x12\x17\n\x0f\x63redit_card_cvv\x18\x02 \x01(\x05\x12#\n\x1b\x63redit_card_expiration_year\x18\x03 \x01(\x05\x12$\n\x1c\x63redit_card_expiration_month\x18\x04 \x01(\x05\"e\n\rChargeRequest\x12\"\n\x06\x61mount\x18\x01 \x01(\x0b\x32\x12.hipstershop.Money\x12\x30\n\x0b\x63redit_card\x18\x02 \x01(\x0b\x32\x1b.hipstershop.CreditCardInfo\"(\n\x0e\x43hargeResponse\x12\x16\n\x0etransaction_id\x18\x01 \x01(\t\"R\n\tOrderItem\x12#\n\x04item\x18\x01 \x01(\x0b\x32\x15.hipstershop.CartItem\x12 \n\x04\x63ost\x18\x02 \x01(\x0b\x32\x12.hipstershop.Money\"\xbf\x01\n\x0bOrderResult\x12\x10\n\x08order_id\x18\x01 \x01(\t\x12\x1c\n\x14shipping_tracking_id\x18\x02 \x01(\t\x12)\n\rshipping_cost\x18\x03 \x01(\x0b\x32\x12.hipstershop.Money\x12.\n\x10shipping_address\x18\x04 \x01(\x0b\x32\x14.hipstershop.Address\x12%\n\x05items\x18\x05 \x03(\x0b\x32\x16.hipstershop.OrderItem\"V\n\x1cSendOrderConfirmationRequest\x12\r\n\x05\x65mail\x18\x01 \x01(\t\x12\'\n\x05order\x18\x02 \x01(\x0b\x32\x18.hipstershop.OrderResult\"\xa3\x01\n\x11PlaceOrderRequest\x12\x0f\n\x07user_id\x18\x01 \x01(\t\x12\x15\n\ruser_currency\x18\x02 \x01(\t\x12%\n\x07\x61\x64\x64ress\x18\x03 \x01(\x0b\x32\x14.hipstershop.Address\x12\r\n\x05\x65mail\x18\x05 \x01(\t\x12\x30\n\x0b\x63redit_card\x18\x06 \x01(\x0b\x32\x1b.hipstershop.CreditCardInfo\"=\n\x12PlaceOrderResponse\x12\'\n\x05order\x18\x01 \x01(\x0b\x32\x18.hipstershop.OrderResult\"!\n\tAdRequest\x12\x14\n\x0c\x63ontext_keys\x18\x01 \x03(\t\"*\n\nAdResponse\x12\x1c\n\x03\x61\x64s\x18\x01 \x03(\x0b\x32\x0f.hipstershop.Ad\"(\n\x02\x41\x64\x12\x14\n\x0credirect_url\x18\x01 \x01(\t\x12\x0c\n\x04text\x18\x02 \x01(\t2\xca\x01\n\x0b\x43\x61rtService\x12<\n\x07\x41\x64\x64Item\x12\x1b.hipstershop.AddItemRequest\x1a\x12.hipstershop.Empty\"\x00\x12;\n\x07GetCart\x12\x1b.hipstershop.GetCartRequest\x1a\x11.hipstershop.Cart\"\x00\x12@\n\tEmptyCart\x12\x1d.hipstershop.EmptyCartRequest\x1a\x12.hipstershop.Empty\"\x00\x32\x83\x01\n\x15RecommendationService\x12j\n\x13ListRecommendations\x12\'.hipstershop.ListRecommendationsRequest\x1a(.hipstershop.ListRecommendationsResponse\"\x00\x32\x83\x02\n\x15ProductCatalogService\x12G\n\x0cListProducts\x12\x12.hipstershop.Empty\x1a!.hipstershop.ListProductsResponse\"\x00\x12\x44\n\nGetProduct\x12\x1e.hipstershop.GetProductRequest\x1a\x14.hipstershop.Product\"\x00\x12[\n\x0eSearchProducts\x12\".hipstershop.SearchProductsRequest\x1a#.hipstershop.SearchProductsResponse\"\x00\x32\xaa\x01\n\x0fShippingService\x12I\n\x08GetQuote\x12\x1c.hipstershop.GetQuoteRequest\x1a\x1d.hipstershop.GetQuoteResponse\"\x00\x12L\n\tShipOrder\x12\x1d.hipstershop.ShipOrderRequest\x1a\x1e.hipstershop.ShipOrderResponse\"\x00\x32\xb7\x01\n\x0f\x43urrencyService\x12[\n\x16GetSupportedCurrencies\x12\x12.hipstershop.Empty\x1a+.hipstershop.GetSupportedCurrenciesResponse\"\x00\x12G\n\x07\x43onvert\x12&.hipstershop.CurrencyConversionRequest\x1a\x12.hipstershop.Money\"\x00\x32U\n\x0ePaymentService\x12\x43\n\x06\x43harge\x12\x1a.hipstershop.ChargeRequest\x1a\x1b.hipstershop.ChargeResponse\"\x00\x32h\n\x0c\x45mailService\x12X\n\x15SendOrderConfirmation\x12).hipstershop.SendOrderConfirmationRequest\x1a\x12.hipstershop.Empty\"\x00\x32\x62\n\x0f\x43heckoutService\x12O\n\nPlaceOrder\x12\x1e.hipstershop.PlaceOrderRequest\x1a\x1f.hipstershop.PlaceOrderResponse\"\x00\x32H\n\tAdService\x12;\n\x06GetAds\x12\x16.hipstershop.AdRequest\x1a\x17.hipstershop.AdResponse\"\x00\x62\x06proto3') + +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals()) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'demo_pb2', globals()) +if _descriptor._USE_C_DESCRIPTORS == False: + + DESCRIPTOR._options = None + _CARTITEM._serialized_start=27 + _CARTITEM._serialized_end=75 + _ADDITEMREQUEST._serialized_start=77 + _ADDITEMREQUEST._serialized_end=147 + _EMPTYCARTREQUEST._serialized_start=149 + _EMPTYCARTREQUEST._serialized_end=184 + _GETCARTREQUEST._serialized_start=186 + _GETCARTREQUEST._serialized_end=219 + _CART._serialized_start=221 + _CART._serialized_end=282 + _EMPTY._serialized_start=284 + _EMPTY._serialized_end=291 + _LISTRECOMMENDATIONSREQUEST._serialized_start=293 + _LISTRECOMMENDATIONSREQUEST._serialized_end=359 + _LISTRECOMMENDATIONSRESPONSE._serialized_start=361 + _LISTRECOMMENDATIONSRESPONSE._serialized_end=411 + _PRODUCT._serialized_start=414 + _PRODUCT._serialized_end=546 + _LISTPRODUCTSRESPONSE._serialized_start=548 + _LISTPRODUCTSRESPONSE._serialized_end=610 + _GETPRODUCTREQUEST._serialized_start=612 + _GETPRODUCTREQUEST._serialized_end=643 + _SEARCHPRODUCTSREQUEST._serialized_start=645 + _SEARCHPRODUCTSREQUEST._serialized_end=683 + _SEARCHPRODUCTSRESPONSE._serialized_start=685 + _SEARCHPRODUCTSRESPONSE._serialized_end=748 + _GETQUOTEREQUEST._serialized_start=750 + _GETQUOTEREQUEST._serialized_end=844 + _GETQUOTERESPONSE._serialized_start=846 + _GETQUOTERESPONSE._serialized_end=902 + _SHIPORDERREQUEST._serialized_start=904 + _SHIPORDERREQUEST._serialized_end=999 + _SHIPORDERRESPONSE._serialized_start=1001 + _SHIPORDERRESPONSE._serialized_end=1041 + _ADDRESS._serialized_start=1043 + _ADDRESS._serialized_end=1140 + _MONEY._serialized_start=1142 + _MONEY._serialized_end=1202 + _GETSUPPORTEDCURRENCIESRESPONSE._serialized_start=1204 + _GETSUPPORTEDCURRENCIESRESPONSE._serialized_end=1260 + _CURRENCYCONVERSIONREQUEST._serialized_start=1262 + _CURRENCYCONVERSIONREQUEST._serialized_end=1340 + _CREDITCARDINFO._serialized_start=1343 + _CREDITCARDINFO._serialized_end=1487 + _CHARGEREQUEST._serialized_start=1489 + _CHARGEREQUEST._serialized_end=1590 + _CHARGERESPONSE._serialized_start=1592 + _CHARGERESPONSE._serialized_end=1632 + _ORDERITEM._serialized_start=1634 + _ORDERITEM._serialized_end=1716 + _ORDERRESULT._serialized_start=1719 + _ORDERRESULT._serialized_end=1910 + _SENDORDERCONFIRMATIONREQUEST._serialized_start=1912 + _SENDORDERCONFIRMATIONREQUEST._serialized_end=1998 + _PLACEORDERREQUEST._serialized_start=2001 + _PLACEORDERREQUEST._serialized_end=2164 + _PLACEORDERRESPONSE._serialized_start=2166 + _PLACEORDERRESPONSE._serialized_end=2227 + _ADREQUEST._serialized_start=2229 + _ADREQUEST._serialized_end=2262 + _ADRESPONSE._serialized_start=2264 + _ADRESPONSE._serialized_end=2306 + _AD._serialized_start=2308 + _AD._serialized_end=2348 + _CARTSERVICE._serialized_start=2351 + _CARTSERVICE._serialized_end=2553 + _RECOMMENDATIONSERVICE._serialized_start=2556 + _RECOMMENDATIONSERVICE._serialized_end=2687 + _PRODUCTCATALOGSERVICE._serialized_start=2690 + _PRODUCTCATALOGSERVICE._serialized_end=2949 + _SHIPPINGSERVICE._serialized_start=2952 + _SHIPPINGSERVICE._serialized_end=3122 + _CURRENCYSERVICE._serialized_start=3125 + _CURRENCYSERVICE._serialized_end=3308 + _PAYMENTSERVICE._serialized_start=3310 + _PAYMENTSERVICE._serialized_end=3395 + _EMAILSERVICE._serialized_start=3397 + _EMAILSERVICE._serialized_end=3501 + _CHECKOUTSERVICE._serialized_start=3503 + _CHECKOUTSERVICE._serialized_end=3601 + _ADSERVICE._serialized_start=3603 + _ADSERVICE._serialized_end=3675 +# @@protoc_insertion_point(module_scope) diff --git a/src/emailservice/demo_pb2_grpc.py b/src/emailservice/demo_pb2_grpc.py new file mode 100755 index 0000000..47bbf0a --- /dev/null +++ b/src/emailservice/demo_pb2_grpc.py @@ -0,0 +1,822 @@ +#!/usr/bin/python +# +# Copyright 2018 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! +"""Client and server classes corresponding to protobuf-defined services.""" +import grpc + +import demo_pb2 as demo__pb2 + + +class CartServiceStub(object): + """-----------------Cart service----------------- + + """ + + def __init__(self, channel): + """Constructor. + + Args: + channel: A grpc.Channel. + """ + self.AddItem = channel.unary_unary( + '/hipstershop.CartService/AddItem', + request_serializer=demo__pb2.AddItemRequest.SerializeToString, + response_deserializer=demo__pb2.Empty.FromString, + ) + self.GetCart = channel.unary_unary( + '/hipstershop.CartService/GetCart', + request_serializer=demo__pb2.GetCartRequest.SerializeToString, + response_deserializer=demo__pb2.Cart.FromString, + ) + self.EmptyCart = channel.unary_unary( + '/hipstershop.CartService/EmptyCart', + request_serializer=demo__pb2.EmptyCartRequest.SerializeToString, + response_deserializer=demo__pb2.Empty.FromString, + ) + + +class CartServiceServicer(object): + """-----------------Cart service----------------- + + """ + + def AddItem(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def GetCart(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def EmptyCart(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + +def add_CartServiceServicer_to_server(servicer, server): + rpc_method_handlers = { + 'AddItem': grpc.unary_unary_rpc_method_handler( + servicer.AddItem, + request_deserializer=demo__pb2.AddItemRequest.FromString, + response_serializer=demo__pb2.Empty.SerializeToString, + ), + 'GetCart': grpc.unary_unary_rpc_method_handler( + servicer.GetCart, + request_deserializer=demo__pb2.GetCartRequest.FromString, + response_serializer=demo__pb2.Cart.SerializeToString, + ), + 'EmptyCart': grpc.unary_unary_rpc_method_handler( + servicer.EmptyCart, + request_deserializer=demo__pb2.EmptyCartRequest.FromString, + response_serializer=demo__pb2.Empty.SerializeToString, + ), + } + generic_handler = grpc.method_handlers_generic_handler( + 'hipstershop.CartService', rpc_method_handlers) + server.add_generic_rpc_handlers((generic_handler,)) + + + # This class is part of an EXPERIMENTAL API. +class CartService(object): + """-----------------Cart service----------------- + + """ + + @staticmethod + def AddItem(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/hipstershop.CartService/AddItem', + demo__pb2.AddItemRequest.SerializeToString, + demo__pb2.Empty.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def GetCart(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/hipstershop.CartService/GetCart', + demo__pb2.GetCartRequest.SerializeToString, + demo__pb2.Cart.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def EmptyCart(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/hipstershop.CartService/EmptyCart', + demo__pb2.EmptyCartRequest.SerializeToString, + demo__pb2.Empty.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + +class RecommendationServiceStub(object): + """---------------Recommendation service---------- + + """ + + def __init__(self, channel): + """Constructor. + + Args: + channel: A grpc.Channel. + """ + self.ListRecommendations = channel.unary_unary( + '/hipstershop.RecommendationService/ListRecommendations', + request_serializer=demo__pb2.ListRecommendationsRequest.SerializeToString, + response_deserializer=demo__pb2.ListRecommendationsResponse.FromString, + ) + + +class RecommendationServiceServicer(object): + """---------------Recommendation service---------- + + """ + + def ListRecommendations(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + +def add_RecommendationServiceServicer_to_server(servicer, server): + rpc_method_handlers = { + 'ListRecommendations': grpc.unary_unary_rpc_method_handler( + servicer.ListRecommendations, + request_deserializer=demo__pb2.ListRecommendationsRequest.FromString, + response_serializer=demo__pb2.ListRecommendationsResponse.SerializeToString, + ), + } + generic_handler = grpc.method_handlers_generic_handler( + 'hipstershop.RecommendationService', rpc_method_handlers) + server.add_generic_rpc_handlers((generic_handler,)) + + + # This class is part of an EXPERIMENTAL API. +class RecommendationService(object): + """---------------Recommendation service---------- + + """ + + @staticmethod + def ListRecommendations(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/hipstershop.RecommendationService/ListRecommendations', + demo__pb2.ListRecommendationsRequest.SerializeToString, + demo__pb2.ListRecommendationsResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + +class ProductCatalogServiceStub(object): + """---------------Product Catalog---------------- + + """ + + def __init__(self, channel): + """Constructor. + + Args: + channel: A grpc.Channel. + """ + self.ListProducts = channel.unary_unary( + '/hipstershop.ProductCatalogService/ListProducts', + request_serializer=demo__pb2.Empty.SerializeToString, + response_deserializer=demo__pb2.ListProductsResponse.FromString, + ) + self.GetProduct = channel.unary_unary( + '/hipstershop.ProductCatalogService/GetProduct', + request_serializer=demo__pb2.GetProductRequest.SerializeToString, + response_deserializer=demo__pb2.Product.FromString, + ) + self.SearchProducts = channel.unary_unary( + '/hipstershop.ProductCatalogService/SearchProducts', + request_serializer=demo__pb2.SearchProductsRequest.SerializeToString, + response_deserializer=demo__pb2.SearchProductsResponse.FromString, + ) + + +class ProductCatalogServiceServicer(object): + """---------------Product Catalog---------------- + + """ + + def ListProducts(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def GetProduct(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def SearchProducts(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + +def add_ProductCatalogServiceServicer_to_server(servicer, server): + rpc_method_handlers = { + 'ListProducts': grpc.unary_unary_rpc_method_handler( + servicer.ListProducts, + request_deserializer=demo__pb2.Empty.FromString, + response_serializer=demo__pb2.ListProductsResponse.SerializeToString, + ), + 'GetProduct': grpc.unary_unary_rpc_method_handler( + servicer.GetProduct, + request_deserializer=demo__pb2.GetProductRequest.FromString, + response_serializer=demo__pb2.Product.SerializeToString, + ), + 'SearchProducts': grpc.unary_unary_rpc_method_handler( + servicer.SearchProducts, + request_deserializer=demo__pb2.SearchProductsRequest.FromString, + response_serializer=demo__pb2.SearchProductsResponse.SerializeToString, + ), + } + generic_handler = grpc.method_handlers_generic_handler( + 'hipstershop.ProductCatalogService', rpc_method_handlers) + server.add_generic_rpc_handlers((generic_handler,)) + + + # This class is part of an EXPERIMENTAL API. +class ProductCatalogService(object): + """---------------Product Catalog---------------- + + """ + + @staticmethod + def ListProducts(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/hipstershop.ProductCatalogService/ListProducts', + demo__pb2.Empty.SerializeToString, + demo__pb2.ListProductsResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def GetProduct(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/hipstershop.ProductCatalogService/GetProduct', + demo__pb2.GetProductRequest.SerializeToString, + demo__pb2.Product.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def SearchProducts(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/hipstershop.ProductCatalogService/SearchProducts', + demo__pb2.SearchProductsRequest.SerializeToString, + demo__pb2.SearchProductsResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + +class ShippingServiceStub(object): + """---------------Shipping Service---------- + + """ + + def __init__(self, channel): + """Constructor. + + Args: + channel: A grpc.Channel. + """ + self.GetQuote = channel.unary_unary( + '/hipstershop.ShippingService/GetQuote', + request_serializer=demo__pb2.GetQuoteRequest.SerializeToString, + response_deserializer=demo__pb2.GetQuoteResponse.FromString, + ) + self.ShipOrder = channel.unary_unary( + '/hipstershop.ShippingService/ShipOrder', + request_serializer=demo__pb2.ShipOrderRequest.SerializeToString, + response_deserializer=demo__pb2.ShipOrderResponse.FromString, + ) + + +class ShippingServiceServicer(object): + """---------------Shipping Service---------- + + """ + + def GetQuote(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def ShipOrder(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + +def add_ShippingServiceServicer_to_server(servicer, server): + rpc_method_handlers = { + 'GetQuote': grpc.unary_unary_rpc_method_handler( + servicer.GetQuote, + request_deserializer=demo__pb2.GetQuoteRequest.FromString, + response_serializer=demo__pb2.GetQuoteResponse.SerializeToString, + ), + 'ShipOrder': grpc.unary_unary_rpc_method_handler( + servicer.ShipOrder, + request_deserializer=demo__pb2.ShipOrderRequest.FromString, + response_serializer=demo__pb2.ShipOrderResponse.SerializeToString, + ), + } + generic_handler = grpc.method_handlers_generic_handler( + 'hipstershop.ShippingService', rpc_method_handlers) + server.add_generic_rpc_handlers((generic_handler,)) + + + # This class is part of an EXPERIMENTAL API. +class ShippingService(object): + """---------------Shipping Service---------- + + """ + + @staticmethod + def GetQuote(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/hipstershop.ShippingService/GetQuote', + demo__pb2.GetQuoteRequest.SerializeToString, + demo__pb2.GetQuoteResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def ShipOrder(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/hipstershop.ShippingService/ShipOrder', + demo__pb2.ShipOrderRequest.SerializeToString, + demo__pb2.ShipOrderResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + +class CurrencyServiceStub(object): + """-----------------Currency service----------------- + + """ + + def __init__(self, channel): + """Constructor. + + Args: + channel: A grpc.Channel. + """ + self.GetSupportedCurrencies = channel.unary_unary( + '/hipstershop.CurrencyService/GetSupportedCurrencies', + request_serializer=demo__pb2.Empty.SerializeToString, + response_deserializer=demo__pb2.GetSupportedCurrenciesResponse.FromString, + ) + self.Convert = channel.unary_unary( + '/hipstershop.CurrencyService/Convert', + request_serializer=demo__pb2.CurrencyConversionRequest.SerializeToString, + response_deserializer=demo__pb2.Money.FromString, + ) + + +class CurrencyServiceServicer(object): + """-----------------Currency service----------------- + + """ + + def GetSupportedCurrencies(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def Convert(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + +def add_CurrencyServiceServicer_to_server(servicer, server): + rpc_method_handlers = { + 'GetSupportedCurrencies': grpc.unary_unary_rpc_method_handler( + servicer.GetSupportedCurrencies, + request_deserializer=demo__pb2.Empty.FromString, + response_serializer=demo__pb2.GetSupportedCurrenciesResponse.SerializeToString, + ), + 'Convert': grpc.unary_unary_rpc_method_handler( + servicer.Convert, + request_deserializer=demo__pb2.CurrencyConversionRequest.FromString, + response_serializer=demo__pb2.Money.SerializeToString, + ), + } + generic_handler = grpc.method_handlers_generic_handler( + 'hipstershop.CurrencyService', rpc_method_handlers) + server.add_generic_rpc_handlers((generic_handler,)) + + + # This class is part of an EXPERIMENTAL API. +class CurrencyService(object): + """-----------------Currency service----------------- + + """ + + @staticmethod + def GetSupportedCurrencies(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/hipstershop.CurrencyService/GetSupportedCurrencies', + demo__pb2.Empty.SerializeToString, + demo__pb2.GetSupportedCurrenciesResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def Convert(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/hipstershop.CurrencyService/Convert', + demo__pb2.CurrencyConversionRequest.SerializeToString, + demo__pb2.Money.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + +class PaymentServiceStub(object): + """-------------Payment service----------------- + + """ + + def __init__(self, channel): + """Constructor. + + Args: + channel: A grpc.Channel. + """ + self.Charge = channel.unary_unary( + '/hipstershop.PaymentService/Charge', + request_serializer=demo__pb2.ChargeRequest.SerializeToString, + response_deserializer=demo__pb2.ChargeResponse.FromString, + ) + + +class PaymentServiceServicer(object): + """-------------Payment service----------------- + + """ + + def Charge(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + +def add_PaymentServiceServicer_to_server(servicer, server): + rpc_method_handlers = { + 'Charge': grpc.unary_unary_rpc_method_handler( + servicer.Charge, + request_deserializer=demo__pb2.ChargeRequest.FromString, + response_serializer=demo__pb2.ChargeResponse.SerializeToString, + ), + } + generic_handler = grpc.method_handlers_generic_handler( + 'hipstershop.PaymentService', rpc_method_handlers) + server.add_generic_rpc_handlers((generic_handler,)) + + + # This class is part of an EXPERIMENTAL API. +class PaymentService(object): + """-------------Payment service----------------- + + """ + + @staticmethod + def Charge(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/hipstershop.PaymentService/Charge', + demo__pb2.ChargeRequest.SerializeToString, + demo__pb2.ChargeResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + +class EmailServiceStub(object): + """-------------Email service----------------- + + """ + + def __init__(self, channel): + """Constructor. + + Args: + channel: A grpc.Channel. + """ + self.SendOrderConfirmation = channel.unary_unary( + '/hipstershop.EmailService/SendOrderConfirmation', + request_serializer=demo__pb2.SendOrderConfirmationRequest.SerializeToString, + response_deserializer=demo__pb2.Empty.FromString, + ) + + +class EmailServiceServicer(object): + """-------------Email service----------------- + + """ + + def SendOrderConfirmation(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + +def add_EmailServiceServicer_to_server(servicer, server): + rpc_method_handlers = { + 'SendOrderConfirmation': grpc.unary_unary_rpc_method_handler( + servicer.SendOrderConfirmation, + request_deserializer=demo__pb2.SendOrderConfirmationRequest.FromString, + response_serializer=demo__pb2.Empty.SerializeToString, + ), + } + generic_handler = grpc.method_handlers_generic_handler( + 'hipstershop.EmailService', rpc_method_handlers) + server.add_generic_rpc_handlers((generic_handler,)) + + + # This class is part of an EXPERIMENTAL API. +class EmailService(object): + """-------------Email service----------------- + + """ + + @staticmethod + def SendOrderConfirmation(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/hipstershop.EmailService/SendOrderConfirmation', + demo__pb2.SendOrderConfirmationRequest.SerializeToString, + demo__pb2.Empty.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + +class CheckoutServiceStub(object): + """-------------Checkout service----------------- + + """ + + def __init__(self, channel): + """Constructor. + + Args: + channel: A grpc.Channel. + """ + self.PlaceOrder = channel.unary_unary( + '/hipstershop.CheckoutService/PlaceOrder', + request_serializer=demo__pb2.PlaceOrderRequest.SerializeToString, + response_deserializer=demo__pb2.PlaceOrderResponse.FromString, + ) + + +class CheckoutServiceServicer(object): + """-------------Checkout service----------------- + + """ + + def PlaceOrder(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + +def add_CheckoutServiceServicer_to_server(servicer, server): + rpc_method_handlers = { + 'PlaceOrder': grpc.unary_unary_rpc_method_handler( + servicer.PlaceOrder, + request_deserializer=demo__pb2.PlaceOrderRequest.FromString, + response_serializer=demo__pb2.PlaceOrderResponse.SerializeToString, + ), + } + generic_handler = grpc.method_handlers_generic_handler( + 'hipstershop.CheckoutService', rpc_method_handlers) + server.add_generic_rpc_handlers((generic_handler,)) + + + # This class is part of an EXPERIMENTAL API. +class CheckoutService(object): + """-------------Checkout service----------------- + + """ + + @staticmethod + def PlaceOrder(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/hipstershop.CheckoutService/PlaceOrder', + demo__pb2.PlaceOrderRequest.SerializeToString, + demo__pb2.PlaceOrderResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + +class AdServiceStub(object): + """------------Ad service------------------ + + """ + + def __init__(self, channel): + """Constructor. + + Args: + channel: A grpc.Channel. + """ + self.GetAds = channel.unary_unary( + '/hipstershop.AdService/GetAds', + request_serializer=demo__pb2.AdRequest.SerializeToString, + response_deserializer=demo__pb2.AdResponse.FromString, + ) + + +class AdServiceServicer(object): + """------------Ad service------------------ + + """ + + def GetAds(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + +def add_AdServiceServicer_to_server(servicer, server): + rpc_method_handlers = { + 'GetAds': grpc.unary_unary_rpc_method_handler( + servicer.GetAds, + request_deserializer=demo__pb2.AdRequest.FromString, + response_serializer=demo__pb2.AdResponse.SerializeToString, + ), + } + generic_handler = grpc.method_handlers_generic_handler( + 'hipstershop.AdService', rpc_method_handlers) + server.add_generic_rpc_handlers((generic_handler,)) + + + # This class is part of an EXPERIMENTAL API. +class AdService(object): + """------------Ad service------------------ + + """ + + @staticmethod + def GetAds(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/hipstershop.AdService/GetAds', + demo__pb2.AdRequest.SerializeToString, + demo__pb2.AdResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) diff --git a/src/emailservice/email_client.py b/src/emailservice/email_client.py new file mode 100755 index 0000000..4411ca0 --- /dev/null +++ b/src/emailservice/email_client.py @@ -0,0 +1,39 @@ +#!/usr/bin/python +# +# Copyright 2018 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import grpc + +import demo_pb2 +import demo_pb2_grpc + +from logger import getJSONLogger +logger = getJSONLogger('emailservice-client') + +def send_confirmation_email(email, order): + channel = grpc.insecure_channel('[::]:8080') + stub = demo_pb2_grpc.EmailServiceStub(channel) + try: + response = stub.SendOrderConfirmation(demo_pb2.SendOrderConfirmationRequest( + email = email, + order = order + )) + logger.info('Request sent.') + except grpc.RpcError as err: + logger.error(err.details()) + logger.error('{}, {}'.format(err.code().name, err.code().value)) + +if __name__ == '__main__': + logger.info('Client for email service.') diff --git a/src/emailservice/email_server.py b/src/emailservice/email_server.py new file mode 100755 index 0000000..b14771e --- /dev/null +++ b/src/emailservice/email_server.py @@ -0,0 +1,200 @@ +#!/usr/bin/python +# +# Copyright 2018 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from concurrent import futures +import argparse +import os +import sys +import time +import grpc +import traceback +from jinja2 import Environment, FileSystemLoader, select_autoescape, TemplateError +from google.api_core.exceptions import GoogleAPICallError +from google.auth.exceptions import DefaultCredentialsError + +import demo_pb2 +import demo_pb2_grpc +from grpc_health.v1 import health_pb2 +from grpc_health.v1 import health_pb2_grpc + +from opentelemetry import trace +from opentelemetry.instrumentation.grpc import GrpcInstrumentorServer +from opentelemetry.sdk.trace import TracerProvider +from opentelemetry.sdk.trace.export import BatchSpanProcessor +from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter + +# @TODO: Temporarily removed in https://github.com/GoogleCloudPlatform/microservices-demo/pull/3196 +# import googlecloudprofiler + +from logger import getJSONLogger +logger = getJSONLogger('emailservice-server') + +# Loads confirmation email template from file +env = Environment( + loader=FileSystemLoader('templates'), + autoescape=select_autoescape(['html', 'xml']) +) +template = env.get_template('confirmation.html') + +class BaseEmailService(demo_pb2_grpc.EmailServiceServicer): + def Check(self, request, context): + return health_pb2.HealthCheckResponse( + status=health_pb2.HealthCheckResponse.SERVING) + + def Watch(self, request, context): + return health_pb2.HealthCheckResponse( + status=health_pb2.HealthCheckResponse.UNIMPLEMENTED) + +class EmailService(BaseEmailService): + def __init__(self): + raise Exception('cloud mail client not implemented') + super().__init__() + + @staticmethod + def send_email(client, email_address, content): + response = client.send_message( + sender = client.sender_path(project_id, region, sender_id), + envelope_from_authority = '', + header_from_authority = '', + envelope_from_address = from_address, + simple_message = { + "from": { + "address_spec": from_address, + }, + "to": [{ + "address_spec": email_address + }], + "subject": "Your Confirmation Email", + "html_body": content + } + ) + logger.info("Message sent: {}".format(response.rfc822_message_id)) + + def SendOrderConfirmation(self, request, context): + email = request.email + order = request.order + + try: + confirmation = template.render(order = order) + except TemplateError as err: + context.set_details("An error occurred when preparing the confirmation mail.") + logger.error(err.message) + context.set_code(grpc.StatusCode.INTERNAL) + return demo_pb2.Empty() + + try: + EmailService.send_email(self.client, email, confirmation) + except GoogleAPICallError as err: + context.set_details("An error occurred when sending the email.") + print(err.message) + context.set_code(grpc.StatusCode.INTERNAL) + return demo_pb2.Empty() + + return demo_pb2.Empty() + +class DummyEmailService(BaseEmailService): + def SendOrderConfirmation(self, request, context): + logger.info('A request to send order confirmation email to {} has been received.'.format(request.email)) + return demo_pb2.Empty() + +class HealthCheck(): + def Check(self, request, context): + return health_pb2.HealthCheckResponse( + status=health_pb2.HealthCheckResponse.SERVING) + +def start(dummy_mode): + server = grpc.server(futures.ThreadPoolExecutor(max_workers=10),) + service = None + if dummy_mode: + service = DummyEmailService() + else: + raise Exception('non-dummy mode not implemented yet') + + demo_pb2_grpc.add_EmailServiceServicer_to_server(service, server) + health_pb2_grpc.add_HealthServicer_to_server(service, server) + + port = os.environ.get('PORT', "8080") + logger.info("listening on port: "+port) + server.add_insecure_port('[::]:'+port) + server.start() + try: + while True: + time.sleep(3600) + except KeyboardInterrupt: + server.stop(0) + +def initStackdriverProfiling(): + project_id = None + try: + project_id = os.environ["GCP_PROJECT_ID"] + except KeyError: + # Environment variable not set + pass + + # @TODO: Temporarily removed in https://github.com/GoogleCloudPlatform/microservices-demo/pull/3196 + # for retry in range(1,4): + # try: + # if project_id: + # googlecloudprofiler.start(service='email_server', service_version='1.0.0', verbose=0, project_id=project_id) + # else: + # googlecloudprofiler.start(service='email_server', service_version='1.0.0', verbose=0) + # logger.info("Successfully started Stackdriver Profiler.") + # return + # except (BaseException) as exc: + # logger.info("Unable to start Stackdriver Profiler Python agent. " + str(exc)) + # if (retry < 4): + # logger.info("Sleeping %d to retry initializing Stackdriver Profiler"%(retry*10)) + # time.sleep (1) + # else: + # logger.warning("Could not initialize Stackdriver Profiler after retrying, giving up") + return + + +if __name__ == '__main__': + logger.info('starting the email service in dummy mode.') + + # Profiler + try: + if "DISABLE_PROFILER" in os.environ: + raise KeyError() + else: + logger.info("Profiler enabled.") + initStackdriverProfiling() + except KeyError: + logger.info("Profiler disabled.") + + # Tracing + try: + if os.environ["ENABLE_TRACING"] == "1": + otel_endpoint = os.getenv("COLLECTOR_SERVICE_ADDR", "localhost:4317") + trace.set_tracer_provider(TracerProvider()) + trace.get_tracer_provider().add_span_processor( + BatchSpanProcessor( + OTLPSpanExporter( + endpoint = otel_endpoint, + insecure = True + ) + ) + ) + grpc_server_instrumentor = GrpcInstrumentorServer() + grpc_server_instrumentor.instrument() + + except (KeyError, DefaultCredentialsError): + logger.info("Tracing disabled.") + except Exception as e: + logger.warn(f"Exception on Cloud Trace setup: {traceback.format_exc()}, tracing disabled.") + + start(dummy_mode = True) diff --git a/src/emailservice/genproto.sh b/src/emailservice/genproto.sh new file mode 100755 index 0000000..f67056a --- /dev/null +++ b/src/emailservice/genproto.sh @@ -0,0 +1,21 @@ +#!/bin/bash -eu +# +# Copyright 2018 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# [START gke_emailservice_genproto] + +python -m grpc_tools.protoc -I../../protos --python_out=. --grpc_python_out=. ../../protos/demo.proto + +# [END gke_emailservice_genproto] \ No newline at end of file diff --git a/src/emailservice/logger.py b/src/emailservice/logger.py new file mode 100755 index 0000000..dc84910 --- /dev/null +++ b/src/emailservice/logger.py @@ -0,0 +1,41 @@ +#!/usr/bin/python +# +# Copyright 2018 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import logging +import sys +from pythonjsonlogger import jsonlogger + +# TODO(yoshifumi) this class is duplicated since other Python services are +# not sharing the modules for logging. +class CustomJsonFormatter(jsonlogger.JsonFormatter): + def add_fields(self, log_record, record, message_dict): + super(CustomJsonFormatter, self).add_fields(log_record, record, message_dict) + if not log_record.get('timestamp'): + log_record['timestamp'] = record.created + if log_record.get('severity'): + log_record['severity'] = log_record['severity'].upper() + else: + log_record['severity'] = record.levelname + +def getJSONLogger(name): + logger = logging.getLogger(name) + handler = logging.StreamHandler(sys.stdout) + formatter = CustomJsonFormatter('%(timestamp)s %(severity)s %(name)s %(message)s') + handler.setFormatter(formatter) + logger.addHandler(handler) + logger.setLevel(logging.INFO) + logger.propagate = False + return logger diff --git a/src/emailservice/requirements.in b/src/emailservice/requirements.in new file mode 100644 index 0000000..f61dbbd --- /dev/null +++ b/src/emailservice/requirements.in @@ -0,0 +1,10 @@ +google-api-core==2.28.1 +grpcio-health-checking==1.76.0 +grpcio==1.76.0 +jinja2==3.1.6 +python-json-logger==4.0.0 +google-cloud-trace==1.17.0 +requests==2.32.5 +opentelemetry-distro==0.60b1 +opentelemetry-instrumentation-grpc==0.60b1 +opentelemetry-exporter-otlp-proto-grpc==1.39.1 diff --git a/src/emailservice/requirements.txt b/src/emailservice/requirements.txt new file mode 100644 index 0000000..765dc1d --- /dev/null +++ b/src/emailservice/requirements.txt @@ -0,0 +1,120 @@ +# This file was autogenerated by uv via the following command: +# uv pip compile requirements.in -o requirements.txt +cachetools==5.3.2 + # via google-auth +certifi==2024.7.4 + # via requests +charset-normalizer==3.3.2 + # via requests +google-api-core[grpc]==2.28.1 + # via + # -r requirements.in + # google-cloud-trace +google-auth==2.23.4 + # via + # google-api-core + # google-cloud-trace +google-cloud-trace==1.17.0 + # via -r requirements.in +googleapis-common-protos==1.72.0 + # via + # google-api-core + # grpcio-status + # opentelemetry-exporter-otlp-proto-grpc +grpcio==1.76.0 + # via + # -r requirements.in + # google-api-core + # google-cloud-trace + # grpcio-health-checking + # grpcio-status + # opentelemetry-exporter-otlp-proto-grpc +grpcio-health-checking==1.76.0 + # via -r requirements.in +grpcio-status==1.76.0 + # via google-api-core +idna==3.7 + # via requests +importlib-metadata==6.8.0 + # via opentelemetry-api +jinja2==3.1.6 + # via -r requirements.in +markupsafe==2.1.3 + # via jinja2 +opentelemetry-api==1.39.1 + # via + # opentelemetry-distro + # opentelemetry-exporter-otlp-proto-grpc + # opentelemetry-instrumentation + # opentelemetry-instrumentation-grpc + # opentelemetry-sdk + # opentelemetry-semantic-conventions +opentelemetry-distro==0.60b1 + # via -r requirements.in +opentelemetry-exporter-otlp-proto-common==1.39.1 + # via opentelemetry-exporter-otlp-proto-grpc +opentelemetry-exporter-otlp-proto-grpc==1.39.1 + # via -r requirements.in +opentelemetry-instrumentation==0.60b1 + # via + # opentelemetry-distro + # opentelemetry-instrumentation-grpc +opentelemetry-instrumentation-grpc==0.60b1 + # via -r requirements.in +opentelemetry-proto==1.39.1 + # via + # opentelemetry-exporter-otlp-proto-common + # opentelemetry-exporter-otlp-proto-grpc +opentelemetry-sdk==1.39.1 + # via + # opentelemetry-distro + # opentelemetry-exporter-otlp-proto-grpc +opentelemetry-semantic-conventions==0.60b1 + # via + # opentelemetry-instrumentation + # opentelemetry-instrumentation-grpc + # opentelemetry-sdk +packaging==25.0 + # via opentelemetry-instrumentation +proto-plus==1.27.0 + # via + # google-api-core + # google-cloud-trace +protobuf==6.33.2 + # via + # google-api-core + # google-cloud-trace + # googleapis-common-protos + # grpcio-health-checking + # grpcio-status + # opentelemetry-proto + # proto-plus +pyasn1==0.5.0 + # via + # pyasn1-modules + # rsa +pyasn1-modules==0.3.0 + # via google-auth +python-json-logger==4.0.0 + # via -r requirements.in +requests==2.32.5 + # via + # -r requirements.in + # google-api-core +rsa==4.9 + # via google-auth +typing-extensions==4.15.0 + # via + # grpcio + # opentelemetry-api + # opentelemetry-exporter-otlp-proto-grpc + # opentelemetry-sdk + # opentelemetry-semantic-conventions +urllib3==2.6.3 + # via requests +wrapt==1.16.0 + # via + # opentelemetry-instrumentation + # opentelemetry-instrumentation-grpc +zipp==3.19.1 + # via importlib-metadata diff --git a/src/emailservice/templates/confirmation.html b/src/emailservice/templates/confirmation.html new file mode 100644 index 0000000..b655745 --- /dev/null +++ b/src/emailservice/templates/confirmation.html @@ -0,0 +1,53 @@ + + + + + + Your Order Confirmation + + + + +

Your Order Confirmation

+

Thanks for shopping with us!

+

Order ID

+

#{{ order.order_id }}

+

Shipping

+

#{{ order.shipping_tracking_id }}

+

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

+

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

+

Items

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

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

+ +
+{{ end }} diff --git a/src/frontend/templates/assistant.html b/src/frontend/templates/assistant.html new file mode 100644 index 0000000..9a23dca --- /dev/null +++ b/src/frontend/templates/assistant.html @@ -0,0 +1,213 @@ + + +{{ define "assistant" }} + +{{ template "header" . }} +
+ + {{$.platform_name}} + +
+ +
+
+
+
+
+
+

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

+

+ What can I help you with? +

+
+
+ + + +
+
+
+
+
+
+ + + +{{ end }} diff --git a/src/frontend/templates/cart.html b/src/frontend/templates/cart.html new file mode 100644 index 0000000..aced5d7 --- /dev/null +++ b/src/frontend/templates/cart.html @@ -0,0 +1,233 @@ + + +{{ define "cart" }} + {{ template "header" . }} + +
+ + {{$.platform_name}} + +
+ +
+ + {{ if eq (len $.items) 0 }} +
+

Your shopping cart is empty!

+

Items you add to your shopping cart will appear here.

+ Continue Shopping +
+ {{ else }} +
+
+ +
+ +
+
+

Cart ({{ $.cart_size }})

+
+
+
+ + + Continue Shopping + +
+
+
+ + {{ range $.items }} +
+
+ + + +
+
+
+
+

{{ .Item.Name }}

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

Shipping Address

+
+
+ +
+
+ + +
+
+ +
+
+ + +
+
+ +
+
+ + +
+
+ +
+
+ + +
+
+ +
+
+ + +
+
+ + +
+
+ +
+
+

Payment Method

+
+
+ +
+
+ + +
+
+ +
+
+ + + +
+
+ + + +
+
+ + +
+
+ +
+
+ +
+
+ +
+ +
+ +
+
+ {{ end }} + +
+ + {{ if $.recommendations }} + {{ template "recommendations" $ }} + {{ end }} + + {{ template "footer" . }} +{{ end }} diff --git a/src/frontend/templates/error.html b/src/frontend/templates/error.html new file mode 100644 index 0000000..6cf38aa --- /dev/null +++ b/src/frontend/templates/error.html @@ -0,0 +1,40 @@ + + +{{ define "error" }} + {{ template "header" . }} +
+ + {{$.platform_name}} + +
+
+
+
+

Uh, oh!

+

Something has failed. Below are some details for debugging.

+ +

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

+
+                    {{- .error -}}
+                
+
+
+
+ + {{ template "footer" . }} + {{ end }} \ No newline at end of file diff --git a/src/frontend/templates/footer.html b/src/frontend/templates/footer.html new file mode 100644 index 0000000..8e03b95 --- /dev/null +++ b/src/frontend/templates/footer.html @@ -0,0 +1,56 @@ + + +{{ define "footer" }} + +
+ +
+ + + + +{{ end }} diff --git a/src/frontend/templates/header.html b/src/frontend/templates/header.html new file mode 100644 index 0000000..21777c4 --- /dev/null +++ b/src/frontend/templates/header.html @@ -0,0 +1,102 @@ + + +{{ define "header" }} + + + + + + + + + {{ if $.is_cymbal_brand }} + Cymbal Shops + {{ else }} + Online Boutique + {{ end }} + + + + + + + + + + + {{ if $.is_cymbal_brand }} + + {{ else }} + + {{ end }} + + + +
+ {{ if $.frontendMessage }} + + {{ end }} + + +
+ {{end}} diff --git a/src/frontend/templates/home.html b/src/frontend/templates/home.html new file mode 100644 index 0000000..afe87a9 --- /dev/null +++ b/src/frontend/templates/home.html @@ -0,0 +1,80 @@ + + +{{ define "home" }} + +{{ template "header" . }} +
+ + {{$.platform_name}} + +
+
+ + +
+ +
+
+ + + + + + +
+ +
+ +
+

Hot Products

+
+ + {{ range $.products }} +
+ + +
+
+
+
{{ .Item.Name }}
+
{{ renderMoney .Price }}
+
+
+ {{ end }} + +
+ + + + +
+ +
+
+ +
+ + +
+ {{ template "footer" . }} +
+ +{{ end }} diff --git a/src/frontend/templates/order.html b/src/frontend/templates/order.html new file mode 100644 index 0000000..7c46151 --- /dev/null +++ b/src/frontend/templates/order.html @@ -0,0 +1,80 @@ + + +{{ define "order" }} + + {{ template "header" . }} + +
+ + {{$.platform_name}} + +
+ +
+ +
+
+
+

+ Your order is complete! +

+
+
+

We've sent you a confirmation email.

+
+
+
+
+ Confirmation # +
+
+ {{.order.OrderId}} +
+
+
+
+ Tracking # +
+
+ {{.order.ShippingTrackingId}} +
+
+
+
+ Total Paid +
+
+ {{renderMoney .total_paid}} +
+
+ +
+ + {{ if $.recommendations }} + {{ template "recommendations" $ }} + {{ end }} + +
+ + {{ template "footer" . }} + {{ end }} diff --git a/src/frontend/templates/product.html b/src/frontend/templates/product.html new file mode 100644 index 0000000..ea811ee --- /dev/null +++ b/src/frontend/templates/product.html @@ -0,0 +1,86 @@ + + +{{ define "product" }} +{{ template "header" . }} +
+ + {{$.platform_name}} + +
+ +
+
+
+
+ +
+
+
+ +

{{ $.product.Item.Name }}

+

{{ renderMoney $.product.Price }}

+

{{ $.product.Item.Description }}

+ + {{ if $.packagingInfo }} +
+

Packaging

+ + Weight: {{ if $.packagingInfo.Weight }}{{ $.packagingInfo.Weight }}lb{{ else }}n/a{{ end }} + + + Width: {{ if $.packagingInfo.Width }}{{ $.packagingInfo.Width }}cm{{ else }}n/a{{ end }} + + + Height: {{ if $.packagingInfo.Height }}{{ $.packagingInfo.Height }}cm{{ else }}n/a{{ end }} + + + Depth: {{ if $.packagingInfo.Depth }}{{ $.packagingInfo.Depth }}cm{{ else }}n/a{{ end }} + +
+ {{ end }} + +
+ +
+ + +
+ +
+
+
+
+
+
+ {{ if $.recommendations}} + {{ template "recommendations" $ }} + {{ end }} +
+
+ {{ if $.ad }}{{ template "text_ad" $ }}{{ end }} +
+ +
+{{ template "footer" . }} +{{ end }} diff --git a/src/frontend/templates/recommendations.html b/src/frontend/templates/recommendations.html new file mode 100644 index 0000000..1700c71 --- /dev/null +++ b/src/frontend/templates/recommendations.html @@ -0,0 +1,43 @@ + + +{{ define "recommendations" }} +
+
+
+
+

You May Also Like

+
+ {{ range .recommendations }} +
+
+ + + +
+
+ {{ .Name }} +
+
+
+
+ {{ end }} +
+
+
+
+
+{{ end }} diff --git a/src/frontend/validator/validator.go b/src/frontend/validator/validator.go new file mode 100644 index 0000000..cc335ff --- /dev/null +++ b/src/frontend/validator/validator.go @@ -0,0 +1,83 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package validator + +import ( + "errors" + "fmt" + + "github.com/go-playground/validator/v10" +) + +var validate *validator.Validate + +// init() is a special function that will run when this package is imported. +// It instantiates a SINGLE instance of *validator.Validate with the added +// benefit of caching struct info and validations. +func init() { + validate = validator.New(validator.WithRequiredStructEnabled()) +} + +type Payload interface { + Validate() error +} + +type AddToCartPayload struct { + Quantity uint64 `validate:"required,gte=1,lte=10"` + ProductID string `validate:"required"` +} + +type PlaceOrderPayload struct { + Email string `validate:"required,email"` + StreetAddress string `validate:"required,max=512"` + ZipCode int64 `validate:"required"` + City string `validate:"required,max=128"` + State string `validate:"required,max=128"` + Country string `validate:"required,max=128"` + CcNumber string `validate:"required,credit_card"` + CcMonth int64 `validate:"required,gte=1,lte=12"` + CcYear int64 `validate:"required"` + CcCVV int64 `validate:"required"` +} + +type SetCurrencyPayload struct { + Currency string `validate:"required,iso4217"` +} + +// Implementations of the 'Payload' interface. +func (ad *AddToCartPayload) Validate() error { + return validate.Struct(ad) +} + +func (po *PlaceOrderPayload) Validate() error { + return validate.Struct(po) +} + +func (sc *SetCurrencyPayload) Validate() error { + return validate.Struct(sc) +} + +// Reusable error response function. +func ValidationErrorResponse(err error) error { + validationErrs, ok := err.(validator.ValidationErrors) + if !ok { + return errors.New("invalid validation error format") + } + var msg string + for _, err := range validationErrs { + msg += fmt.Sprintf("Field '%s' is invalid: %s\n", err.Field(), err.Tag()) + } + return fmt.Errorf("%s", msg) +} diff --git a/src/frontend/validator/validator_test.go b/src/frontend/validator/validator_test.go new file mode 100644 index 0000000..429c52a --- /dev/null +++ b/src/frontend/validator/validator_test.go @@ -0,0 +1,185 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package validator + +import ( + "strings" + "testing" +) + +func TestPlaceOrderPassesValidation(t *testing.T) { + tests := []struct { + name string + email string + streetAddress string + zipCode int64 + city string + state string + country string + ccNumber string + ccMonth int64 + ccYear int64 + ccCVV int64 + }{ + {"valid", "test@example.com", "12345 example street", 10004, "New York", "New York", "United States", "5272940000751666", 4, 2024, 584}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + payload := PlaceOrderPayload{ + Email: tt.email, + StreetAddress: tt.streetAddress, + ZipCode: tt.zipCode, + City: tt.city, + State: tt.state, + Country: tt.country, + CcNumber: tt.ccNumber, + CcMonth: tt.ccMonth, + CcYear: tt.ccYear, + CcCVV: tt.ccCVV, + } + if err := payload.Validate(); err != nil { + t.Errorf("want validation on %v, got %v", payload, err) + } + }) + } +} + +func TestPlaceOrderFailsValidation(t *testing.T) { + tests := []struct { + name string + email string + streetAddress string + zipCode int64 + city string + state string + country string + ccNumber string + ccMonth int64 + ccYear int64 + ccCVV int64 + }{ + {"invalid email", "test@example", "12345 example street", 10004, "New York", "New York", "United States", "5272940000751666", 4, 2024, 584}, + {"invalid address (too long)", "test@example.com", strings.Repeat("12345 example street", 513), 10004, "New York", "New York", "United States", "5272940000751666", 4, 2024, 584}, + {"invalid zip code", "test@example.com", "12345 example street", 0, "New York", "New York", "United States", "5272940000751666", 4, 2024, 584}, + {"invalid city", "test@example.com", "12345 example street", 10004, "", "New York", "United States", "5272940000751666", 4, 2024, 584}, + {"invalid state", "test@example.com", "12345 example street", 10004, "New York", "", "United States", "5272940000751666", 4, 2024, 584}, + {"invalid country", "test@example.com", "12345 example street", 10004, "New York", "New York", "", "5272940000751666", 4, 2024, 584}, + {"invalid ccNumber", "test@example.com", "12345 example street", 10004, "New York", "New York", "United States", "5272940000", 4, 2024, 584}, + {"invalid ccMonth (month < 1)", "test@example.com", "12345 example street", 10004, "New York", "New York", "United States", "5272940000751666", 0, 2024, 584}, + {"invalid ccMonth (month > 12)", "test@example.com", "12345 example street", 10004, "New York", "New York", "United States", "5272940000751666", 13, 2024, 584}, + {"invalid ccYear (not provided)", "test@example.com", "12345 example street", 10004, "New York", "New York", "United States", "5272940000751666", 12, 0, 584}, + {"invalid ccCVV (not provided)", "test@example.com", "12345 example street", 10004, "New York", "New York", "United States", "5272940000751666", 12, 2024, 0}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + payload := PlaceOrderPayload{ + Email: tt.email, + StreetAddress: tt.streetAddress, + ZipCode: tt.zipCode, + City: tt.city, + State: tt.state, + Country: tt.country, + CcNumber: tt.ccNumber, + CcMonth: tt.ccMonth, + CcYear: tt.ccYear, + CcCVV: tt.ccCVV, + } + if err := payload.Validate(); err == nil { + t.Errorf("want validation on %v, got %v", payload, err) + } + }) + } +} + +func TestAddToCartPassesValidation(t *testing.T) { + tests := []struct { + name string + quantity uint64 + productID string + }{ + {"valid min quantity and product id", 1, "OLJCESPC7Z"}, + {"valid max quantity and product id", 10, "OLJCESPC7Z"}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + payload := AddToCartPayload{Quantity: tt.quantity, ProductID: tt.productID} + if err := payload.Validate(); err != nil { + t.Errorf("want validation on %v, got %v", payload, err) + } + }) + } +} + +func TestAddToCartFailsValidation(t *testing.T) { + tests := []struct { + name string + quantity uint64 + productID string + }{ + {"invalid min quantity", 0, "OLJCESPC7Z"}, + {"invalid max quantity", 11, "OLJCESPC7Z"}, + {"invalid product id", 1, ""}, + {"invalid quantity and product id", 0, ""}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + payload := AddToCartPayload{Quantity: tt.quantity, ProductID: tt.productID} + if err := payload.Validate(); err == nil { + t.Errorf("want validation on %v, got %v", payload, err) + } + }) + } +} + +func TestSetCurrencyPassesValidation(t *testing.T) { + tests := []struct { + name string + currency string + }{ + {"valid currency (EUR)", "EUR"}, + {"valid currency (USD)", "USD"}, + {"valid currency (JPY)", "JPY"}, + {"valid currency (GBP)", "GBP"}, + {"valid currency (TRY)", "TRY"}, + {"valid currency (CAD)", "CAD"}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + payload := SetCurrencyPayload{Currency: tt.currency} + if err := payload.Validate(); err != nil { + t.Errorf("want validation on %v, got %v", payload, err) + } + }) + } +} + +func TestSetCurrencyFailsValidation(t *testing.T) { + tests := []struct { + name string + currency string + }{ + {"invalid currency", "ABC"}, + {"invalid currency (symbol)", "$"}, + {"invalid (no currency)", ""}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + payload := SetCurrencyPayload{Currency: tt.currency} + if err := payload.Validate(); err == nil { + t.Errorf("want validation on %v, got %v", payload, err) + } + }) + } +} diff --git a/src/loadgenerator/Dockerfile b/src/loadgenerator/Dockerfile new file mode 100644 index 0000000..ffb8e85 --- /dev/null +++ b/src/loadgenerator/Dockerfile @@ -0,0 +1,49 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +FROM --platform=$BUILDPLATFORM python:3.14.2-alpine@sha256:31da4cb527055e4e3d7e9e006dffe9329f84ebea79eaca0a1f1c27ce61e40ca5 AS base + +FROM base AS builder + +ENV PYTHONDONTWRITEBYTECODE=1 +ENV PYTHONUNBUFFERED=1 + +RUN apk update \ + && apk add --no-cache g++ linux-headers \ + && rm -rf /var/cache/apk/* + +COPY requirements.txt . + +RUN pip install --prefix="/install" -r requirements.txt + +FROM base + +ENV PYTHONDONTWRITEBYTECODE=1 +ENV PYTHONUNBUFFERED=1 + +RUN apk update \ + && apk add --no-cache libstdc++ \ + && rm -rf /var/cache/apk/* + +WORKDIR /loadgen + +COPY --from=builder /install /usr/local + +# Add application code. +COPY locustfile.py . + +# enable gevent support in debugger +ENV GEVENT_SUPPORT=True + +ENTRYPOINT locust --host="http://${FRONTEND_ADDR}" --headless -u "${USERS:-10}" -r "${RATE:-1}" 2>&1 diff --git a/src/loadgenerator/locustfile.py b/src/loadgenerator/locustfile.py new file mode 100755 index 0000000..3b38354 --- /dev/null +++ b/src/loadgenerator/locustfile.py @@ -0,0 +1,92 @@ +#!/usr/bin/python +# +# Copyright 2018 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import random +from locust import FastHttpUser, TaskSet, between +from faker import Faker +import datetime +fake = Faker() + +products = [ + '0PUK6V6EV0', + '1YMWWN1N4O', + '2ZYFJ3GM2N', + '66VCHSJNUP', + '6E92ZMYYFZ', + '9SIQT8TOJO', + 'L9ECAV7KIM', + 'LS4PSXUNUM', + 'OLJCESPC7Z'] + +def index(l): + l.client.get("/") + +def setCurrency(l): + currencies = ['EUR', 'USD', 'JPY', 'CAD', 'GBP', 'TRY'] + l.client.post("/setCurrency", + {'currency_code': random.choice(currencies)}) + +def browseProduct(l): + l.client.get("/product/" + random.choice(products)) + +def viewCart(l): + l.client.get("/cart") + +def addToCart(l): + product = random.choice(products) + l.client.get("/product/" + product) + l.client.post("/cart", { + 'product_id': product, + 'quantity': random.randint(1,10)}) + +def empty_cart(l): + l.client.post('/cart/empty') + +def checkout(l): + addToCart(l) + current_year = datetime.datetime.now().year+1 + l.client.post("/cart/checkout", { + 'email': fake.email(), + 'street_address': fake.street_address(), + 'zip_code': fake.zipcode(), + 'city': fake.city(), + 'state': fake.state_abbr(), + 'country': fake.country(), + 'credit_card_number': fake.credit_card_number(card_type="visa"), + 'credit_card_expiration_month': random.randint(1, 12), + 'credit_card_expiration_year': random.randint(current_year, current_year + 70), + 'credit_card_cvv': f"{random.randint(100, 999)}", + }) + +def logout(l): + l.client.get('/logout') + + +class UserBehavior(TaskSet): + + def on_start(self): + index(self) + + tasks = {index: 1, + setCurrency: 2, + browseProduct: 10, + addToCart: 2, + viewCart: 3, + checkout: 1} + +class WebsiteUser(FastHttpUser): + tasks = [UserBehavior] + wait_time = between(1, 10) diff --git a/src/loadgenerator/requirements.in b/src/loadgenerator/requirements.in new file mode 100644 index 0000000..deea27f --- /dev/null +++ b/src/loadgenerator/requirements.in @@ -0,0 +1,2 @@ +locust==2.43.0 +faker==40.1.0 diff --git a/src/loadgenerator/requirements.txt b/src/loadgenerator/requirements.txt new file mode 100644 index 0000000..9336d2f --- /dev/null +++ b/src/loadgenerator/requirements.txt @@ -0,0 +1,100 @@ +# This file was autogenerated by uv via the following command: +# uv pip compile requirements.in -o requirements.txt +bidict==0.23.1 + # via python-socketio +blinker==1.9.0 + # via flask +brotli==1.2.0 + # via geventhttpclient +certifi==2025.8.3 + # via + # geventhttpclient + # requests +charset-normalizer==3.4.3 + # via requests +click==8.3.0 + # via flask +configargparse==1.7.1 + # via locust +faker==40.1.0 + # via -r requirements.in +flask==3.1.2 + # via + # flask-cors + # flask-login + # locust +flask-cors==6.0.1 + # via locust +flask-login==0.6.3 + # via locust +gevent==25.9.1 + # via + # geventhttpclient + # locust +geventhttpclient==2.3.4 + # via locust +greenlet==3.2.4 + # via gevent +h11==0.16.0 + # via wsproto +idna==3.10 + # via requests +iniconfig==2.1.0 + # via pytest +itsdangerous==2.2.0 + # via flask +jinja2==3.1.6 + # via flask +locust==2.43.0 + # via -r requirements.in +markupsafe==3.0.2 + # via + # flask + # jinja2 + # werkzeug +msgpack==1.1.1 + # via locust +packaging==25.0 + # via pytest +pluggy==1.6.0 + # via pytest +psutil==7.1.0 + # via locust +pygments==2.19.2 + # via pytest +pytest==8.4.2 + # via locust +python-engineio==4.12.2 + # via + # locust + # python-socketio +python-socketio[client]==5.13.0 + # via locust +pyzmq==27.1.0 + # via locust +requests==2.32.5 + # via + # locust + # python-socketio +simple-websocket==1.1.0 + # via python-engineio +tzdata==2025.2 + # via faker +urllib3==2.6.3 + # via + # geventhttpclient + # requests +websocket-client==1.8.0 + # via python-socketio +werkzeug==3.1.5 + # via + # flask + # flask-cors + # flask-login + # locust +wsproto==1.2.0 + # via simple-websocket +zope-event==6.0 + # via gevent +zope-interface==8.0 + # via gevent diff --git a/src/paymentservice/.dockerignore b/src/paymentservice/.dockerignore new file mode 100644 index 0000000..3c3629e --- /dev/null +++ b/src/paymentservice/.dockerignore @@ -0,0 +1 @@ +node_modules diff --git a/src/paymentservice/Dockerfile b/src/paymentservice/Dockerfile new file mode 100644 index 0000000..aa05d64 --- /dev/null +++ b/src/paymentservice/Dockerfile @@ -0,0 +1,42 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +FROM --platform=$BUILDPLATFORM node:20.20.0-alpine@sha256:09e2b3d9726018aecf269bd35325f46bf75046a643a66d28360ec71132750ec8 AS builder + +# Some packages (e.g. @google-cloud/profiler) require additional +# deps for post-install scripts +RUN apk add --update --no-cache \ + python3 \ + make \ + g++ + +WORKDIR /usr/src/app + +COPY package*.json ./ + +RUN npm install --only=production + +FROM alpine:3.23.2@sha256:865b95f46d98cf867a156fe4a135ad3fe50d2056aa3f25ed31662dff6da4eb62 + +RUN apk add --no-cache nodejs + +WORKDIR /usr/src/app + +COPY --from=builder /usr/src/app/node_modules ./node_modules + +COPY . . + +EXPOSE 50051 + +ENTRYPOINT [ "node", "index.js" ] diff --git a/src/paymentservice/charge.js b/src/paymentservice/charge.js new file mode 100644 index 0000000..ffeae34 --- /dev/null +++ b/src/paymentservice/charge.js @@ -0,0 +1,86 @@ +// Copyright 2018 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +const cardValidator = require('simple-card-validator'); +const { v4: uuidv4 } = require('uuid'); +const pino = require('pino'); + +const logger = pino({ + name: 'paymentservice-charge', + messageKey: 'message', + formatters: { + level (logLevelString, logLevelNum) { + return { severity: logLevelString } + } + } +}); + + +class CreditCardError extends Error { + constructor (message) { + super(message); + this.code = 400; // Invalid argument error + } +} + +class InvalidCreditCard extends CreditCardError { + constructor (cardType) { + super(`Credit card info is invalid`); + } +} + +class UnacceptedCreditCard extends CreditCardError { + constructor (cardType) { + super(`Sorry, we cannot process ${cardType} credit cards. Only VISA or MasterCard is accepted.`); + } +} + +class ExpiredCreditCard extends CreditCardError { + constructor (number, month, year) { + super(`Your credit card (ending ${number.substr(-4)}) expired on ${month}/${year}`); + } +} + +/** + * Verifies the credit card number and (pretend) charges the card. + * + * @param {*} request + * @return transaction_id - a random uuid. + */ +module.exports = function charge (request) { + const { amount, credit_card: creditCard } = request; + const cardNumber = creditCard.credit_card_number; + const cardInfo = cardValidator(cardNumber); + const { + card_type: cardType, + valid + } = cardInfo.getCardDetails(); + + if (!valid) { throw new InvalidCreditCard(); } + + // Only VISA and mastercard is accepted, other card types (AMEX, dinersclub) will + // throw UnacceptedCreditCard error. + if (!(cardType === 'visa' || cardType === 'mastercard')) { throw new UnacceptedCreditCard(cardType); } + + // Also validate expiration is > today. + const currentMonth = new Date().getMonth() + 1; + const currentYear = new Date().getFullYear(); + const { credit_card_expiration_year: year, credit_card_expiration_month: month } = creditCard; + if ((currentYear * 12 + currentMonth) > (year * 12 + month)) { throw new ExpiredCreditCard(cardNumber.replace('-', ''), month, year); } + + logger.info(`Transaction processed: ${cardType} ending ${cardNumber.substr(-4)} \ + Amount: ${amount.currency_code}${amount.units}.${amount.nanos}`); + + return { transaction_id: uuidv4() }; +}; diff --git a/src/paymentservice/genproto.sh b/src/paymentservice/genproto.sh new file mode 100755 index 0000000..aab28e2 --- /dev/null +++ b/src/paymentservice/genproto.sh @@ -0,0 +1,23 @@ +#!/bin/bash -eu +# +# Copyright 2018 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# [START gke_paymentservice_genproto] + +# protos are loaded dynamically for node, simply copies over the proto. +mkdir -p proto +cp -r ../../protos/* ./proto + +# [END gke_paymentservice_genproto] \ No newline at end of file diff --git a/src/paymentservice/index.js b/src/paymentservice/index.js new file mode 100644 index 0000000..77f0516 --- /dev/null +++ b/src/paymentservice/index.js @@ -0,0 +1,75 @@ +/* + * Copyright 2018 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +'use strict'; + +const logger = require('./logger') + +if (process.env.DISABLE_PROFILER) { + logger.info("Profiler disabled.") +} else { + logger.info("Profiler enabled.") + require('@google-cloud/profiler').start({ + serviceContext: { + service: 'paymentservice', + version: '1.0.0' + } + }); +} + + +if (process.env.ENABLE_TRACING == "1") { + logger.info("Tracing enabled.") + + const { resourceFromAttributes } = require('@opentelemetry/resources'); + + const { ATTR_SERVICE_NAME }= require('@opentelemetry/semantic-conventions'); + + const { GrpcInstrumentation } = require('@opentelemetry/instrumentation-grpc'); + const { registerInstrumentations } = require('@opentelemetry/instrumentation'); + const opentelemetry = require('@opentelemetry/sdk-node'); + + const { OTLPTraceExporter } = require('@opentelemetry/exporter-otlp-grpc'); + + const collectorUrl = process.env.COLLECTOR_SERVICE_ADDR; + const traceExporter = new OTLPTraceExporter({url: collectorUrl}); + + const sdk = new opentelemetry.NodeSDK({ + resource: resourceFromAttributes({ + [ATTR_SERVICE_NAME]: process.env.OTEL_SERVICE_NAME || 'paymentservice', + }), + traceExporter: traceExporter, + }); + + registerInstrumentations({ + instrumentations: [new GrpcInstrumentation()] + }); + + sdk.start() +} else { + logger.info("Tracing disabled.") +} + + +const path = require('path'); +const HipsterShopServer = require('./server'); + +const PORT = process.env['PORT']; +const PROTO_PATH = path.join(__dirname, '/proto/'); + +const server = new HipsterShopServer(PROTO_PATH, PORT); + +server.listen(); diff --git a/src/paymentservice/logger.js b/src/paymentservice/logger.js new file mode 100644 index 0000000..5aa5b8f --- /dev/null +++ b/src/paymentservice/logger.js @@ -0,0 +1,27 @@ +/* + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +const pino = require('pino'); + +module.exports = pino({ + name: 'paymentservice-server', + messageKey: 'message', + formatters: { + level (logLevelString, logLevelNum) { + return { severity: logLevelString } + } + } +}); \ No newline at end of file diff --git a/src/paymentservice/package-lock.json b/src/paymentservice/package-lock.json new file mode 100644 index 0000000..853a7bd --- /dev/null +++ b/src/paymentservice/package-lock.json @@ -0,0 +1,5581 @@ +{ + "name": "paymentservice", + "version": "0.0.1", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "paymentservice", + "version": "0.0.1", + "license": "ISC", + "dependencies": { + "@google-cloud/profiler": "6.0.3", + "@grpc/grpc-js": "1.14.3", + "@grpc/proto-loader": "0.8.0", + "@opentelemetry/api": "1.9.0", + "@opentelemetry/exporter-otlp-grpc": "0.26.0", + "@opentelemetry/instrumentation-grpc": "0.211.0", + "@opentelemetry/resources": "2.5.0", + "@opentelemetry/sdk-node": "0.211.0", + "@opentelemetry/sdk-trace-base": "2.5.0", + "@opentelemetry/semantic-conventions": "1.39.0", + "pino": "10.3.0", + "simple-card-validator": "^1.1.0", + "uuid": "^13.0.0" + } + }, + "node_modules/@google-cloud/common": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@google-cloud/common/-/common-5.0.2.tgz", + "integrity": "sha512-V7bmBKYQyu0eVG2BFejuUjlBt+zrya6vtsKdY+JxMM/dNntPF41vZ9+LhOshEUH01zOHEqBSvI7Dad7ZS6aUeA==", + "dependencies": { + "@google-cloud/projectify": "^4.0.0", + "@google-cloud/promisify": "^4.0.0", + "arrify": "^2.0.1", + "duplexify": "^4.1.1", + "extend": "^3.0.2", + "google-auth-library": "^9.0.0", + "html-entities": "^2.5.2", + "retry-request": "^7.0.0", + "teeny-request": "^9.0.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@google-cloud/logging-min": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/@google-cloud/logging-min/-/logging-min-11.2.0.tgz", + "integrity": "sha512-o1mwzi1+9NMEjwYZJ0X3tK64obf9PzPVBAhzEJv65L0h7jVl3Fw7GswtsMUkdUvZexf96vAvlZZMvXB9jAIW2Q==", + "dependencies": { + "@google-cloud/common": "^5.0.0", + "@google-cloud/paginator": "^5.0.0", + "@google-cloud/projectify": "^4.0.0", + "@google-cloud/promisify": "^4.0.0", + "@opentelemetry/api": "^1.7.0", + "arrify": "^2.0.1", + "dot-prop": "^6.0.0", + "eventid": "^2.0.0", + "extend": "^3.0.2", + "gcp-metadata": "^6.0.0", + "google-auth-library": "^9.0.0", + "google-gax": "^4.0.3", + "on-finished": "^2.3.0", + "pumpify": "^2.0.1", + "stream-events": "^1.0.5", + "uuid": "^9.0.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@google-cloud/logging-min/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/@google-cloud/paginator": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@google-cloud/paginator/-/paginator-5.0.2.tgz", + "integrity": "sha512-DJS3s0OVH4zFDB1PzjxAsHqJT6sKVbRwwML0ZBP9PbU7Yebtu/7SWMRzvO2J3nUi9pRNITCfu4LJeooM2w4pjg==", + "dependencies": { + "arrify": "^2.0.0", + "extend": "^3.0.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@google-cloud/profiler": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@google-cloud/profiler/-/profiler-6.0.3.tgz", + "integrity": "sha512-Ey8li6Vc2CbfEzOTSZaqKolxPMGacxVUQuhChNT0Wi55a3nfImMiiuDgqYw1In/a9Q3Z62O7jUg2L8f1XwMN7Q==", + "dependencies": { + "@google-cloud/common": "^5.0.0", + "@google-cloud/logging-min": "^11.0.0", + "@google-cloud/promisify": "~4.0.0", + "@types/console-log-level": "^1.4.0", + "@types/semver": "^7.0.0", + "console-log-level": "^1.4.0", + "delay": "^5.0.0", + "extend": "^3.0.2", + "gcp-metadata": "^6.0.0", + "ms": "^2.1.3", + "pprof": "4.0.0", + "pretty-ms": "^7.0.0", + "protobufjs": "~7.4.0", + "semver": "^7.0.0", + "teeny-request": "^9.0.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@google-cloud/projectify": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@google-cloud/projectify/-/projectify-4.0.0.tgz", + "integrity": "sha512-MmaX6HeSvyPbWGwFq7mXdo0uQZLGBYCwziiLIGq5JVX+/bdI3SAq6bP98trV5eTWfLuvsMcIC1YJOF2vfteLFA==", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@google-cloud/promisify": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@google-cloud/promisify/-/promisify-4.0.0.tgz", + "integrity": "sha512-Orxzlfb9c67A15cq2JQEyVc7wEsmFBmHjZWZYQMUyJ1qivXyMwdyNOs9odi79hze+2zqdTtu1E19IM/FtqZ10g==", + "engines": { + "node": ">=14" + } + }, + "node_modules/@grpc/grpc-js": { + "version": "1.14.3", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.14.3.tgz", + "integrity": "sha512-Iq8QQQ/7X3Sac15oB6p0FmUg/klxQvXLeileoqrTRGJYLV+/9tubbr9ipz0GKHjmXVsgFPo/+W+2cA8eNcR+XA==", + "dependencies": { + "@grpc/proto-loader": "^0.8.0", + "@js-sdsl/ordered-map": "^4.4.2" + }, + "engines": { + "node": ">=12.10.0" + } + }, + "node_modules/@grpc/proto-loader": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.8.0.tgz", + "integrity": "sha512-rc1hOQtjIWGxcxpb9aHAfLpIctjEnsDehj0DAiVfBlmT84uvR0uUtN2hEi/ecvWVjXUGf5qPF4qEgiLOx1YIMQ==", + "dependencies": { + "lodash.camelcase": "^4.3.0", + "long": "^5.0.0", + "protobufjs": "^7.5.3", + "yargs": "^17.7.2" + }, + "bin": { + "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@grpc/proto-loader/node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@grpc/proto-loader/node_modules/long": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", + "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==" + }, + "node_modules/@grpc/proto-loader/node_modules/protobufjs": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.4.tgz", + "integrity": "sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg==", + "hasInstallScript": true, + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/node": ">=13.7.0", + "long": "^5.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@grpc/proto-loader/node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@grpc/proto-loader/node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "engines": { + "node": ">=12" + } + }, + "node_modules/@js-sdsl/ordered-map": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/@js-sdsl/ordered-map/-/ordered-map-4.4.2.tgz", + "integrity": "sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/js-sdsl" + } + }, + "node_modules/@mapbox/node-pre-gyp": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.10.tgz", + "integrity": "sha512-4ySo4CjzStuprMwk35H5pPbkymjv1SF3jGLj6rAHp/xT/RF7TL7bd9CTm1xDY49K2qF7jmR/g7k+SkLETP6opA==", + "dependencies": { + "detect-libc": "^2.0.0", + "https-proxy-agent": "^5.0.0", + "make-dir": "^3.1.0", + "node-fetch": "^2.6.7", + "nopt": "^5.0.0", + "npmlog": "^5.0.1", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "tar": "^6.1.11" + }, + "bin": { + "node-pre-gyp": "bin/node-pre-gyp" + } + }, + "node_modules/@opentelemetry/api": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", + "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@opentelemetry/api-logs": { + "version": "0.211.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.211.0.tgz", + "integrity": "sha512-swFdZq8MCdmdR22jTVGQDhwqDzcI4M10nhjXkLr1EsIzXgZBqm4ZlmmcWsg3TSNf+3mzgOiqveXmBLZuDi2Lgg==", + "dependencies": { + "@opentelemetry/api": "^1.3.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@opentelemetry/api-metrics": { + "version": "0.26.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-metrics/-/api-metrics-0.26.0.tgz", + "integrity": "sha512-idDSUTx+LRwJiHhVHhdh45SWow5u9lKNDROKu5AMzsIVPI29utH5FfT9vor8qMM6blxWWvlT22HUNdNMWqUQfQ==", + "deprecated": "Please use @opentelemetry/api >= 1.3.0", + "engines": { + "node": ">=8.0.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.0.2" + } + }, + "node_modules/@opentelemetry/configuration": { + "version": "0.211.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/configuration/-/configuration-0.211.0.tgz", + "integrity": "sha512-PNsCkzsYQKyv8wiUIsH+loC4RYyblOaDnVASBtKS22hK55ToWs2UP6IsrcfSWWn54wWTvVe2gnfwz67Pvrxf2Q==", + "dependencies": { + "@opentelemetry/core": "2.5.0", + "yaml": "^2.0.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.9.0" + } + }, + "node_modules/@opentelemetry/configuration/node_modules/@opentelemetry/core": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.5.0.tgz", + "integrity": "sha512-ka4H8OM6+DlUhSAZpONu0cPBtPPTQKxbxVzC4CzVx5+K4JnroJVBtDzLAMx4/3CDTJXRvVFhpFjtl4SaiTNoyQ==", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/context-async-hooks": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/context-async-hooks/-/context-async-hooks-2.5.0.tgz", + "integrity": "sha512-uOXpVX0ZjO7heSVjhheW2XEPrhQAWr2BScDPoZ9UDycl5iuHG+Usyc3AIfG6kZeC1GyLpMInpQ6X5+9n69yOFw==", + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/core": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-1.0.0.tgz", + "integrity": "sha512-1+qvKilADnSFW4PiXy+f7D22pvfGVxepZ69GcbF8cTcbQTUt7w63xEBWn5f5j92x9I3c0sqbW1RUx5/a4wgzxA==", + "dependencies": { + "@opentelemetry/semantic-conventions": "1.0.0", + "semver": "^7.3.5" + }, + "engines": { + "node": ">=8.5.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.0.2" + } + }, + "node_modules/@opentelemetry/core/node_modules/@opentelemetry/semantic-conventions": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.0.0.tgz", + "integrity": "sha512-XCZ6ZSmc8FOspxKUU+Ow9UtJeSSRcS5rFBYGpjzix02U2v+X9ofjOjgNRnpvxlSvkccYIhdTuwcvNskmZ46SeA==", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@opentelemetry/exporter-logs-otlp-grpc": { + "version": "0.211.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-logs-otlp-grpc/-/exporter-logs-otlp-grpc-0.211.0.tgz", + "integrity": "sha512-UhOoWENNqyaAMP/dL1YXLkXt6ZBtovkDDs1p4rxto9YwJX1+wMjwg+Obfyg2kwpcMoaiIFT3KQIcLNW8nNGNfQ==", + "dependencies": { + "@grpc/grpc-js": "^1.7.1", + "@opentelemetry/core": "2.5.0", + "@opentelemetry/otlp-exporter-base": "0.211.0", + "@opentelemetry/otlp-grpc-exporter-base": "0.211.0", + "@opentelemetry/otlp-transformer": "0.211.0", + "@opentelemetry/sdk-logs": "0.211.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/exporter-logs-otlp-grpc/node_modules/@opentelemetry/core": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.5.0.tgz", + "integrity": "sha512-ka4H8OM6+DlUhSAZpONu0cPBtPPTQKxbxVzC4CzVx5+K4JnroJVBtDzLAMx4/3CDTJXRvVFhpFjtl4SaiTNoyQ==", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-logs-otlp-http": { + "version": "0.211.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-logs-otlp-http/-/exporter-logs-otlp-http-0.211.0.tgz", + "integrity": "sha512-c118Awf1kZirHkqxdcF+rF5qqWwNjJh+BB1CmQvN9AQHC/DUIldy6dIkJn3EKlQnQ3HmuNRKc/nHHt5IusN7mA==", + "dependencies": { + "@opentelemetry/api-logs": "0.211.0", + "@opentelemetry/core": "2.5.0", + "@opentelemetry/otlp-exporter-base": "0.211.0", + "@opentelemetry/otlp-transformer": "0.211.0", + "@opentelemetry/sdk-logs": "0.211.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/exporter-logs-otlp-http/node_modules/@opentelemetry/core": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.5.0.tgz", + "integrity": "sha512-ka4H8OM6+DlUhSAZpONu0cPBtPPTQKxbxVzC4CzVx5+K4JnroJVBtDzLAMx4/3CDTJXRvVFhpFjtl4SaiTNoyQ==", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-logs-otlp-proto": { + "version": "0.211.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-logs-otlp-proto/-/exporter-logs-otlp-proto-0.211.0.tgz", + "integrity": "sha512-kMvfKMtY5vJDXeLnwhrZMEwhZ2PN8sROXmzacFU/Fnl4Z79CMrOaL7OE+5X3SObRYlDUa7zVqaXp9ZetYCxfDQ==", + "dependencies": { + "@opentelemetry/api-logs": "0.211.0", + "@opentelemetry/core": "2.5.0", + "@opentelemetry/otlp-exporter-base": "0.211.0", + "@opentelemetry/otlp-transformer": "0.211.0", + "@opentelemetry/resources": "2.5.0", + "@opentelemetry/sdk-logs": "0.211.0", + "@opentelemetry/sdk-trace-base": "2.5.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/exporter-logs-otlp-proto/node_modules/@opentelemetry/core": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.5.0.tgz", + "integrity": "sha512-ka4H8OM6+DlUhSAZpONu0cPBtPPTQKxbxVzC4CzVx5+K4JnroJVBtDzLAMx4/3CDTJXRvVFhpFjtl4SaiTNoyQ==", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-metrics-otlp-grpc": { + "version": "0.211.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-metrics-otlp-grpc/-/exporter-metrics-otlp-grpc-0.211.0.tgz", + "integrity": "sha512-D/U3G8L4PzZp8ot5hX9wpgbTymgtLZCiwR7heMe4LsbGV4OdctS1nfyvaQHLT6CiGZ6FjKc1Vk9s6kbo9SWLXQ==", + "dependencies": { + "@grpc/grpc-js": "^1.7.1", + "@opentelemetry/core": "2.5.0", + "@opentelemetry/exporter-metrics-otlp-http": "0.211.0", + "@opentelemetry/otlp-exporter-base": "0.211.0", + "@opentelemetry/otlp-grpc-exporter-base": "0.211.0", + "@opentelemetry/otlp-transformer": "0.211.0", + "@opentelemetry/resources": "2.5.0", + "@opentelemetry/sdk-metrics": "2.5.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/exporter-metrics-otlp-grpc/node_modules/@opentelemetry/core": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.5.0.tgz", + "integrity": "sha512-ka4H8OM6+DlUhSAZpONu0cPBtPPTQKxbxVzC4CzVx5+K4JnroJVBtDzLAMx4/3CDTJXRvVFhpFjtl4SaiTNoyQ==", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-metrics-otlp-http": { + "version": "0.211.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-metrics-otlp-http/-/exporter-metrics-otlp-http-0.211.0.tgz", + "integrity": "sha512-lfHXElPAoDSPpPO59DJdN5FLUnwi1wxluLTWQDayqrSPfWRnluzxRhD+g7rF8wbj1qCz0sdqABl//ug1IZyWvA==", + "dependencies": { + "@opentelemetry/core": "2.5.0", + "@opentelemetry/otlp-exporter-base": "0.211.0", + "@opentelemetry/otlp-transformer": "0.211.0", + "@opentelemetry/resources": "2.5.0", + "@opentelemetry/sdk-metrics": "2.5.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/exporter-metrics-otlp-http/node_modules/@opentelemetry/core": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.5.0.tgz", + "integrity": "sha512-ka4H8OM6+DlUhSAZpONu0cPBtPPTQKxbxVzC4CzVx5+K4JnroJVBtDzLAMx4/3CDTJXRvVFhpFjtl4SaiTNoyQ==", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-metrics-otlp-proto": { + "version": "0.211.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-metrics-otlp-proto/-/exporter-metrics-otlp-proto-0.211.0.tgz", + "integrity": "sha512-61iNbffEpyZv/abHaz3BQM3zUtA2kVIDBM+0dS9RK68ML0QFLRGYa50xVMn2PYMToyfszEPEgFC3ypGae2z8FA==", + "dependencies": { + "@opentelemetry/core": "2.5.0", + "@opentelemetry/exporter-metrics-otlp-http": "0.211.0", + "@opentelemetry/otlp-exporter-base": "0.211.0", + "@opentelemetry/otlp-transformer": "0.211.0", + "@opentelemetry/resources": "2.5.0", + "@opentelemetry/sdk-metrics": "2.5.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/exporter-metrics-otlp-proto/node_modules/@opentelemetry/core": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.5.0.tgz", + "integrity": "sha512-ka4H8OM6+DlUhSAZpONu0cPBtPPTQKxbxVzC4CzVx5+K4JnroJVBtDzLAMx4/3CDTJXRvVFhpFjtl4SaiTNoyQ==", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-otlp-grpc": { + "version": "0.26.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-otlp-grpc/-/exporter-otlp-grpc-0.26.0.tgz", + "integrity": "sha512-64VPck7CbGhI7c2bj54xaGGHCy4mP+rMnrKBAruNBmUUnN9OgihddNcMsIiHyddUcyC1I+hXS3JLW1G6AvlAmg==", + "deprecated": "Please use trace and metric specific exporters @opentelemetry/exporter-trace-otlp-grpc and @opentelemetry/exporter-metrics-otlp-grpc", + "dependencies": { + "@grpc/grpc-js": "^1.3.7", + "@grpc/proto-loader": "^0.6.4", + "@opentelemetry/core": "1.0.0", + "@opentelemetry/exporter-otlp-http": "0.26.0", + "@opentelemetry/resources": "1.0.0", + "@opentelemetry/sdk-metrics-base": "0.26.0", + "@opentelemetry/sdk-trace-base": "1.0.0" + }, + "engines": { + "node": ">=8.0.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.0.2" + } + }, + "node_modules/@opentelemetry/exporter-otlp-grpc/node_modules/@grpc/proto-loader": { + "version": "0.6.13", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.6.13.tgz", + "integrity": "sha512-FjxPYDRTn6Ec3V0arm1FtSpmP6V50wuph2yILpyvTKzjc76oDdoihXqM1DzOW5ubvCC8GivfCnNtfaRE8myJ7g==", + "dependencies": { + "@types/long": "^4.0.1", + "lodash.camelcase": "^4.3.0", + "long": "^4.0.0", + "protobufjs": "^6.11.3", + "yargs": "^16.2.0" + }, + "bin": { + "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@opentelemetry/exporter-otlp-grpc/node_modules/@opentelemetry/resources": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-1.0.0.tgz", + "integrity": "sha512-ORP8F2LLcJEm5M3H24RmdlMdiDc70ySPushpkrAW34KZGdZXwkrFoFXZhhs5MUxPT+fLrTuBafXxZVr8eHtFuQ==", + "dependencies": { + "@opentelemetry/core": "1.0.0", + "@opentelemetry/semantic-conventions": "1.0.0" + }, + "engines": { + "node": ">=8.0.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.0.2" + } + }, + "node_modules/@opentelemetry/exporter-otlp-grpc/node_modules/@opentelemetry/sdk-trace-base": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-1.0.0.tgz", + "integrity": "sha512-/rXoyQlDlJTJ4SOVAbP0Gpj89B8oZ2hJApYG2Dq5klkgFAtDifN8271TIzwtM8/ET8HUhgx9eyoUJi42LhIesg==", + "dependencies": { + "@opentelemetry/core": "1.0.0", + "@opentelemetry/resources": "1.0.0", + "@opentelemetry/semantic-conventions": "1.0.0", + "lodash.merge": "^4.6.2" + }, + "engines": { + "node": ">=8.0.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.0.2" + } + }, + "node_modules/@opentelemetry/exporter-otlp-grpc/node_modules/@opentelemetry/semantic-conventions": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.0.0.tgz", + "integrity": "sha512-XCZ6ZSmc8FOspxKUU+Ow9UtJeSSRcS5rFBYGpjzix02U2v+X9ofjOjgNRnpvxlSvkccYIhdTuwcvNskmZ46SeA==", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@opentelemetry/exporter-otlp-grpc/node_modules/protobufjs": { + "version": "6.11.4", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.4.tgz", + "integrity": "sha512-5kQWPaJHi1WoCpjTGszzQ32PG2F4+wRY6BmAT4Vfw56Q2FZ4YZzK20xUYQH4YkfehY1e6QSICrJquM6xXZNcrw==", + "hasInstallScript": true, + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/long": "^4.0.1", + "@types/node": ">=13.7.0", + "long": "^4.0.0" + }, + "bin": { + "pbjs": "bin/pbjs", + "pbts": "bin/pbts" + } + }, + "node_modules/@opentelemetry/exporter-otlp-http": { + "version": "0.26.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-otlp-http/-/exporter-otlp-http-0.26.0.tgz", + "integrity": "sha512-V3FcUEIVDZ66b3/6vjSBjwwozf/XV5eUXuELNzN8PAvGZH4mw36vaWlaxnGEV8HaZb2hbu2KbRpcOzqxx3tFDA==", + "deprecated": "Please use trace and metric specific exporters @opentelemetry/exporter-trace-otlp-http and @opentelemetry/exporter-metrics-otlp-http", + "dependencies": { + "@opentelemetry/api-metrics": "0.26.0", + "@opentelemetry/core": "1.0.0", + "@opentelemetry/resources": "1.0.0", + "@opentelemetry/sdk-metrics-base": "0.26.0", + "@opentelemetry/sdk-trace-base": "1.0.0" + }, + "engines": { + "node": ">=8.0.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.0.2" + } + }, + "node_modules/@opentelemetry/exporter-otlp-http/node_modules/@opentelemetry/resources": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-1.0.0.tgz", + "integrity": "sha512-ORP8F2LLcJEm5M3H24RmdlMdiDc70ySPushpkrAW34KZGdZXwkrFoFXZhhs5MUxPT+fLrTuBafXxZVr8eHtFuQ==", + "dependencies": { + "@opentelemetry/core": "1.0.0", + "@opentelemetry/semantic-conventions": "1.0.0" + }, + "engines": { + "node": ">=8.0.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.0.2" + } + }, + "node_modules/@opentelemetry/exporter-otlp-http/node_modules/@opentelemetry/sdk-trace-base": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-1.0.0.tgz", + "integrity": "sha512-/rXoyQlDlJTJ4SOVAbP0Gpj89B8oZ2hJApYG2Dq5klkgFAtDifN8271TIzwtM8/ET8HUhgx9eyoUJi42LhIesg==", + "dependencies": { + "@opentelemetry/core": "1.0.0", + "@opentelemetry/resources": "1.0.0", + "@opentelemetry/semantic-conventions": "1.0.0", + "lodash.merge": "^4.6.2" + }, + "engines": { + "node": ">=8.0.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.0.2" + } + }, + "node_modules/@opentelemetry/exporter-otlp-http/node_modules/@opentelemetry/semantic-conventions": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.0.0.tgz", + "integrity": "sha512-XCZ6ZSmc8FOspxKUU+Ow9UtJeSSRcS5rFBYGpjzix02U2v+X9ofjOjgNRnpvxlSvkccYIhdTuwcvNskmZ46SeA==", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@opentelemetry/exporter-prometheus": { + "version": "0.211.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-prometheus/-/exporter-prometheus-0.211.0.tgz", + "integrity": "sha512-cD0WleEL3TPqJbvxwz5MVdVJ82H8jl8mvMad4bNU24cB5SH2mRW5aMLDTuV4614ll46R//R3RMmci26mc2L99g==", + "dependencies": { + "@opentelemetry/core": "2.5.0", + "@opentelemetry/resources": "2.5.0", + "@opentelemetry/sdk-metrics": "2.5.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/exporter-prometheus/node_modules/@opentelemetry/core": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.5.0.tgz", + "integrity": "sha512-ka4H8OM6+DlUhSAZpONu0cPBtPPTQKxbxVzC4CzVx5+K4JnroJVBtDzLAMx4/3CDTJXRvVFhpFjtl4SaiTNoyQ==", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-trace-otlp-grpc": { + "version": "0.211.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-trace-otlp-grpc/-/exporter-trace-otlp-grpc-0.211.0.tgz", + "integrity": "sha512-eFwx4Gvu6LaEiE1rOd4ypgAiWEdZu7Qzm2QNN2nJqPW1XDeAVH1eNwVcVQl+QK9HR/JCDZ78PZgD7xD/DBDqbw==", + "dependencies": { + "@grpc/grpc-js": "^1.7.1", + "@opentelemetry/core": "2.5.0", + "@opentelemetry/otlp-exporter-base": "0.211.0", + "@opentelemetry/otlp-grpc-exporter-base": "0.211.0", + "@opentelemetry/otlp-transformer": "0.211.0", + "@opentelemetry/resources": "2.5.0", + "@opentelemetry/sdk-trace-base": "2.5.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/exporter-trace-otlp-grpc/node_modules/@opentelemetry/core": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.5.0.tgz", + "integrity": "sha512-ka4H8OM6+DlUhSAZpONu0cPBtPPTQKxbxVzC4CzVx5+K4JnroJVBtDzLAMx4/3CDTJXRvVFhpFjtl4SaiTNoyQ==", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-trace-otlp-http": { + "version": "0.211.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-trace-otlp-http/-/exporter-trace-otlp-http-0.211.0.tgz", + "integrity": "sha512-F1Rv3JeMkgS//xdVjbQMrI3+26e5SXC7vXA6trx8SWEA0OUhw4JHB+qeHtH0fJn46eFItrYbL5m8j4qi9Sfaxw==", + "dependencies": { + "@opentelemetry/core": "2.5.0", + "@opentelemetry/otlp-exporter-base": "0.211.0", + "@opentelemetry/otlp-transformer": "0.211.0", + "@opentelemetry/resources": "2.5.0", + "@opentelemetry/sdk-trace-base": "2.5.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/exporter-trace-otlp-http/node_modules/@opentelemetry/core": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.5.0.tgz", + "integrity": "sha512-ka4H8OM6+DlUhSAZpONu0cPBtPPTQKxbxVzC4CzVx5+K4JnroJVBtDzLAMx4/3CDTJXRvVFhpFjtl4SaiTNoyQ==", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-trace-otlp-proto": { + "version": "0.211.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-trace-otlp-proto/-/exporter-trace-otlp-proto-0.211.0.tgz", + "integrity": "sha512-DkjXwbPiqpcPlycUojzG2RmR0/SIK8Gi9qWO9znNvSqgzrnAIE9x2n6yPfpZ+kWHZGafvsvA1lVXucTyyQa5Kg==", + "dependencies": { + "@opentelemetry/core": "2.5.0", + "@opentelemetry/otlp-exporter-base": "0.211.0", + "@opentelemetry/otlp-transformer": "0.211.0", + "@opentelemetry/resources": "2.5.0", + "@opentelemetry/sdk-trace-base": "2.5.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/exporter-trace-otlp-proto/node_modules/@opentelemetry/core": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.5.0.tgz", + "integrity": "sha512-ka4H8OM6+DlUhSAZpONu0cPBtPPTQKxbxVzC4CzVx5+K4JnroJVBtDzLAMx4/3CDTJXRvVFhpFjtl4SaiTNoyQ==", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-zipkin": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-zipkin/-/exporter-zipkin-2.5.0.tgz", + "integrity": "sha512-bk9VJgFgUAzkZzU8ZyXBSWiUGLOM3mZEgKJ1+jsZclhRnAoDNf+YBdq+G9R3cP0+TKjjWad+vVrY/bE/vRR9lA==", + "dependencies": { + "@opentelemetry/core": "2.5.0", + "@opentelemetry/resources": "2.5.0", + "@opentelemetry/sdk-trace-base": "2.5.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.0.0" + } + }, + "node_modules/@opentelemetry/exporter-zipkin/node_modules/@opentelemetry/core": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.5.0.tgz", + "integrity": "sha512-ka4H8OM6+DlUhSAZpONu0cPBtPPTQKxbxVzC4CzVx5+K4JnroJVBtDzLAMx4/3CDTJXRvVFhpFjtl4SaiTNoyQ==", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/instrumentation": { + "version": "0.211.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.211.0.tgz", + "integrity": "sha512-h0nrZEC/zvI994nhg7EgQ8URIHt0uDTwN90r3qQUdZORS455bbx+YebnGeEuFghUT0HlJSrLF4iHw67f+odY+Q==", + "dependencies": { + "@opentelemetry/api-logs": "0.211.0", + "import-in-the-middle": "^2.0.0", + "require-in-the-middle": "^8.0.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-grpc": { + "version": "0.211.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-grpc/-/instrumentation-grpc-0.211.0.tgz", + "integrity": "sha512-bshedE3TaD18OE3oPU15j8bn4vz+3X5mvg9jluoSn/ZjlshCb1FrstjNkTYQuRERWzeMl7WcR8sShr91FcUBXA==", + "dependencies": { + "@opentelemetry/instrumentation": "0.211.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/otlp-exporter-base": { + "version": "0.211.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-exporter-base/-/otlp-exporter-base-0.211.0.tgz", + "integrity": "sha512-bp1+63V8WPV+bRI9EQG6E9YID1LIHYSZVbp7f+44g9tRzCq+rtw/o4fpL5PC31adcUsFiz/oN0MdLISSrZDdrg==", + "dependencies": { + "@opentelemetry/core": "2.5.0", + "@opentelemetry/otlp-transformer": "0.211.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/otlp-exporter-base/node_modules/@opentelemetry/core": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.5.0.tgz", + "integrity": "sha512-ka4H8OM6+DlUhSAZpONu0cPBtPPTQKxbxVzC4CzVx5+K4JnroJVBtDzLAMx4/3CDTJXRvVFhpFjtl4SaiTNoyQ==", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/otlp-grpc-exporter-base": { + "version": "0.211.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-grpc-exporter-base/-/otlp-grpc-exporter-base-0.211.0.tgz", + "integrity": "sha512-mR5X+N4SuphJeb7/K7y0JNMC8N1mB6gEtjyTLv+TSAhl0ZxNQzpSKP8S5Opk90fhAqVYD4R0SQSAirEBlH1KSA==", + "dependencies": { + "@grpc/grpc-js": "^1.7.1", + "@opentelemetry/core": "2.5.0", + "@opentelemetry/otlp-exporter-base": "0.211.0", + "@opentelemetry/otlp-transformer": "0.211.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/otlp-grpc-exporter-base/node_modules/@opentelemetry/core": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.5.0.tgz", + "integrity": "sha512-ka4H8OM6+DlUhSAZpONu0cPBtPPTQKxbxVzC4CzVx5+K4JnroJVBtDzLAMx4/3CDTJXRvVFhpFjtl4SaiTNoyQ==", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/otlp-transformer": { + "version": "0.211.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-transformer/-/otlp-transformer-0.211.0.tgz", + "integrity": "sha512-julhCJ9dXwkOg9svuuYqqjXLhVaUgyUvO2hWbTxwjvLXX2rG3VtAaB0SzxMnGTuoCZizBT7Xqqm2V7+ggrfCXA==", + "dependencies": { + "@opentelemetry/api-logs": "0.211.0", + "@opentelemetry/core": "2.5.0", + "@opentelemetry/resources": "2.5.0", + "@opentelemetry/sdk-logs": "0.211.0", + "@opentelemetry/sdk-metrics": "2.5.0", + "@opentelemetry/sdk-trace-base": "2.5.0", + "protobufjs": "8.0.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/otlp-transformer/node_modules/@opentelemetry/core": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.5.0.tgz", + "integrity": "sha512-ka4H8OM6+DlUhSAZpONu0cPBtPPTQKxbxVzC4CzVx5+K4JnroJVBtDzLAMx4/3CDTJXRvVFhpFjtl4SaiTNoyQ==", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/otlp-transformer/node_modules/long": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", + "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==" + }, + "node_modules/@opentelemetry/otlp-transformer/node_modules/protobufjs": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-8.0.0.tgz", + "integrity": "sha512-jx6+sE9h/UryaCZhsJWbJtTEy47yXoGNYI4z8ZaRncM0zBKeRqjO2JEcOUYwrYGb1WLhXM1FfMzW3annvFv0rw==", + "hasInstallScript": true, + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/node": ">=13.7.0", + "long": "^5.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@opentelemetry/propagator-b3": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/propagator-b3/-/propagator-b3-2.5.0.tgz", + "integrity": "sha512-g10m4KD73RjHrSvUge+sUxUl8m4VlgnGc6OKvo68a4uMfaLjdFU+AULfvMQE/APq38k92oGUxEzBsAZ8RN/YHg==", + "dependencies": { + "@opentelemetry/core": "2.5.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/propagator-b3/node_modules/@opentelemetry/core": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.5.0.tgz", + "integrity": "sha512-ka4H8OM6+DlUhSAZpONu0cPBtPPTQKxbxVzC4CzVx5+K4JnroJVBtDzLAMx4/3CDTJXRvVFhpFjtl4SaiTNoyQ==", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/propagator-jaeger": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/propagator-jaeger/-/propagator-jaeger-2.5.0.tgz", + "integrity": "sha512-t70ErZCncAR/zz5AcGkL0TF25mJiK1FfDPEQCgreyAHZ+mRJ/bNUiCnImIBDlP3mSDXy6N09DbUEKq0ktW98Hg==", + "dependencies": { + "@opentelemetry/core": "2.5.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/propagator-jaeger/node_modules/@opentelemetry/core": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.5.0.tgz", + "integrity": "sha512-ka4H8OM6+DlUhSAZpONu0cPBtPPTQKxbxVzC4CzVx5+K4JnroJVBtDzLAMx4/3CDTJXRvVFhpFjtl4SaiTNoyQ==", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/resources": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.5.0.tgz", + "integrity": "sha512-F8W52ApePshpoSrfsSk1H2yJn9aKjCrbpQF1M9Qii0GHzbfVeFUB+rc3X4aggyZD8x9Gu3Slua+s6krmq6Dt8g==", + "dependencies": { + "@opentelemetry/core": "2.5.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/resources/node_modules/@opentelemetry/core": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.5.0.tgz", + "integrity": "sha512-ka4H8OM6+DlUhSAZpONu0cPBtPPTQKxbxVzC4CzVx5+K4JnroJVBtDzLAMx4/3CDTJXRvVFhpFjtl4SaiTNoyQ==", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-logs": { + "version": "0.211.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-logs/-/sdk-logs-0.211.0.tgz", + "integrity": "sha512-O5nPwzgg2JHzo59kpQTPUOTzFi0Nv5LxryG27QoXBciX3zWM3z83g+SNOHhiQVYRWFSxoWn1JM2TGD5iNjOwdA==", + "dependencies": { + "@opentelemetry/api-logs": "0.211.0", + "@opentelemetry/core": "2.5.0", + "@opentelemetry/resources": "2.5.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.4.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-logs/node_modules/@opentelemetry/core": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.5.0.tgz", + "integrity": "sha512-ka4H8OM6+DlUhSAZpONu0cPBtPPTQKxbxVzC4CzVx5+K4JnroJVBtDzLAMx4/3CDTJXRvVFhpFjtl4SaiTNoyQ==", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-metrics": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-2.5.0.tgz", + "integrity": "sha512-BeJLtU+f5Gf905cJX9vXFQorAr6TAfK3SPvTFqP+scfIpDQEJfRaGJWta7sJgP+m4dNtBf9y3yvBKVAZZtJQVA==", + "dependencies": { + "@opentelemetry/core": "2.5.0", + "@opentelemetry/resources": "2.5.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.9.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-metrics-base": { + "version": "0.26.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics-base/-/sdk-metrics-base-0.26.0.tgz", + "integrity": "sha512-PbJsso7Vy/CLATAOyXbt/VP7ZQ2QYnvlq28lhOWaLPw8aqLogMBvidNGRrt7rF4/hfzLT6pMgpAAcit2C/nUMA==", + "deprecated": "Please use @opentelemetry/sdk-metrics", + "dependencies": { + "@opentelemetry/api-metrics": "0.26.0", + "@opentelemetry/core": "1.0.0", + "@opentelemetry/resources": "1.0.0", + "lodash.merge": "^4.6.2" + }, + "engines": { + "node": ">=8.0.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.0.2" + } + }, + "node_modules/@opentelemetry/sdk-metrics-base/node_modules/@opentelemetry/resources": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-1.0.0.tgz", + "integrity": "sha512-ORP8F2LLcJEm5M3H24RmdlMdiDc70ySPushpkrAW34KZGdZXwkrFoFXZhhs5MUxPT+fLrTuBafXxZVr8eHtFuQ==", + "dependencies": { + "@opentelemetry/core": "1.0.0", + "@opentelemetry/semantic-conventions": "1.0.0" + }, + "engines": { + "node": ">=8.0.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.0.2" + } + }, + "node_modules/@opentelemetry/sdk-metrics-base/node_modules/@opentelemetry/semantic-conventions": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.0.0.tgz", + "integrity": "sha512-XCZ6ZSmc8FOspxKUU+Ow9UtJeSSRcS5rFBYGpjzix02U2v+X9ofjOjgNRnpvxlSvkccYIhdTuwcvNskmZ46SeA==", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@opentelemetry/sdk-metrics/node_modules/@opentelemetry/core": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.5.0.tgz", + "integrity": "sha512-ka4H8OM6+DlUhSAZpONu0cPBtPPTQKxbxVzC4CzVx5+K4JnroJVBtDzLAMx4/3CDTJXRvVFhpFjtl4SaiTNoyQ==", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-node": { + "version": "0.211.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-node/-/sdk-node-0.211.0.tgz", + "integrity": "sha512-+s1eGjoqmPCMptNxcJJD4IxbWJKNLOQFNKhpwkzi2gLkEbCj6LzSHJNhPcLeBrBlBLtlSpibM+FuS7fjZ8SSFQ==", + "dependencies": { + "@opentelemetry/api-logs": "0.211.0", + "@opentelemetry/configuration": "0.211.0", + "@opentelemetry/context-async-hooks": "2.5.0", + "@opentelemetry/core": "2.5.0", + "@opentelemetry/exporter-logs-otlp-grpc": "0.211.0", + "@opentelemetry/exporter-logs-otlp-http": "0.211.0", + "@opentelemetry/exporter-logs-otlp-proto": "0.211.0", + "@opentelemetry/exporter-metrics-otlp-grpc": "0.211.0", + "@opentelemetry/exporter-metrics-otlp-http": "0.211.0", + "@opentelemetry/exporter-metrics-otlp-proto": "0.211.0", + "@opentelemetry/exporter-prometheus": "0.211.0", + "@opentelemetry/exporter-trace-otlp-grpc": "0.211.0", + "@opentelemetry/exporter-trace-otlp-http": "0.211.0", + "@opentelemetry/exporter-trace-otlp-proto": "0.211.0", + "@opentelemetry/exporter-zipkin": "2.5.0", + "@opentelemetry/instrumentation": "0.211.0", + "@opentelemetry/propagator-b3": "2.5.0", + "@opentelemetry/propagator-jaeger": "2.5.0", + "@opentelemetry/resources": "2.5.0", + "@opentelemetry/sdk-logs": "0.211.0", + "@opentelemetry/sdk-metrics": "2.5.0", + "@opentelemetry/sdk-trace-base": "2.5.0", + "@opentelemetry/sdk-trace-node": "2.5.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-node/node_modules/@opentelemetry/core": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.5.0.tgz", + "integrity": "sha512-ka4H8OM6+DlUhSAZpONu0cPBtPPTQKxbxVzC4CzVx5+K4JnroJVBtDzLAMx4/3CDTJXRvVFhpFjtl4SaiTNoyQ==", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-trace-base": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.5.0.tgz", + "integrity": "sha512-VzRf8LzotASEyNDUxTdaJ9IRJ1/h692WyArDBInf5puLCjxbICD6XkHgpuudis56EndyS7LYFmtTMny6UABNdQ==", + "dependencies": { + "@opentelemetry/core": "2.5.0", + "@opentelemetry/resources": "2.5.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-trace-base/node_modules/@opentelemetry/core": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.5.0.tgz", + "integrity": "sha512-ka4H8OM6+DlUhSAZpONu0cPBtPPTQKxbxVzC4CzVx5+K4JnroJVBtDzLAMx4/3CDTJXRvVFhpFjtl4SaiTNoyQ==", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-trace-node": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-node/-/sdk-trace-node-2.5.0.tgz", + "integrity": "sha512-O6N/ejzburFm2C84aKNrwJVPpt6HSTSq8T0ZUMq3xT2XmqT4cwxUItcL5UWGThYuq8RTcbH8u1sfj6dmRci0Ow==", + "dependencies": { + "@opentelemetry/context-async-hooks": "2.5.0", + "@opentelemetry/core": "2.5.0", + "@opentelemetry/sdk-trace-base": "2.5.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-trace-node/node_modules/@opentelemetry/core": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.5.0.tgz", + "integrity": "sha512-ka4H8OM6+DlUhSAZpONu0cPBtPPTQKxbxVzC4CzVx5+K4JnroJVBtDzLAMx4/3CDTJXRvVFhpFjtl4SaiTNoyQ==", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/semantic-conventions": { + "version": "1.39.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.39.0.tgz", + "integrity": "sha512-R5R9tb2AXs2IRLNKLBJDynhkfmx7mX0vi8NkhZb3gUkPWHn6HXk5J8iQ/dql0U3ApfWym4kXXmBDRGO+oeOfjg==", + "engines": { + "node": ">=14" + } + }, + "node_modules/@pinojs/redact": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@pinojs/redact/-/redact-0.4.0.tgz", + "integrity": "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==" + }, + "node_modules/@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==" + }, + "node_modules/@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==" + }, + "node_modules/@protobufjs/codegen": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==" + }, + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==" + }, + "node_modules/@protobufjs/fetch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", + "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", + "dependencies": { + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" + } + }, + "node_modules/@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==" + }, + "node_modules/@protobufjs/inquire": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", + "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==" + }, + "node_modules/@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==" + }, + "node_modules/@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==" + }, + "node_modules/@protobufjs/utf8": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", + "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==" + }, + "node_modules/@tootallnate/once": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", + "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", + "engines": { + "node": ">= 10" + } + }, + "node_modules/@types/caseless": { + "version": "0.12.5", + "resolved": "https://registry.npmjs.org/@types/caseless/-/caseless-0.12.5.tgz", + "integrity": "sha512-hWtVTC2q7hc7xZ/RLbxapMvDMgUnDvKvMOpKal4DrMyfGBUfB1oKaZlIRr6mJL+If3bAP6sV/QneGzF6tJjZDg==" + }, + "node_modules/@types/console-log-level": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@types/console-log-level/-/console-log-level-1.4.2.tgz", + "integrity": "sha512-TnhDAntcJthcCMrR3OAKAUjgHyQgoms1yaBJepGv+BtXi8PLf8aX2L/NMCfofRTpVqW0bLklpGTsuqmUSCR2Uw==" + }, + "node_modules/@types/long": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz", + "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==" + }, + "node_modules/@types/node": { + "version": "18.11.9", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.9.tgz", + "integrity": "sha512-CRpX21/kGdzjOpFsZSkcrXMGIBWMGNIHXXBVFSH+ggkftxg+XYP20TESbh+zFvFj3EQOl5byk0HTRn1IL6hbqg==" + }, + "node_modules/@types/request": { + "version": "2.48.12", + "resolved": "https://registry.npmjs.org/@types/request/-/request-2.48.12.tgz", + "integrity": "sha512-G3sY+NpsA9jnwm0ixhAFQSJ3Q9JkpLZpJbI3GMv0mIAT0y3mRabYeINzal5WOChIiaTEGQYlHOKgkaM9EisWHw==", + "dependencies": { + "@types/caseless": "*", + "@types/node": "*", + "@types/tough-cookie": "*", + "form-data": "^2.5.0" + } + }, + "node_modules/@types/semver": { + "version": "7.3.13", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.13.tgz", + "integrity": "sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw==" + }, + "node_modules/@types/tough-cookie": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz", + "integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==" + }, + "node_modules/abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" + }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-import-attributes": { + "version": "1.9.5", + "resolved": "https://registry.npmjs.org/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz", + "integrity": "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==", + "peerDependencies": { + "acorn": "^8" + } + }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/aproba": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", + "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==" + }, + "node_modules/are-we-there-yet": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", + "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==", + "dependencies": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/arrify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", + "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==", + "engines": { + "node": ">=8" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "node_modules/atomic-sleep": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz", + "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/bignumber.js": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.2.tgz", + "integrity": "sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug==", + "engines": { + "node": "*" + } + }, + "node_modules/bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "dependencies": { + "file-uri-to-path": "1.0.0" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==" + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "engines": { + "node": ">=10" + } + }, + "node_modules/cjs-module-lexer": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz", + "integrity": "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==" + }, + "node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "bin": { + "color-support": "bin.js" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + }, + "node_modules/console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==" + }, + "node_modules/console-log-level": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/console-log-level/-/console-log-level-1.4.1.tgz", + "integrity": "sha512-VZzbIORbP+PPcN/gg3DXClTLPLg5Slwd5fL2MIc+o1qZ4BXBvWyc6QxPk6T/Mkr6IVjRpoAGf32XxP3ZWMVRcQ==" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/delay": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/delay/-/delay-5.0.0.tgz", + "integrity": "sha512-ReEBKkIfe4ya47wlPYf/gu5ib6yUG0/Aez0JQZQz94kiWtRQvZIQbTiehsnwHvLSWJnQdhVeqYue7Id1dKr0qw==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==" + }, + "node_modules/detect-libc": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.1.tgz", + "integrity": "sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w==", + "engines": { + "node": ">=8" + } + }, + "node_modules/dot-prop": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-6.0.1.tgz", + "integrity": "sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA==", + "dependencies": { + "is-obj": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/duplexify": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.2.tgz", + "integrity": "sha512-fz3OjcNCHmRP12MJoZMPglx8m4rrFP8rovnk4vT8Fs+aonZoCwGg10dSsQsfP/E62eZcPTMSMP6686fu9Qlqtw==", + "dependencies": { + "end-of-stream": "^1.4.1", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1", + "stream-shift": "^1.0.0" + } + }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "engines": { + "node": ">=6" + } + }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "engines": { + "node": ">=6" + } + }, + "node_modules/eventid": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/eventid/-/eventid-2.0.1.tgz", + "integrity": "sha512-sPNTqiMokAvV048P2c9+foqVJzk49o6d4e0D/sq5jog3pw+4kBgyR0gaM1FM7Mx6Kzd9dztesh9oYz1LWWOpzw==", + "dependencies": { + "uuid": "^8.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/eventid/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + }, + "node_modules/file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==" + }, + "node_modules/findit2": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/findit2/-/findit2-2.2.3.tgz", + "integrity": "sha512-lg/Moejf4qXovVutL0Lz4IsaPoNYMuxt4PA0nGqFxnJ1CTTGGlEO2wKgoDpwknhvZ8k4Q2F+eesgkLbG2Mxfog==", + "engines": { + "node": ">=0.8.22" + } + }, + "node_modules/form-data": { + "version": "2.5.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.5.tgz", + "integrity": "sha512-jqdObeR2rxZZbPSGL+3VckHMYtu+f9//KXBsVny6JSX/pa38Fy+bGjuG8eW/H6USNQWhLi8Num++cU2yOCNz4A==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.35", + "safe-buffer": "^5.2.1" + }, + "engines": { + "node": ">= 0.12" + } + }, + "node_modules/fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gauge": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz", + "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==", + "dependencies": { + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.2", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.1", + "object-assign": "^4.1.1", + "signal-exit": "^3.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/gaxios": { + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-6.7.1.tgz", + "integrity": "sha512-LDODD4TMYx7XXdpwxAVRAIAuB0bzv0s+ywFonY46k126qzQHT9ygyoa9tncmOiQmmDrik65UYsEkv3lbfqQ3yQ==", + "dependencies": { + "extend": "^3.0.2", + "https-proxy-agent": "^7.0.1", + "is-stream": "^2.0.0", + "node-fetch": "^2.6.9", + "uuid": "^9.0.1" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/gaxios/node_modules/agent-base": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", + "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/gaxios/node_modules/https-proxy-agent": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz", + "integrity": "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==", + "dependencies": { + "agent-base": "^7.0.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/gaxios/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/gcp-metadata": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-6.1.0.tgz", + "integrity": "sha512-Jh/AIwwgaxan+7ZUUmRLCjtchyDiqh4KjBJ5tW3plBZb5iL/BPcso8A5DlzeD9qlw0duCamnNdpFjxwaT0KyKg==", + "dependencies": { + "gaxios": "^6.0.0", + "json-bigint": "^1.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/google-auth-library": { + "version": "9.14.1", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-9.14.1.tgz", + "integrity": "sha512-Rj+PMjoNFGFTmtItH7gHfbHpGVSb3vmnGK3nwNBqxQF9NoBpttSZI/rc0WiM63ma2uGDQtYEkMHkK9U6937NiA==", + "dependencies": { + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "gaxios": "^6.1.1", + "gcp-metadata": "^6.1.0", + "gtoken": "^7.0.0", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/google-gax": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-4.4.1.tgz", + "integrity": "sha512-Phyp9fMfA00J3sZbJxbbB4jC55b7DBjE3F6poyL3wKMEBVKA79q6BGuHcTiM28yOzVql0NDbRL8MLLh8Iwk9Dg==", + "dependencies": { + "@grpc/grpc-js": "^1.10.9", + "@grpc/proto-loader": "^0.7.13", + "@types/long": "^4.0.0", + "abort-controller": "^3.0.0", + "duplexify": "^4.0.0", + "google-auth-library": "^9.3.0", + "node-fetch": "^2.7.0", + "object-hash": "^3.0.0", + "proto3-json-serializer": "^2.0.2", + "protobufjs": "^7.3.2", + "retry-request": "^7.0.0", + "uuid": "^9.0.1" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/google-gax/node_modules/@grpc/proto-loader": { + "version": "0.7.15", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.15.tgz", + "integrity": "sha512-tMXdRCfYVixjuFK+Hk0Q1s38gV9zDiDJfWL3h1rv4Qc39oILCu1TRTDt7+fGUI8K4G1Fj125Hx/ru3azECWTyQ==", + "dependencies": { + "lodash.camelcase": "^4.3.0", + "long": "^5.0.0", + "protobufjs": "^7.2.5", + "yargs": "^17.7.2" + }, + "bin": { + "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/google-gax/node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/google-gax/node_modules/long": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", + "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==" + }, + "node_modules/google-gax/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/google-gax/node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/google-gax/node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "engines": { + "node": ">=12" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gtoken": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-7.1.0.tgz", + "integrity": "sha512-pCcEwRi+TKpMlxAQObHDQ56KawURgyAf6jtIY046fJ5tIv3zDe/LEIubckAO8fj6JnAxLdmWkUfNyulQ2iKdEw==", + "dependencies": { + "gaxios": "^6.0.0", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==" + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/html-entities": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.5.2.tgz", + "integrity": "sha512-K//PSRMQk4FZ78Kyau+mZurHn3FH0Vwr+H36eE0rPbeYkRRi9YxceYPhuN60UwWorxyKHhqoAJl2OFKa4BVtaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/mdevils" + }, + { + "type": "patreon", + "url": "https://patreon.com/mdevils" + } + ] + }, + "node_modules/http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "dependencies": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/import-in-the-middle": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/import-in-the-middle/-/import-in-the-middle-2.0.0.tgz", + "integrity": "sha512-yNZhyQYqXpkT0AKq3F3KLasUSK4fHvebNH5hOsKQw2dhGSALvQ4U0BqUc5suziKvydO5u5hgN2hy1RJaho8U5A==", + "dependencies": { + "acorn": "^8.14.0", + "acorn-import-attributes": "^1.9.5", + "cjs-module-lexer": "^1.2.2", + "module-details-from-path": "^1.0.3" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/json-bigint": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", + "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", + "dependencies": { + "bignumber.js": "^9.0.0" + } + }, + "node_modules/jwa": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", + "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", + "dependencies": { + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", + "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", + "dependencies": { + "jwa": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==" + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==" + }, + "node_modules/lodash.sortby": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", + "integrity": "sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==" + }, + "node_modules/long": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", + "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" + }, + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/module-details-from-path": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/module-details-from-path/-/module-details-from-path-1.0.3.tgz", + "integrity": "sha512-ySViT69/76t8VhE1xXHK6Ch4NcDd26gx0MzKXLO+F7NOtnqH68d9zF94nT8ZWSxXh8ELOERsnJO/sWt1xZYw5A==" + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/nan": { + "version": "2.17.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.17.0.tgz", + "integrity": "sha512-2ZTgtl0nJsO0KQCjEpxcIr5D+Yv90plTitZt9JBfQvVJDS5seMl3FOvsh3+9CoYWXf/1l5OaZzzF6nDm4cagaQ==" + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/nopt": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", + "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", + "dependencies": { + "abbrev": "1" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/npmlog": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", + "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==", + "dependencies": { + "are-we-there-yet": "^2.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^3.0.0", + "set-blocking": "^2.0.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "engines": { + "node": ">= 6" + } + }, + "node_modules/on-exit-leak-free": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.0.tgz", + "integrity": "sha512-VuCaZZAjReZ3vUwgOB8LxAosIurDiAW0s13rI1YwmaP++jvcxP77AWoQvenZebpCA2m8WC1/EosPYPMjnRAp/w==" + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parse-ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-2.1.0.tgz", + "integrity": "sha512-kHt7kzLoS9VBZfUsiKjv43mr91ea+U05EyKkEtqp7vNbHxmaVuEqN7XxeEVnGrMtYOAxGrDElSi96K7EgO1zCA==", + "engines": { + "node": ">=6" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pino": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/pino/-/pino-10.3.0.tgz", + "integrity": "sha512-0GNPNzHXBKw6U/InGe79A3Crzyk9bcSyObF9/Gfo9DLEf5qj5RF50RSjsu0W1rZ6ZqRGdzDFCRBQvi9/rSGPtA==", + "dependencies": { + "@pinojs/redact": "^0.4.0", + "atomic-sleep": "^1.0.0", + "on-exit-leak-free": "^2.1.0", + "pino-abstract-transport": "^3.0.0", + "pino-std-serializers": "^7.0.0", + "process-warning": "^5.0.0", + "quick-format-unescaped": "^4.0.3", + "real-require": "^0.2.0", + "safe-stable-stringify": "^2.3.1", + "sonic-boom": "^4.0.1", + "thread-stream": "^4.0.0" + }, + "bin": { + "pino": "bin.js" + } + }, + "node_modules/pino-abstract-transport": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-3.0.0.tgz", + "integrity": "sha512-wlfUczU+n7Hy/Ha5j9a/gZNy7We5+cXp8YL+X+PG8S0KXxw7n/JXA3c46Y0zQznIJ83URJiwy7Lh56WLokNuxg==", + "dependencies": { + "split2": "^4.0.0" + } + }, + "node_modules/pino-std-serializers": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-7.0.0.tgz", + "integrity": "sha512-e906FRY0+tV27iq4juKzSYPbUj2do2X2JX4EzSca1631EB2QJQUqGbDuERal7LCtOpxl6x3+nvo9NPZcmjkiFA==" + }, + "node_modules/pprof": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/pprof/-/pprof-4.0.0.tgz", + "integrity": "sha512-Yhfk7Y0G1MYsy97oXxmSG5nvbM1sCz9EALiNhW/isAv5Xf7svzP+1RfGeBlS6mLSgRJvgSLh6Mi5DaisQuPttw==", + "hasInstallScript": true, + "dependencies": { + "@mapbox/node-pre-gyp": "^1.0.9", + "bindings": "^1.2.1", + "delay": "^5.0.0", + "findit2": "^2.2.3", + "nan": "^2.17.0", + "p-limit": "^3.0.0", + "protobufjs": "~7.2.4", + "source-map": "~0.8.0-beta.0", + "split": "^1.0.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/pprof/node_modules/long": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz", + "integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==" + }, + "node_modules/pprof/node_modules/protobufjs": { + "version": "7.2.5", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.2.5.tgz", + "integrity": "sha512-gGXRSXvxQ7UiPgfw8gevrfRWcTlSbOFg+p/N+JVJEK5VhueL2miT6qTymqAmjr1Q5WbOCyJbyrk6JfWKwlFn6A==", + "hasInstallScript": true, + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/node": ">=13.7.0", + "long": "^5.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/pretty-ms": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-7.0.1.tgz", + "integrity": "sha512-973driJZvxiGOQ5ONsFhOF/DtzPMOMtgC11kCpUrPGMTgqp2q/1gwzCquocrN33is0VZ5GFHXZYMM9l6h67v2Q==", + "dependencies": { + "parse-ms": "^2.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/process-warning": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-5.0.0.tgz", + "integrity": "sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ] + }, + "node_modules/proto3-json-serializer": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/proto3-json-serializer/-/proto3-json-serializer-2.0.2.tgz", + "integrity": "sha512-SAzp/O4Yh02jGdRc+uIrGoe87dkN/XtwxfZ4ZyafJHymd79ozp5VG5nyZ7ygqPM5+cpLDjjGnYFUkngonyDPOQ==", + "dependencies": { + "protobufjs": "^7.2.5" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/protobufjs": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.4.0.tgz", + "integrity": "sha512-mRUWCc3KUU4w1jU8sGxICXH/gNS94DvI1gxqDvBzhj1JpcsimQkYiOJfwsPUykUI5ZaspFbSgmBLER8IrQ3tqw==", + "hasInstallScript": true, + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/node": ">=13.7.0", + "long": "^5.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/protobufjs/node_modules/long": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz", + "integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==" + }, + "node_modules/pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/pumpify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-2.0.1.tgz", + "integrity": "sha512-m7KOje7jZxrmutanlkS1daj1dS6z6BgslzOXmcSEpIlCxM3VJH7lG5QLeck/6hgF6F4crFf01UtQmNsJfweTAw==", + "dependencies": { + "duplexify": "^4.1.1", + "inherits": "^2.0.3", + "pump": "^3.0.0" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "engines": { + "node": ">=6" + } + }, + "node_modules/quick-format-unescaped": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz", + "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==" + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/real-require": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz", + "integrity": "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==", + "engines": { + "node": ">= 12.13.0" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-in-the-middle": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/require-in-the-middle/-/require-in-the-middle-8.0.0.tgz", + "integrity": "sha512-9s0pnM5tH8G4dSI3pms2GboYOs25LwOGnRMxN/Hx3TYT1K0rh6OjaWf4dI0DAQnMyaEXWoGVnSTPQasqwzTTAA==", + "dependencies": { + "debug": "^4.3.5", + "module-details-from-path": "^1.0.3" + }, + "engines": { + "node": ">=9.3.0 || >=8.10.0 <9.0.0" + } + }, + "node_modules/retry-request": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/retry-request/-/retry-request-7.0.2.tgz", + "integrity": "sha512-dUOvLMJ0/JJYEn8NrpOaGNE7X3vpI5XlZS/u0ANjqtcZVKnIxP7IgCFwrKTxENw29emmwug53awKtaMm4i9g5w==", + "dependencies": { + "@types/request": "^2.48.8", + "extend": "^3.0.2", + "teeny-request": "^9.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/safe-stable-stringify": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.4.1.tgz", + "integrity": "sha512-dVHE6bMtS/bnL2mwualjc6IxEv1F+OCUpA46pKUj6F8uDbUM0jCCulPqRNPSnWwGNKx5etqMjZYdXtrm5KJZGA==", + "engines": { + "node": ">=10" + } + }, + "node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==" + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" + }, + "node_modules/simple-card-validator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/simple-card-validator/-/simple-card-validator-1.1.0.tgz", + "integrity": "sha512-ag7OoeyWwzDrDyiffyrIWumxDJ7+VCbhOoHxzY6ck8b7qmGEA7A5NS9GxXbqmuE2wIgBAQ0S5vNiIclHtgliQg==" + }, + "node_modules/sonic-boom": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-4.0.1.tgz", + "integrity": "sha512-hTSD/6JMLyT4r9zeof6UtuBDpjJ9sO08/nmS5djaA9eozT9oOlNdpXSnzcgj4FTqpk3nkLrs61l4gip9r1HCrQ==", + "dependencies": { + "atomic-sleep": "^1.0.0" + } + }, + "node_modules/source-map": { + "version": "0.8.0-beta.0", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.8.0-beta.0.tgz", + "integrity": "sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==", + "dependencies": { + "whatwg-url": "^7.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/source-map/node_modules/tr46": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", + "integrity": "sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/source-map/node_modules/webidl-conversions": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", + "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==" + }, + "node_modules/source-map/node_modules/whatwg-url": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.1.0.tgz", + "integrity": "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==", + "dependencies": { + "lodash.sortby": "^4.7.0", + "tr46": "^1.0.1", + "webidl-conversions": "^4.0.2" + } + }, + "node_modules/split": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz", + "integrity": "sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==", + "dependencies": { + "through": "2" + }, + "engines": { + "node": "*" + } + }, + "node_modules/split2": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.1.0.tgz", + "integrity": "sha512-VBiJxFkxiXRlUIeyMQi8s4hgvKCSjtknJv/LVYbrgALPwf5zSKmEwV9Lst25AkvMDnvxODugjdl6KZgwKM1WYQ==", + "engines": { + "node": ">= 10.x" + } + }, + "node_modules/stream-events": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/stream-events/-/stream-events-1.0.5.tgz", + "integrity": "sha512-E1GUzBSgvct8Jsb3v2X15pjzN1tYebtbLaMg+eBOUOAxgbLoSbT2NS91ckc5lJD1KfLjId+jXJRgo0qnV5Nerg==", + "dependencies": { + "stubs": "^3.0.0" + } + }, + "node_modules/stream-shift": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz", + "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==" + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/stubs": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/stubs/-/stubs-3.0.0.tgz", + "integrity": "sha512-PdHt7hHUJKxvTCgbKX9C1V/ftOcjJQgz8BZwNfV5c4B6dcGqlpelTbJ999jBGZ2jYiPAwcX5dP6oBwVlBlUbxw==" + }, + "node_modules/tar": { + "version": "6.1.12", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.12.tgz", + "integrity": "sha512-jU4TdemS31uABHd+Lt5WEYJuzn+TJTCBLljvIAHZOz6M9Os5pJ4dD+vRFLxPa/n3T0iEFzpi+0x1UfuDZYbRMw==", + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^3.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/teeny-request": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-9.0.0.tgz", + "integrity": "sha512-resvxdc6Mgb7YEThw6G6bExlXKkv6+YbuzGg9xuXxSgxJF7Ozs+o8Y9+2R3sArdWdW8nOokoQb1yrpFB0pQK2g==", + "dependencies": { + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.0", + "node-fetch": "^2.6.9", + "stream-events": "^1.0.5", + "uuid": "^9.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/teeny-request/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/thread-stream": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-4.0.0.tgz", + "integrity": "sha512-4iMVL6HAINXWf1ZKZjIPcz5wYaOdPhtO8ATvZ+Xqp3BTdaqtAwQkNmKORqcIo5YkQqGXq5cwfswDwMqqQNrpJA==", + "dependencies": { + "real-require": "^0.2.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==" + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + }, + "node_modules/uuid": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-13.0.0.tgz", + "integrity": "sha512-XQegIaBTVUjSHliKqcnFqYypAd4S+WCYt5NIeRs6w/UAry7z8Y9j5ZwRRL4kzq9U3sD6v+85er9FvkEaBpji2w==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "bin": { + "uuid": "dist-node/bin/uuid" + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/wide-align": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", + "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", + "dependencies": { + "string-width": "^1.0.2 || 2 || 3 || 4" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, + "node_modules/yaml": { + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.2.tgz", + "integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + }, + "node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "engines": { + "node": ">=10" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + }, + "dependencies": { + "@google-cloud/common": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@google-cloud/common/-/common-5.0.2.tgz", + "integrity": "sha512-V7bmBKYQyu0eVG2BFejuUjlBt+zrya6vtsKdY+JxMM/dNntPF41vZ9+LhOshEUH01zOHEqBSvI7Dad7ZS6aUeA==", + "requires": { + "@google-cloud/projectify": "^4.0.0", + "@google-cloud/promisify": "^4.0.0", + "arrify": "^2.0.1", + "duplexify": "^4.1.1", + "extend": "^3.0.2", + "google-auth-library": "^9.0.0", + "html-entities": "^2.5.2", + "retry-request": "^7.0.0", + "teeny-request": "^9.0.0" + } + }, + "@google-cloud/logging-min": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/@google-cloud/logging-min/-/logging-min-11.2.0.tgz", + "integrity": "sha512-o1mwzi1+9NMEjwYZJ0X3tK64obf9PzPVBAhzEJv65L0h7jVl3Fw7GswtsMUkdUvZexf96vAvlZZMvXB9jAIW2Q==", + "requires": { + "@google-cloud/common": "^5.0.0", + "@google-cloud/paginator": "^5.0.0", + "@google-cloud/projectify": "^4.0.0", + "@google-cloud/promisify": "^4.0.0", + "@opentelemetry/api": "^1.7.0", + "arrify": "^2.0.1", + "dot-prop": "^6.0.0", + "eventid": "^2.0.0", + "extend": "^3.0.2", + "gcp-metadata": "^6.0.0", + "google-auth-library": "^9.0.0", + "google-gax": "^4.0.3", + "on-finished": "^2.3.0", + "pumpify": "^2.0.1", + "stream-events": "^1.0.5", + "uuid": "^9.0.0" + }, + "dependencies": { + "uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==" + } + } + }, + "@google-cloud/paginator": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@google-cloud/paginator/-/paginator-5.0.2.tgz", + "integrity": "sha512-DJS3s0OVH4zFDB1PzjxAsHqJT6sKVbRwwML0ZBP9PbU7Yebtu/7SWMRzvO2J3nUi9pRNITCfu4LJeooM2w4pjg==", + "requires": { + "arrify": "^2.0.0", + "extend": "^3.0.2" + } + }, + "@google-cloud/profiler": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@google-cloud/profiler/-/profiler-6.0.3.tgz", + "integrity": "sha512-Ey8li6Vc2CbfEzOTSZaqKolxPMGacxVUQuhChNT0Wi55a3nfImMiiuDgqYw1In/a9Q3Z62O7jUg2L8f1XwMN7Q==", + "requires": { + "@google-cloud/common": "^5.0.0", + "@google-cloud/logging-min": "^11.0.0", + "@google-cloud/promisify": "~4.0.0", + "@types/console-log-level": "^1.4.0", + "@types/semver": "^7.0.0", + "console-log-level": "^1.4.0", + "delay": "^5.0.0", + "extend": "^3.0.2", + "gcp-metadata": "^6.0.0", + "ms": "^2.1.3", + "pprof": "4.0.0", + "pretty-ms": "^7.0.0", + "protobufjs": "~7.4.0", + "semver": "^7.0.0", + "teeny-request": "^9.0.0" + } + }, + "@google-cloud/projectify": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@google-cloud/projectify/-/projectify-4.0.0.tgz", + "integrity": "sha512-MmaX6HeSvyPbWGwFq7mXdo0uQZLGBYCwziiLIGq5JVX+/bdI3SAq6bP98trV5eTWfLuvsMcIC1YJOF2vfteLFA==" + }, + "@google-cloud/promisify": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@google-cloud/promisify/-/promisify-4.0.0.tgz", + "integrity": "sha512-Orxzlfb9c67A15cq2JQEyVc7wEsmFBmHjZWZYQMUyJ1qivXyMwdyNOs9odi79hze+2zqdTtu1E19IM/FtqZ10g==" + }, + "@grpc/grpc-js": { + "version": "1.14.3", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.14.3.tgz", + "integrity": "sha512-Iq8QQQ/7X3Sac15oB6p0FmUg/klxQvXLeileoqrTRGJYLV+/9tubbr9ipz0GKHjmXVsgFPo/+W+2cA8eNcR+XA==", + "requires": { + "@grpc/proto-loader": "^0.8.0", + "@js-sdsl/ordered-map": "^4.4.2" + } + }, + "@grpc/proto-loader": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.8.0.tgz", + "integrity": "sha512-rc1hOQtjIWGxcxpb9aHAfLpIctjEnsDehj0DAiVfBlmT84uvR0uUtN2hEi/ecvWVjXUGf5qPF4qEgiLOx1YIMQ==", + "requires": { + "lodash.camelcase": "^4.3.0", + "long": "^5.0.0", + "protobufjs": "^7.5.3", + "yargs": "^17.7.2" + }, + "dependencies": { + "cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + } + }, + "long": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", + "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==" + }, + "protobufjs": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.4.tgz", + "integrity": "sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg==", + "requires": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/node": ">=13.7.0", + "long": "^5.0.0" + } + }, + "yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "requires": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + } + }, + "yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==" + } + } + }, + "@js-sdsl/ordered-map": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/@js-sdsl/ordered-map/-/ordered-map-4.4.2.tgz", + "integrity": "sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==" + }, + "@mapbox/node-pre-gyp": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.10.tgz", + "integrity": "sha512-4ySo4CjzStuprMwk35H5pPbkymjv1SF3jGLj6rAHp/xT/RF7TL7bd9CTm1xDY49K2qF7jmR/g7k+SkLETP6opA==", + "requires": { + "detect-libc": "^2.0.0", + "https-proxy-agent": "^5.0.0", + "make-dir": "^3.1.0", + "node-fetch": "^2.6.7", + "nopt": "^5.0.0", + "npmlog": "^5.0.1", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "tar": "^6.1.11" + } + }, + "@opentelemetry/api": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", + "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==" + }, + "@opentelemetry/api-logs": { + "version": "0.211.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.211.0.tgz", + "integrity": "sha512-swFdZq8MCdmdR22jTVGQDhwqDzcI4M10nhjXkLr1EsIzXgZBqm4ZlmmcWsg3TSNf+3mzgOiqveXmBLZuDi2Lgg==", + "requires": { + "@opentelemetry/api": "^1.3.0" + } + }, + "@opentelemetry/api-metrics": { + "version": "0.26.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-metrics/-/api-metrics-0.26.0.tgz", + "integrity": "sha512-idDSUTx+LRwJiHhVHhdh45SWow5u9lKNDROKu5AMzsIVPI29utH5FfT9vor8qMM6blxWWvlT22HUNdNMWqUQfQ==", + "requires": {} + }, + "@opentelemetry/configuration": { + "version": "0.211.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/configuration/-/configuration-0.211.0.tgz", + "integrity": "sha512-PNsCkzsYQKyv8wiUIsH+loC4RYyblOaDnVASBtKS22hK55ToWs2UP6IsrcfSWWn54wWTvVe2gnfwz67Pvrxf2Q==", + "requires": { + "@opentelemetry/core": "2.5.0", + "yaml": "^2.0.0" + }, + "dependencies": { + "@opentelemetry/core": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.5.0.tgz", + "integrity": "sha512-ka4H8OM6+DlUhSAZpONu0cPBtPPTQKxbxVzC4CzVx5+K4JnroJVBtDzLAMx4/3CDTJXRvVFhpFjtl4SaiTNoyQ==", + "requires": { + "@opentelemetry/semantic-conventions": "^1.29.0" + } + } + } + }, + "@opentelemetry/context-async-hooks": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/context-async-hooks/-/context-async-hooks-2.5.0.tgz", + "integrity": "sha512-uOXpVX0ZjO7heSVjhheW2XEPrhQAWr2BScDPoZ9UDycl5iuHG+Usyc3AIfG6kZeC1GyLpMInpQ6X5+9n69yOFw==", + "requires": {} + }, + "@opentelemetry/core": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-1.0.0.tgz", + "integrity": "sha512-1+qvKilADnSFW4PiXy+f7D22pvfGVxepZ69GcbF8cTcbQTUt7w63xEBWn5f5j92x9I3c0sqbW1RUx5/a4wgzxA==", + "requires": { + "@opentelemetry/semantic-conventions": "1.0.0", + "semver": "^7.3.5" + }, + "dependencies": { + "@opentelemetry/semantic-conventions": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.0.0.tgz", + "integrity": "sha512-XCZ6ZSmc8FOspxKUU+Ow9UtJeSSRcS5rFBYGpjzix02U2v+X9ofjOjgNRnpvxlSvkccYIhdTuwcvNskmZ46SeA==" + } + } + }, + "@opentelemetry/exporter-logs-otlp-grpc": { + "version": "0.211.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-logs-otlp-grpc/-/exporter-logs-otlp-grpc-0.211.0.tgz", + "integrity": "sha512-UhOoWENNqyaAMP/dL1YXLkXt6ZBtovkDDs1p4rxto9YwJX1+wMjwg+Obfyg2kwpcMoaiIFT3KQIcLNW8nNGNfQ==", + "requires": { + "@grpc/grpc-js": "^1.7.1", + "@opentelemetry/core": "2.5.0", + "@opentelemetry/otlp-exporter-base": "0.211.0", + "@opentelemetry/otlp-grpc-exporter-base": "0.211.0", + "@opentelemetry/otlp-transformer": "0.211.0", + "@opentelemetry/sdk-logs": "0.211.0" + }, + "dependencies": { + "@opentelemetry/core": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.5.0.tgz", + "integrity": "sha512-ka4H8OM6+DlUhSAZpONu0cPBtPPTQKxbxVzC4CzVx5+K4JnroJVBtDzLAMx4/3CDTJXRvVFhpFjtl4SaiTNoyQ==", + "requires": { + "@opentelemetry/semantic-conventions": "^1.29.0" + } + } + } + }, + "@opentelemetry/exporter-logs-otlp-http": { + "version": "0.211.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-logs-otlp-http/-/exporter-logs-otlp-http-0.211.0.tgz", + "integrity": "sha512-c118Awf1kZirHkqxdcF+rF5qqWwNjJh+BB1CmQvN9AQHC/DUIldy6dIkJn3EKlQnQ3HmuNRKc/nHHt5IusN7mA==", + "requires": { + "@opentelemetry/api-logs": "0.211.0", + "@opentelemetry/core": "2.5.0", + "@opentelemetry/otlp-exporter-base": "0.211.0", + "@opentelemetry/otlp-transformer": "0.211.0", + "@opentelemetry/sdk-logs": "0.211.0" + }, + "dependencies": { + "@opentelemetry/core": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.5.0.tgz", + "integrity": "sha512-ka4H8OM6+DlUhSAZpONu0cPBtPPTQKxbxVzC4CzVx5+K4JnroJVBtDzLAMx4/3CDTJXRvVFhpFjtl4SaiTNoyQ==", + "requires": { + "@opentelemetry/semantic-conventions": "^1.29.0" + } + } + } + }, + "@opentelemetry/exporter-logs-otlp-proto": { + "version": "0.211.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-logs-otlp-proto/-/exporter-logs-otlp-proto-0.211.0.tgz", + "integrity": "sha512-kMvfKMtY5vJDXeLnwhrZMEwhZ2PN8sROXmzacFU/Fnl4Z79CMrOaL7OE+5X3SObRYlDUa7zVqaXp9ZetYCxfDQ==", + "requires": { + "@opentelemetry/api-logs": "0.211.0", + "@opentelemetry/core": "2.5.0", + "@opentelemetry/otlp-exporter-base": "0.211.0", + "@opentelemetry/otlp-transformer": "0.211.0", + "@opentelemetry/resources": "2.5.0", + "@opentelemetry/sdk-logs": "0.211.0", + "@opentelemetry/sdk-trace-base": "2.5.0" + }, + "dependencies": { + "@opentelemetry/core": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.5.0.tgz", + "integrity": "sha512-ka4H8OM6+DlUhSAZpONu0cPBtPPTQKxbxVzC4CzVx5+K4JnroJVBtDzLAMx4/3CDTJXRvVFhpFjtl4SaiTNoyQ==", + "requires": { + "@opentelemetry/semantic-conventions": "^1.29.0" + } + } + } + }, + "@opentelemetry/exporter-metrics-otlp-grpc": { + "version": "0.211.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-metrics-otlp-grpc/-/exporter-metrics-otlp-grpc-0.211.0.tgz", + "integrity": "sha512-D/U3G8L4PzZp8ot5hX9wpgbTymgtLZCiwR7heMe4LsbGV4OdctS1nfyvaQHLT6CiGZ6FjKc1Vk9s6kbo9SWLXQ==", + "requires": { + "@grpc/grpc-js": "^1.7.1", + "@opentelemetry/core": "2.5.0", + "@opentelemetry/exporter-metrics-otlp-http": "0.211.0", + "@opentelemetry/otlp-exporter-base": "0.211.0", + "@opentelemetry/otlp-grpc-exporter-base": "0.211.0", + "@opentelemetry/otlp-transformer": "0.211.0", + "@opentelemetry/resources": "2.5.0", + "@opentelemetry/sdk-metrics": "2.5.0" + }, + "dependencies": { + "@opentelemetry/core": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.5.0.tgz", + "integrity": "sha512-ka4H8OM6+DlUhSAZpONu0cPBtPPTQKxbxVzC4CzVx5+K4JnroJVBtDzLAMx4/3CDTJXRvVFhpFjtl4SaiTNoyQ==", + "requires": { + "@opentelemetry/semantic-conventions": "^1.29.0" + } + } + } + }, + "@opentelemetry/exporter-metrics-otlp-http": { + "version": "0.211.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-metrics-otlp-http/-/exporter-metrics-otlp-http-0.211.0.tgz", + "integrity": "sha512-lfHXElPAoDSPpPO59DJdN5FLUnwi1wxluLTWQDayqrSPfWRnluzxRhD+g7rF8wbj1qCz0sdqABl//ug1IZyWvA==", + "requires": { + "@opentelemetry/core": "2.5.0", + "@opentelemetry/otlp-exporter-base": "0.211.0", + "@opentelemetry/otlp-transformer": "0.211.0", + "@opentelemetry/resources": "2.5.0", + "@opentelemetry/sdk-metrics": "2.5.0" + }, + "dependencies": { + "@opentelemetry/core": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.5.0.tgz", + "integrity": "sha512-ka4H8OM6+DlUhSAZpONu0cPBtPPTQKxbxVzC4CzVx5+K4JnroJVBtDzLAMx4/3CDTJXRvVFhpFjtl4SaiTNoyQ==", + "requires": { + "@opentelemetry/semantic-conventions": "^1.29.0" + } + } + } + }, + "@opentelemetry/exporter-metrics-otlp-proto": { + "version": "0.211.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-metrics-otlp-proto/-/exporter-metrics-otlp-proto-0.211.0.tgz", + "integrity": "sha512-61iNbffEpyZv/abHaz3BQM3zUtA2kVIDBM+0dS9RK68ML0QFLRGYa50xVMn2PYMToyfszEPEgFC3ypGae2z8FA==", + "requires": { + "@opentelemetry/core": "2.5.0", + "@opentelemetry/exporter-metrics-otlp-http": "0.211.0", + "@opentelemetry/otlp-exporter-base": "0.211.0", + "@opentelemetry/otlp-transformer": "0.211.0", + "@opentelemetry/resources": "2.5.0", + "@opentelemetry/sdk-metrics": "2.5.0" + }, + "dependencies": { + "@opentelemetry/core": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.5.0.tgz", + "integrity": "sha512-ka4H8OM6+DlUhSAZpONu0cPBtPPTQKxbxVzC4CzVx5+K4JnroJVBtDzLAMx4/3CDTJXRvVFhpFjtl4SaiTNoyQ==", + "requires": { + "@opentelemetry/semantic-conventions": "^1.29.0" + } + } + } + }, + "@opentelemetry/exporter-otlp-grpc": { + "version": "0.26.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-otlp-grpc/-/exporter-otlp-grpc-0.26.0.tgz", + "integrity": "sha512-64VPck7CbGhI7c2bj54xaGGHCy4mP+rMnrKBAruNBmUUnN9OgihddNcMsIiHyddUcyC1I+hXS3JLW1G6AvlAmg==", + "requires": { + "@grpc/grpc-js": "^1.3.7", + "@grpc/proto-loader": "^0.6.4", + "@opentelemetry/core": "1.0.0", + "@opentelemetry/exporter-otlp-http": "0.26.0", + "@opentelemetry/resources": "1.0.0", + "@opentelemetry/sdk-metrics-base": "0.26.0", + "@opentelemetry/sdk-trace-base": "1.0.0" + }, + "dependencies": { + "@grpc/proto-loader": { + "version": "0.6.13", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.6.13.tgz", + "integrity": "sha512-FjxPYDRTn6Ec3V0arm1FtSpmP6V50wuph2yILpyvTKzjc76oDdoihXqM1DzOW5ubvCC8GivfCnNtfaRE8myJ7g==", + "requires": { + "@types/long": "^4.0.1", + "lodash.camelcase": "^4.3.0", + "long": "^4.0.0", + "protobufjs": "^6.11.3", + "yargs": "^16.2.0" + } + }, + "@opentelemetry/resources": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-1.0.0.tgz", + "integrity": "sha512-ORP8F2LLcJEm5M3H24RmdlMdiDc70ySPushpkrAW34KZGdZXwkrFoFXZhhs5MUxPT+fLrTuBafXxZVr8eHtFuQ==", + "requires": { + "@opentelemetry/core": "1.0.0", + "@opentelemetry/semantic-conventions": "1.0.0" + } + }, + "@opentelemetry/sdk-trace-base": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-1.0.0.tgz", + "integrity": "sha512-/rXoyQlDlJTJ4SOVAbP0Gpj89B8oZ2hJApYG2Dq5klkgFAtDifN8271TIzwtM8/ET8HUhgx9eyoUJi42LhIesg==", + "requires": { + "@opentelemetry/core": "1.0.0", + "@opentelemetry/resources": "1.0.0", + "@opentelemetry/semantic-conventions": "1.0.0", + "lodash.merge": "^4.6.2" + } + }, + "@opentelemetry/semantic-conventions": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.0.0.tgz", + "integrity": "sha512-XCZ6ZSmc8FOspxKUU+Ow9UtJeSSRcS5rFBYGpjzix02U2v+X9ofjOjgNRnpvxlSvkccYIhdTuwcvNskmZ46SeA==" + }, + "protobufjs": { + "version": "6.11.4", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.4.tgz", + "integrity": "sha512-5kQWPaJHi1WoCpjTGszzQ32PG2F4+wRY6BmAT4Vfw56Q2FZ4YZzK20xUYQH4YkfehY1e6QSICrJquM6xXZNcrw==", + "requires": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/long": "^4.0.1", + "@types/node": ">=13.7.0", + "long": "^4.0.0" + } + } + } + }, + "@opentelemetry/exporter-otlp-http": { + "version": "0.26.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-otlp-http/-/exporter-otlp-http-0.26.0.tgz", + "integrity": "sha512-V3FcUEIVDZ66b3/6vjSBjwwozf/XV5eUXuELNzN8PAvGZH4mw36vaWlaxnGEV8HaZb2hbu2KbRpcOzqxx3tFDA==", + "requires": { + "@opentelemetry/api-metrics": "0.26.0", + "@opentelemetry/core": "1.0.0", + "@opentelemetry/resources": "1.0.0", + "@opentelemetry/sdk-metrics-base": "0.26.0", + "@opentelemetry/sdk-trace-base": "1.0.0" + }, + "dependencies": { + "@opentelemetry/resources": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-1.0.0.tgz", + "integrity": "sha512-ORP8F2LLcJEm5M3H24RmdlMdiDc70ySPushpkrAW34KZGdZXwkrFoFXZhhs5MUxPT+fLrTuBafXxZVr8eHtFuQ==", + "requires": { + "@opentelemetry/core": "1.0.0", + "@opentelemetry/semantic-conventions": "1.0.0" + } + }, + "@opentelemetry/sdk-trace-base": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-1.0.0.tgz", + "integrity": "sha512-/rXoyQlDlJTJ4SOVAbP0Gpj89B8oZ2hJApYG2Dq5klkgFAtDifN8271TIzwtM8/ET8HUhgx9eyoUJi42LhIesg==", + "requires": { + "@opentelemetry/core": "1.0.0", + "@opentelemetry/resources": "1.0.0", + "@opentelemetry/semantic-conventions": "1.0.0", + "lodash.merge": "^4.6.2" + } + }, + "@opentelemetry/semantic-conventions": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.0.0.tgz", + "integrity": "sha512-XCZ6ZSmc8FOspxKUU+Ow9UtJeSSRcS5rFBYGpjzix02U2v+X9ofjOjgNRnpvxlSvkccYIhdTuwcvNskmZ46SeA==" + } + } + }, + "@opentelemetry/exporter-prometheus": { + "version": "0.211.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-prometheus/-/exporter-prometheus-0.211.0.tgz", + "integrity": "sha512-cD0WleEL3TPqJbvxwz5MVdVJ82H8jl8mvMad4bNU24cB5SH2mRW5aMLDTuV4614ll46R//R3RMmci26mc2L99g==", + "requires": { + "@opentelemetry/core": "2.5.0", + "@opentelemetry/resources": "2.5.0", + "@opentelemetry/sdk-metrics": "2.5.0" + }, + "dependencies": { + "@opentelemetry/core": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.5.0.tgz", + "integrity": "sha512-ka4H8OM6+DlUhSAZpONu0cPBtPPTQKxbxVzC4CzVx5+K4JnroJVBtDzLAMx4/3CDTJXRvVFhpFjtl4SaiTNoyQ==", + "requires": { + "@opentelemetry/semantic-conventions": "^1.29.0" + } + } + } + }, + "@opentelemetry/exporter-trace-otlp-grpc": { + "version": "0.211.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-trace-otlp-grpc/-/exporter-trace-otlp-grpc-0.211.0.tgz", + "integrity": "sha512-eFwx4Gvu6LaEiE1rOd4ypgAiWEdZu7Qzm2QNN2nJqPW1XDeAVH1eNwVcVQl+QK9HR/JCDZ78PZgD7xD/DBDqbw==", + "requires": { + "@grpc/grpc-js": "^1.7.1", + "@opentelemetry/core": "2.5.0", + "@opentelemetry/otlp-exporter-base": "0.211.0", + "@opentelemetry/otlp-grpc-exporter-base": "0.211.0", + "@opentelemetry/otlp-transformer": "0.211.0", + "@opentelemetry/resources": "2.5.0", + "@opentelemetry/sdk-trace-base": "2.5.0" + }, + "dependencies": { + "@opentelemetry/core": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.5.0.tgz", + "integrity": "sha512-ka4H8OM6+DlUhSAZpONu0cPBtPPTQKxbxVzC4CzVx5+K4JnroJVBtDzLAMx4/3CDTJXRvVFhpFjtl4SaiTNoyQ==", + "requires": { + "@opentelemetry/semantic-conventions": "^1.29.0" + } + } + } + }, + "@opentelemetry/exporter-trace-otlp-http": { + "version": "0.211.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-trace-otlp-http/-/exporter-trace-otlp-http-0.211.0.tgz", + "integrity": "sha512-F1Rv3JeMkgS//xdVjbQMrI3+26e5SXC7vXA6trx8SWEA0OUhw4JHB+qeHtH0fJn46eFItrYbL5m8j4qi9Sfaxw==", + "requires": { + "@opentelemetry/core": "2.5.0", + "@opentelemetry/otlp-exporter-base": "0.211.0", + "@opentelemetry/otlp-transformer": "0.211.0", + "@opentelemetry/resources": "2.5.0", + "@opentelemetry/sdk-trace-base": "2.5.0" + }, + "dependencies": { + "@opentelemetry/core": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.5.0.tgz", + "integrity": "sha512-ka4H8OM6+DlUhSAZpONu0cPBtPPTQKxbxVzC4CzVx5+K4JnroJVBtDzLAMx4/3CDTJXRvVFhpFjtl4SaiTNoyQ==", + "requires": { + "@opentelemetry/semantic-conventions": "^1.29.0" + } + } + } + }, + "@opentelemetry/exporter-trace-otlp-proto": { + "version": "0.211.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-trace-otlp-proto/-/exporter-trace-otlp-proto-0.211.0.tgz", + "integrity": "sha512-DkjXwbPiqpcPlycUojzG2RmR0/SIK8Gi9qWO9znNvSqgzrnAIE9x2n6yPfpZ+kWHZGafvsvA1lVXucTyyQa5Kg==", + "requires": { + "@opentelemetry/core": "2.5.0", + "@opentelemetry/otlp-exporter-base": "0.211.0", + "@opentelemetry/otlp-transformer": "0.211.0", + "@opentelemetry/resources": "2.5.0", + "@opentelemetry/sdk-trace-base": "2.5.0" + }, + "dependencies": { + "@opentelemetry/core": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.5.0.tgz", + "integrity": "sha512-ka4H8OM6+DlUhSAZpONu0cPBtPPTQKxbxVzC4CzVx5+K4JnroJVBtDzLAMx4/3CDTJXRvVFhpFjtl4SaiTNoyQ==", + "requires": { + "@opentelemetry/semantic-conventions": "^1.29.0" + } + } + } + }, + "@opentelemetry/exporter-zipkin": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-zipkin/-/exporter-zipkin-2.5.0.tgz", + "integrity": "sha512-bk9VJgFgUAzkZzU8ZyXBSWiUGLOM3mZEgKJ1+jsZclhRnAoDNf+YBdq+G9R3cP0+TKjjWad+vVrY/bE/vRR9lA==", + "requires": { + "@opentelemetry/core": "2.5.0", + "@opentelemetry/resources": "2.5.0", + "@opentelemetry/sdk-trace-base": "2.5.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "dependencies": { + "@opentelemetry/core": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.5.0.tgz", + "integrity": "sha512-ka4H8OM6+DlUhSAZpONu0cPBtPPTQKxbxVzC4CzVx5+K4JnroJVBtDzLAMx4/3CDTJXRvVFhpFjtl4SaiTNoyQ==", + "requires": { + "@opentelemetry/semantic-conventions": "^1.29.0" + } + } + } + }, + "@opentelemetry/instrumentation": { + "version": "0.211.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.211.0.tgz", + "integrity": "sha512-h0nrZEC/zvI994nhg7EgQ8URIHt0uDTwN90r3qQUdZORS455bbx+YebnGeEuFghUT0HlJSrLF4iHw67f+odY+Q==", + "requires": { + "@opentelemetry/api-logs": "0.211.0", + "import-in-the-middle": "^2.0.0", + "require-in-the-middle": "^8.0.0" + } + }, + "@opentelemetry/instrumentation-grpc": { + "version": "0.211.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-grpc/-/instrumentation-grpc-0.211.0.tgz", + "integrity": "sha512-bshedE3TaD18OE3oPU15j8bn4vz+3X5mvg9jluoSn/ZjlshCb1FrstjNkTYQuRERWzeMl7WcR8sShr91FcUBXA==", + "requires": { + "@opentelemetry/instrumentation": "0.211.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + } + }, + "@opentelemetry/otlp-exporter-base": { + "version": "0.211.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-exporter-base/-/otlp-exporter-base-0.211.0.tgz", + "integrity": "sha512-bp1+63V8WPV+bRI9EQG6E9YID1LIHYSZVbp7f+44g9tRzCq+rtw/o4fpL5PC31adcUsFiz/oN0MdLISSrZDdrg==", + "requires": { + "@opentelemetry/core": "2.5.0", + "@opentelemetry/otlp-transformer": "0.211.0" + }, + "dependencies": { + "@opentelemetry/core": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.5.0.tgz", + "integrity": "sha512-ka4H8OM6+DlUhSAZpONu0cPBtPPTQKxbxVzC4CzVx5+K4JnroJVBtDzLAMx4/3CDTJXRvVFhpFjtl4SaiTNoyQ==", + "requires": { + "@opentelemetry/semantic-conventions": "^1.29.0" + } + } + } + }, + "@opentelemetry/otlp-grpc-exporter-base": { + "version": "0.211.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-grpc-exporter-base/-/otlp-grpc-exporter-base-0.211.0.tgz", + "integrity": "sha512-mR5X+N4SuphJeb7/K7y0JNMC8N1mB6gEtjyTLv+TSAhl0ZxNQzpSKP8S5Opk90fhAqVYD4R0SQSAirEBlH1KSA==", + "requires": { + "@grpc/grpc-js": "^1.7.1", + "@opentelemetry/core": "2.5.0", + "@opentelemetry/otlp-exporter-base": "0.211.0", + "@opentelemetry/otlp-transformer": "0.211.0" + }, + "dependencies": { + "@opentelemetry/core": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.5.0.tgz", + "integrity": "sha512-ka4H8OM6+DlUhSAZpONu0cPBtPPTQKxbxVzC4CzVx5+K4JnroJVBtDzLAMx4/3CDTJXRvVFhpFjtl4SaiTNoyQ==", + "requires": { + "@opentelemetry/semantic-conventions": "^1.29.0" + } + } + } + }, + "@opentelemetry/otlp-transformer": { + "version": "0.211.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-transformer/-/otlp-transformer-0.211.0.tgz", + "integrity": "sha512-julhCJ9dXwkOg9svuuYqqjXLhVaUgyUvO2hWbTxwjvLXX2rG3VtAaB0SzxMnGTuoCZizBT7Xqqm2V7+ggrfCXA==", + "requires": { + "@opentelemetry/api-logs": "0.211.0", + "@opentelemetry/core": "2.5.0", + "@opentelemetry/resources": "2.5.0", + "@opentelemetry/sdk-logs": "0.211.0", + "@opentelemetry/sdk-metrics": "2.5.0", + "@opentelemetry/sdk-trace-base": "2.5.0", + "protobufjs": "8.0.0" + }, + "dependencies": { + "@opentelemetry/core": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.5.0.tgz", + "integrity": "sha512-ka4H8OM6+DlUhSAZpONu0cPBtPPTQKxbxVzC4CzVx5+K4JnroJVBtDzLAMx4/3CDTJXRvVFhpFjtl4SaiTNoyQ==", + "requires": { + "@opentelemetry/semantic-conventions": "^1.29.0" + } + }, + "long": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", + "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==" + }, + "protobufjs": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-8.0.0.tgz", + "integrity": "sha512-jx6+sE9h/UryaCZhsJWbJtTEy47yXoGNYI4z8ZaRncM0zBKeRqjO2JEcOUYwrYGb1WLhXM1FfMzW3annvFv0rw==", + "requires": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/node": ">=13.7.0", + "long": "^5.0.0" + } + } + } + }, + "@opentelemetry/propagator-b3": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/propagator-b3/-/propagator-b3-2.5.0.tgz", + "integrity": "sha512-g10m4KD73RjHrSvUge+sUxUl8m4VlgnGc6OKvo68a4uMfaLjdFU+AULfvMQE/APq38k92oGUxEzBsAZ8RN/YHg==", + "requires": { + "@opentelemetry/core": "2.5.0" + }, + "dependencies": { + "@opentelemetry/core": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.5.0.tgz", + "integrity": "sha512-ka4H8OM6+DlUhSAZpONu0cPBtPPTQKxbxVzC4CzVx5+K4JnroJVBtDzLAMx4/3CDTJXRvVFhpFjtl4SaiTNoyQ==", + "requires": { + "@opentelemetry/semantic-conventions": "^1.29.0" + } + } + } + }, + "@opentelemetry/propagator-jaeger": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/propagator-jaeger/-/propagator-jaeger-2.5.0.tgz", + "integrity": "sha512-t70ErZCncAR/zz5AcGkL0TF25mJiK1FfDPEQCgreyAHZ+mRJ/bNUiCnImIBDlP3mSDXy6N09DbUEKq0ktW98Hg==", + "requires": { + "@opentelemetry/core": "2.5.0" + }, + "dependencies": { + "@opentelemetry/core": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.5.0.tgz", + "integrity": "sha512-ka4H8OM6+DlUhSAZpONu0cPBtPPTQKxbxVzC4CzVx5+K4JnroJVBtDzLAMx4/3CDTJXRvVFhpFjtl4SaiTNoyQ==", + "requires": { + "@opentelemetry/semantic-conventions": "^1.29.0" + } + } + } + }, + "@opentelemetry/resources": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.5.0.tgz", + "integrity": "sha512-F8W52ApePshpoSrfsSk1H2yJn9aKjCrbpQF1M9Qii0GHzbfVeFUB+rc3X4aggyZD8x9Gu3Slua+s6krmq6Dt8g==", + "requires": { + "@opentelemetry/core": "2.5.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "dependencies": { + "@opentelemetry/core": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.5.0.tgz", + "integrity": "sha512-ka4H8OM6+DlUhSAZpONu0cPBtPPTQKxbxVzC4CzVx5+K4JnroJVBtDzLAMx4/3CDTJXRvVFhpFjtl4SaiTNoyQ==", + "requires": { + "@opentelemetry/semantic-conventions": "^1.29.0" + } + } + } + }, + "@opentelemetry/sdk-logs": { + "version": "0.211.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-logs/-/sdk-logs-0.211.0.tgz", + "integrity": "sha512-O5nPwzgg2JHzo59kpQTPUOTzFi0Nv5LxryG27QoXBciX3zWM3z83g+SNOHhiQVYRWFSxoWn1JM2TGD5iNjOwdA==", + "requires": { + "@opentelemetry/api-logs": "0.211.0", + "@opentelemetry/core": "2.5.0", + "@opentelemetry/resources": "2.5.0" + }, + "dependencies": { + "@opentelemetry/core": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.5.0.tgz", + "integrity": "sha512-ka4H8OM6+DlUhSAZpONu0cPBtPPTQKxbxVzC4CzVx5+K4JnroJVBtDzLAMx4/3CDTJXRvVFhpFjtl4SaiTNoyQ==", + "requires": { + "@opentelemetry/semantic-conventions": "^1.29.0" + } + } + } + }, + "@opentelemetry/sdk-metrics": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-2.5.0.tgz", + "integrity": "sha512-BeJLtU+f5Gf905cJX9vXFQorAr6TAfK3SPvTFqP+scfIpDQEJfRaGJWta7sJgP+m4dNtBf9y3yvBKVAZZtJQVA==", + "requires": { + "@opentelemetry/core": "2.5.0", + "@opentelemetry/resources": "2.5.0" + }, + "dependencies": { + "@opentelemetry/core": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.5.0.tgz", + "integrity": "sha512-ka4H8OM6+DlUhSAZpONu0cPBtPPTQKxbxVzC4CzVx5+K4JnroJVBtDzLAMx4/3CDTJXRvVFhpFjtl4SaiTNoyQ==", + "requires": { + "@opentelemetry/semantic-conventions": "^1.29.0" + } + } + } + }, + "@opentelemetry/sdk-metrics-base": { + "version": "0.26.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics-base/-/sdk-metrics-base-0.26.0.tgz", + "integrity": "sha512-PbJsso7Vy/CLATAOyXbt/VP7ZQ2QYnvlq28lhOWaLPw8aqLogMBvidNGRrt7rF4/hfzLT6pMgpAAcit2C/nUMA==", + "requires": { + "@opentelemetry/api-metrics": "0.26.0", + "@opentelemetry/core": "1.0.0", + "@opentelemetry/resources": "1.0.0", + "lodash.merge": "^4.6.2" + }, + "dependencies": { + "@opentelemetry/resources": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-1.0.0.tgz", + "integrity": "sha512-ORP8F2LLcJEm5M3H24RmdlMdiDc70ySPushpkrAW34KZGdZXwkrFoFXZhhs5MUxPT+fLrTuBafXxZVr8eHtFuQ==", + "requires": { + "@opentelemetry/core": "1.0.0", + "@opentelemetry/semantic-conventions": "1.0.0" + } + }, + "@opentelemetry/semantic-conventions": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.0.0.tgz", + "integrity": "sha512-XCZ6ZSmc8FOspxKUU+Ow9UtJeSSRcS5rFBYGpjzix02U2v+X9ofjOjgNRnpvxlSvkccYIhdTuwcvNskmZ46SeA==" + } + } + }, + "@opentelemetry/sdk-node": { + "version": "0.211.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-node/-/sdk-node-0.211.0.tgz", + "integrity": "sha512-+s1eGjoqmPCMptNxcJJD4IxbWJKNLOQFNKhpwkzi2gLkEbCj6LzSHJNhPcLeBrBlBLtlSpibM+FuS7fjZ8SSFQ==", + "requires": { + "@opentelemetry/api-logs": "0.211.0", + "@opentelemetry/configuration": "0.211.0", + "@opentelemetry/context-async-hooks": "2.5.0", + "@opentelemetry/core": "2.5.0", + "@opentelemetry/exporter-logs-otlp-grpc": "0.211.0", + "@opentelemetry/exporter-logs-otlp-http": "0.211.0", + "@opentelemetry/exporter-logs-otlp-proto": "0.211.0", + "@opentelemetry/exporter-metrics-otlp-grpc": "0.211.0", + "@opentelemetry/exporter-metrics-otlp-http": "0.211.0", + "@opentelemetry/exporter-metrics-otlp-proto": "0.211.0", + "@opentelemetry/exporter-prometheus": "0.211.0", + "@opentelemetry/exporter-trace-otlp-grpc": "0.211.0", + "@opentelemetry/exporter-trace-otlp-http": "0.211.0", + "@opentelemetry/exporter-trace-otlp-proto": "0.211.0", + "@opentelemetry/exporter-zipkin": "2.5.0", + "@opentelemetry/instrumentation": "0.211.0", + "@opentelemetry/propagator-b3": "2.5.0", + "@opentelemetry/propagator-jaeger": "2.5.0", + "@opentelemetry/resources": "2.5.0", + "@opentelemetry/sdk-logs": "0.211.0", + "@opentelemetry/sdk-metrics": "2.5.0", + "@opentelemetry/sdk-trace-base": "2.5.0", + "@opentelemetry/sdk-trace-node": "2.5.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "dependencies": { + "@opentelemetry/core": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.5.0.tgz", + "integrity": "sha512-ka4H8OM6+DlUhSAZpONu0cPBtPPTQKxbxVzC4CzVx5+K4JnroJVBtDzLAMx4/3CDTJXRvVFhpFjtl4SaiTNoyQ==", + "requires": { + "@opentelemetry/semantic-conventions": "^1.29.0" + } + } + } + }, + "@opentelemetry/sdk-trace-base": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.5.0.tgz", + "integrity": "sha512-VzRf8LzotASEyNDUxTdaJ9IRJ1/h692WyArDBInf5puLCjxbICD6XkHgpuudis56EndyS7LYFmtTMny6UABNdQ==", + "requires": { + "@opentelemetry/core": "2.5.0", + "@opentelemetry/resources": "2.5.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "dependencies": { + "@opentelemetry/core": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.5.0.tgz", + "integrity": "sha512-ka4H8OM6+DlUhSAZpONu0cPBtPPTQKxbxVzC4CzVx5+K4JnroJVBtDzLAMx4/3CDTJXRvVFhpFjtl4SaiTNoyQ==", + "requires": { + "@opentelemetry/semantic-conventions": "^1.29.0" + } + } + } + }, + "@opentelemetry/sdk-trace-node": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-node/-/sdk-trace-node-2.5.0.tgz", + "integrity": "sha512-O6N/ejzburFm2C84aKNrwJVPpt6HSTSq8T0ZUMq3xT2XmqT4cwxUItcL5UWGThYuq8RTcbH8u1sfj6dmRci0Ow==", + "requires": { + "@opentelemetry/context-async-hooks": "2.5.0", + "@opentelemetry/core": "2.5.0", + "@opentelemetry/sdk-trace-base": "2.5.0" + }, + "dependencies": { + "@opentelemetry/core": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.5.0.tgz", + "integrity": "sha512-ka4H8OM6+DlUhSAZpONu0cPBtPPTQKxbxVzC4CzVx5+K4JnroJVBtDzLAMx4/3CDTJXRvVFhpFjtl4SaiTNoyQ==", + "requires": { + "@opentelemetry/semantic-conventions": "^1.29.0" + } + } + } + }, + "@opentelemetry/semantic-conventions": { + "version": "1.39.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.39.0.tgz", + "integrity": "sha512-R5R9tb2AXs2IRLNKLBJDynhkfmx7mX0vi8NkhZb3gUkPWHn6HXk5J8iQ/dql0U3ApfWym4kXXmBDRGO+oeOfjg==" + }, + "@pinojs/redact": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@pinojs/redact/-/redact-0.4.0.tgz", + "integrity": "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==" + }, + "@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==" + }, + "@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==" + }, + "@protobufjs/codegen": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==" + }, + "@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==" + }, + "@protobufjs/fetch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", + "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", + "requires": { + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" + } + }, + "@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==" + }, + "@protobufjs/inquire": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", + "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==" + }, + "@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==" + }, + "@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==" + }, + "@protobufjs/utf8": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", + "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==" + }, + "@tootallnate/once": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", + "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==" + }, + "@types/caseless": { + "version": "0.12.5", + "resolved": "https://registry.npmjs.org/@types/caseless/-/caseless-0.12.5.tgz", + "integrity": "sha512-hWtVTC2q7hc7xZ/RLbxapMvDMgUnDvKvMOpKal4DrMyfGBUfB1oKaZlIRr6mJL+If3bAP6sV/QneGzF6tJjZDg==" + }, + "@types/console-log-level": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@types/console-log-level/-/console-log-level-1.4.2.tgz", + "integrity": "sha512-TnhDAntcJthcCMrR3OAKAUjgHyQgoms1yaBJepGv+BtXi8PLf8aX2L/NMCfofRTpVqW0bLklpGTsuqmUSCR2Uw==" + }, + "@types/long": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz", + "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==" + }, + "@types/node": { + "version": "18.11.9", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.9.tgz", + "integrity": "sha512-CRpX21/kGdzjOpFsZSkcrXMGIBWMGNIHXXBVFSH+ggkftxg+XYP20TESbh+zFvFj3EQOl5byk0HTRn1IL6hbqg==" + }, + "@types/request": { + "version": "2.48.12", + "resolved": "https://registry.npmjs.org/@types/request/-/request-2.48.12.tgz", + "integrity": "sha512-G3sY+NpsA9jnwm0ixhAFQSJ3Q9JkpLZpJbI3GMv0mIAT0y3mRabYeINzal5WOChIiaTEGQYlHOKgkaM9EisWHw==", + "requires": { + "@types/caseless": "*", + "@types/node": "*", + "@types/tough-cookie": "*", + "form-data": "^2.5.0" + } + }, + "@types/semver": { + "version": "7.3.13", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.13.tgz", + "integrity": "sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw==" + }, + "@types/tough-cookie": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz", + "integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==" + }, + "abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" + }, + "abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "requires": { + "event-target-shim": "^5.0.0" + } + }, + "acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==" + }, + "acorn-import-attributes": { + "version": "1.9.5", + "resolved": "https://registry.npmjs.org/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz", + "integrity": "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==", + "requires": {} + }, + "agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "requires": { + "debug": "4" + } + }, + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "^2.0.1" + } + }, + "aproba": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", + "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==" + }, + "are-we-there-yet": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", + "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==", + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + } + }, + "arrify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", + "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==" + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "atomic-sleep": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz", + "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==" + }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" + }, + "bignumber.js": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.2.tgz", + "integrity": "sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug==" + }, + "bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "requires": { + "file-uri-to-path": "1.0.0" + } + }, + "brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==" + }, + "call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "requires": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + } + }, + "chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==" + }, + "cjs-module-lexer": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz", + "integrity": "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==" + }, + "cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==" + }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + }, + "console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==" + }, + "console-log-level": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/console-log-level/-/console-log-level-1.4.1.tgz", + "integrity": "sha512-VZzbIORbP+PPcN/gg3DXClTLPLg5Slwd5fL2MIc+o1qZ4BXBvWyc6QxPk6T/Mkr6IVjRpoAGf32XxP3ZWMVRcQ==" + }, + "debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "requires": { + "ms": "^2.1.3" + } + }, + "delay": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/delay/-/delay-5.0.0.tgz", + "integrity": "sha512-ReEBKkIfe4ya47wlPYf/gu5ib6yUG0/Aez0JQZQz94kiWtRQvZIQbTiehsnwHvLSWJnQdhVeqYue7Id1dKr0qw==" + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==" + }, + "delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==" + }, + "detect-libc": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.1.tgz", + "integrity": "sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w==" + }, + "dot-prop": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-6.0.1.tgz", + "integrity": "sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA==", + "requires": { + "is-obj": "^2.0.0" + } + }, + "dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "requires": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + } + }, + "duplexify": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.2.tgz", + "integrity": "sha512-fz3OjcNCHmRP12MJoZMPglx8m4rrFP8rovnk4vT8Fs+aonZoCwGg10dSsQsfP/E62eZcPTMSMP6686fu9Qlqtw==", + "requires": { + "end-of-stream": "^1.4.1", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1", + "stream-shift": "^1.0.0" + } + }, + "ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "requires": { + "once": "^1.4.0" + } + }, + "es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==" + }, + "es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==" + }, + "es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "requires": { + "es-errors": "^1.3.0" + } + }, + "es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "requires": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + } + }, + "escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==" + }, + "event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==" + }, + "eventid": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/eventid/-/eventid-2.0.1.tgz", + "integrity": "sha512-sPNTqiMokAvV048P2c9+foqVJzk49o6d4e0D/sq5jog3pw+4kBgyR0gaM1FM7Mx6Kzd9dztesh9oYz1LWWOpzw==", + "requires": { + "uuid": "^8.0.0" + }, + "dependencies": { + "uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==" + } + } + }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + }, + "file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==" + }, + "findit2": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/findit2/-/findit2-2.2.3.tgz", + "integrity": "sha512-lg/Moejf4qXovVutL0Lz4IsaPoNYMuxt4PA0nGqFxnJ1CTTGGlEO2wKgoDpwknhvZ8k4Q2F+eesgkLbG2Mxfog==" + }, + "form-data": { + "version": "2.5.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.5.tgz", + "integrity": "sha512-jqdObeR2rxZZbPSGL+3VckHMYtu+f9//KXBsVny6JSX/pa38Fy+bGjuG8eW/H6USNQWhLi8Num++cU2yOCNz4A==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.35", + "safe-buffer": "^5.2.1" + } + }, + "fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "requires": { + "minipass": "^3.0.0" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" + }, + "function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==" + }, + "gauge": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz", + "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==", + "requires": { + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.2", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.1", + "object-assign": "^4.1.1", + "signal-exit": "^3.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.2" + } + }, + "gaxios": { + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-6.7.1.tgz", + "integrity": "sha512-LDODD4TMYx7XXdpwxAVRAIAuB0bzv0s+ywFonY46k126qzQHT9ygyoa9tncmOiQmmDrik65UYsEkv3lbfqQ3yQ==", + "requires": { + "extend": "^3.0.2", + "https-proxy-agent": "^7.0.1", + "is-stream": "^2.0.0", + "node-fetch": "^2.6.9", + "uuid": "^9.0.1" + }, + "dependencies": { + "agent-base": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", + "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", + "requires": { + "debug": "^4.3.4" + } + }, + "https-proxy-agent": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz", + "integrity": "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==", + "requires": { + "agent-base": "^7.0.2", + "debug": "4" + } + }, + "uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==" + } + } + }, + "gcp-metadata": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-6.1.0.tgz", + "integrity": "sha512-Jh/AIwwgaxan+7ZUUmRLCjtchyDiqh4KjBJ5tW3plBZb5iL/BPcso8A5DlzeD9qlw0duCamnNdpFjxwaT0KyKg==", + "requires": { + "gaxios": "^6.0.0", + "json-bigint": "^1.0.0" + } + }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==" + }, + "get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "requires": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + } + }, + "get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "requires": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + } + }, + "google-auth-library": { + "version": "9.14.1", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-9.14.1.tgz", + "integrity": "sha512-Rj+PMjoNFGFTmtItH7gHfbHpGVSb3vmnGK3nwNBqxQF9NoBpttSZI/rc0WiM63ma2uGDQtYEkMHkK9U6937NiA==", + "requires": { + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "gaxios": "^6.1.1", + "gcp-metadata": "^6.1.0", + "gtoken": "^7.0.0", + "jws": "^4.0.0" + } + }, + "google-gax": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-4.4.1.tgz", + "integrity": "sha512-Phyp9fMfA00J3sZbJxbbB4jC55b7DBjE3F6poyL3wKMEBVKA79q6BGuHcTiM28yOzVql0NDbRL8MLLh8Iwk9Dg==", + "requires": { + "@grpc/grpc-js": "^1.10.9", + "@grpc/proto-loader": "^0.7.13", + "@types/long": "^4.0.0", + "abort-controller": "^3.0.0", + "duplexify": "^4.0.0", + "google-auth-library": "^9.3.0", + "node-fetch": "^2.7.0", + "object-hash": "^3.0.0", + "proto3-json-serializer": "^2.0.2", + "protobufjs": "^7.3.2", + "retry-request": "^7.0.0", + "uuid": "^9.0.1" + }, + "dependencies": { + "@grpc/proto-loader": { + "version": "0.7.15", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.15.tgz", + "integrity": "sha512-tMXdRCfYVixjuFK+Hk0Q1s38gV9zDiDJfWL3h1rv4Qc39oILCu1TRTDt7+fGUI8K4G1Fj125Hx/ru3azECWTyQ==", + "requires": { + "lodash.camelcase": "^4.3.0", + "long": "^5.0.0", + "protobufjs": "^7.2.5", + "yargs": "^17.7.2" + } + }, + "cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + } + }, + "long": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", + "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==" + }, + "uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==" + }, + "yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "requires": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + } + }, + "yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==" + } + } + }, + "gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==" + }, + "gtoken": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-7.1.0.tgz", + "integrity": "sha512-pCcEwRi+TKpMlxAQObHDQ56KawURgyAf6jtIY046fJ5tIv3zDe/LEIubckAO8fj6JnAxLdmWkUfNyulQ2iKdEw==", + "requires": { + "gaxios": "^6.0.0", + "jws": "^4.0.0" + } + }, + "has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==" + }, + "has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "requires": { + "has-symbols": "^1.0.3" + } + }, + "has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==" + }, + "hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "requires": { + "function-bind": "^1.1.2" + } + }, + "html-entities": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.5.2.tgz", + "integrity": "sha512-K//PSRMQk4FZ78Kyau+mZurHn3FH0Vwr+H36eE0rPbeYkRRi9YxceYPhuN60UwWorxyKHhqoAJl2OFKa4BVtaA==" + }, + "http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "requires": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + } + }, + "https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "requires": { + "agent-base": "6", + "debug": "4" + } + }, + "import-in-the-middle": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/import-in-the-middle/-/import-in-the-middle-2.0.0.tgz", + "integrity": "sha512-yNZhyQYqXpkT0AKq3F3KLasUSK4fHvebNH5hOsKQw2dhGSALvQ4U0BqUc5suziKvydO5u5hgN2hy1RJaho8U5A==", + "requires": { + "acorn": "^8.14.0", + "acorn-import-attributes": "^1.9.5", + "cjs-module-lexer": "^1.2.2", + "module-details-from-path": "^1.0.3" + } + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" + }, + "is-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==" + }, + "is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==" + }, + "json-bigint": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", + "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", + "requires": { + "bignumber.js": "^9.0.0" + } + }, + "jwa": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", + "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", + "requires": { + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "jws": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", + "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", + "requires": { + "jwa": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==" + }, + "lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==" + }, + "lodash.sortby": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", + "integrity": "sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==" + }, + "long": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", + "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "requires": { + "yallist": "^4.0.0" + } + }, + "make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "requires": { + "semver": "^6.0.0" + }, + "dependencies": { + "semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==" + } + } + }, + "math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==" + }, + "mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" + }, + "mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "requires": { + "mime-db": "1.52.0" + } + }, + "minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "requires": { + "yallist": "^4.0.0" + } + }, + "minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "requires": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + } + }, + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==" + }, + "module-details-from-path": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/module-details-from-path/-/module-details-from-path-1.0.3.tgz", + "integrity": "sha512-ySViT69/76t8VhE1xXHK6Ch4NcDd26gx0MzKXLO+F7NOtnqH68d9zF94nT8ZWSxXh8ELOERsnJO/sWt1xZYw5A==" + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "nan": { + "version": "2.17.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.17.0.tgz", + "integrity": "sha512-2ZTgtl0nJsO0KQCjEpxcIr5D+Yv90plTitZt9JBfQvVJDS5seMl3FOvsh3+9CoYWXf/1l5OaZzzF6nDm4cagaQ==" + }, + "node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "requires": { + "whatwg-url": "^5.0.0" + } + }, + "nopt": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", + "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", + "requires": { + "abbrev": "1" + } + }, + "npmlog": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", + "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==", + "requires": { + "are-we-there-yet": "^2.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^3.0.0", + "set-blocking": "^2.0.0" + } + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==" + }, + "object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==" + }, + "on-exit-leak-free": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.0.tgz", + "integrity": "sha512-VuCaZZAjReZ3vUwgOB8LxAosIurDiAW0s13rI1YwmaP++jvcxP77AWoQvenZebpCA2m8WC1/EosPYPMjnRAp/w==" + }, + "on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "requires": { + "ee-first": "1.1.1" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "requires": { + "wrappy": "1" + } + }, + "p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "requires": { + "yocto-queue": "^0.1.0" + } + }, + "parse-ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-2.1.0.tgz", + "integrity": "sha512-kHt7kzLoS9VBZfUsiKjv43mr91ea+U05EyKkEtqp7vNbHxmaVuEqN7XxeEVnGrMtYOAxGrDElSi96K7EgO1zCA==" + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==" + }, + "pino": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/pino/-/pino-10.3.0.tgz", + "integrity": "sha512-0GNPNzHXBKw6U/InGe79A3Crzyk9bcSyObF9/Gfo9DLEf5qj5RF50RSjsu0W1rZ6ZqRGdzDFCRBQvi9/rSGPtA==", + "requires": { + "@pinojs/redact": "^0.4.0", + "atomic-sleep": "^1.0.0", + "on-exit-leak-free": "^2.1.0", + "pino-abstract-transport": "^3.0.0", + "pino-std-serializers": "^7.0.0", + "process-warning": "^5.0.0", + "quick-format-unescaped": "^4.0.3", + "real-require": "^0.2.0", + "safe-stable-stringify": "^2.3.1", + "sonic-boom": "^4.0.1", + "thread-stream": "^4.0.0" + } + }, + "pino-abstract-transport": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-3.0.0.tgz", + "integrity": "sha512-wlfUczU+n7Hy/Ha5j9a/gZNy7We5+cXp8YL+X+PG8S0KXxw7n/JXA3c46Y0zQznIJ83URJiwy7Lh56WLokNuxg==", + "requires": { + "split2": "^4.0.0" + } + }, + "pino-std-serializers": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-7.0.0.tgz", + "integrity": "sha512-e906FRY0+tV27iq4juKzSYPbUj2do2X2JX4EzSca1631EB2QJQUqGbDuERal7LCtOpxl6x3+nvo9NPZcmjkiFA==" + }, + "pprof": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/pprof/-/pprof-4.0.0.tgz", + "integrity": "sha512-Yhfk7Y0G1MYsy97oXxmSG5nvbM1sCz9EALiNhW/isAv5Xf7svzP+1RfGeBlS6mLSgRJvgSLh6Mi5DaisQuPttw==", + "requires": { + "@mapbox/node-pre-gyp": "^1.0.9", + "bindings": "^1.2.1", + "delay": "^5.0.0", + "findit2": "^2.2.3", + "nan": "^2.17.0", + "p-limit": "^3.0.0", + "protobufjs": "~7.2.4", + "source-map": "~0.8.0-beta.0", + "split": "^1.0.1" + }, + "dependencies": { + "long": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz", + "integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==" + }, + "protobufjs": { + "version": "7.2.5", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.2.5.tgz", + "integrity": "sha512-gGXRSXvxQ7UiPgfw8gevrfRWcTlSbOFg+p/N+JVJEK5VhueL2miT6qTymqAmjr1Q5WbOCyJbyrk6JfWKwlFn6A==", + "requires": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/node": ">=13.7.0", + "long": "^5.0.0" + } + } + } + }, + "pretty-ms": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-7.0.1.tgz", + "integrity": "sha512-973driJZvxiGOQ5ONsFhOF/DtzPMOMtgC11kCpUrPGMTgqp2q/1gwzCquocrN33is0VZ5GFHXZYMM9l6h67v2Q==", + "requires": { + "parse-ms": "^2.1.0" + } + }, + "process-warning": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-5.0.0.tgz", + "integrity": "sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==" + }, + "proto3-json-serializer": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/proto3-json-serializer/-/proto3-json-serializer-2.0.2.tgz", + "integrity": "sha512-SAzp/O4Yh02jGdRc+uIrGoe87dkN/XtwxfZ4ZyafJHymd79ozp5VG5nyZ7ygqPM5+cpLDjjGnYFUkngonyDPOQ==", + "requires": { + "protobufjs": "^7.2.5" + } + }, + "protobufjs": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.4.0.tgz", + "integrity": "sha512-mRUWCc3KUU4w1jU8sGxICXH/gNS94DvI1gxqDvBzhj1JpcsimQkYiOJfwsPUykUI5ZaspFbSgmBLER8IrQ3tqw==", + "requires": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/node": ">=13.7.0", + "long": "^5.0.0" + }, + "dependencies": { + "long": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz", + "integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==" + } + } + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "pumpify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-2.0.1.tgz", + "integrity": "sha512-m7KOje7jZxrmutanlkS1daj1dS6z6BgslzOXmcSEpIlCxM3VJH7lG5QLeck/6hgF6F4crFf01UtQmNsJfweTAw==", + "requires": { + "duplexify": "^4.1.1", + "inherits": "^2.0.3", + "pump": "^3.0.0" + } + }, + "punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==" + }, + "quick-format-unescaped": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz", + "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==" + }, + "readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "real-require": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz", + "integrity": "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==" + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==" + }, + "require-in-the-middle": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/require-in-the-middle/-/require-in-the-middle-8.0.0.tgz", + "integrity": "sha512-9s0pnM5tH8G4dSI3pms2GboYOs25LwOGnRMxN/Hx3TYT1K0rh6OjaWf4dI0DAQnMyaEXWoGVnSTPQasqwzTTAA==", + "requires": { + "debug": "^4.3.5", + "module-details-from-path": "^1.0.3" + } + }, + "retry-request": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/retry-request/-/retry-request-7.0.2.tgz", + "integrity": "sha512-dUOvLMJ0/JJYEn8NrpOaGNE7X3vpI5XlZS/u0ANjqtcZVKnIxP7IgCFwrKTxENw29emmwug53awKtaMm4i9g5w==", + "requires": { + "@types/request": "^2.48.8", + "extend": "^3.0.2", + "teeny-request": "^9.0.0" + } + }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "requires": { + "glob": "^7.1.3" + }, + "dependencies": { + "glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "requires": { + "brace-expansion": "^1.1.7" + } + } + } + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + }, + "safe-stable-stringify": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.4.1.tgz", + "integrity": "sha512-dVHE6bMtS/bnL2mwualjc6IxEv1F+OCUpA46pKUj6F8uDbUM0jCCulPqRNPSnWwGNKx5etqMjZYdXtrm5KJZGA==" + }, + "semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "requires": { + "lru-cache": "^6.0.0" + } + }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==" + }, + "signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" + }, + "simple-card-validator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/simple-card-validator/-/simple-card-validator-1.1.0.tgz", + "integrity": "sha512-ag7OoeyWwzDrDyiffyrIWumxDJ7+VCbhOoHxzY6ck8b7qmGEA7A5NS9GxXbqmuE2wIgBAQ0S5vNiIclHtgliQg==" + }, + "sonic-boom": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-4.0.1.tgz", + "integrity": "sha512-hTSD/6JMLyT4r9zeof6UtuBDpjJ9sO08/nmS5djaA9eozT9oOlNdpXSnzcgj4FTqpk3nkLrs61l4gip9r1HCrQ==", + "requires": { + "atomic-sleep": "^1.0.0" + } + }, + "source-map": { + "version": "0.8.0-beta.0", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.8.0-beta.0.tgz", + "integrity": "sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==", + "requires": { + "whatwg-url": "^7.0.0" + }, + "dependencies": { + "tr46": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", + "integrity": "sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==", + "requires": { + "punycode": "^2.1.0" + } + }, + "webidl-conversions": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", + "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==" + }, + "whatwg-url": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.1.0.tgz", + "integrity": "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==", + "requires": { + "lodash.sortby": "^4.7.0", + "tr46": "^1.0.1", + "webidl-conversions": "^4.0.2" + } + } + } + }, + "split": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz", + "integrity": "sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==", + "requires": { + "through": "2" + } + }, + "split2": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.1.0.tgz", + "integrity": "sha512-VBiJxFkxiXRlUIeyMQi8s4hgvKCSjtknJv/LVYbrgALPwf5zSKmEwV9Lst25AkvMDnvxODugjdl6KZgwKM1WYQ==" + }, + "stream-events": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/stream-events/-/stream-events-1.0.5.tgz", + "integrity": "sha512-E1GUzBSgvct8Jsb3v2X15pjzN1tYebtbLaMg+eBOUOAxgbLoSbT2NS91ckc5lJD1KfLjId+jXJRgo0qnV5Nerg==", + "requires": { + "stubs": "^3.0.0" + } + }, + "stream-shift": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz", + "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==" + }, + "string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "requires": { + "safe-buffer": "~5.2.0" + } + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "stubs": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/stubs/-/stubs-3.0.0.tgz", + "integrity": "sha512-PdHt7hHUJKxvTCgbKX9C1V/ftOcjJQgz8BZwNfV5c4B6dcGqlpelTbJ999jBGZ2jYiPAwcX5dP6oBwVlBlUbxw==" + }, + "tar": { + "version": "6.1.12", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.12.tgz", + "integrity": "sha512-jU4TdemS31uABHd+Lt5WEYJuzn+TJTCBLljvIAHZOz6M9Os5pJ4dD+vRFLxPa/n3T0iEFzpi+0x1UfuDZYbRMw==", + "requires": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^3.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + } + }, + "teeny-request": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-9.0.0.tgz", + "integrity": "sha512-resvxdc6Mgb7YEThw6G6bExlXKkv6+YbuzGg9xuXxSgxJF7Ozs+o8Y9+2R3sArdWdW8nOokoQb1yrpFB0pQK2g==", + "requires": { + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.0", + "node-fetch": "^2.6.9", + "stream-events": "^1.0.5", + "uuid": "^9.0.0" + }, + "dependencies": { + "uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==" + } + } + }, + "thread-stream": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-4.0.0.tgz", + "integrity": "sha512-4iMVL6HAINXWf1ZKZjIPcz5wYaOdPhtO8ATvZ+Xqp3BTdaqtAwQkNmKORqcIo5YkQqGXq5cwfswDwMqqQNrpJA==", + "requires": { + "real-require": "^0.2.0" + } + }, + "through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==" + }, + "tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + }, + "uuid": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-13.0.0.tgz", + "integrity": "sha512-XQegIaBTVUjSHliKqcnFqYypAd4S+WCYt5NIeRs6w/UAry7z8Y9j5ZwRRL4kzq9U3sD6v+85er9FvkEaBpji2w==" + }, + "webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "requires": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "wide-align": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", + "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", + "requires": { + "string-width": "^1.0.2 || 2 || 3 || 4" + } + }, + "wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + }, + "y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==" + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, + "yaml": { + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.2.tgz", + "integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==" + }, + "yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "requires": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + } + }, + "yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==" + }, + "yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==" + } + } +} diff --git a/src/paymentservice/package.json b/src/paymentservice/package.json new file mode 100644 index 0000000..c2b8235 --- /dev/null +++ b/src/paymentservice/package.json @@ -0,0 +1,27 @@ +{ + "name": "paymentservice", + "version": "0.0.1", + "description": "Payment Microservice demo", + "repository": "https://github.com/GoogleCloudPlatform/microservices-demo", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "Jonathan Lui", + "license": "ISC", + "dependencies": { + "@google-cloud/profiler": "6.0.3", + "@grpc/grpc-js": "1.14.3", + "@grpc/proto-loader": "0.8.0", + "@opentelemetry/api": "1.9.0", + "@opentelemetry/exporter-otlp-grpc": "0.26.0", + "@opentelemetry/instrumentation-grpc": "0.211.0", + "@opentelemetry/sdk-trace-base": "2.5.0", + "@opentelemetry/sdk-node": "0.211.0", + "@opentelemetry/resources": "2.5.0", + "@opentelemetry/semantic-conventions": "1.39.0", + "pino": "10.3.0", + "simple-card-validator": "^1.1.0", + "uuid": "^13.0.0" + } +} diff --git a/src/paymentservice/proto/demo.proto b/src/paymentservice/proto/demo.proto new file mode 100644 index 0000000..9693928 --- /dev/null +++ b/src/paymentservice/proto/demo.proto @@ -0,0 +1,260 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package hipstershop; + +// -----------------Cart service----------------- + +service CartService { + rpc AddItem(AddItemRequest) returns (Empty) {} + rpc GetCart(GetCartRequest) returns (Cart) {} + rpc EmptyCart(EmptyCartRequest) returns (Empty) {} +} + +message CartItem { + string product_id = 1; + int32 quantity = 2; +} + +message AddItemRequest { + string user_id = 1; + CartItem item = 2; +} + +message EmptyCartRequest { + string user_id = 1; +} + +message GetCartRequest { + string user_id = 1; +} + +message Cart { + string user_id = 1; + repeated CartItem items = 2; +} + +message Empty {} + +// ---------------Recommendation service---------- + +service RecommendationService { + rpc ListRecommendations(ListRecommendationsRequest) returns (ListRecommendationsResponse){} +} + +message ListRecommendationsRequest { + string user_id = 1; + repeated string product_ids = 2; +} + +message ListRecommendationsResponse { + repeated string product_ids = 1; +} + +// ---------------Product Catalog---------------- + +service ProductCatalogService { + rpc ListProducts(Empty) returns (ListProductsResponse) {} + rpc GetProduct(GetProductRequest) returns (Product) {} + rpc SearchProducts(SearchProductsRequest) returns (SearchProductsResponse) {} +} + +message Product { + string id = 1; + string name = 2; + string description = 3; + string picture = 4; + Money price_usd = 5; + + // Categories such as "clothing" or "kitchen" that can be used to look up + // other related products. + repeated string categories = 6; +} + +message ListProductsResponse { + repeated Product products = 1; +} + +message GetProductRequest { + string id = 1; +} + +message SearchProductsRequest { + string query = 1; +} + +message SearchProductsResponse { + repeated Product results = 1; +} + +// ---------------Shipping Service---------- + +service ShippingService { + rpc GetQuote(GetQuoteRequest) returns (GetQuoteResponse) {} + rpc ShipOrder(ShipOrderRequest) returns (ShipOrderResponse) {} +} + +message GetQuoteRequest { + Address address = 1; + repeated CartItem items = 2; +} + +message GetQuoteResponse { + Money cost_usd = 1; +} + +message ShipOrderRequest { + Address address = 1; + repeated CartItem items = 2; +} + +message ShipOrderResponse { + string tracking_id = 1; +} + +message Address { + string street_address = 1; + string city = 2; + string state = 3; + string country = 4; + int32 zip_code = 5; +} + +// -----------------Currency service----------------- + +service CurrencyService { + rpc GetSupportedCurrencies(Empty) returns (GetSupportedCurrenciesResponse) {} + rpc Convert(CurrencyConversionRequest) returns (Money) {} +} + +// Represents an amount of money with its currency type. +message Money { + // The 3-letter currency code defined in ISO 4217. + string currency_code = 1; + + // The whole units of the amount. + // For example if `currencyCode` is `"USD"`, then 1 unit is one US dollar. + int64 units = 2; + + // Number of nano (10^-9) units of the amount. + // The value must be between -999,999,999 and +999,999,999 inclusive. + // If `units` is positive, `nanos` must be positive or zero. + // If `units` is zero, `nanos` can be positive, zero, or negative. + // If `units` is negative, `nanos` must be negative or zero. + // For example $-1.75 is represented as `units`=-1 and `nanos`=-750,000,000. + int32 nanos = 3; +} + +message GetSupportedCurrenciesResponse { + // The 3-letter currency code defined in ISO 4217. + repeated string currency_codes = 1; +} + +message CurrencyConversionRequest { + Money from = 1; + + // The 3-letter currency code defined in ISO 4217. + string to_code = 2; +} + +// -------------Payment service----------------- + +service PaymentService { + rpc Charge(ChargeRequest) returns (ChargeResponse) {} +} + +message CreditCardInfo { + string credit_card_number = 1; + int32 credit_card_cvv = 2; + int32 credit_card_expiration_year = 3; + int32 credit_card_expiration_month = 4; +} + +message ChargeRequest { + Money amount = 1; + CreditCardInfo credit_card = 2; +} + +message ChargeResponse { + string transaction_id = 1; +} + +// -------------Email service----------------- + +service EmailService { + rpc SendOrderConfirmation(SendOrderConfirmationRequest) returns (Empty) {} +} + +message OrderItem { + CartItem item = 1; + Money cost = 2; +} + +message OrderResult { + string order_id = 1; + string shipping_tracking_id = 2; + Money shipping_cost = 3; + Address shipping_address = 4; + repeated OrderItem items = 5; +} + +message SendOrderConfirmationRequest { + string email = 1; + OrderResult order = 2; +} + + +// -------------Checkout service----------------- + +service CheckoutService { + rpc PlaceOrder(PlaceOrderRequest) returns (PlaceOrderResponse) {} +} + +message PlaceOrderRequest { + string user_id = 1; + string user_currency = 2; + + Address address = 3; + string email = 5; + CreditCardInfo credit_card = 6; +} + +message PlaceOrderResponse { + OrderResult order = 1; +} + +// ------------Ad service------------------ + +service AdService { + rpc GetAds(AdRequest) returns (AdResponse) {} +} + +message AdRequest { + // List of important key words from the current page describing the context. + repeated string context_keys = 1; +} + +message AdResponse { + repeated Ad ads = 1; +} + +message Ad { + // url to redirect to when an ad is clicked. + string redirect_url = 1; + + // short advertisement text to display. + string text = 2; +} diff --git a/src/paymentservice/proto/grpc/health/v1/health.proto b/src/paymentservice/proto/grpc/health/v1/health.proto new file mode 100644 index 0000000..4b4677b --- /dev/null +++ b/src/paymentservice/proto/grpc/health/v1/health.proto @@ -0,0 +1,43 @@ +// Copyright 2015 The gRPC Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// The canonical version of this proto can be found at +// https://github.com/grpc/grpc-proto/blob/master/grpc/health/v1/health.proto + +syntax = "proto3"; + +package grpc.health.v1; + +option csharp_namespace = "Grpc.Health.V1"; +option go_package = "google.golang.org/grpc/health/grpc_health_v1"; +option java_multiple_files = true; +option java_outer_classname = "HealthProto"; +option java_package = "io.grpc.health.v1"; + +message HealthCheckRequest { + string service = 1; +} + +message HealthCheckResponse { + enum ServingStatus { + UNKNOWN = 0; + SERVING = 1; + NOT_SERVING = 2; + } + ServingStatus status = 1; +} + +service Health { + rpc Check(HealthCheckRequest) returns (HealthCheckResponse); +} diff --git a/src/paymentservice/server.js b/src/paymentservice/server.js new file mode 100644 index 0000000..67271e4 --- /dev/null +++ b/src/paymentservice/server.js @@ -0,0 +1,106 @@ +// Copyright 2018 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +const path = require('path'); +const grpc = require('@grpc/grpc-js'); +const protoLoader = require('@grpc/proto-loader'); + +const charge = require('./charge'); + +const logger = require('./logger') + +class HipsterShopServer { + constructor(protoRoot, port = HipsterShopServer.PORT) { + this.port = port; + + this.packages = { + hipsterShop: this.loadProto(path.join(protoRoot, 'demo.proto')), + health: this.loadProto(path.join(protoRoot, 'grpc/health/v1/health.proto')) + }; + + this.server = new grpc.Server(); + this.loadAllProtos(protoRoot); + } + + /** + * Handler for PaymentService.Charge. + * @param {*} call { ChargeRequest } + * @param {*} callback fn(err, ChargeResponse) + */ + static ChargeServiceHandler(call, callback) { + try { + logger.info(`PaymentService#Charge invoked with request ${JSON.stringify(call.request)}`); + const response = charge(call.request); + callback(null, response); + } catch (err) { + console.warn(err); + callback(err); + } + } + + static CheckHandler(call, callback) { + callback(null, { status: 'SERVING' }); + } + + + listen() { + const server = this.server + const port = this.port + server.bindAsync( + `[::]:${port}`, + grpc.ServerCredentials.createInsecure(), + function () { + logger.info(`PaymentService gRPC server started on port ${port}`); + server.start(); + } + ); + } + + loadProto(path) { + const packageDefinition = protoLoader.loadSync( + path, + { + keepCase: true, + longs: String, + enums: String, + defaults: true, + oneofs: true + } + ); + return grpc.loadPackageDefinition(packageDefinition); + } + + loadAllProtos(protoRoot) { + const hipsterShopPackage = this.packages.hipsterShop.hipstershop; + const healthPackage = this.packages.health.grpc.health.v1; + + this.server.addService( + hipsterShopPackage.PaymentService.service, + { + charge: HipsterShopServer.ChargeServiceHandler.bind(this) + } + ); + + this.server.addService( + healthPackage.Health.service, + { + check: HipsterShopServer.CheckHandler.bind(this) + } + ); + } +} + +HipsterShopServer.PORT = process.env.PORT; + +module.exports = HipsterShopServer; diff --git a/src/productcatalogservice/.dockerignore b/src/productcatalogservice/.dockerignore new file mode 100644 index 0000000..48b8bf9 --- /dev/null +++ b/src/productcatalogservice/.dockerignore @@ -0,0 +1 @@ +vendor/ diff --git a/src/productcatalogservice/Dockerfile b/src/productcatalogservice/Dockerfile new file mode 100644 index 0000000..5bb66ac --- /dev/null +++ b/src/productcatalogservice/Dockerfile @@ -0,0 +1,41 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +FROM --platform=$BUILDPLATFORM golang:1.25.6-alpine@sha256:98e6cffc31ccc44c7c15d83df1d69891efee8115a5bb7ede2bf30a38af3e3c92 AS builder +ARG TARGETOS +ARG TARGETARCH + +WORKDIR /src +# restore dependencies +COPY go.mod go.sum ./ +RUN go mod download +COPY . . + +# Skaffold passes in debug-oriented compiler flags +ARG SKAFFOLD_GO_GCFLAGS +RUN GOOS=${TARGETOS} GOARCH=${TARGETARCH} CGO_ENABLED=0 go build -ldflags="-s -w" -gcflags="${SKAFFOLD_GO_GCFLAGS}" -o /productcatalogservice . + +FROM gcr.io/distroless/static + +WORKDIR /src +COPY --from=builder /productcatalogservice ./server +COPY products.json . + +# Definition of this variable is used by 'skaffold debug' to identify a golang binary. +# Default behavior - a failure prints a stack trace for the current goroutine. +# See https://golang.org/pkg/runtime/ +ENV GOTRACEBACK=single + +EXPOSE 3550 +ENTRYPOINT ["/src/server"] diff --git a/src/productcatalogservice/README.md b/src/productcatalogservice/README.md new file mode 100644 index 0000000..04870bb --- /dev/null +++ b/src/productcatalogservice/README.md @@ -0,0 +1,38 @@ +# productcatalogservice + +Run the following command to restore dependencies to `vendor/` directory: + + go mod vendor + +## Dynamic catalog reloading / artificial delay + +This service has a "dynamic catalog reloading" feature that is purposefully +not well implemented. The goal of this feature is to allow you to modify the +`products.json` file and have the changes be picked up without having to +restart the service. + +However, this feature is bugged: the catalog is actually reloaded on each +request, introducing a noticeable delay in the frontend. This delay will also +show up in profiling tools: the `parseCatalog` function will take more than 80% +of the CPU time. + +You can trigger this feature (and the delay) by sending a `USR1` signal and +remove it (if needed) by sending a `USR2` signal: + +``` +# Trigger bug +kubectl exec \ + $(kubectl get pods -l app=productcatalogservice -o jsonpath='{.items[0].metadata.name}') \ + -c server -- kill -USR1 1 +# Remove bug +kubectl exec \ + $(kubectl get pods -l app=productcatalogservice -o jsonpath='{.items[0].metadata.name}') \ + -c server -- kill -USR2 1 +``` + +## Latency injection + +This service has an `EXTRA_LATENCY` environment variable. This will inject a sleep for the specified [time.Duration](https://golang.org/pkg/time/#ParseDuration) on every call to +to the server. + +For example, use `EXTRA_LATENCY="5.5s"` to sleep for 5.5 seconds on every request. diff --git a/src/productcatalogservice/catalog_loader.go b/src/productcatalogservice/catalog_loader.go new file mode 100644 index 0000000..75d3c72 --- /dev/null +++ b/src/productcatalogservice/catalog_loader.go @@ -0,0 +1,161 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +import ( + "bytes" + "context" + "fmt" + "net" + "os" + "strings" + + "cloud.google.com/go/alloydbconn" + secretmanager "cloud.google.com/go/secretmanager/apiv1" + "cloud.google.com/go/secretmanager/apiv1/secretmanagerpb" + pb "github.com/GoogleCloudPlatform/microservices-demo/src/productcatalogservice/genproto" + "github.com/golang/protobuf/jsonpb" + "github.com/jackc/pgx/v5/pgxpool" +) + +func loadCatalog(catalog *pb.ListProductsResponse) error { + catalogMutex.Lock() + defer catalogMutex.Unlock() + + if os.Getenv("ALLOYDB_CLUSTER_NAME") != "" { + return loadCatalogFromAlloyDB(catalog) + } + + return loadCatalogFromLocalFile(catalog) +} + +func loadCatalogFromLocalFile(catalog *pb.ListProductsResponse) error { + log.Info("loading catalog from local products.json file...") + + catalogJSON, err := os.ReadFile("products.json") + if err != nil { + log.Warnf("failed to open product catalog json file: %v", err) + return err + } + + if err := jsonpb.Unmarshal(bytes.NewReader(catalogJSON), catalog); err != nil { + log.Warnf("failed to parse the catalog JSON: %v", err) + return err + } + + log.Info("successfully parsed product catalog json") + return nil +} + +func getSecretPayload(project, secret, version string) (string, error) { + ctx := context.Background() + client, err := secretmanager.NewClient(ctx) + if err != nil { + log.Warnf("failed to create SecretManager client: %v", err) + return "", err + } + defer client.Close() + + req := &secretmanagerpb.AccessSecretVersionRequest{ + Name: fmt.Sprintf("projects/%s/secrets/%s/versions/%s", project, secret, version), + } + + // Call the API. + result, err := client.AccessSecretVersion(ctx, req) + if err != nil { + log.Warnf("failed to access SecretVersion: %v", err) + return "", err + } + + return string(result.Payload.Data), nil +} + +func loadCatalogFromAlloyDB(catalog *pb.ListProductsResponse) error { + log.Info("loading catalog from AlloyDB...") + + projectID := os.Getenv("PROJECT_ID") + region := os.Getenv("REGION") + pgClusterName := os.Getenv("ALLOYDB_CLUSTER_NAME") + pgInstanceName := os.Getenv("ALLOYDB_INSTANCE_NAME") + pgDatabaseName := os.Getenv("ALLOYDB_DATABASE_NAME") + pgTableName := os.Getenv("ALLOYDB_TABLE_NAME") + pgSecretName := os.Getenv("ALLOYDB_SECRET_NAME") + + pgPassword, err := getSecretPayload(projectID, pgSecretName, "latest") + if err != nil { + return err + } + + dialer, err := alloydbconn.NewDialer(context.Background()) + if err != nil { + log.Warnf("failed to set-up dialer connection: %v", err) + return err + } + cleanup := func() error { return dialer.Close() } + defer cleanup() + + dsn := fmt.Sprintf( + "user=%s password=%s dbname=%s sslmode=disable", + "postgres", pgPassword, pgDatabaseName, + ) + + config, err := pgxpool.ParseConfig(dsn) + if err != nil { + log.Warnf("failed to parse DSN config: %v", err) + return err + } + + pgInstanceURI := fmt.Sprintf("projects/%s/locations/%s/clusters/%s/instances/%s", projectID, region, pgClusterName, pgInstanceName) + config.ConnConfig.DialFunc = func(ctx context.Context, _ string, _ string) (net.Conn, error) { + return dialer.Dial(ctx, pgInstanceURI) + } + + pool, err := pgxpool.NewWithConfig(context.Background(), config) + if err != nil { + log.Warnf("failed to set-up pgx pool: %v", err) + return err + } + defer pool.Close() + + query := "SELECT id, name, description, picture, price_usd_currency_code, price_usd_units, price_usd_nanos, categories FROM " + pgTableName + rows, err := pool.Query(context.Background(), query) + if err != nil { + log.Warnf("failed to query database: %v", err) + return err + } + defer rows.Close() + + catalog.Products = catalog.Products[:0] + for rows.Next() { + product := &pb.Product{} + product.PriceUsd = &pb.Money{} + + var categories string + err = rows.Scan(&product.Id, &product.Name, &product.Description, + &product.Picture, &product.PriceUsd.CurrencyCode, &product.PriceUsd.Units, + &product.PriceUsd.Nanos, &categories) + if err != nil { + log.Warnf("failed to scan query result row: %v", err) + return err + } + categories = strings.ToLower(categories) + product.Categories = strings.Split(categories, ",") + + catalog.Products = append(catalog.Products, product) + } + + log.Info("successfully parsed product catalog from AlloyDB") + return nil +} diff --git a/src/productcatalogservice/genproto.sh b/src/productcatalogservice/genproto.sh new file mode 100755 index 0000000..07831b6 --- /dev/null +++ b/src/productcatalogservice/genproto.sh @@ -0,0 +1,25 @@ +#!/bin/bash -eu +# +# Copyright 2018 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# [START gke_productcatalogservice_genproto] + +PATH=$PATH:$(go env GOPATH)/bin +protodir=../../protos +outdir=./genproto + +protoc --proto_path=$protodir --go_out=./$outdir --go_opt=paths=source_relative --go-grpc_out=./$outdir --go-grpc_opt=paths=source_relative $protodir/demo.proto + +# [END gke_productcatalogservice_genproto] \ No newline at end of file diff --git a/src/productcatalogservice/genproto/demo.pb.go b/src/productcatalogservice/genproto/demo.pb.go new file mode 100644 index 0000000..332cb9c --- /dev/null +++ b/src/productcatalogservice/genproto/demo.pb.go @@ -0,0 +1,2610 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.34.2 +// protoc v3.6.1 +// source: demo.proto + +package hipstershop + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type CartItem struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ProductId string `protobuf:"bytes,1,opt,name=product_id,json=productId,proto3" json:"product_id,omitempty"` + Quantity int32 `protobuf:"varint,2,opt,name=quantity,proto3" json:"quantity,omitempty"` +} + +func (x *CartItem) Reset() { + *x = CartItem{} + if protoimpl.UnsafeEnabled { + mi := &file_demo_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CartItem) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CartItem) ProtoMessage() {} + +func (x *CartItem) ProtoReflect() protoreflect.Message { + mi := &file_demo_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CartItem.ProtoReflect.Descriptor instead. +func (*CartItem) Descriptor() ([]byte, []int) { + return file_demo_proto_rawDescGZIP(), []int{0} +} + +func (x *CartItem) GetProductId() string { + if x != nil { + return x.ProductId + } + return "" +} + +func (x *CartItem) GetQuantity() int32 { + if x != nil { + return x.Quantity + } + return 0 +} + +type AddItemRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + UserId string `protobuf:"bytes,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` + Item *CartItem `protobuf:"bytes,2,opt,name=item,proto3" json:"item,omitempty"` +} + +func (x *AddItemRequest) Reset() { + *x = AddItemRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_demo_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *AddItemRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AddItemRequest) ProtoMessage() {} + +func (x *AddItemRequest) ProtoReflect() protoreflect.Message { + mi := &file_demo_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AddItemRequest.ProtoReflect.Descriptor instead. +func (*AddItemRequest) Descriptor() ([]byte, []int) { + return file_demo_proto_rawDescGZIP(), []int{1} +} + +func (x *AddItemRequest) GetUserId() string { + if x != nil { + return x.UserId + } + return "" +} + +func (x *AddItemRequest) GetItem() *CartItem { + if x != nil { + return x.Item + } + return nil +} + +type EmptyCartRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + UserId string `protobuf:"bytes,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` +} + +func (x *EmptyCartRequest) Reset() { + *x = EmptyCartRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_demo_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *EmptyCartRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*EmptyCartRequest) ProtoMessage() {} + +func (x *EmptyCartRequest) ProtoReflect() protoreflect.Message { + mi := &file_demo_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use EmptyCartRequest.ProtoReflect.Descriptor instead. +func (*EmptyCartRequest) Descriptor() ([]byte, []int) { + return file_demo_proto_rawDescGZIP(), []int{2} +} + +func (x *EmptyCartRequest) GetUserId() string { + if x != nil { + return x.UserId + } + return "" +} + +type GetCartRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + UserId string `protobuf:"bytes,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` +} + +func (x *GetCartRequest) Reset() { + *x = GetCartRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_demo_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetCartRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetCartRequest) ProtoMessage() {} + +func (x *GetCartRequest) ProtoReflect() protoreflect.Message { + mi := &file_demo_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetCartRequest.ProtoReflect.Descriptor instead. +func (*GetCartRequest) Descriptor() ([]byte, []int) { + return file_demo_proto_rawDescGZIP(), []int{3} +} + +func (x *GetCartRequest) GetUserId() string { + if x != nil { + return x.UserId + } + return "" +} + +type Cart struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + UserId string `protobuf:"bytes,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` + Items []*CartItem `protobuf:"bytes,2,rep,name=items,proto3" json:"items,omitempty"` +} + +func (x *Cart) Reset() { + *x = Cart{} + if protoimpl.UnsafeEnabled { + mi := &file_demo_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Cart) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Cart) ProtoMessage() {} + +func (x *Cart) ProtoReflect() protoreflect.Message { + mi := &file_demo_proto_msgTypes[4] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Cart.ProtoReflect.Descriptor instead. +func (*Cart) Descriptor() ([]byte, []int) { + return file_demo_proto_rawDescGZIP(), []int{4} +} + +func (x *Cart) GetUserId() string { + if x != nil { + return x.UserId + } + return "" +} + +func (x *Cart) GetItems() []*CartItem { + if x != nil { + return x.Items + } + return nil +} + +type Empty struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *Empty) Reset() { + *x = Empty{} + if protoimpl.UnsafeEnabled { + mi := &file_demo_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Empty) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Empty) ProtoMessage() {} + +func (x *Empty) ProtoReflect() protoreflect.Message { + mi := &file_demo_proto_msgTypes[5] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Empty.ProtoReflect.Descriptor instead. +func (*Empty) Descriptor() ([]byte, []int) { + return file_demo_proto_rawDescGZIP(), []int{5} +} + +type ListRecommendationsRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + UserId string `protobuf:"bytes,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` + ProductIds []string `protobuf:"bytes,2,rep,name=product_ids,json=productIds,proto3" json:"product_ids,omitempty"` +} + +func (x *ListRecommendationsRequest) Reset() { + *x = ListRecommendationsRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_demo_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListRecommendationsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListRecommendationsRequest) ProtoMessage() {} + +func (x *ListRecommendationsRequest) ProtoReflect() protoreflect.Message { + mi := &file_demo_proto_msgTypes[6] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListRecommendationsRequest.ProtoReflect.Descriptor instead. +func (*ListRecommendationsRequest) Descriptor() ([]byte, []int) { + return file_demo_proto_rawDescGZIP(), []int{6} +} + +func (x *ListRecommendationsRequest) GetUserId() string { + if x != nil { + return x.UserId + } + return "" +} + +func (x *ListRecommendationsRequest) GetProductIds() []string { + if x != nil { + return x.ProductIds + } + return nil +} + +type ListRecommendationsResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ProductIds []string `protobuf:"bytes,1,rep,name=product_ids,json=productIds,proto3" json:"product_ids,omitempty"` +} + +func (x *ListRecommendationsResponse) Reset() { + *x = ListRecommendationsResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_demo_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListRecommendationsResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListRecommendationsResponse) ProtoMessage() {} + +func (x *ListRecommendationsResponse) ProtoReflect() protoreflect.Message { + mi := &file_demo_proto_msgTypes[7] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListRecommendationsResponse.ProtoReflect.Descriptor instead. +func (*ListRecommendationsResponse) Descriptor() ([]byte, []int) { + return file_demo_proto_rawDescGZIP(), []int{7} +} + +func (x *ListRecommendationsResponse) GetProductIds() []string { + if x != nil { + return x.ProductIds + } + return nil +} + +type Product struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` + Description string `protobuf:"bytes,3,opt,name=description,proto3" json:"description,omitempty"` + Picture string `protobuf:"bytes,4,opt,name=picture,proto3" json:"picture,omitempty"` + PriceUsd *Money `protobuf:"bytes,5,opt,name=price_usd,json=priceUsd,proto3" json:"price_usd,omitempty"` + // Categories such as "clothing" or "kitchen" that can be used to look up + // other related products. + Categories []string `protobuf:"bytes,6,rep,name=categories,proto3" json:"categories,omitempty"` +} + +func (x *Product) Reset() { + *x = Product{} + if protoimpl.UnsafeEnabled { + mi := &file_demo_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Product) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Product) ProtoMessage() {} + +func (x *Product) ProtoReflect() protoreflect.Message { + mi := &file_demo_proto_msgTypes[8] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Product.ProtoReflect.Descriptor instead. +func (*Product) Descriptor() ([]byte, []int) { + return file_demo_proto_rawDescGZIP(), []int{8} +} + +func (x *Product) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *Product) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *Product) GetDescription() string { + if x != nil { + return x.Description + } + return "" +} + +func (x *Product) GetPicture() string { + if x != nil { + return x.Picture + } + return "" +} + +func (x *Product) GetPriceUsd() *Money { + if x != nil { + return x.PriceUsd + } + return nil +} + +func (x *Product) GetCategories() []string { + if x != nil { + return x.Categories + } + return nil +} + +type ListProductsResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Products []*Product `protobuf:"bytes,1,rep,name=products,proto3" json:"products,omitempty"` +} + +func (x *ListProductsResponse) Reset() { + *x = ListProductsResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_demo_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListProductsResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListProductsResponse) ProtoMessage() {} + +func (x *ListProductsResponse) ProtoReflect() protoreflect.Message { + mi := &file_demo_proto_msgTypes[9] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListProductsResponse.ProtoReflect.Descriptor instead. +func (*ListProductsResponse) Descriptor() ([]byte, []int) { + return file_demo_proto_rawDescGZIP(), []int{9} +} + +func (x *ListProductsResponse) GetProducts() []*Product { + if x != nil { + return x.Products + } + return nil +} + +type GetProductRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` +} + +func (x *GetProductRequest) Reset() { + *x = GetProductRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_demo_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetProductRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetProductRequest) ProtoMessage() {} + +func (x *GetProductRequest) ProtoReflect() protoreflect.Message { + mi := &file_demo_proto_msgTypes[10] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetProductRequest.ProtoReflect.Descriptor instead. +func (*GetProductRequest) Descriptor() ([]byte, []int) { + return file_demo_proto_rawDescGZIP(), []int{10} +} + +func (x *GetProductRequest) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +type SearchProductsRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Query string `protobuf:"bytes,1,opt,name=query,proto3" json:"query,omitempty"` +} + +func (x *SearchProductsRequest) Reset() { + *x = SearchProductsRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_demo_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SearchProductsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SearchProductsRequest) ProtoMessage() {} + +func (x *SearchProductsRequest) ProtoReflect() protoreflect.Message { + mi := &file_demo_proto_msgTypes[11] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SearchProductsRequest.ProtoReflect.Descriptor instead. +func (*SearchProductsRequest) Descriptor() ([]byte, []int) { + return file_demo_proto_rawDescGZIP(), []int{11} +} + +func (x *SearchProductsRequest) GetQuery() string { + if x != nil { + return x.Query + } + return "" +} + +type SearchProductsResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Results []*Product `protobuf:"bytes,1,rep,name=results,proto3" json:"results,omitempty"` +} + +func (x *SearchProductsResponse) Reset() { + *x = SearchProductsResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_demo_proto_msgTypes[12] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SearchProductsResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SearchProductsResponse) ProtoMessage() {} + +func (x *SearchProductsResponse) ProtoReflect() protoreflect.Message { + mi := &file_demo_proto_msgTypes[12] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SearchProductsResponse.ProtoReflect.Descriptor instead. +func (*SearchProductsResponse) Descriptor() ([]byte, []int) { + return file_demo_proto_rawDescGZIP(), []int{12} +} + +func (x *SearchProductsResponse) GetResults() []*Product { + if x != nil { + return x.Results + } + return nil +} + +type GetQuoteRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Address *Address `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"` + Items []*CartItem `protobuf:"bytes,2,rep,name=items,proto3" json:"items,omitempty"` +} + +func (x *GetQuoteRequest) Reset() { + *x = GetQuoteRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_demo_proto_msgTypes[13] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetQuoteRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetQuoteRequest) ProtoMessage() {} + +func (x *GetQuoteRequest) ProtoReflect() protoreflect.Message { + mi := &file_demo_proto_msgTypes[13] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetQuoteRequest.ProtoReflect.Descriptor instead. +func (*GetQuoteRequest) Descriptor() ([]byte, []int) { + return file_demo_proto_rawDescGZIP(), []int{13} +} + +func (x *GetQuoteRequest) GetAddress() *Address { + if x != nil { + return x.Address + } + return nil +} + +func (x *GetQuoteRequest) GetItems() []*CartItem { + if x != nil { + return x.Items + } + return nil +} + +type GetQuoteResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + CostUsd *Money `protobuf:"bytes,1,opt,name=cost_usd,json=costUsd,proto3" json:"cost_usd,omitempty"` +} + +func (x *GetQuoteResponse) Reset() { + *x = GetQuoteResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_demo_proto_msgTypes[14] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetQuoteResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetQuoteResponse) ProtoMessage() {} + +func (x *GetQuoteResponse) ProtoReflect() protoreflect.Message { + mi := &file_demo_proto_msgTypes[14] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetQuoteResponse.ProtoReflect.Descriptor instead. +func (*GetQuoteResponse) Descriptor() ([]byte, []int) { + return file_demo_proto_rawDescGZIP(), []int{14} +} + +func (x *GetQuoteResponse) GetCostUsd() *Money { + if x != nil { + return x.CostUsd + } + return nil +} + +type ShipOrderRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Address *Address `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"` + Items []*CartItem `protobuf:"bytes,2,rep,name=items,proto3" json:"items,omitempty"` +} + +func (x *ShipOrderRequest) Reset() { + *x = ShipOrderRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_demo_proto_msgTypes[15] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ShipOrderRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ShipOrderRequest) ProtoMessage() {} + +func (x *ShipOrderRequest) ProtoReflect() protoreflect.Message { + mi := &file_demo_proto_msgTypes[15] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ShipOrderRequest.ProtoReflect.Descriptor instead. +func (*ShipOrderRequest) Descriptor() ([]byte, []int) { + return file_demo_proto_rawDescGZIP(), []int{15} +} + +func (x *ShipOrderRequest) GetAddress() *Address { + if x != nil { + return x.Address + } + return nil +} + +func (x *ShipOrderRequest) GetItems() []*CartItem { + if x != nil { + return x.Items + } + return nil +} + +type ShipOrderResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + TrackingId string `protobuf:"bytes,1,opt,name=tracking_id,json=trackingId,proto3" json:"tracking_id,omitempty"` +} + +func (x *ShipOrderResponse) Reset() { + *x = ShipOrderResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_demo_proto_msgTypes[16] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ShipOrderResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ShipOrderResponse) ProtoMessage() {} + +func (x *ShipOrderResponse) ProtoReflect() protoreflect.Message { + mi := &file_demo_proto_msgTypes[16] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ShipOrderResponse.ProtoReflect.Descriptor instead. +func (*ShipOrderResponse) Descriptor() ([]byte, []int) { + return file_demo_proto_rawDescGZIP(), []int{16} +} + +func (x *ShipOrderResponse) GetTrackingId() string { + if x != nil { + return x.TrackingId + } + return "" +} + +type Address struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + StreetAddress string `protobuf:"bytes,1,opt,name=street_address,json=streetAddress,proto3" json:"street_address,omitempty"` + City string `protobuf:"bytes,2,opt,name=city,proto3" json:"city,omitempty"` + State string `protobuf:"bytes,3,opt,name=state,proto3" json:"state,omitempty"` + Country string `protobuf:"bytes,4,opt,name=country,proto3" json:"country,omitempty"` + ZipCode int32 `protobuf:"varint,5,opt,name=zip_code,json=zipCode,proto3" json:"zip_code,omitempty"` +} + +func (x *Address) Reset() { + *x = Address{} + if protoimpl.UnsafeEnabled { + mi := &file_demo_proto_msgTypes[17] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Address) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Address) ProtoMessage() {} + +func (x *Address) ProtoReflect() protoreflect.Message { + mi := &file_demo_proto_msgTypes[17] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Address.ProtoReflect.Descriptor instead. +func (*Address) Descriptor() ([]byte, []int) { + return file_demo_proto_rawDescGZIP(), []int{17} +} + +func (x *Address) GetStreetAddress() string { + if x != nil { + return x.StreetAddress + } + return "" +} + +func (x *Address) GetCity() string { + if x != nil { + return x.City + } + return "" +} + +func (x *Address) GetState() string { + if x != nil { + return x.State + } + return "" +} + +func (x *Address) GetCountry() string { + if x != nil { + return x.Country + } + return "" +} + +func (x *Address) GetZipCode() int32 { + if x != nil { + return x.ZipCode + } + return 0 +} + +// Represents an amount of money with its currency type. +type Money struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The 3-letter currency code defined in ISO 4217. + CurrencyCode string `protobuf:"bytes,1,opt,name=currency_code,json=currencyCode,proto3" json:"currency_code,omitempty"` + // The whole units of the amount. + // For example if `currencyCode` is `"USD"`, then 1 unit is one US dollar. + Units int64 `protobuf:"varint,2,opt,name=units,proto3" json:"units,omitempty"` + // Number of nano (10^-9) units of the amount. + // The value must be between -999,999,999 and +999,999,999 inclusive. + // If `units` is positive, `nanos` must be positive or zero. + // If `units` is zero, `nanos` can be positive, zero, or negative. + // If `units` is negative, `nanos` must be negative or zero. + // For example $-1.75 is represented as `units`=-1 and `nanos`=-750,000,000. + Nanos int32 `protobuf:"varint,3,opt,name=nanos,proto3" json:"nanos,omitempty"` +} + +func (x *Money) Reset() { + *x = Money{} + if protoimpl.UnsafeEnabled { + mi := &file_demo_proto_msgTypes[18] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Money) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Money) ProtoMessage() {} + +func (x *Money) ProtoReflect() protoreflect.Message { + mi := &file_demo_proto_msgTypes[18] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Money.ProtoReflect.Descriptor instead. +func (*Money) Descriptor() ([]byte, []int) { + return file_demo_proto_rawDescGZIP(), []int{18} +} + +func (x *Money) GetCurrencyCode() string { + if x != nil { + return x.CurrencyCode + } + return "" +} + +func (x *Money) GetUnits() int64 { + if x != nil { + return x.Units + } + return 0 +} + +func (x *Money) GetNanos() int32 { + if x != nil { + return x.Nanos + } + return 0 +} + +type GetSupportedCurrenciesResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The 3-letter currency code defined in ISO 4217. + CurrencyCodes []string `protobuf:"bytes,1,rep,name=currency_codes,json=currencyCodes,proto3" json:"currency_codes,omitempty"` +} + +func (x *GetSupportedCurrenciesResponse) Reset() { + *x = GetSupportedCurrenciesResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_demo_proto_msgTypes[19] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetSupportedCurrenciesResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetSupportedCurrenciesResponse) ProtoMessage() {} + +func (x *GetSupportedCurrenciesResponse) ProtoReflect() protoreflect.Message { + mi := &file_demo_proto_msgTypes[19] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetSupportedCurrenciesResponse.ProtoReflect.Descriptor instead. +func (*GetSupportedCurrenciesResponse) Descriptor() ([]byte, []int) { + return file_demo_proto_rawDescGZIP(), []int{19} +} + +func (x *GetSupportedCurrenciesResponse) GetCurrencyCodes() []string { + if x != nil { + return x.CurrencyCodes + } + return nil +} + +type CurrencyConversionRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + From *Money `protobuf:"bytes,1,opt,name=from,proto3" json:"from,omitempty"` + // The 3-letter currency code defined in ISO 4217. + ToCode string `protobuf:"bytes,2,opt,name=to_code,json=toCode,proto3" json:"to_code,omitempty"` +} + +func (x *CurrencyConversionRequest) Reset() { + *x = CurrencyConversionRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_demo_proto_msgTypes[20] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CurrencyConversionRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CurrencyConversionRequest) ProtoMessage() {} + +func (x *CurrencyConversionRequest) ProtoReflect() protoreflect.Message { + mi := &file_demo_proto_msgTypes[20] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CurrencyConversionRequest.ProtoReflect.Descriptor instead. +func (*CurrencyConversionRequest) Descriptor() ([]byte, []int) { + return file_demo_proto_rawDescGZIP(), []int{20} +} + +func (x *CurrencyConversionRequest) GetFrom() *Money { + if x != nil { + return x.From + } + return nil +} + +func (x *CurrencyConversionRequest) GetToCode() string { + if x != nil { + return x.ToCode + } + return "" +} + +type CreditCardInfo struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + CreditCardNumber string `protobuf:"bytes,1,opt,name=credit_card_number,json=creditCardNumber,proto3" json:"credit_card_number,omitempty"` + CreditCardCvv int32 `protobuf:"varint,2,opt,name=credit_card_cvv,json=creditCardCvv,proto3" json:"credit_card_cvv,omitempty"` + CreditCardExpirationYear int32 `protobuf:"varint,3,opt,name=credit_card_expiration_year,json=creditCardExpirationYear,proto3" json:"credit_card_expiration_year,omitempty"` + CreditCardExpirationMonth int32 `protobuf:"varint,4,opt,name=credit_card_expiration_month,json=creditCardExpirationMonth,proto3" json:"credit_card_expiration_month,omitempty"` +} + +func (x *CreditCardInfo) Reset() { + *x = CreditCardInfo{} + if protoimpl.UnsafeEnabled { + mi := &file_demo_proto_msgTypes[21] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CreditCardInfo) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreditCardInfo) ProtoMessage() {} + +func (x *CreditCardInfo) ProtoReflect() protoreflect.Message { + mi := &file_demo_proto_msgTypes[21] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CreditCardInfo.ProtoReflect.Descriptor instead. +func (*CreditCardInfo) Descriptor() ([]byte, []int) { + return file_demo_proto_rawDescGZIP(), []int{21} +} + +func (x *CreditCardInfo) GetCreditCardNumber() string { + if x != nil { + return x.CreditCardNumber + } + return "" +} + +func (x *CreditCardInfo) GetCreditCardCvv() int32 { + if x != nil { + return x.CreditCardCvv + } + return 0 +} + +func (x *CreditCardInfo) GetCreditCardExpirationYear() int32 { + if x != nil { + return x.CreditCardExpirationYear + } + return 0 +} + +func (x *CreditCardInfo) GetCreditCardExpirationMonth() int32 { + if x != nil { + return x.CreditCardExpirationMonth + } + return 0 +} + +type ChargeRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Amount *Money `protobuf:"bytes,1,opt,name=amount,proto3" json:"amount,omitempty"` + CreditCard *CreditCardInfo `protobuf:"bytes,2,opt,name=credit_card,json=creditCard,proto3" json:"credit_card,omitempty"` +} + +func (x *ChargeRequest) Reset() { + *x = ChargeRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_demo_proto_msgTypes[22] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ChargeRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ChargeRequest) ProtoMessage() {} + +func (x *ChargeRequest) ProtoReflect() protoreflect.Message { + mi := &file_demo_proto_msgTypes[22] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ChargeRequest.ProtoReflect.Descriptor instead. +func (*ChargeRequest) Descriptor() ([]byte, []int) { + return file_demo_proto_rawDescGZIP(), []int{22} +} + +func (x *ChargeRequest) GetAmount() *Money { + if x != nil { + return x.Amount + } + return nil +} + +func (x *ChargeRequest) GetCreditCard() *CreditCardInfo { + if x != nil { + return x.CreditCard + } + return nil +} + +type ChargeResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + TransactionId string `protobuf:"bytes,1,opt,name=transaction_id,json=transactionId,proto3" json:"transaction_id,omitempty"` +} + +func (x *ChargeResponse) Reset() { + *x = ChargeResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_demo_proto_msgTypes[23] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ChargeResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ChargeResponse) ProtoMessage() {} + +func (x *ChargeResponse) ProtoReflect() protoreflect.Message { + mi := &file_demo_proto_msgTypes[23] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ChargeResponse.ProtoReflect.Descriptor instead. +func (*ChargeResponse) Descriptor() ([]byte, []int) { + return file_demo_proto_rawDescGZIP(), []int{23} +} + +func (x *ChargeResponse) GetTransactionId() string { + if x != nil { + return x.TransactionId + } + return "" +} + +type OrderItem struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Item *CartItem `protobuf:"bytes,1,opt,name=item,proto3" json:"item,omitempty"` + Cost *Money `protobuf:"bytes,2,opt,name=cost,proto3" json:"cost,omitempty"` +} + +func (x *OrderItem) Reset() { + *x = OrderItem{} + if protoimpl.UnsafeEnabled { + mi := &file_demo_proto_msgTypes[24] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *OrderItem) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*OrderItem) ProtoMessage() {} + +func (x *OrderItem) ProtoReflect() protoreflect.Message { + mi := &file_demo_proto_msgTypes[24] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use OrderItem.ProtoReflect.Descriptor instead. +func (*OrderItem) Descriptor() ([]byte, []int) { + return file_demo_proto_rawDescGZIP(), []int{24} +} + +func (x *OrderItem) GetItem() *CartItem { + if x != nil { + return x.Item + } + return nil +} + +func (x *OrderItem) GetCost() *Money { + if x != nil { + return x.Cost + } + return nil +} + +type OrderResult struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + OrderId string `protobuf:"bytes,1,opt,name=order_id,json=orderId,proto3" json:"order_id,omitempty"` + ShippingTrackingId string `protobuf:"bytes,2,opt,name=shipping_tracking_id,json=shippingTrackingId,proto3" json:"shipping_tracking_id,omitempty"` + ShippingCost *Money `protobuf:"bytes,3,opt,name=shipping_cost,json=shippingCost,proto3" json:"shipping_cost,omitempty"` + ShippingAddress *Address `protobuf:"bytes,4,opt,name=shipping_address,json=shippingAddress,proto3" json:"shipping_address,omitempty"` + Items []*OrderItem `protobuf:"bytes,5,rep,name=items,proto3" json:"items,omitempty"` +} + +func (x *OrderResult) Reset() { + *x = OrderResult{} + if protoimpl.UnsafeEnabled { + mi := &file_demo_proto_msgTypes[25] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *OrderResult) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*OrderResult) ProtoMessage() {} + +func (x *OrderResult) ProtoReflect() protoreflect.Message { + mi := &file_demo_proto_msgTypes[25] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use OrderResult.ProtoReflect.Descriptor instead. +func (*OrderResult) Descriptor() ([]byte, []int) { + return file_demo_proto_rawDescGZIP(), []int{25} +} + +func (x *OrderResult) GetOrderId() string { + if x != nil { + return x.OrderId + } + return "" +} + +func (x *OrderResult) GetShippingTrackingId() string { + if x != nil { + return x.ShippingTrackingId + } + return "" +} + +func (x *OrderResult) GetShippingCost() *Money { + if x != nil { + return x.ShippingCost + } + return nil +} + +func (x *OrderResult) GetShippingAddress() *Address { + if x != nil { + return x.ShippingAddress + } + return nil +} + +func (x *OrderResult) GetItems() []*OrderItem { + if x != nil { + return x.Items + } + return nil +} + +type SendOrderConfirmationRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Email string `protobuf:"bytes,1,opt,name=email,proto3" json:"email,omitempty"` + Order *OrderResult `protobuf:"bytes,2,opt,name=order,proto3" json:"order,omitempty"` +} + +func (x *SendOrderConfirmationRequest) Reset() { + *x = SendOrderConfirmationRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_demo_proto_msgTypes[26] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SendOrderConfirmationRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SendOrderConfirmationRequest) ProtoMessage() {} + +func (x *SendOrderConfirmationRequest) ProtoReflect() protoreflect.Message { + mi := &file_demo_proto_msgTypes[26] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SendOrderConfirmationRequest.ProtoReflect.Descriptor instead. +func (*SendOrderConfirmationRequest) Descriptor() ([]byte, []int) { + return file_demo_proto_rawDescGZIP(), []int{26} +} + +func (x *SendOrderConfirmationRequest) GetEmail() string { + if x != nil { + return x.Email + } + return "" +} + +func (x *SendOrderConfirmationRequest) GetOrder() *OrderResult { + if x != nil { + return x.Order + } + return nil +} + +type PlaceOrderRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + UserId string `protobuf:"bytes,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` + UserCurrency string `protobuf:"bytes,2,opt,name=user_currency,json=userCurrency,proto3" json:"user_currency,omitempty"` + Address *Address `protobuf:"bytes,3,opt,name=address,proto3" json:"address,omitempty"` + Email string `protobuf:"bytes,5,opt,name=email,proto3" json:"email,omitempty"` + CreditCard *CreditCardInfo `protobuf:"bytes,6,opt,name=credit_card,json=creditCard,proto3" json:"credit_card,omitempty"` +} + +func (x *PlaceOrderRequest) Reset() { + *x = PlaceOrderRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_demo_proto_msgTypes[27] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *PlaceOrderRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PlaceOrderRequest) ProtoMessage() {} + +func (x *PlaceOrderRequest) ProtoReflect() protoreflect.Message { + mi := &file_demo_proto_msgTypes[27] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PlaceOrderRequest.ProtoReflect.Descriptor instead. +func (*PlaceOrderRequest) Descriptor() ([]byte, []int) { + return file_demo_proto_rawDescGZIP(), []int{27} +} + +func (x *PlaceOrderRequest) GetUserId() string { + if x != nil { + return x.UserId + } + return "" +} + +func (x *PlaceOrderRequest) GetUserCurrency() string { + if x != nil { + return x.UserCurrency + } + return "" +} + +func (x *PlaceOrderRequest) GetAddress() *Address { + if x != nil { + return x.Address + } + return nil +} + +func (x *PlaceOrderRequest) GetEmail() string { + if x != nil { + return x.Email + } + return "" +} + +func (x *PlaceOrderRequest) GetCreditCard() *CreditCardInfo { + if x != nil { + return x.CreditCard + } + return nil +} + +type PlaceOrderResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Order *OrderResult `protobuf:"bytes,1,opt,name=order,proto3" json:"order,omitempty"` +} + +func (x *PlaceOrderResponse) Reset() { + *x = PlaceOrderResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_demo_proto_msgTypes[28] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *PlaceOrderResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PlaceOrderResponse) ProtoMessage() {} + +func (x *PlaceOrderResponse) ProtoReflect() protoreflect.Message { + mi := &file_demo_proto_msgTypes[28] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PlaceOrderResponse.ProtoReflect.Descriptor instead. +func (*PlaceOrderResponse) Descriptor() ([]byte, []int) { + return file_demo_proto_rawDescGZIP(), []int{28} +} + +func (x *PlaceOrderResponse) GetOrder() *OrderResult { + if x != nil { + return x.Order + } + return nil +} + +type AdRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // List of important key words from the current page describing the context. + ContextKeys []string `protobuf:"bytes,1,rep,name=context_keys,json=contextKeys,proto3" json:"context_keys,omitempty"` +} + +func (x *AdRequest) Reset() { + *x = AdRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_demo_proto_msgTypes[29] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *AdRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AdRequest) ProtoMessage() {} + +func (x *AdRequest) ProtoReflect() protoreflect.Message { + mi := &file_demo_proto_msgTypes[29] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AdRequest.ProtoReflect.Descriptor instead. +func (*AdRequest) Descriptor() ([]byte, []int) { + return file_demo_proto_rawDescGZIP(), []int{29} +} + +func (x *AdRequest) GetContextKeys() []string { + if x != nil { + return x.ContextKeys + } + return nil +} + +type AdResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Ads []*Ad `protobuf:"bytes,1,rep,name=ads,proto3" json:"ads,omitempty"` +} + +func (x *AdResponse) Reset() { + *x = AdResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_demo_proto_msgTypes[30] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *AdResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AdResponse) ProtoMessage() {} + +func (x *AdResponse) ProtoReflect() protoreflect.Message { + mi := &file_demo_proto_msgTypes[30] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AdResponse.ProtoReflect.Descriptor instead. +func (*AdResponse) Descriptor() ([]byte, []int) { + return file_demo_proto_rawDescGZIP(), []int{30} +} + +func (x *AdResponse) GetAds() []*Ad { + if x != nil { + return x.Ads + } + return nil +} + +type Ad struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // url to redirect to when an ad is clicked. + RedirectUrl string `protobuf:"bytes,1,opt,name=redirect_url,json=redirectUrl,proto3" json:"redirect_url,omitempty"` + // short advertisement text to display. + Text string `protobuf:"bytes,2,opt,name=text,proto3" json:"text,omitempty"` +} + +func (x *Ad) Reset() { + *x = Ad{} + if protoimpl.UnsafeEnabled { + mi := &file_demo_proto_msgTypes[31] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Ad) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Ad) ProtoMessage() {} + +func (x *Ad) ProtoReflect() protoreflect.Message { + mi := &file_demo_proto_msgTypes[31] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Ad.ProtoReflect.Descriptor instead. +func (*Ad) Descriptor() ([]byte, []int) { + return file_demo_proto_rawDescGZIP(), []int{31} +} + +func (x *Ad) GetRedirectUrl() string { + if x != nil { + return x.RedirectUrl + } + return "" +} + +func (x *Ad) GetText() string { + if x != nil { + return x.Text + } + return "" +} + +var File_demo_proto protoreflect.FileDescriptor + +var file_demo_proto_rawDesc = []byte{ + 0x0a, 0x0a, 0x64, 0x65, 0x6d, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0b, 0x68, 0x69, + 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x22, 0x45, 0x0a, 0x08, 0x43, 0x61, 0x72, + 0x74, 0x49, 0x74, 0x65, 0x6d, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, + 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x70, 0x72, 0x6f, 0x64, 0x75, + 0x63, 0x74, 0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x71, 0x75, 0x61, 0x6e, 0x74, 0x69, 0x74, 0x79, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x71, 0x75, 0x61, 0x6e, 0x74, 0x69, 0x74, 0x79, + 0x22, 0x54, 0x0a, 0x0e, 0x41, 0x64, 0x64, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x29, 0x0a, 0x04, 0x69, + 0x74, 0x65, 0x6d, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x68, 0x69, 0x70, 0x73, + 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x43, 0x61, 0x72, 0x74, 0x49, 0x74, 0x65, 0x6d, + 0x52, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x22, 0x2b, 0x0a, 0x10, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x43, + 0x61, 0x72, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, + 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, + 0x72, 0x49, 0x64, 0x22, 0x29, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x43, 0x61, 0x72, 0x74, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0x4c, + 0x0a, 0x04, 0x43, 0x61, 0x72, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, + 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, + 0x2b, 0x0a, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, + 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x43, 0x61, 0x72, + 0x74, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x22, 0x07, 0x0a, 0x05, + 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x56, 0x0a, 0x1a, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x63, + 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b, + 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, + 0x09, 0x52, 0x0a, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x49, 0x64, 0x73, 0x22, 0x3e, 0x0a, + 0x1b, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x64, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1f, 0x0a, 0x0b, + 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, + 0x09, 0x52, 0x0a, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x49, 0x64, 0x73, 0x22, 0xba, 0x01, + 0x0a, 0x07, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, + 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x20, 0x0a, + 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, + 0x18, 0x0a, 0x07, 0x70, 0x69, 0x63, 0x74, 0x75, 0x72, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x07, 0x70, 0x69, 0x63, 0x74, 0x75, 0x72, 0x65, 0x12, 0x2f, 0x0a, 0x09, 0x70, 0x72, 0x69, + 0x63, 0x65, 0x5f, 0x75, 0x73, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x68, + 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x4d, 0x6f, 0x6e, 0x65, 0x79, + 0x52, 0x08, 0x70, 0x72, 0x69, 0x63, 0x65, 0x55, 0x73, 0x64, 0x12, 0x1e, 0x0a, 0x0a, 0x63, 0x61, + 0x74, 0x65, 0x67, 0x6f, 0x72, 0x69, 0x65, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, + 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x69, 0x65, 0x73, 0x22, 0x48, 0x0a, 0x14, 0x4c, 0x69, + 0x73, 0x74, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x30, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x73, 0x18, 0x01, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, + 0x6f, 0x70, 0x2e, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x64, + 0x75, 0x63, 0x74, 0x73, 0x22, 0x23, 0x0a, 0x11, 0x47, 0x65, 0x74, 0x50, 0x72, 0x6f, 0x64, 0x75, + 0x63, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x22, 0x2d, 0x0a, 0x15, 0x53, 0x65, 0x61, + 0x72, 0x63, 0x68, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x22, 0x48, 0x0a, 0x16, 0x53, 0x65, 0x61, 0x72, + 0x63, 0x68, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x2e, 0x0a, 0x07, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x18, 0x01, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, + 0x70, 0x2e, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x52, 0x07, 0x72, 0x65, 0x73, 0x75, 0x6c, + 0x74, 0x73, 0x22, 0x6e, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x51, 0x75, 0x6f, 0x74, 0x65, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, + 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, 0x07, 0x61, 0x64, + 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x2b, 0x0a, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x18, 0x02, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, + 0x6f, 0x70, 0x2e, 0x43, 0x61, 0x72, 0x74, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x05, 0x69, 0x74, 0x65, + 0x6d, 0x73, 0x22, 0x41, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x51, 0x75, 0x6f, 0x74, 0x65, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2d, 0x0a, 0x08, 0x63, 0x6f, 0x73, 0x74, 0x5f, 0x75, + 0x73, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, + 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x4d, 0x6f, 0x6e, 0x65, 0x79, 0x52, 0x07, 0x63, 0x6f, + 0x73, 0x74, 0x55, 0x73, 0x64, 0x22, 0x6f, 0x0a, 0x10, 0x53, 0x68, 0x69, 0x70, 0x4f, 0x72, 0x64, + 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x07, 0x61, 0x64, 0x64, + 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x68, 0x69, 0x70, + 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, + 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x2b, 0x0a, 0x05, 0x69, 0x74, 0x65, + 0x6d, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, + 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x43, 0x61, 0x72, 0x74, 0x49, 0x74, 0x65, 0x6d, 0x52, + 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x22, 0x34, 0x0a, 0x11, 0x53, 0x68, 0x69, 0x70, 0x4f, 0x72, + 0x64, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x74, + 0x72, 0x61, 0x63, 0x6b, 0x69, 0x6e, 0x67, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0a, 0x74, 0x72, 0x61, 0x63, 0x6b, 0x69, 0x6e, 0x67, 0x49, 0x64, 0x22, 0x8f, 0x01, 0x0a, + 0x07, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x25, 0x0a, 0x0e, 0x73, 0x74, 0x72, 0x65, + 0x65, 0x74, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0d, 0x73, 0x74, 0x72, 0x65, 0x65, 0x74, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, + 0x12, 0x0a, 0x04, 0x63, 0x69, 0x74, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x63, + 0x69, 0x74, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x75, + 0x6e, 0x74, 0x72, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x6f, 0x75, 0x6e, + 0x74, 0x72, 0x79, 0x12, 0x19, 0x0a, 0x08, 0x7a, 0x69, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, + 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x07, 0x7a, 0x69, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x22, 0x58, + 0x0a, 0x05, 0x4d, 0x6f, 0x6e, 0x65, 0x79, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x75, 0x72, 0x72, 0x65, + 0x6e, 0x63, 0x79, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, + 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x14, 0x0a, 0x05, + 0x75, 0x6e, 0x69, 0x74, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x75, 0x6e, 0x69, + 0x74, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x6e, 0x61, 0x6e, 0x6f, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x05, 0x52, 0x05, 0x6e, 0x61, 0x6e, 0x6f, 0x73, 0x22, 0x47, 0x0a, 0x1e, 0x47, 0x65, 0x74, 0x53, + 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x69, + 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x63, 0x75, + 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, + 0x28, 0x09, 0x52, 0x0d, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x43, 0x6f, 0x64, 0x65, + 0x73, 0x22, 0x5c, 0x0a, 0x19, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x43, 0x6f, 0x6e, + 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x26, + 0x0a, 0x04, 0x66, 0x72, 0x6f, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x68, + 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x4d, 0x6f, 0x6e, 0x65, 0x79, + 0x52, 0x04, 0x66, 0x72, 0x6f, 0x6d, 0x12, 0x17, 0x0a, 0x07, 0x74, 0x6f, 0x5f, 0x63, 0x6f, 0x64, + 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x74, 0x6f, 0x43, 0x6f, 0x64, 0x65, 0x22, + 0xe6, 0x01, 0x0a, 0x0e, 0x43, 0x72, 0x65, 0x64, 0x69, 0x74, 0x43, 0x61, 0x72, 0x64, 0x49, 0x6e, + 0x66, 0x6f, 0x12, 0x2c, 0x0a, 0x12, 0x63, 0x72, 0x65, 0x64, 0x69, 0x74, 0x5f, 0x63, 0x61, 0x72, + 0x64, 0x5f, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, + 0x63, 0x72, 0x65, 0x64, 0x69, 0x74, 0x43, 0x61, 0x72, 0x64, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, + 0x12, 0x26, 0x0a, 0x0f, 0x63, 0x72, 0x65, 0x64, 0x69, 0x74, 0x5f, 0x63, 0x61, 0x72, 0x64, 0x5f, + 0x63, 0x76, 0x76, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0d, 0x63, 0x72, 0x65, 0x64, 0x69, + 0x74, 0x43, 0x61, 0x72, 0x64, 0x43, 0x76, 0x76, 0x12, 0x3d, 0x0a, 0x1b, 0x63, 0x72, 0x65, 0x64, + 0x69, 0x74, 0x5f, 0x63, 0x61, 0x72, 0x64, 0x5f, 0x65, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x5f, 0x79, 0x65, 0x61, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x18, 0x63, + 0x72, 0x65, 0x64, 0x69, 0x74, 0x43, 0x61, 0x72, 0x64, 0x45, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x59, 0x65, 0x61, 0x72, 0x12, 0x3f, 0x0a, 0x1c, 0x63, 0x72, 0x65, 0x64, 0x69, + 0x74, 0x5f, 0x63, 0x61, 0x72, 0x64, 0x5f, 0x65, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x5f, 0x6d, 0x6f, 0x6e, 0x74, 0x68, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x19, 0x63, + 0x72, 0x65, 0x64, 0x69, 0x74, 0x43, 0x61, 0x72, 0x64, 0x45, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x4d, 0x6f, 0x6e, 0x74, 0x68, 0x22, 0x79, 0x0a, 0x0d, 0x43, 0x68, 0x61, 0x72, + 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2a, 0x0a, 0x06, 0x61, 0x6d, 0x6f, + 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x68, 0x69, 0x70, 0x73, + 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x4d, 0x6f, 0x6e, 0x65, 0x79, 0x52, 0x06, 0x61, + 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x3c, 0x0a, 0x0b, 0x63, 0x72, 0x65, 0x64, 0x69, 0x74, 0x5f, + 0x63, 0x61, 0x72, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x68, 0x69, 0x70, + 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x43, 0x72, 0x65, 0x64, 0x69, 0x74, 0x43, + 0x61, 0x72, 0x64, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x0a, 0x63, 0x72, 0x65, 0x64, 0x69, 0x74, 0x43, + 0x61, 0x72, 0x64, 0x22, 0x37, 0x0a, 0x0e, 0x43, 0x68, 0x61, 0x72, 0x67, 0x65, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, + 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x74, + 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x22, 0x5e, 0x0a, 0x09, + 0x4f, 0x72, 0x64, 0x65, 0x72, 0x49, 0x74, 0x65, 0x6d, 0x12, 0x29, 0x0a, 0x04, 0x69, 0x74, 0x65, + 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, + 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x43, 0x61, 0x72, 0x74, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x04, + 0x69, 0x74, 0x65, 0x6d, 0x12, 0x26, 0x0a, 0x04, 0x63, 0x6f, 0x73, 0x74, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, + 0x2e, 0x4d, 0x6f, 0x6e, 0x65, 0x79, 0x52, 0x04, 0x63, 0x6f, 0x73, 0x74, 0x22, 0x82, 0x02, 0x0a, + 0x0b, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x19, 0x0a, 0x08, + 0x6f, 0x72, 0x64, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, + 0x6f, 0x72, 0x64, 0x65, 0x72, 0x49, 0x64, 0x12, 0x30, 0x0a, 0x14, 0x73, 0x68, 0x69, 0x70, 0x70, + 0x69, 0x6e, 0x67, 0x5f, 0x74, 0x72, 0x61, 0x63, 0x6b, 0x69, 0x6e, 0x67, 0x5f, 0x69, 0x64, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x12, 0x73, 0x68, 0x69, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x54, + 0x72, 0x61, 0x63, 0x6b, 0x69, 0x6e, 0x67, 0x49, 0x64, 0x12, 0x37, 0x0a, 0x0d, 0x73, 0x68, 0x69, + 0x70, 0x70, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x6f, 0x73, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x12, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x4d, + 0x6f, 0x6e, 0x65, 0x79, 0x52, 0x0c, 0x73, 0x68, 0x69, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x43, 0x6f, + 0x73, 0x74, 0x12, 0x3f, 0x0a, 0x10, 0x73, 0x68, 0x69, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x5f, 0x61, + 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x68, + 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x41, 0x64, 0x64, 0x72, 0x65, + 0x73, 0x73, 0x52, 0x0f, 0x73, 0x68, 0x69, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x41, 0x64, 0x64, 0x72, + 0x65, 0x73, 0x73, 0x12, 0x2c, 0x0a, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x18, 0x05, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, + 0x2e, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d, + 0x73, 0x22, 0x64, 0x0a, 0x1c, 0x53, 0x65, 0x6e, 0x64, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x43, 0x6f, + 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x12, 0x2e, 0x0a, 0x05, 0x6f, 0x72, 0x64, 0x65, 0x72, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, + 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, + 0x52, 0x05, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x22, 0xd5, 0x01, 0x0a, 0x11, 0x50, 0x6c, 0x61, 0x63, + 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x17, 0x0a, + 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, + 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x63, + 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x75, + 0x73, 0x65, 0x72, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x12, 0x2e, 0x0a, 0x07, 0x61, + 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x68, + 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x41, 0x64, 0x64, 0x72, 0x65, + 0x73, 0x73, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x65, + 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x6d, 0x61, 0x69, + 0x6c, 0x12, 0x3c, 0x0a, 0x0b, 0x63, 0x72, 0x65, 0x64, 0x69, 0x74, 0x5f, 0x63, 0x61, 0x72, 0x64, + 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, + 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x43, 0x72, 0x65, 0x64, 0x69, 0x74, 0x43, 0x61, 0x72, 0x64, 0x49, + 0x6e, 0x66, 0x6f, 0x52, 0x0a, 0x63, 0x72, 0x65, 0x64, 0x69, 0x74, 0x43, 0x61, 0x72, 0x64, 0x22, + 0x44, 0x0a, 0x12, 0x50, 0x6c, 0x61, 0x63, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2e, 0x0a, 0x05, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, + 0x6f, 0x70, 0x2e, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x05, + 0x6f, 0x72, 0x64, 0x65, 0x72, 0x22, 0x2e, 0x0a, 0x09, 0x41, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x5f, 0x6b, 0x65, + 0x79, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, + 0x74, 0x4b, 0x65, 0x79, 0x73, 0x22, 0x2f, 0x0a, 0x0a, 0x41, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x21, 0x0a, 0x03, 0x61, 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x0f, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x41, + 0x64, 0x52, 0x03, 0x61, 0x64, 0x73, 0x22, 0x3b, 0x0a, 0x02, 0x41, 0x64, 0x12, 0x21, 0x0a, 0x0c, + 0x72, 0x65, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0b, 0x72, 0x65, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x55, 0x72, 0x6c, 0x12, + 0x12, 0x0a, 0x04, 0x74, 0x65, 0x78, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, + 0x65, 0x78, 0x74, 0x32, 0xca, 0x01, 0x0a, 0x0b, 0x43, 0x61, 0x72, 0x74, 0x53, 0x65, 0x72, 0x76, + 0x69, 0x63, 0x65, 0x12, 0x3c, 0x0a, 0x07, 0x41, 0x64, 0x64, 0x49, 0x74, 0x65, 0x6d, 0x12, 0x1b, + 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x41, 0x64, 0x64, + 0x49, 0x74, 0x65, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x68, 0x69, + 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, + 0x00, 0x12, 0x3b, 0x0a, 0x07, 0x47, 0x65, 0x74, 0x43, 0x61, 0x72, 0x74, 0x12, 0x1b, 0x2e, 0x68, + 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x61, + 0x72, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x11, 0x2e, 0x68, 0x69, 0x70, 0x73, + 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x43, 0x61, 0x72, 0x74, 0x22, 0x00, 0x12, 0x40, + 0x0a, 0x09, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x43, 0x61, 0x72, 0x74, 0x12, 0x1d, 0x2e, 0x68, 0x69, + 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x43, + 0x61, 0x72, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x68, 0x69, 0x70, + 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, + 0x32, 0x83, 0x01, 0x0a, 0x15, 0x52, 0x65, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x64, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x6a, 0x0a, 0x13, 0x4c, 0x69, + 0x73, 0x74, 0x52, 0x65, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x73, 0x12, 0x27, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, + 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x64, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, 0x68, 0x69, 0x70, + 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x63, + 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x32, 0x83, 0x02, 0x0a, 0x15, 0x50, 0x72, 0x6f, 0x64, 0x75, + 0x63, 0x74, 0x43, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, + 0x12, 0x47, 0x0a, 0x0c, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x73, + 0x12, 0x12, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x45, + 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x21, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, + 0x6f, 0x70, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x73, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x44, 0x0a, 0x0a, 0x47, 0x65, 0x74, + 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x12, 0x1e, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, + 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x47, 0x65, 0x74, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, + 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x22, 0x00, 0x12, + 0x5b, 0x0a, 0x0e, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, + 0x73, 0x12, 0x22, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, + 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x73, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, + 0x68, 0x6f, 0x70, 0x2e, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, + 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x32, 0xaa, 0x01, 0x0a, + 0x0f, 0x53, 0x68, 0x69, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, + 0x12, 0x49, 0x0a, 0x08, 0x47, 0x65, 0x74, 0x51, 0x75, 0x6f, 0x74, 0x65, 0x12, 0x1c, 0x2e, 0x68, + 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x47, 0x65, 0x74, 0x51, 0x75, + 0x6f, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x68, 0x69, 0x70, + 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x47, 0x65, 0x74, 0x51, 0x75, 0x6f, 0x74, + 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x4c, 0x0a, 0x09, 0x53, + 0x68, 0x69, 0x70, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x1d, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, + 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x53, 0x68, 0x69, 0x70, 0x4f, 0x72, 0x64, 0x65, 0x72, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, + 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x53, 0x68, 0x69, 0x70, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x32, 0xb7, 0x01, 0x0a, 0x0f, 0x43, 0x75, + 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x5b, 0x0a, + 0x16, 0x47, 0x65, 0x74, 0x53, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x43, 0x75, 0x72, + 0x72, 0x65, 0x6e, 0x63, 0x69, 0x65, 0x73, 0x12, 0x12, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, + 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x2b, 0x2e, 0x68, 0x69, + 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x75, 0x70, + 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x69, 0x65, 0x73, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x47, 0x0a, 0x07, 0x43, 0x6f, + 0x6e, 0x76, 0x65, 0x72, 0x74, 0x12, 0x26, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, + 0x68, 0x6f, 0x70, 0x2e, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x43, 0x6f, 0x6e, 0x76, + 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, + 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x4d, 0x6f, 0x6e, 0x65, + 0x79, 0x22, 0x00, 0x32, 0x55, 0x0a, 0x0e, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x65, + 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x43, 0x0a, 0x06, 0x43, 0x68, 0x61, 0x72, 0x67, 0x65, 0x12, + 0x1a, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x43, 0x68, + 0x61, 0x72, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x68, 0x69, + 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x43, 0x68, 0x61, 0x72, 0x67, 0x65, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x32, 0x68, 0x0a, 0x0c, 0x45, 0x6d, + 0x61, 0x69, 0x6c, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x58, 0x0a, 0x15, 0x53, 0x65, + 0x6e, 0x64, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x12, 0x29, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, + 0x70, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, + 0x72, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, + 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x45, 0x6d, 0x70, + 0x74, 0x79, 0x22, 0x00, 0x32, 0x62, 0x0a, 0x0f, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x6f, 0x75, 0x74, + 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x4f, 0x0a, 0x0a, 0x50, 0x6c, 0x61, 0x63, 0x65, + 0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x1e, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, + 0x68, 0x6f, 0x70, 0x2e, 0x50, 0x6c, 0x61, 0x63, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, + 0x68, 0x6f, 0x70, 0x2e, 0x50, 0x6c, 0x61, 0x63, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x32, 0x48, 0x0a, 0x09, 0x41, 0x64, 0x53, 0x65, + 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x3b, 0x0a, 0x06, 0x47, 0x65, 0x74, 0x41, 0x64, 0x73, 0x12, + 0x16, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x41, 0x64, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, + 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x41, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x22, 0x00, 0x42, 0x40, 0x5a, 0x3e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, + 0x2f, 0x47, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x43, 0x6c, 0x6f, 0x75, 0x64, 0x50, 0x6c, 0x61, 0x74, + 0x66, 0x6f, 0x72, 0x6d, 0x2f, 0x6d, 0x69, 0x73, 0x63, 0x72, 0x6f, 0x73, 0x65, 0x72, 0x76, 0x69, + 0x63, 0x65, 0x73, 0x2d, 0x64, 0x65, 0x6d, 0x6f, 0x2f, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, + 0x73, 0x68, 0x6f, 0x70, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_demo_proto_rawDescOnce sync.Once + file_demo_proto_rawDescData = file_demo_proto_rawDesc +) + +func file_demo_proto_rawDescGZIP() []byte { + file_demo_proto_rawDescOnce.Do(func() { + file_demo_proto_rawDescData = protoimpl.X.CompressGZIP(file_demo_proto_rawDescData) + }) + return file_demo_proto_rawDescData +} + +var file_demo_proto_msgTypes = make([]protoimpl.MessageInfo, 32) +var file_demo_proto_goTypes = []any{ + (*CartItem)(nil), // 0: hipstershop.CartItem + (*AddItemRequest)(nil), // 1: hipstershop.AddItemRequest + (*EmptyCartRequest)(nil), // 2: hipstershop.EmptyCartRequest + (*GetCartRequest)(nil), // 3: hipstershop.GetCartRequest + (*Cart)(nil), // 4: hipstershop.Cart + (*Empty)(nil), // 5: hipstershop.Empty + (*ListRecommendationsRequest)(nil), // 6: hipstershop.ListRecommendationsRequest + (*ListRecommendationsResponse)(nil), // 7: hipstershop.ListRecommendationsResponse + (*Product)(nil), // 8: hipstershop.Product + (*ListProductsResponse)(nil), // 9: hipstershop.ListProductsResponse + (*GetProductRequest)(nil), // 10: hipstershop.GetProductRequest + (*SearchProductsRequest)(nil), // 11: hipstershop.SearchProductsRequest + (*SearchProductsResponse)(nil), // 12: hipstershop.SearchProductsResponse + (*GetQuoteRequest)(nil), // 13: hipstershop.GetQuoteRequest + (*GetQuoteResponse)(nil), // 14: hipstershop.GetQuoteResponse + (*ShipOrderRequest)(nil), // 15: hipstershop.ShipOrderRequest + (*ShipOrderResponse)(nil), // 16: hipstershop.ShipOrderResponse + (*Address)(nil), // 17: hipstershop.Address + (*Money)(nil), // 18: hipstershop.Money + (*GetSupportedCurrenciesResponse)(nil), // 19: hipstershop.GetSupportedCurrenciesResponse + (*CurrencyConversionRequest)(nil), // 20: hipstershop.CurrencyConversionRequest + (*CreditCardInfo)(nil), // 21: hipstershop.CreditCardInfo + (*ChargeRequest)(nil), // 22: hipstershop.ChargeRequest + (*ChargeResponse)(nil), // 23: hipstershop.ChargeResponse + (*OrderItem)(nil), // 24: hipstershop.OrderItem + (*OrderResult)(nil), // 25: hipstershop.OrderResult + (*SendOrderConfirmationRequest)(nil), // 26: hipstershop.SendOrderConfirmationRequest + (*PlaceOrderRequest)(nil), // 27: hipstershop.PlaceOrderRequest + (*PlaceOrderResponse)(nil), // 28: hipstershop.PlaceOrderResponse + (*AdRequest)(nil), // 29: hipstershop.AdRequest + (*AdResponse)(nil), // 30: hipstershop.AdResponse + (*Ad)(nil), // 31: hipstershop.Ad +} +var file_demo_proto_depIdxs = []int32{ + 0, // 0: hipstershop.AddItemRequest.item:type_name -> hipstershop.CartItem + 0, // 1: hipstershop.Cart.items:type_name -> hipstershop.CartItem + 18, // 2: hipstershop.Product.price_usd:type_name -> hipstershop.Money + 8, // 3: hipstershop.ListProductsResponse.products:type_name -> hipstershop.Product + 8, // 4: hipstershop.SearchProductsResponse.results:type_name -> hipstershop.Product + 17, // 5: hipstershop.GetQuoteRequest.address:type_name -> hipstershop.Address + 0, // 6: hipstershop.GetQuoteRequest.items:type_name -> hipstershop.CartItem + 18, // 7: hipstershop.GetQuoteResponse.cost_usd:type_name -> hipstershop.Money + 17, // 8: hipstershop.ShipOrderRequest.address:type_name -> hipstershop.Address + 0, // 9: hipstershop.ShipOrderRequest.items:type_name -> hipstershop.CartItem + 18, // 10: hipstershop.CurrencyConversionRequest.from:type_name -> hipstershop.Money + 18, // 11: hipstershop.ChargeRequest.amount:type_name -> hipstershop.Money + 21, // 12: hipstershop.ChargeRequest.credit_card:type_name -> hipstershop.CreditCardInfo + 0, // 13: hipstershop.OrderItem.item:type_name -> hipstershop.CartItem + 18, // 14: hipstershop.OrderItem.cost:type_name -> hipstershop.Money + 18, // 15: hipstershop.OrderResult.shipping_cost:type_name -> hipstershop.Money + 17, // 16: hipstershop.OrderResult.shipping_address:type_name -> hipstershop.Address + 24, // 17: hipstershop.OrderResult.items:type_name -> hipstershop.OrderItem + 25, // 18: hipstershop.SendOrderConfirmationRequest.order:type_name -> hipstershop.OrderResult + 17, // 19: hipstershop.PlaceOrderRequest.address:type_name -> hipstershop.Address + 21, // 20: hipstershop.PlaceOrderRequest.credit_card:type_name -> hipstershop.CreditCardInfo + 25, // 21: hipstershop.PlaceOrderResponse.order:type_name -> hipstershop.OrderResult + 31, // 22: hipstershop.AdResponse.ads:type_name -> hipstershop.Ad + 1, // 23: hipstershop.CartService.AddItem:input_type -> hipstershop.AddItemRequest + 3, // 24: hipstershop.CartService.GetCart:input_type -> hipstershop.GetCartRequest + 2, // 25: hipstershop.CartService.EmptyCart:input_type -> hipstershop.EmptyCartRequest + 6, // 26: hipstershop.RecommendationService.ListRecommendations:input_type -> hipstershop.ListRecommendationsRequest + 5, // 27: hipstershop.ProductCatalogService.ListProducts:input_type -> hipstershop.Empty + 10, // 28: hipstershop.ProductCatalogService.GetProduct:input_type -> hipstershop.GetProductRequest + 11, // 29: hipstershop.ProductCatalogService.SearchProducts:input_type -> hipstershop.SearchProductsRequest + 13, // 30: hipstershop.ShippingService.GetQuote:input_type -> hipstershop.GetQuoteRequest + 15, // 31: hipstershop.ShippingService.ShipOrder:input_type -> hipstershop.ShipOrderRequest + 5, // 32: hipstershop.CurrencyService.GetSupportedCurrencies:input_type -> hipstershop.Empty + 20, // 33: hipstershop.CurrencyService.Convert:input_type -> hipstershop.CurrencyConversionRequest + 22, // 34: hipstershop.PaymentService.Charge:input_type -> hipstershop.ChargeRequest + 26, // 35: hipstershop.EmailService.SendOrderConfirmation:input_type -> hipstershop.SendOrderConfirmationRequest + 27, // 36: hipstershop.CheckoutService.PlaceOrder:input_type -> hipstershop.PlaceOrderRequest + 29, // 37: hipstershop.AdService.GetAds:input_type -> hipstershop.AdRequest + 5, // 38: hipstershop.CartService.AddItem:output_type -> hipstershop.Empty + 4, // 39: hipstershop.CartService.GetCart:output_type -> hipstershop.Cart + 5, // 40: hipstershop.CartService.EmptyCart:output_type -> hipstershop.Empty + 7, // 41: hipstershop.RecommendationService.ListRecommendations:output_type -> hipstershop.ListRecommendationsResponse + 9, // 42: hipstershop.ProductCatalogService.ListProducts:output_type -> hipstershop.ListProductsResponse + 8, // 43: hipstershop.ProductCatalogService.GetProduct:output_type -> hipstershop.Product + 12, // 44: hipstershop.ProductCatalogService.SearchProducts:output_type -> hipstershop.SearchProductsResponse + 14, // 45: hipstershop.ShippingService.GetQuote:output_type -> hipstershop.GetQuoteResponse + 16, // 46: hipstershop.ShippingService.ShipOrder:output_type -> hipstershop.ShipOrderResponse + 19, // 47: hipstershop.CurrencyService.GetSupportedCurrencies:output_type -> hipstershop.GetSupportedCurrenciesResponse + 18, // 48: hipstershop.CurrencyService.Convert:output_type -> hipstershop.Money + 23, // 49: hipstershop.PaymentService.Charge:output_type -> hipstershop.ChargeResponse + 5, // 50: hipstershop.EmailService.SendOrderConfirmation:output_type -> hipstershop.Empty + 28, // 51: hipstershop.CheckoutService.PlaceOrder:output_type -> hipstershop.PlaceOrderResponse + 30, // 52: hipstershop.AdService.GetAds:output_type -> hipstershop.AdResponse + 38, // [38:53] is the sub-list for method output_type + 23, // [23:38] is the sub-list for method input_type + 23, // [23:23] is the sub-list for extension type_name + 23, // [23:23] is the sub-list for extension extendee + 0, // [0:23] is the sub-list for field type_name +} + +func init() { file_demo_proto_init() } +func file_demo_proto_init() { + if File_demo_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_demo_proto_msgTypes[0].Exporter = func(v any, i int) any { + switch v := v.(*CartItem); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_demo_proto_msgTypes[1].Exporter = func(v any, i int) any { + switch v := v.(*AddItemRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_demo_proto_msgTypes[2].Exporter = func(v any, i int) any { + switch v := v.(*EmptyCartRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_demo_proto_msgTypes[3].Exporter = func(v any, i int) any { + switch v := v.(*GetCartRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_demo_proto_msgTypes[4].Exporter = func(v any, i int) any { + switch v := v.(*Cart); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_demo_proto_msgTypes[5].Exporter = func(v any, i int) any { + switch v := v.(*Empty); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_demo_proto_msgTypes[6].Exporter = func(v any, i int) any { + switch v := v.(*ListRecommendationsRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_demo_proto_msgTypes[7].Exporter = func(v any, i int) any { + switch v := v.(*ListRecommendationsResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_demo_proto_msgTypes[8].Exporter = func(v any, i int) any { + switch v := v.(*Product); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_demo_proto_msgTypes[9].Exporter = func(v any, i int) any { + switch v := v.(*ListProductsResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_demo_proto_msgTypes[10].Exporter = func(v any, i int) any { + switch v := v.(*GetProductRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_demo_proto_msgTypes[11].Exporter = func(v any, i int) any { + switch v := v.(*SearchProductsRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_demo_proto_msgTypes[12].Exporter = func(v any, i int) any { + switch v := v.(*SearchProductsResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_demo_proto_msgTypes[13].Exporter = func(v any, i int) any { + switch v := v.(*GetQuoteRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_demo_proto_msgTypes[14].Exporter = func(v any, i int) any { + switch v := v.(*GetQuoteResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_demo_proto_msgTypes[15].Exporter = func(v any, i int) any { + switch v := v.(*ShipOrderRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_demo_proto_msgTypes[16].Exporter = func(v any, i int) any { + switch v := v.(*ShipOrderResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_demo_proto_msgTypes[17].Exporter = func(v any, i int) any { + switch v := v.(*Address); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_demo_proto_msgTypes[18].Exporter = func(v any, i int) any { + switch v := v.(*Money); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_demo_proto_msgTypes[19].Exporter = func(v any, i int) any { + switch v := v.(*GetSupportedCurrenciesResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_demo_proto_msgTypes[20].Exporter = func(v any, i int) any { + switch v := v.(*CurrencyConversionRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_demo_proto_msgTypes[21].Exporter = func(v any, i int) any { + switch v := v.(*CreditCardInfo); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_demo_proto_msgTypes[22].Exporter = func(v any, i int) any { + switch v := v.(*ChargeRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_demo_proto_msgTypes[23].Exporter = func(v any, i int) any { + switch v := v.(*ChargeResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_demo_proto_msgTypes[24].Exporter = func(v any, i int) any { + switch v := v.(*OrderItem); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_demo_proto_msgTypes[25].Exporter = func(v any, i int) any { + switch v := v.(*OrderResult); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_demo_proto_msgTypes[26].Exporter = func(v any, i int) any { + switch v := v.(*SendOrderConfirmationRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_demo_proto_msgTypes[27].Exporter = func(v any, i int) any { + switch v := v.(*PlaceOrderRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_demo_proto_msgTypes[28].Exporter = func(v any, i int) any { + switch v := v.(*PlaceOrderResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_demo_proto_msgTypes[29].Exporter = func(v any, i int) any { + switch v := v.(*AdRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_demo_proto_msgTypes[30].Exporter = func(v any, i int) any { + switch v := v.(*AdResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_demo_proto_msgTypes[31].Exporter = func(v any, i int) any { + switch v := v.(*Ad); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_demo_proto_rawDesc, + NumEnums: 0, + NumMessages: 32, + NumExtensions: 0, + NumServices: 9, + }, + GoTypes: file_demo_proto_goTypes, + DependencyIndexes: file_demo_proto_depIdxs, + MessageInfos: file_demo_proto_msgTypes, + }.Build() + File_demo_proto = out.File + file_demo_proto_rawDesc = nil + file_demo_proto_goTypes = nil + file_demo_proto_depIdxs = nil +} diff --git a/src/productcatalogservice/genproto/demo_grpc.pb.go b/src/productcatalogservice/genproto/demo_grpc.pb.go new file mode 100644 index 0000000..e99e6d6 --- /dev/null +++ b/src/productcatalogservice/genproto/demo_grpc.pb.go @@ -0,0 +1,1179 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.5.1 +// - protoc v3.6.1 +// source: demo.proto + +package hipstershop + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.64.0 or later. +const _ = grpc.SupportPackageIsVersion9 + +const ( + CartService_AddItem_FullMethodName = "/hipstershop.CartService/AddItem" + CartService_GetCart_FullMethodName = "/hipstershop.CartService/GetCart" + CartService_EmptyCart_FullMethodName = "/hipstershop.CartService/EmptyCart" +) + +// CartServiceClient is the client API for CartService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type CartServiceClient interface { + AddItem(ctx context.Context, in *AddItemRequest, opts ...grpc.CallOption) (*Empty, error) + GetCart(ctx context.Context, in *GetCartRequest, opts ...grpc.CallOption) (*Cart, error) + EmptyCart(ctx context.Context, in *EmptyCartRequest, opts ...grpc.CallOption) (*Empty, error) +} + +type cartServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewCartServiceClient(cc grpc.ClientConnInterface) CartServiceClient { + return &cartServiceClient{cc} +} + +func (c *cartServiceClient) AddItem(ctx context.Context, in *AddItemRequest, opts ...grpc.CallOption) (*Empty, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(Empty) + err := c.cc.Invoke(ctx, CartService_AddItem_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *cartServiceClient) GetCart(ctx context.Context, in *GetCartRequest, opts ...grpc.CallOption) (*Cart, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(Cart) + err := c.cc.Invoke(ctx, CartService_GetCart_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *cartServiceClient) EmptyCart(ctx context.Context, in *EmptyCartRequest, opts ...grpc.CallOption) (*Empty, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(Empty) + err := c.cc.Invoke(ctx, CartService_EmptyCart_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +// CartServiceServer is the server API for CartService service. +// All implementations must embed UnimplementedCartServiceServer +// for forward compatibility. +type CartServiceServer interface { + AddItem(context.Context, *AddItemRequest) (*Empty, error) + GetCart(context.Context, *GetCartRequest) (*Cart, error) + EmptyCart(context.Context, *EmptyCartRequest) (*Empty, error) + mustEmbedUnimplementedCartServiceServer() +} + +// UnimplementedCartServiceServer must be embedded to have +// forward compatible implementations. +// +// NOTE: this should be embedded by value instead of pointer to avoid a nil +// pointer dereference when methods are called. +type UnimplementedCartServiceServer struct{} + +func (UnimplementedCartServiceServer) AddItem(context.Context, *AddItemRequest) (*Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method AddItem not implemented") +} +func (UnimplementedCartServiceServer) GetCart(context.Context, *GetCartRequest) (*Cart, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetCart not implemented") +} +func (UnimplementedCartServiceServer) EmptyCart(context.Context, *EmptyCartRequest) (*Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method EmptyCart not implemented") +} +func (UnimplementedCartServiceServer) mustEmbedUnimplementedCartServiceServer() {} +func (UnimplementedCartServiceServer) testEmbeddedByValue() {} + +// UnsafeCartServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to CartServiceServer will +// result in compilation errors. +type UnsafeCartServiceServer interface { + mustEmbedUnimplementedCartServiceServer() +} + +func RegisterCartServiceServer(s grpc.ServiceRegistrar, srv CartServiceServer) { + // If the following call pancis, it indicates UnimplementedCartServiceServer was + // embedded by pointer and is nil. This will cause panics if an + // unimplemented method is ever invoked, so we test this at initialization + // time to prevent it from happening at runtime later due to I/O. + if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { + t.testEmbeddedByValue() + } + s.RegisterService(&CartService_ServiceDesc, srv) +} + +func _CartService_AddItem_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(AddItemRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(CartServiceServer).AddItem(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: CartService_AddItem_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(CartServiceServer).AddItem(ctx, req.(*AddItemRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _CartService_GetCart_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetCartRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(CartServiceServer).GetCart(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: CartService_GetCart_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(CartServiceServer).GetCart(ctx, req.(*GetCartRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _CartService_EmptyCart_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(EmptyCartRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(CartServiceServer).EmptyCart(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: CartService_EmptyCart_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(CartServiceServer).EmptyCart(ctx, req.(*EmptyCartRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// CartService_ServiceDesc is the grpc.ServiceDesc for CartService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var CartService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "hipstershop.CartService", + HandlerType: (*CartServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "AddItem", + Handler: _CartService_AddItem_Handler, + }, + { + MethodName: "GetCart", + Handler: _CartService_GetCart_Handler, + }, + { + MethodName: "EmptyCart", + Handler: _CartService_EmptyCart_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "demo.proto", +} + +const ( + RecommendationService_ListRecommendations_FullMethodName = "/hipstershop.RecommendationService/ListRecommendations" +) + +// RecommendationServiceClient is the client API for RecommendationService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type RecommendationServiceClient interface { + ListRecommendations(ctx context.Context, in *ListRecommendationsRequest, opts ...grpc.CallOption) (*ListRecommendationsResponse, error) +} + +type recommendationServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewRecommendationServiceClient(cc grpc.ClientConnInterface) RecommendationServiceClient { + return &recommendationServiceClient{cc} +} + +func (c *recommendationServiceClient) ListRecommendations(ctx context.Context, in *ListRecommendationsRequest, opts ...grpc.CallOption) (*ListRecommendationsResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ListRecommendationsResponse) + err := c.cc.Invoke(ctx, RecommendationService_ListRecommendations_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +// RecommendationServiceServer is the server API for RecommendationService service. +// All implementations must embed UnimplementedRecommendationServiceServer +// for forward compatibility. +type RecommendationServiceServer interface { + ListRecommendations(context.Context, *ListRecommendationsRequest) (*ListRecommendationsResponse, error) + mustEmbedUnimplementedRecommendationServiceServer() +} + +// UnimplementedRecommendationServiceServer must be embedded to have +// forward compatible implementations. +// +// NOTE: this should be embedded by value instead of pointer to avoid a nil +// pointer dereference when methods are called. +type UnimplementedRecommendationServiceServer struct{} + +func (UnimplementedRecommendationServiceServer) ListRecommendations(context.Context, *ListRecommendationsRequest) (*ListRecommendationsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method ListRecommendations not implemented") +} +func (UnimplementedRecommendationServiceServer) mustEmbedUnimplementedRecommendationServiceServer() {} +func (UnimplementedRecommendationServiceServer) testEmbeddedByValue() {} + +// UnsafeRecommendationServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to RecommendationServiceServer will +// result in compilation errors. +type UnsafeRecommendationServiceServer interface { + mustEmbedUnimplementedRecommendationServiceServer() +} + +func RegisterRecommendationServiceServer(s grpc.ServiceRegistrar, srv RecommendationServiceServer) { + // If the following call pancis, it indicates UnimplementedRecommendationServiceServer was + // embedded by pointer and is nil. This will cause panics if an + // unimplemented method is ever invoked, so we test this at initialization + // time to prevent it from happening at runtime later due to I/O. + if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { + t.testEmbeddedByValue() + } + s.RegisterService(&RecommendationService_ServiceDesc, srv) +} + +func _RecommendationService_ListRecommendations_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ListRecommendationsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(RecommendationServiceServer).ListRecommendations(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: RecommendationService_ListRecommendations_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(RecommendationServiceServer).ListRecommendations(ctx, req.(*ListRecommendationsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// RecommendationService_ServiceDesc is the grpc.ServiceDesc for RecommendationService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var RecommendationService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "hipstershop.RecommendationService", + HandlerType: (*RecommendationServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "ListRecommendations", + Handler: _RecommendationService_ListRecommendations_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "demo.proto", +} + +const ( + ProductCatalogService_ListProducts_FullMethodName = "/hipstershop.ProductCatalogService/ListProducts" + ProductCatalogService_GetProduct_FullMethodName = "/hipstershop.ProductCatalogService/GetProduct" + ProductCatalogService_SearchProducts_FullMethodName = "/hipstershop.ProductCatalogService/SearchProducts" +) + +// ProductCatalogServiceClient is the client API for ProductCatalogService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type ProductCatalogServiceClient interface { + ListProducts(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*ListProductsResponse, error) + GetProduct(ctx context.Context, in *GetProductRequest, opts ...grpc.CallOption) (*Product, error) + SearchProducts(ctx context.Context, in *SearchProductsRequest, opts ...grpc.CallOption) (*SearchProductsResponse, error) +} + +type productCatalogServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewProductCatalogServiceClient(cc grpc.ClientConnInterface) ProductCatalogServiceClient { + return &productCatalogServiceClient{cc} +} + +func (c *productCatalogServiceClient) ListProducts(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*ListProductsResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ListProductsResponse) + err := c.cc.Invoke(ctx, ProductCatalogService_ListProducts_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *productCatalogServiceClient) GetProduct(ctx context.Context, in *GetProductRequest, opts ...grpc.CallOption) (*Product, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(Product) + err := c.cc.Invoke(ctx, ProductCatalogService_GetProduct_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *productCatalogServiceClient) SearchProducts(ctx context.Context, in *SearchProductsRequest, opts ...grpc.CallOption) (*SearchProductsResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(SearchProductsResponse) + err := c.cc.Invoke(ctx, ProductCatalogService_SearchProducts_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +// ProductCatalogServiceServer is the server API for ProductCatalogService service. +// All implementations must embed UnimplementedProductCatalogServiceServer +// for forward compatibility. +type ProductCatalogServiceServer interface { + ListProducts(context.Context, *Empty) (*ListProductsResponse, error) + GetProduct(context.Context, *GetProductRequest) (*Product, error) + SearchProducts(context.Context, *SearchProductsRequest) (*SearchProductsResponse, error) + mustEmbedUnimplementedProductCatalogServiceServer() +} + +// UnimplementedProductCatalogServiceServer must be embedded to have +// forward compatible implementations. +// +// NOTE: this should be embedded by value instead of pointer to avoid a nil +// pointer dereference when methods are called. +type UnimplementedProductCatalogServiceServer struct{} + +func (UnimplementedProductCatalogServiceServer) ListProducts(context.Context, *Empty) (*ListProductsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method ListProducts not implemented") +} +func (UnimplementedProductCatalogServiceServer) GetProduct(context.Context, *GetProductRequest) (*Product, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetProduct not implemented") +} +func (UnimplementedProductCatalogServiceServer) SearchProducts(context.Context, *SearchProductsRequest) (*SearchProductsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method SearchProducts not implemented") +} +func (UnimplementedProductCatalogServiceServer) mustEmbedUnimplementedProductCatalogServiceServer() {} +func (UnimplementedProductCatalogServiceServer) testEmbeddedByValue() {} + +// UnsafeProductCatalogServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to ProductCatalogServiceServer will +// result in compilation errors. +type UnsafeProductCatalogServiceServer interface { + mustEmbedUnimplementedProductCatalogServiceServer() +} + +func RegisterProductCatalogServiceServer(s grpc.ServiceRegistrar, srv ProductCatalogServiceServer) { + // If the following call pancis, it indicates UnimplementedProductCatalogServiceServer was + // embedded by pointer and is nil. This will cause panics if an + // unimplemented method is ever invoked, so we test this at initialization + // time to prevent it from happening at runtime later due to I/O. + if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { + t.testEmbeddedByValue() + } + s.RegisterService(&ProductCatalogService_ServiceDesc, srv) +} + +func _ProductCatalogService_ListProducts_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(Empty) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ProductCatalogServiceServer).ListProducts(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: ProductCatalogService_ListProducts_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ProductCatalogServiceServer).ListProducts(ctx, req.(*Empty)) + } + return interceptor(ctx, in, info, handler) +} + +func _ProductCatalogService_GetProduct_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetProductRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ProductCatalogServiceServer).GetProduct(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: ProductCatalogService_GetProduct_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ProductCatalogServiceServer).GetProduct(ctx, req.(*GetProductRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _ProductCatalogService_SearchProducts_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(SearchProductsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ProductCatalogServiceServer).SearchProducts(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: ProductCatalogService_SearchProducts_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ProductCatalogServiceServer).SearchProducts(ctx, req.(*SearchProductsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// ProductCatalogService_ServiceDesc is the grpc.ServiceDesc for ProductCatalogService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var ProductCatalogService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "hipstershop.ProductCatalogService", + HandlerType: (*ProductCatalogServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "ListProducts", + Handler: _ProductCatalogService_ListProducts_Handler, + }, + { + MethodName: "GetProduct", + Handler: _ProductCatalogService_GetProduct_Handler, + }, + { + MethodName: "SearchProducts", + Handler: _ProductCatalogService_SearchProducts_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "demo.proto", +} + +const ( + ShippingService_GetQuote_FullMethodName = "/hipstershop.ShippingService/GetQuote" + ShippingService_ShipOrder_FullMethodName = "/hipstershop.ShippingService/ShipOrder" +) + +// ShippingServiceClient is the client API for ShippingService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type ShippingServiceClient interface { + GetQuote(ctx context.Context, in *GetQuoteRequest, opts ...grpc.CallOption) (*GetQuoteResponse, error) + ShipOrder(ctx context.Context, in *ShipOrderRequest, opts ...grpc.CallOption) (*ShipOrderResponse, error) +} + +type shippingServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewShippingServiceClient(cc grpc.ClientConnInterface) ShippingServiceClient { + return &shippingServiceClient{cc} +} + +func (c *shippingServiceClient) GetQuote(ctx context.Context, in *GetQuoteRequest, opts ...grpc.CallOption) (*GetQuoteResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(GetQuoteResponse) + err := c.cc.Invoke(ctx, ShippingService_GetQuote_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *shippingServiceClient) ShipOrder(ctx context.Context, in *ShipOrderRequest, opts ...grpc.CallOption) (*ShipOrderResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ShipOrderResponse) + err := c.cc.Invoke(ctx, ShippingService_ShipOrder_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +// ShippingServiceServer is the server API for ShippingService service. +// All implementations must embed UnimplementedShippingServiceServer +// for forward compatibility. +type ShippingServiceServer interface { + GetQuote(context.Context, *GetQuoteRequest) (*GetQuoteResponse, error) + ShipOrder(context.Context, *ShipOrderRequest) (*ShipOrderResponse, error) + mustEmbedUnimplementedShippingServiceServer() +} + +// UnimplementedShippingServiceServer must be embedded to have +// forward compatible implementations. +// +// NOTE: this should be embedded by value instead of pointer to avoid a nil +// pointer dereference when methods are called. +type UnimplementedShippingServiceServer struct{} + +func (UnimplementedShippingServiceServer) GetQuote(context.Context, *GetQuoteRequest) (*GetQuoteResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetQuote not implemented") +} +func (UnimplementedShippingServiceServer) ShipOrder(context.Context, *ShipOrderRequest) (*ShipOrderResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method ShipOrder not implemented") +} +func (UnimplementedShippingServiceServer) mustEmbedUnimplementedShippingServiceServer() {} +func (UnimplementedShippingServiceServer) testEmbeddedByValue() {} + +// UnsafeShippingServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to ShippingServiceServer will +// result in compilation errors. +type UnsafeShippingServiceServer interface { + mustEmbedUnimplementedShippingServiceServer() +} + +func RegisterShippingServiceServer(s grpc.ServiceRegistrar, srv ShippingServiceServer) { + // If the following call pancis, it indicates UnimplementedShippingServiceServer was + // embedded by pointer and is nil. This will cause panics if an + // unimplemented method is ever invoked, so we test this at initialization + // time to prevent it from happening at runtime later due to I/O. + if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { + t.testEmbeddedByValue() + } + s.RegisterService(&ShippingService_ServiceDesc, srv) +} + +func _ShippingService_GetQuote_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetQuoteRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ShippingServiceServer).GetQuote(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: ShippingService_GetQuote_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ShippingServiceServer).GetQuote(ctx, req.(*GetQuoteRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _ShippingService_ShipOrder_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ShipOrderRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ShippingServiceServer).ShipOrder(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: ShippingService_ShipOrder_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ShippingServiceServer).ShipOrder(ctx, req.(*ShipOrderRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// ShippingService_ServiceDesc is the grpc.ServiceDesc for ShippingService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var ShippingService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "hipstershop.ShippingService", + HandlerType: (*ShippingServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "GetQuote", + Handler: _ShippingService_GetQuote_Handler, + }, + { + MethodName: "ShipOrder", + Handler: _ShippingService_ShipOrder_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "demo.proto", +} + +const ( + CurrencyService_GetSupportedCurrencies_FullMethodName = "/hipstershop.CurrencyService/GetSupportedCurrencies" + CurrencyService_Convert_FullMethodName = "/hipstershop.CurrencyService/Convert" +) + +// CurrencyServiceClient is the client API for CurrencyService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type CurrencyServiceClient interface { + GetSupportedCurrencies(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*GetSupportedCurrenciesResponse, error) + Convert(ctx context.Context, in *CurrencyConversionRequest, opts ...grpc.CallOption) (*Money, error) +} + +type currencyServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewCurrencyServiceClient(cc grpc.ClientConnInterface) CurrencyServiceClient { + return ¤cyServiceClient{cc} +} + +func (c *currencyServiceClient) GetSupportedCurrencies(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*GetSupportedCurrenciesResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(GetSupportedCurrenciesResponse) + err := c.cc.Invoke(ctx, CurrencyService_GetSupportedCurrencies_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *currencyServiceClient) Convert(ctx context.Context, in *CurrencyConversionRequest, opts ...grpc.CallOption) (*Money, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(Money) + err := c.cc.Invoke(ctx, CurrencyService_Convert_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +// CurrencyServiceServer is the server API for CurrencyService service. +// All implementations must embed UnimplementedCurrencyServiceServer +// for forward compatibility. +type CurrencyServiceServer interface { + GetSupportedCurrencies(context.Context, *Empty) (*GetSupportedCurrenciesResponse, error) + Convert(context.Context, *CurrencyConversionRequest) (*Money, error) + mustEmbedUnimplementedCurrencyServiceServer() +} + +// UnimplementedCurrencyServiceServer must be embedded to have +// forward compatible implementations. +// +// NOTE: this should be embedded by value instead of pointer to avoid a nil +// pointer dereference when methods are called. +type UnimplementedCurrencyServiceServer struct{} + +func (UnimplementedCurrencyServiceServer) GetSupportedCurrencies(context.Context, *Empty) (*GetSupportedCurrenciesResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetSupportedCurrencies not implemented") +} +func (UnimplementedCurrencyServiceServer) Convert(context.Context, *CurrencyConversionRequest) (*Money, error) { + return nil, status.Errorf(codes.Unimplemented, "method Convert not implemented") +} +func (UnimplementedCurrencyServiceServer) mustEmbedUnimplementedCurrencyServiceServer() {} +func (UnimplementedCurrencyServiceServer) testEmbeddedByValue() {} + +// UnsafeCurrencyServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to CurrencyServiceServer will +// result in compilation errors. +type UnsafeCurrencyServiceServer interface { + mustEmbedUnimplementedCurrencyServiceServer() +} + +func RegisterCurrencyServiceServer(s grpc.ServiceRegistrar, srv CurrencyServiceServer) { + // If the following call pancis, it indicates UnimplementedCurrencyServiceServer was + // embedded by pointer and is nil. This will cause panics if an + // unimplemented method is ever invoked, so we test this at initialization + // time to prevent it from happening at runtime later due to I/O. + if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { + t.testEmbeddedByValue() + } + s.RegisterService(&CurrencyService_ServiceDesc, srv) +} + +func _CurrencyService_GetSupportedCurrencies_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(Empty) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(CurrencyServiceServer).GetSupportedCurrencies(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: CurrencyService_GetSupportedCurrencies_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(CurrencyServiceServer).GetSupportedCurrencies(ctx, req.(*Empty)) + } + return interceptor(ctx, in, info, handler) +} + +func _CurrencyService_Convert_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(CurrencyConversionRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(CurrencyServiceServer).Convert(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: CurrencyService_Convert_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(CurrencyServiceServer).Convert(ctx, req.(*CurrencyConversionRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// CurrencyService_ServiceDesc is the grpc.ServiceDesc for CurrencyService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var CurrencyService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "hipstershop.CurrencyService", + HandlerType: (*CurrencyServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "GetSupportedCurrencies", + Handler: _CurrencyService_GetSupportedCurrencies_Handler, + }, + { + MethodName: "Convert", + Handler: _CurrencyService_Convert_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "demo.proto", +} + +const ( + PaymentService_Charge_FullMethodName = "/hipstershop.PaymentService/Charge" +) + +// PaymentServiceClient is the client API for PaymentService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type PaymentServiceClient interface { + Charge(ctx context.Context, in *ChargeRequest, opts ...grpc.CallOption) (*ChargeResponse, error) +} + +type paymentServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewPaymentServiceClient(cc grpc.ClientConnInterface) PaymentServiceClient { + return &paymentServiceClient{cc} +} + +func (c *paymentServiceClient) Charge(ctx context.Context, in *ChargeRequest, opts ...grpc.CallOption) (*ChargeResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ChargeResponse) + err := c.cc.Invoke(ctx, PaymentService_Charge_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +// PaymentServiceServer is the server API for PaymentService service. +// All implementations must embed UnimplementedPaymentServiceServer +// for forward compatibility. +type PaymentServiceServer interface { + Charge(context.Context, *ChargeRequest) (*ChargeResponse, error) + mustEmbedUnimplementedPaymentServiceServer() +} + +// UnimplementedPaymentServiceServer must be embedded to have +// forward compatible implementations. +// +// NOTE: this should be embedded by value instead of pointer to avoid a nil +// pointer dereference when methods are called. +type UnimplementedPaymentServiceServer struct{} + +func (UnimplementedPaymentServiceServer) Charge(context.Context, *ChargeRequest) (*ChargeResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method Charge not implemented") +} +func (UnimplementedPaymentServiceServer) mustEmbedUnimplementedPaymentServiceServer() {} +func (UnimplementedPaymentServiceServer) testEmbeddedByValue() {} + +// UnsafePaymentServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to PaymentServiceServer will +// result in compilation errors. +type UnsafePaymentServiceServer interface { + mustEmbedUnimplementedPaymentServiceServer() +} + +func RegisterPaymentServiceServer(s grpc.ServiceRegistrar, srv PaymentServiceServer) { + // If the following call pancis, it indicates UnimplementedPaymentServiceServer was + // embedded by pointer and is nil. This will cause panics if an + // unimplemented method is ever invoked, so we test this at initialization + // time to prevent it from happening at runtime later due to I/O. + if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { + t.testEmbeddedByValue() + } + s.RegisterService(&PaymentService_ServiceDesc, srv) +} + +func _PaymentService_Charge_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ChargeRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(PaymentServiceServer).Charge(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: PaymentService_Charge_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(PaymentServiceServer).Charge(ctx, req.(*ChargeRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// PaymentService_ServiceDesc is the grpc.ServiceDesc for PaymentService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var PaymentService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "hipstershop.PaymentService", + HandlerType: (*PaymentServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "Charge", + Handler: _PaymentService_Charge_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "demo.proto", +} + +const ( + EmailService_SendOrderConfirmation_FullMethodName = "/hipstershop.EmailService/SendOrderConfirmation" +) + +// EmailServiceClient is the client API for EmailService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type EmailServiceClient interface { + SendOrderConfirmation(ctx context.Context, in *SendOrderConfirmationRequest, opts ...grpc.CallOption) (*Empty, error) +} + +type emailServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewEmailServiceClient(cc grpc.ClientConnInterface) EmailServiceClient { + return &emailServiceClient{cc} +} + +func (c *emailServiceClient) SendOrderConfirmation(ctx context.Context, in *SendOrderConfirmationRequest, opts ...grpc.CallOption) (*Empty, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(Empty) + err := c.cc.Invoke(ctx, EmailService_SendOrderConfirmation_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +// EmailServiceServer is the server API for EmailService service. +// All implementations must embed UnimplementedEmailServiceServer +// for forward compatibility. +type EmailServiceServer interface { + SendOrderConfirmation(context.Context, *SendOrderConfirmationRequest) (*Empty, error) + mustEmbedUnimplementedEmailServiceServer() +} + +// UnimplementedEmailServiceServer must be embedded to have +// forward compatible implementations. +// +// NOTE: this should be embedded by value instead of pointer to avoid a nil +// pointer dereference when methods are called. +type UnimplementedEmailServiceServer struct{} + +func (UnimplementedEmailServiceServer) SendOrderConfirmation(context.Context, *SendOrderConfirmationRequest) (*Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method SendOrderConfirmation not implemented") +} +func (UnimplementedEmailServiceServer) mustEmbedUnimplementedEmailServiceServer() {} +func (UnimplementedEmailServiceServer) testEmbeddedByValue() {} + +// UnsafeEmailServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to EmailServiceServer will +// result in compilation errors. +type UnsafeEmailServiceServer interface { + mustEmbedUnimplementedEmailServiceServer() +} + +func RegisterEmailServiceServer(s grpc.ServiceRegistrar, srv EmailServiceServer) { + // If the following call pancis, it indicates UnimplementedEmailServiceServer was + // embedded by pointer and is nil. This will cause panics if an + // unimplemented method is ever invoked, so we test this at initialization + // time to prevent it from happening at runtime later due to I/O. + if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { + t.testEmbeddedByValue() + } + s.RegisterService(&EmailService_ServiceDesc, srv) +} + +func _EmailService_SendOrderConfirmation_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(SendOrderConfirmationRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(EmailServiceServer).SendOrderConfirmation(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: EmailService_SendOrderConfirmation_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(EmailServiceServer).SendOrderConfirmation(ctx, req.(*SendOrderConfirmationRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// EmailService_ServiceDesc is the grpc.ServiceDesc for EmailService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var EmailService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "hipstershop.EmailService", + HandlerType: (*EmailServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "SendOrderConfirmation", + Handler: _EmailService_SendOrderConfirmation_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "demo.proto", +} + +const ( + CheckoutService_PlaceOrder_FullMethodName = "/hipstershop.CheckoutService/PlaceOrder" +) + +// CheckoutServiceClient is the client API for CheckoutService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type CheckoutServiceClient interface { + PlaceOrder(ctx context.Context, in *PlaceOrderRequest, opts ...grpc.CallOption) (*PlaceOrderResponse, error) +} + +type checkoutServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewCheckoutServiceClient(cc grpc.ClientConnInterface) CheckoutServiceClient { + return &checkoutServiceClient{cc} +} + +func (c *checkoutServiceClient) PlaceOrder(ctx context.Context, in *PlaceOrderRequest, opts ...grpc.CallOption) (*PlaceOrderResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(PlaceOrderResponse) + err := c.cc.Invoke(ctx, CheckoutService_PlaceOrder_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +// CheckoutServiceServer is the server API for CheckoutService service. +// All implementations must embed UnimplementedCheckoutServiceServer +// for forward compatibility. +type CheckoutServiceServer interface { + PlaceOrder(context.Context, *PlaceOrderRequest) (*PlaceOrderResponse, error) + mustEmbedUnimplementedCheckoutServiceServer() +} + +// UnimplementedCheckoutServiceServer must be embedded to have +// forward compatible implementations. +// +// NOTE: this should be embedded by value instead of pointer to avoid a nil +// pointer dereference when methods are called. +type UnimplementedCheckoutServiceServer struct{} + +func (UnimplementedCheckoutServiceServer) PlaceOrder(context.Context, *PlaceOrderRequest) (*PlaceOrderResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method PlaceOrder not implemented") +} +func (UnimplementedCheckoutServiceServer) mustEmbedUnimplementedCheckoutServiceServer() {} +func (UnimplementedCheckoutServiceServer) testEmbeddedByValue() {} + +// UnsafeCheckoutServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to CheckoutServiceServer will +// result in compilation errors. +type UnsafeCheckoutServiceServer interface { + mustEmbedUnimplementedCheckoutServiceServer() +} + +func RegisterCheckoutServiceServer(s grpc.ServiceRegistrar, srv CheckoutServiceServer) { + // If the following call pancis, it indicates UnimplementedCheckoutServiceServer was + // embedded by pointer and is nil. This will cause panics if an + // unimplemented method is ever invoked, so we test this at initialization + // time to prevent it from happening at runtime later due to I/O. + if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { + t.testEmbeddedByValue() + } + s.RegisterService(&CheckoutService_ServiceDesc, srv) +} + +func _CheckoutService_PlaceOrder_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(PlaceOrderRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(CheckoutServiceServer).PlaceOrder(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: CheckoutService_PlaceOrder_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(CheckoutServiceServer).PlaceOrder(ctx, req.(*PlaceOrderRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// CheckoutService_ServiceDesc is the grpc.ServiceDesc for CheckoutService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var CheckoutService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "hipstershop.CheckoutService", + HandlerType: (*CheckoutServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "PlaceOrder", + Handler: _CheckoutService_PlaceOrder_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "demo.proto", +} + +const ( + AdService_GetAds_FullMethodName = "/hipstershop.AdService/GetAds" +) + +// AdServiceClient is the client API for AdService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type AdServiceClient interface { + GetAds(ctx context.Context, in *AdRequest, opts ...grpc.CallOption) (*AdResponse, error) +} + +type adServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewAdServiceClient(cc grpc.ClientConnInterface) AdServiceClient { + return &adServiceClient{cc} +} + +func (c *adServiceClient) GetAds(ctx context.Context, in *AdRequest, opts ...grpc.CallOption) (*AdResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(AdResponse) + err := c.cc.Invoke(ctx, AdService_GetAds_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +// AdServiceServer is the server API for AdService service. +// All implementations must embed UnimplementedAdServiceServer +// for forward compatibility. +type AdServiceServer interface { + GetAds(context.Context, *AdRequest) (*AdResponse, error) + mustEmbedUnimplementedAdServiceServer() +} + +// UnimplementedAdServiceServer must be embedded to have +// forward compatible implementations. +// +// NOTE: this should be embedded by value instead of pointer to avoid a nil +// pointer dereference when methods are called. +type UnimplementedAdServiceServer struct{} + +func (UnimplementedAdServiceServer) GetAds(context.Context, *AdRequest) (*AdResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetAds not implemented") +} +func (UnimplementedAdServiceServer) mustEmbedUnimplementedAdServiceServer() {} +func (UnimplementedAdServiceServer) testEmbeddedByValue() {} + +// UnsafeAdServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to AdServiceServer will +// result in compilation errors. +type UnsafeAdServiceServer interface { + mustEmbedUnimplementedAdServiceServer() +} + +func RegisterAdServiceServer(s grpc.ServiceRegistrar, srv AdServiceServer) { + // If the following call pancis, it indicates UnimplementedAdServiceServer was + // embedded by pointer and is nil. This will cause panics if an + // unimplemented method is ever invoked, so we test this at initialization + // time to prevent it from happening at runtime later due to I/O. + if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { + t.testEmbeddedByValue() + } + s.RegisterService(&AdService_ServiceDesc, srv) +} + +func _AdService_GetAds_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(AdRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AdServiceServer).GetAds(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: AdService_GetAds_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AdServiceServer).GetAds(ctx, req.(*AdRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// AdService_ServiceDesc is the grpc.ServiceDesc for AdService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var AdService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "hipstershop.AdService", + HandlerType: (*AdServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "GetAds", + Handler: _AdService_GetAds_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "demo.proto", +} diff --git a/src/productcatalogservice/go.mod b/src/productcatalogservice/go.mod new file mode 100644 index 0000000..1d0f5df --- /dev/null +++ b/src/productcatalogservice/go.mod @@ -0,0 +1,68 @@ +module github.com/GoogleCloudPlatform/microservices-demo/src/productcatalogservice + +go 1.25 + +toolchain go1.25.6 + +require ( + cloud.google.com/go/alloydbconn v1.17.2 + cloud.google.com/go/profiler v0.4.3 + cloud.google.com/go/secretmanager v1.16.0 + github.com/golang/protobuf v1.5.4 + github.com/jackc/pgx/v5 v5.8.0 + github.com/pkg/errors v0.9.1 + github.com/sirupsen/logrus v1.9.4 + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.64.0 + go.opentelemetry.io/otel v1.39.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0 + go.opentelemetry.io/otel/sdk v1.39.0 + google.golang.org/grpc v1.78.0 + google.golang.org/protobuf v1.36.11 +) + +require ( + cloud.google.com/go v0.123.0 // indirect + cloud.google.com/go/alloydb v1.20.0 // indirect + cloud.google.com/go/auth v0.18.0 // indirect + cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect + cloud.google.com/go/compute/metadata v0.9.0 // indirect + cloud.google.com/go/iam v1.5.3 // indirect + cloud.google.com/go/longrunning v0.7.0 // indirect + cloud.google.com/go/monitoring v1.24.3 // indirect + github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.54.0 // indirect + github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.54.0 // indirect + github.com/cenkalti/backoff/v5 v5.0.3 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/felixge/httpsnoop v1.0.4 // indirect + github.com/go-logr/logr v1.4.3 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect + github.com/google/pprof v0.0.0-20251114195745-4902fdda35c8 // indirect + github.com/google/s2a-go v0.1.9 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/googleapis/enterprise-certificate-proxy v0.3.7 // indirect + github.com/googleapis/gax-go/v2 v2.16.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect + github.com/jackc/puddle/v2 v2.2.2 // indirect + go.opencensus.io v0.24.0 // indirect + go.opentelemetry.io/auto/sdk v1.2.1 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0 // indirect + go.opentelemetry.io/otel/metric v1.39.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.39.0 // indirect + go.opentelemetry.io/otel/trace v1.39.0 // indirect + go.opentelemetry.io/proto/otlp v1.9.0 // indirect + golang.org/x/crypto v0.47.0 // indirect + golang.org/x/net v0.49.0 // indirect + golang.org/x/oauth2 v0.34.0 // indirect + golang.org/x/sync v0.19.0 // indirect + golang.org/x/sys v0.40.0 // indirect + golang.org/x/text v0.33.0 // indirect + golang.org/x/time v0.14.0 // indirect + google.golang.org/api v0.259.0 // indirect + google.golang.org/genproto v0.0.0-20251202230838-ff82c1b0f217 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260112192933-99fd39fd28a9 // indirect +) diff --git a/src/productcatalogservice/go.sum b/src/productcatalogservice/go.sum new file mode 100644 index 0000000..fb30d98 --- /dev/null +++ b/src/productcatalogservice/go.sum @@ -0,0 +1,324 @@ +cel.dev/expr v0.24.0 h1:56OvJKSH3hDGL0ml5uSxZmz3/3Pq4tJ+fb1unVLAFcY= +cel.dev/expr v0.24.0/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw= +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.123.0 h1:2NAUJwPR47q+E35uaJeYoNhuNEM9kM8SjgRgdeOJUSE= +cloud.google.com/go v0.123.0/go.mod h1:xBoMV08QcqUGuPW65Qfm1o9Y4zKZBpGS+7bImXLTAZU= +cloud.google.com/go/alloydb v1.19.0 h1:JVcIM43+ndcjdXXvOFWkLKjhF6kREQKAEtRxpsaKIQo= +cloud.google.com/go/alloydb v1.19.0/go.mod h1:WQYERzf1+LwX5zCbmeyza4DNalaBiCN8P04POkZ/vds= +cloud.google.com/go/alloydb v1.20.0 h1:p9SbcJhdi6s39SAIpz4lpJJTkfboSQUCwDd7go0bJ6o= +cloud.google.com/go/alloydb v1.20.0/go.mod h1:ZwDNJOn6Z2uzzwDIlS6ctpM/EgMD1LqPzI+/ynJNaIU= +cloud.google.com/go/alloydbconn v1.17.0 h1:3tmudS+MI4g2jkdVyJ6EeaGHEHvBtJ+iHMsu3LJX91s= +cloud.google.com/go/alloydbconn v1.17.0/go.mod h1:XUHWgUfH5fhzZ50vIK31KBOecerzpH6NBAwEoG7fic4= +cloud.google.com/go/alloydbconn v1.17.1 h1:S1wj+0KN6x89+VnRLMZjvCiVNBH6WSj5dP/7PGZ/dzE= +cloud.google.com/go/alloydbconn v1.17.1/go.mod h1:vzlHhKPy1epUXIJSPPOq94NK1yFSXZ0t1uQlv8mC/ZA= +cloud.google.com/go/alloydbconn v1.17.2 h1:VvJ+hxcs0EHI9VnQh65D8VwpjV3hFS6GT6YMo/5oC9s= +cloud.google.com/go/alloydbconn v1.17.2/go.mod h1:VpdGJOWemdCfuNgQM7DKCAcr2H0sl+eU6V/IuEcjeuU= +cloud.google.com/go/auth v0.17.0 h1:74yCm7hCj2rUyyAocqnFzsAYXgJhrG26XCFimrc/Kz4= +cloud.google.com/go/auth v0.17.0/go.mod h1:6wv/t5/6rOPAX4fJiRjKkJCvswLwdet7G8+UGXt7nCQ= +cloud.google.com/go/auth v0.18.0 h1:wnqy5hrv7p3k7cShwAU/Br3nzod7fxoqG+k0VZ+/Pk0= +cloud.google.com/go/auth v0.18.0/go.mod h1:wwkPM1AgE1f2u6dG443MiWoD8C3BtOywNsUMcUTVDRo= +cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc= +cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c= +cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs= +cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10= +cloud.google.com/go/iam v1.5.3 h1:+vMINPiDF2ognBJ97ABAYYwRgsaqxPbQDlMnbHMjolc= +cloud.google.com/go/iam v1.5.3/go.mod h1:MR3v9oLkZCTlaqljW6Eb2d3HGDGK5/bDv93jhfISFvU= +cloud.google.com/go/logging v1.13.1 h1:O7LvmO0kGLaHY/gq8cV7T0dyp6zJhYAOtZPX4TF3QtY= +cloud.google.com/go/logging v1.13.1/go.mod h1:XAQkfkMBxQRjQek96WLPNze7vsOmay9H5PqfsNYDqvw= +cloud.google.com/go/longrunning v0.7.0 h1:FV0+SYF1RIj59gyoWDRi45GiYUMM3K1qO51qoboQT1E= +cloud.google.com/go/longrunning v0.7.0/go.mod h1:ySn2yXmjbK9Ba0zsQqunhDkYi0+9rlXIwnoAf+h+TPY= +cloud.google.com/go/monitoring v1.24.3 h1:dde+gMNc0UhPZD1Azu6at2e79bfdztVDS5lvhOdsgaE= +cloud.google.com/go/monitoring v1.24.3/go.mod h1:nYP6W0tm3N9H/bOw8am7t62YTzZY+zUeQ+Bi6+2eonI= +cloud.google.com/go/profiler v0.4.3 h1:IY3QNKlr8VbXwGWHcZbJQsMA/83ZTH6uAHf8jYyj7OI= +cloud.google.com/go/profiler v0.4.3/go.mod h1:3xFodugWfPIQZWFcXdUmfa+yTiiyQ8fWrdT+d2Sg4J0= +cloud.google.com/go/secretmanager v1.16.0 h1:19QT7ZsLJ8FSP1k+4esQvuCD7npMJml6hYzilxVyT+k= +cloud.google.com/go/secretmanager v1.16.0/go.mod h1://C/e4I8D26SDTz1f3TQcddhcmiC3rMEl0S1Cakvs3Q= +cloud.google.com/go/storage v1.56.0 h1:iixmq2Fse2tqxMbWhLWC9HfBj1qdxqAmiK8/eqtsLxI= +cloud.google.com/go/storage v1.56.0/go.mod h1:Tpuj6t4NweCLzlNbw9Z9iwxEkrSem20AetIeH/shgVU= +cloud.google.com/go/trace v1.11.7 h1:kDNDX8JkaAG3R2nq1lIdkb7FCSi1rCmsEtKVsty7p+U= +cloud.google.com/go/trace v1.11.7/go.mod h1:TNn9d5V3fQVf6s4SCveVMIBS2LJUqo73GACmq/Tky0s= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0 h1:sBEjpZlNHzK1voKq9695PJSX2o5NEXl7/OL3coiIY0c= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0/go.mod h1:P4WPRUkOhJC13W//jWpyfJNDAIpvRbAUIYLX/4jtlE0= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.54.0 h1:lhhYARPUu3LmHysQ/igznQphfzynnqI3D75oUyw1HXk= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.54.0/go.mod h1:l9rva3ApbBpEJxSNYnwT9N4CDLrWgtq3u8736C5hyJw= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.54.0 h1:xfK3bbi6F2RDtaZFtUdKO3osOBIhNb+xTs8lFW6yx9o= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.54.0/go.mod h1:vB2GH9GAYYJTO3mEn8oYwzEdhlayZIdQz6zdzgUIRvA= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.54.0 h1:s0WlVbf9qpvkh1c/uDAPElam0WrL7fHRIidgZJ7UqZI= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.54.0/go.mod h1:Mf6O40IAyB9zR/1J8nGDDPirZQQPbYJni8Yisy7NTMc= +github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= +github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/xds/go v0.0.0-20251022180443-0feb69152e9f h1:Y8xYupdHxryycyPlc9Y+bSQAYZnetRJ70VMVKm5CKI0= +github.com/cncf/xds/go v0.0.0-20251022180443-0feb69152e9f/go.mod h1:HlzOvOjVBOfTGSRXRyY0OiCS/3J1akRGQQpRO/7zyF4= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.13.5-0.20251024222203-75eaa193e329 h1:K+fnvUM0VZ7ZFJf0n4L/BRlnsb9pL/GuDG6FqaH+PwM= +github.com/envoyproxy/go-control-plane/envoy v1.35.0 h1:ixjkELDE+ru6idPxcHLj8LBVc2bFP7iBytj353BoHUo= +github.com/envoyproxy/go-control-plane/envoy v1.35.0/go.mod h1:09qwbGVuSWWAyN5t/b3iyVfz5+z8QWGrzkoqm/8SbEs= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/envoyproxy/protoc-gen-validate v1.2.1 h1:DEo3O99U8j4hBFwbJfrz9VtgcDfUKS7KJ7spH3d86P8= +github.com/envoyproxy/protoc-gen-validate v1.2.1/go.mod h1:d/C80l/jxXLdfEIhX1W2TmLfsJ31lvEjwamM4DxlWXU= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/go-jose/go-jose/v4 v4.1.3 h1:CVLmWDhDVRa6Mi/IgCgaopNosCaHz7zrMeF9MlZRkrs= +github.com/go-jose/go-jose/v4 v4.1.3/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ= +github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.7.0-rc.1 h1:YojYx61/OLFsiv6Rw1Z96LpldJIy31o+UHmwAUMJ6/U= +github.com/golang/mock v1.7.0-rc.1/go.mod h1:s42URUywIqd+OcERslBJvOjepvNymP31m3q8d/GkuRs= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/pprof v0.0.0-20251114195745-4902fdda35c8 h1:3DsUAV+VNEQa2CUVLxCY3f87278uWfIDhJnbdvDjvmE= +github.com/google/pprof v0.0.0-20251114195745-4902fdda35c8/go.mod h1:I6V7YzU0XDpsHqbsyrghnFZLO1gwK6NPTNvmetQIk9U= +github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0= +github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/enterprise-certificate-proxy v0.3.7 h1:zrn2Ee/nWmHulBx5sAVrGgAa0f2/R35S4DJwfFaUPFQ= +github.com/googleapis/enterprise-certificate-proxy v0.3.7/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA= +github.com/googleapis/gax-go/v2 v2.15.0 h1:SyjDc1mGgZU5LncH8gimWo9lW1DtIfPibOG81vgd/bo= +github.com/googleapis/gax-go/v2 v2.15.0/go.mod h1:zVVkkxAQHa1RQpg9z2AUCMnKhi0Qld9rcmyfL1OZhoc= +github.com/googleapis/gax-go/v2 v2.16.0 h1:iHbQmKLLZrexmb0OSsNGTeSTS0HO4YvFOG8g5E4Zd0Y= +github.com/googleapis/gax-go/v2 v2.16.0/go.mod h1:o1vfQjjNZn4+dPnRdl/4ZD7S9414Y4xA+a/6Icj6l14= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 h1:NmZ1PKzSTQbuGHw9DGPFomqkkLWMC+vZCkfs+FHv1Vg= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3/go.mod h1:zQrxl1YP88HQlA6i9c63DSVPFklWpGX4OWAc9bFuaH4= +github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8= +github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= +github.com/jackc/pgconn v1.14.3 h1:bVoTr12EGANZz66nZPkMInAV/KHD2TxH9npjXXgiB3w= +github.com/jackc/pgconn v1.14.3/go.mod h1:RZbme4uasqzybK2RK5c65VsHxoyaml09lx3tXOcO/VM= +github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE= +github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgproto3/v2 v2.3.3 h1:1HLSx5H+tXR9pW3in3zaztoEwQYRC9SQaYUHjTSUOag= +github.com/jackc/pgproto3/v2 v2.3.3/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgtype v1.14.0 h1:y+xUdabmyMkJLyApYuPj38mW+aAIqCe5uuBB51rH3Vw= +github.com/jackc/pgtype v1.14.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4= +github.com/jackc/pgx/v4 v4.18.3 h1:dE2/TrEsGX3RBprb3qryqSV9Y60iZN1C6i8IrmW9/BA= +github.com/jackc/pgx/v4 v4.18.3/go.mod h1:Ey4Oru5tH5sB6tV7hDmfWFahwF15Eb7DNXlRKx2CkVw= +github.com/jackc/pgx/v5 v5.7.6 h1:rWQc5FwZSPX58r1OQmkuaNicxdmExaEz5A2DO2hUuTk= +github.com/jackc/pgx/v5 v5.7.6/go.mod h1:aruU7o91Tc2q2cFp5h4uP3f6ztExVpyVv88Xl/8Vl8M= +github.com/jackc/pgx/v5 v5.8.0 h1:TYPDoleBBme0xGSAX3/+NujXXtpZn9HBONkQC7IEZSo= +github.com/jackc/pgx/v5 v5.8.0/go.mod h1:QVeDInX2m9VyzvNeiCJVjCkNFqzsNb43204HshNSZKw= +github.com/jackc/puddle v1.3.0 h1:eHK/5clGOatcjX3oWGBO/MpxpbHzSwud5EWTSCI+MX0= +github.com/jackc/puddle v1.3.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= +github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo= +github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w= +github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g= +github.com/spiffe/go-spiffe/v2 v2.6.0 h1:l+DolpxNWYgruGQVV0xsfeya3CsC7m8iBzDnMpsbLuo= +github.com/spiffe/go-spiffe/v2 v2.6.0/go.mod h1:gm2SeUoMZEtpnzPNs2Csc0D/gX33k1xIx7lEzqblHEs= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= +go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= +go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= +go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= +go.opentelemetry.io/contrib/detectors/gcp v1.38.0 h1:ZoYbqX7OaA/TAikspPl3ozPI6iY6LiIY9I8cUfm+pJs= +go.opentelemetry.io/contrib/detectors/gcp v1.38.0/go.mod h1:SU+iU7nu5ud4oCb3LQOhIZ3nRLj6FNVrKgtflbaf2ts= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0 h1:YH4g8lQroajqUwWbq/tr2QX1JFmEXaDLgG+ew9bLMWo= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0/go.mod h1:fvPi2qXDqFs8M4B4fmJhE92TyQs9Ydjlg3RvfUp+NbQ= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.64.0 h1:RN3ifU8y4prNWeEnQp2kRRHz8UwonAEYZl8tUzHEXAk= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.64.0/go.mod h1:habDz3tEWiFANTo6oUE99EmaFUrCNYAAg3wiVmusm70= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 h1:RbKq8BG0FI8OiXhBfcRtqqHcZcka+gU3cskNuf05R18= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0/go.mod h1:h06DGIukJOevXaj/xrNjhi/2098RZzcLTbc0jDAUbsg= +go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= +go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= +go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= +go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 h1:GqRJVj7UmLjCVyVJ3ZFLdPRmhDUp2zFmQe3RHIOsw24= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0/go.mod h1:ri3aaHSmCTVYu2AWv44YMauwAQc0aqI9gHKIcSbI1pU= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0 h1:f0cb2XPmrqn4XMy9PNliTgRKJgS5WcL/u0/WRYGz4t0= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0/go.mod h1:vnakAaFckOMiMtOIhFI2MNH4FYrZzXCYxmb1LlhoGz8= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 h1:lwI4Dc5leUqENgGuQImwLo4WnuXFPetmPpkLi2IrX54= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0/go.mod h1:Kz/oCE7z5wuyhPxsXDuaPteSWqjSBD5YaSdbxZYGbGk= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0 h1:in9O8ESIOlwJAEGTkkf34DesGRAc/Pn8qJ7k3r/42LM= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0/go.mod h1:Rp0EXBm5tfnv0WL+ARyO/PHBEaEAT8UUHQ6AGJcSq6c= +go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= +go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= +go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= +go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= +go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E= +go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg= +go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= +go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= +go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM= +go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= +go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= +go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= +go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= +go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= +go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= +go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= +go.opentelemetry.io/proto/otlp v1.9.0 h1:l706jCMITVouPOqEnii2fIAuO3IVGBRPV5ICjceRb/A= +go.opentelemetry.io/proto/otlp v1.9.0/go.mod h1:xE+Cx5E/eEHw+ISFkwPLwCZefwVjY+pqKg1qcK03+/4= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q= +golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4= +golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8= +golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= +golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= +golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o= +golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.33.0 h1:4Q+qn+E5z8gPRJfmRy7C2gGG3T4jIprK6aSYgTXGRpo= +golang.org/x/oauth2 v0.33.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= +golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw= +golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= +golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= +golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= +golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= +golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= +golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= +golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= +golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= +golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= +golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= +golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= +gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= +google.golang.org/api v0.256.0 h1:u6Khm8+F9sxbCTYNoBHg6/Hwv0N/i+V94MvkOSor6oI= +google.golang.org/api v0.256.0/go.mod h1:KIgPhksXADEKJlnEoRa9qAII4rXcy40vfI8HRqcU964= +google.golang.org/api v0.257.0 h1:8Y0lzvHlZps53PEaw+G29SsQIkuKrumGWs9puiexNAA= +google.golang.org/api v0.257.0/go.mod h1:4eJrr+vbVaZSqs7vovFd1Jb/A6ml6iw2e6FBYf3GAO4= +google.golang.org/api v0.259.0 h1:90TaGVIxScrh1Vn/XI2426kRpBqHwWIzVBzJsVZ5XrQ= +google.golang.org/api v0.259.0/go.mod h1:LC2ISWGWbRoyQVpxGntWwLWN/vLNxxKBK9KuJRI8Te4= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20251124214823-79d6a2a48846 h1:dDbsTLIK7EzwUq36kCSAsk0slouq/S0tWHeeGi97cD8= +google.golang.org/genproto v0.0.0-20251124214823-79d6a2a48846/go.mod h1:PP0g88Dz3C7hRAfbQCQggeWAXjuqGsNPLE4s7jh0RGU= +google.golang.org/genproto v0.0.0-20251202230838-ff82c1b0f217 h1:GvESR9BIyHUahIb0NcTum6itIWtdoglGX+rnGxm2934= +google.golang.org/genproto v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:yJ2HH4EHEDTd3JiLmhds6NkJ17ITVYOdV3m3VKOnws0= +google.golang.org/genproto/googleapis/api v0.0.0-20251124214823-79d6a2a48846 h1:ZdyUkS9po3H7G0tuh955QVyyotWvOD4W0aEapeGeUYk= +google.golang.org/genproto/googleapis/api v0.0.0-20251124214823-79d6a2a48846/go.mod h1:Fk4kyraUvqD7i5H6S43sj2W98fbZa75lpZz/eUyhfO0= +google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 h1:fCvbg86sFXwdrl5LgVcTEvNC+2txB5mgROGmRL5mrls= +google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:+rXWjjaukWZun3mLfjmVnQi18E1AsFbDN9QdJ5YXLto= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251124214823-79d6a2a48846 h1:Wgl1rcDNThT+Zn47YyCXOXyX/COgMTIdhJ717F0l4xk= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251124214823-79d6a2a48846/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 h1:gRkg/vSppuSQoDjxyiGfN4Upv/h/DQmIR10ZU8dh4Ww= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260112192933-99fd39fd28a9 h1:IY6/YYRrFUk0JPp0xOVctvFIVuRnjccihY5kxf5g0TE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260112192933-99fd39fd28a9/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM= +google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig= +google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc= +google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= +google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= +google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/src/productcatalogservice/product_catalog.go b/src/productcatalogservice/product_catalog.go new file mode 100644 index 0000000..e70e0cb --- /dev/null +++ b/src/productcatalogservice/product_catalog.go @@ -0,0 +1,86 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +import ( + "context" + "strings" + "time" + + pb "github.com/GoogleCloudPlatform/microservices-demo/src/productcatalogservice/genproto" + "google.golang.org/grpc/codes" + healthpb "google.golang.org/grpc/health/grpc_health_v1" + "google.golang.org/grpc/status" +) + +type productCatalog struct { + pb.UnimplementedProductCatalogServiceServer + catalog pb.ListProductsResponse +} + +func (p *productCatalog) Check(ctx context.Context, req *healthpb.HealthCheckRequest) (*healthpb.HealthCheckResponse, error) { + return &healthpb.HealthCheckResponse{Status: healthpb.HealthCheckResponse_SERVING}, nil +} + +func (p *productCatalog) Watch(req *healthpb.HealthCheckRequest, ws healthpb.Health_WatchServer) error { + return status.Errorf(codes.Unimplemented, "health check via Watch not implemented") +} + +func (p *productCatalog) ListProducts(context.Context, *pb.Empty) (*pb.ListProductsResponse, error) { + time.Sleep(extraLatency) + + return &pb.ListProductsResponse{Products: p.parseCatalog()}, nil +} + +func (p *productCatalog) GetProduct(ctx context.Context, req *pb.GetProductRequest) (*pb.Product, error) { + time.Sleep(extraLatency) + + var found *pb.Product + for i := 0; i < len(p.parseCatalog()); i++ { + if req.Id == p.parseCatalog()[i].Id { + found = p.parseCatalog()[i] + } + } + + if found == nil { + return nil, status.Errorf(codes.NotFound, "no product with ID %s", req.Id) + } + return found, nil +} + +func (p *productCatalog) SearchProducts(ctx context.Context, req *pb.SearchProductsRequest) (*pb.SearchProductsResponse, error) { + time.Sleep(extraLatency) + + var ps []*pb.Product + for _, product := range p.parseCatalog() { + if strings.Contains(strings.ToLower(product.Name), strings.ToLower(req.Query)) || + strings.Contains(strings.ToLower(product.Description), strings.ToLower(req.Query)) { + ps = append(ps, product) + } + } + + return &pb.SearchProductsResponse{Results: ps}, nil +} + +func (p *productCatalog) parseCatalog() []*pb.Product { + if reloadCatalog || len(p.catalog.Products) == 0 { + err := loadCatalog(&p.catalog) + if err != nil { + return []*pb.Product{} + } + } + + return p.catalog.Products +} diff --git a/src/productcatalogservice/product_catalog_test.go b/src/productcatalogservice/product_catalog_test.go new file mode 100644 index 0000000..3ac9c06 --- /dev/null +++ b/src/productcatalogservice/product_catalog_test.go @@ -0,0 +1,101 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +import ( + "context" + "os" + "testing" + + pb "github.com/GoogleCloudPlatform/microservices-demo/src/productcatalogservice/genproto" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +var ( + mockProductCatalog *productCatalog +) + +func TestMain(m *testing.M) { + mockProductCatalog = &productCatalog{ + catalog: pb.ListProductsResponse{ + Products: []*pb.Product{}, + }, + } + + mockProductCatalog.catalog.Products = append(mockProductCatalog.catalog.Products, &pb.Product{ + Id: "abc001", + Name: "Product Alpha One", + }) + mockProductCatalog.catalog.Products = append(mockProductCatalog.catalog.Products, &pb.Product{ + Id: "abc002", + Name: "Product Delta", + }) + mockProductCatalog.catalog.Products = append(mockProductCatalog.catalog.Products, &pb.Product{ + Id: "abc003", + Name: "Product Alpha Two", + }) + mockProductCatalog.catalog.Products = append(mockProductCatalog.catalog.Products, &pb.Product{ + Id: "abc004", + Name: "Product Gamma", + }) + + os.Exit(m.Run()) +} + +func TestGetProductExists(t *testing.T) { + product, err := mockProductCatalog.GetProduct(context.Background(), + &pb.GetProductRequest{Id: "abc003"}, + ) + if err != nil { + t.Fatal(err) + } + if got, want := product.Name, "Product Alpha Two"; got != want { + t.Errorf("got %s, want %s", got, want) + } +} + +func TestGetProductNotFound(t *testing.T) { + _, err := mockProductCatalog.GetProduct(context.Background(), + &pb.GetProductRequest{Id: "abc005"}, + ) + if got, want := status.Code(err), codes.NotFound; got != want { + t.Errorf("got %s, want %s", got, want) + } +} + +func TestListProducts(t *testing.T) { + products, err := mockProductCatalog.ListProducts(context.Background(), + &pb.Empty{}, + ) + if err != nil { + t.Fatal(err) + } + if got, want := len(products.Products), 4; got != want { + t.Errorf("got %d, want %d", got, want) + } +} + +func TestSearchProducts(t *testing.T) { + products, err := mockProductCatalog.SearchProducts(context.Background(), + &pb.SearchProductsRequest{Query: "alpha"}, + ) + if err != nil { + t.Fatal(err) + } + if got, want := len(products.Results), 2; got != want { + t.Errorf("got %d, want %d", got, want) + } +} diff --git a/src/productcatalogservice/products.json b/src/productcatalogservice/products.json new file mode 100644 index 0000000..d5dd591 --- /dev/null +++ b/src/productcatalogservice/products.json @@ -0,0 +1,112 @@ +{ + "products": [ + { + "id": "OLJCESPC7Z", + "name": "Sunglasses", + "description": "Add a modern touch to your outfits with these sleek aviator sunglasses.", + "picture": "/static/img/products/sunglasses.jpg", + "priceUsd": { + "currencyCode": "USD", + "units": 19, + "nanos": 990000000 + }, + "categories": ["accessories"] + }, + { + "id": "66VCHSJNUP", + "name": "Tank Top", + "description": "Perfectly cropped cotton tank, with a scooped neckline.", + "picture": "/static/img/products/tank-top.jpg", + "priceUsd": { + "currencyCode": "USD", + "units": 18, + "nanos": 990000000 + }, + "categories": ["clothing", "tops"] + }, + { + "id": "1YMWWN1N4O", + "name": "Watch", + "description": "This gold-tone stainless steel watch will work with most of your outfits.", + "picture": "/static/img/products/watch.jpg", + "priceUsd": { + "currencyCode": "USD", + "units": 109, + "nanos": 990000000 + }, + "categories": ["accessories"] + }, + { + "id": "L9ECAV7KIM", + "name": "Loafers", + "description": "A neat addition to your summer wardrobe.", + "picture": "/static/img/products/loafers.jpg", + "priceUsd": { + "currencyCode": "USD", + "units": 89, + "nanos": 990000000 + }, + "categories": ["footwear"] + }, + { + "id": "2ZYFJ3GM2N", + "name": "Hairdryer", + "description": "This lightweight hairdryer has 3 heat and speed settings. It's perfect for travel.", + "picture": "/static/img/products/hairdryer.jpg", + "priceUsd": { + "currencyCode": "USD", + "units": 24, + "nanos": 990000000 + }, + "categories": ["hair", "beauty"] + }, + { + "id": "0PUK6V6EV0", + "name": "Candle Holder", + "description": "This small but intricate candle holder is an excellent gift.", + "picture": "/static/img/products/candle-holder.jpg", + "priceUsd": { + "currencyCode": "USD", + "units": 18, + "nanos": 990000000 + }, + "categories": ["decor", "home"] + }, + { + "id": "LS4PSXUNUM", + "name": "Salt & Pepper Shakers", + "description": "Add some flavor to your kitchen.", + "picture": "/static/img/products/salt-and-pepper-shakers.jpg", + "priceUsd": { + "currencyCode": "USD", + "units": 18, + "nanos": 490000000 + }, + "categories": ["kitchen"] + }, + { + "id": "9SIQT8TOJO", + "name": "Bamboo Glass Jar", + "description": "This bamboo glass jar can hold 57 oz (1.7 l) and is perfect for any kitchen.", + "picture": "/static/img/products/bamboo-glass-jar.jpg", + "priceUsd": { + "currencyCode": "USD", + "units": 5, + "nanos": 490000000 + }, + "categories": ["kitchen"] + }, + { + "id": "6E92ZMYYFZ", + "name": "Mug", + "description": "A simple mug with a mustard interior.", + "picture": "/static/img/products/mug.jpg", + "priceUsd": { + "currencyCode": "USD", + "units": 8, + "nanos": 990000000 + }, + "categories": ["kitchen"] + } + ] +} diff --git a/src/productcatalogservice/server.go b/src/productcatalogservice/server.go new file mode 100644 index 0000000..3243b92 --- /dev/null +++ b/src/productcatalogservice/server.go @@ -0,0 +1,217 @@ +// Copyright 2018 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +import ( + "context" + "flag" + "fmt" + "net" + "os" + "os/signal" + "sync" + "syscall" + "time" + + pb "github.com/GoogleCloudPlatform/microservices-demo/src/productcatalogservice/genproto" + "google.golang.org/grpc/credentials/insecure" + "google.golang.org/grpc/health" + healthpb "google.golang.org/grpc/health/grpc_health_v1" + + "cloud.google.com/go/profiler" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" + "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc" + "go.opentelemetry.io/otel/propagation" + sdktrace "go.opentelemetry.io/otel/sdk/trace" + "google.golang.org/grpc" +) + +var ( + catalogMutex *sync.Mutex + log *logrus.Logger + extraLatency time.Duration + + port = "3550" + + reloadCatalog bool +) + +func init() { + log = logrus.New() + log.Formatter = &logrus.JSONFormatter{ + FieldMap: logrus.FieldMap{ + logrus.FieldKeyTime: "timestamp", + logrus.FieldKeyLevel: "severity", + logrus.FieldKeyMsg: "message", + }, + TimestampFormat: time.RFC3339Nano, + } + log.Out = os.Stdout + catalogMutex = &sync.Mutex{} +} + +func main() { + if os.Getenv("ENABLE_TRACING") == "1" { + err := initTracing() + if err != nil { + log.Warnf("warn: failed to start tracer: %+v", err) + } + } else { + log.Info("Tracing disabled.") + } + + if os.Getenv("DISABLE_PROFILER") == "" { + log.Info("Profiling enabled.") + go initProfiling("productcatalogservice", "1.0.0") + } else { + log.Info("Profiling disabled.") + } + + flag.Parse() + + // set injected latency + if s := os.Getenv("EXTRA_LATENCY"); s != "" { + v, err := time.ParseDuration(s) + if err != nil { + log.Fatalf("failed to parse EXTRA_LATENCY (%s) as time.Duration: %+v", v, err) + } + extraLatency = v + log.Infof("extra latency enabled (duration: %v)", extraLatency) + } else { + extraLatency = time.Duration(0) + } + + sigs := make(chan os.Signal, 1) + signal.Notify(sigs, syscall.SIGUSR1, syscall.SIGUSR2) + go func() { + for { + sig := <-sigs + log.Printf("Received signal: %s", sig) + if sig == syscall.SIGUSR1 { + reloadCatalog = true + log.Infof("Enable catalog reloading") + } else { + reloadCatalog = false + log.Infof("Disable catalog reloading") + } + } + }() + + if os.Getenv("PORT") != "" { + port = os.Getenv("PORT") + } + log.Infof("starting grpc server at :%s", port) + run(port) + select {} +} + +func run(port string) string { + listener, err := net.Listen("tcp", fmt.Sprintf(":%s", port)) + if err != nil { + log.Fatal(err) + } + + // Propagate trace context + otel.SetTextMapPropagator( + propagation.NewCompositeTextMapPropagator( + propagation.TraceContext{}, propagation.Baggage{})) + var srv *grpc.Server + srv = grpc.NewServer( + grpc.StatsHandler(otelgrpc.NewServerHandler())) + + svc := &productCatalog{} + err = loadCatalog(&svc.catalog) + if err != nil { + log.Fatalf("could not parse product catalog: %v", err) + } + + pb.RegisterProductCatalogServiceServer(srv, svc) + healthcheck := health.NewServer() + healthpb.RegisterHealthServer(srv, healthcheck) + go srv.Serve(listener) + + return listener.Addr().String() +} + +func initStats() { + // TODO(drewbr) Implement OpenTelemetry stats +} + +func initTracing() error { + var ( + collectorAddr string + collectorConn *grpc.ClientConn + ) + + ctx := context.Background() + + mustMapEnv(&collectorAddr, "COLLECTOR_SERVICE_ADDR") + mustConnGRPC(ctx, &collectorConn, collectorAddr) + + exporter, err := otlptracegrpc.New( + ctx, + otlptracegrpc.WithGRPCConn(collectorConn)) + if err != nil { + log.Warnf("warn: Failed to create trace exporter: %v", err) + } + tp := sdktrace.NewTracerProvider( + sdktrace.WithBatcher(exporter), + sdktrace.WithSampler(sdktrace.AlwaysSample())) + otel.SetTracerProvider(tp) + return err +} + +func initProfiling(service, version string) { + for i := 1; i <= 3; i++ { + if err := profiler.Start(profiler.Config{ + Service: service, + ServiceVersion: version, + // ProjectID must be set if not running on GCP. + // ProjectID: "my-project", + }); err != nil { + log.Warnf("failed to start profiler: %+v", err) + } else { + log.Info("started Stackdriver profiler") + return + } + d := time.Second * 10 * time.Duration(i) + log.Infof("sleeping %v to retry initializing Stackdriver profiler", d) + time.Sleep(d) + } + log.Warn("could not initialize Stackdriver profiler after retrying, giving up") +} + +func mustMapEnv(target *string, envKey string) { + v := os.Getenv(envKey) + if v == "" { + panic(fmt.Sprintf("environment variable %q not set", envKey)) + } + *target = v +} + +func mustConnGRPC(ctx context.Context, conn **grpc.ClientConn, addr string) { + var err error + _, cancel := context.WithTimeout(ctx, time.Second*3) + defer cancel() + *conn, err = grpc.NewClient(addr, + grpc.WithTransportCredentials(insecure.NewCredentials()), + grpc.WithStatsHandler(otelgrpc.NewClientHandler())) + if err != nil { + panic(errors.Wrapf(err, "grpc: failed to connect %s", addr)) + } +} diff --git a/src/recommendationservice/Dockerfile b/src/recommendationservice/Dockerfile new file mode 100644 index 0000000..0fd53d5 --- /dev/null +++ b/src/recommendationservice/Dockerfile @@ -0,0 +1,52 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +FROM --platform=$BUILDPLATFORM python:3.14.2-alpine@sha256:31da4cb527055e4e3d7e9e006dffe9329f84ebea79eaca0a1f1c27ce61e40ca5 AS base + +FROM base AS builder + +ENV PYTHONDONTWRITEBYTECODE=1 +ENV PYTHONUNBUFFERED=1 + +RUN apk update \ + && apk add --no-cache g++ linux-headers \ + && rm -rf /var/cache/apk/* + +# get packages +COPY requirements.txt . +RUN pip install -r requirements.txt + +FROM base + +ENV PYTHONDONTWRITEBYTECODE=1 +ENV PYTHONUNBUFFERED=1 + +RUN apk update \ + && apk add --no-cache libstdc++ \ + && rm -rf /var/cache/apk/* + +# get packages +WORKDIR /recommendationservice + +# Grab packages from builder +COPY --from=builder /usr/local/lib/python3.14/ /usr/local/lib/python3.14/ + +# Add the application +COPY . . + +# set listen port +ENV PORT="8080" +EXPOSE 8080 + +ENTRYPOINT ["python", "recommendation_server.py"] diff --git a/src/recommendationservice/client.py b/src/recommendationservice/client.py new file mode 100755 index 0000000..6f6ccc5 --- /dev/null +++ b/src/recommendationservice/client.py @@ -0,0 +1,39 @@ +#!/usr/bin/python +# +# Copyright 2018 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import sys +import grpc +import demo_pb2 +import demo_pb2_grpc + +from logger import getJSONLogger +logger = getJSONLogger('recommendationservice-server') + +if __name__ == "__main__": + # get port + if len(sys.argv) > 1: + port = sys.argv[1] + else: + port = "8080" + + # set up server stub + channel = grpc.insecure_channel('localhost:'+port) + stub = demo_pb2_grpc.RecommendationServiceStub(channel) + # form request + request = demo_pb2.ListRecommendationsRequest(user_id="test", product_ids=["test"]) + # make call to server + response = stub.ListRecommendations(request) + logger.info(response) diff --git a/src/recommendationservice/demo_pb2.py b/src/recommendationservice/demo_pb2.py new file mode 100755 index 0000000..fa7589f --- /dev/null +++ b/src/recommendationservice/demo_pb2.py @@ -0,0 +1,121 @@ +#!/usr/bin/python +# +# Copyright 2018 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: demo.proto +"""Generated protocol buffer code.""" +from google.protobuf.internal import builder as _builder +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import symbol_database as _symbol_database +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\ndemo.proto\x12\x0bhipstershop\"0\n\x08\x43\x61rtItem\x12\x12\n\nproduct_id\x18\x01 \x01(\t\x12\x10\n\x08quantity\x18\x02 \x01(\x05\"F\n\x0e\x41\x64\x64ItemRequest\x12\x0f\n\x07user_id\x18\x01 \x01(\t\x12#\n\x04item\x18\x02 \x01(\x0b\x32\x15.hipstershop.CartItem\"#\n\x10\x45mptyCartRequest\x12\x0f\n\x07user_id\x18\x01 \x01(\t\"!\n\x0eGetCartRequest\x12\x0f\n\x07user_id\x18\x01 \x01(\t\"=\n\x04\x43\x61rt\x12\x0f\n\x07user_id\x18\x01 \x01(\t\x12$\n\x05items\x18\x02 \x03(\x0b\x32\x15.hipstershop.CartItem\"\x07\n\x05\x45mpty\"B\n\x1aListRecommendationsRequest\x12\x0f\n\x07user_id\x18\x01 \x01(\t\x12\x13\n\x0bproduct_ids\x18\x02 \x03(\t\"2\n\x1bListRecommendationsResponse\x12\x13\n\x0bproduct_ids\x18\x01 \x03(\t\"\x84\x01\n\x07Product\x12\n\n\x02id\x18\x01 \x01(\t\x12\x0c\n\x04name\x18\x02 \x01(\t\x12\x13\n\x0b\x64\x65scription\x18\x03 \x01(\t\x12\x0f\n\x07picture\x18\x04 \x01(\t\x12%\n\tprice_usd\x18\x05 \x01(\x0b\x32\x12.hipstershop.Money\x12\x12\n\ncategories\x18\x06 \x03(\t\">\n\x14ListProductsResponse\x12&\n\x08products\x18\x01 \x03(\x0b\x32\x14.hipstershop.Product\"\x1f\n\x11GetProductRequest\x12\n\n\x02id\x18\x01 \x01(\t\"&\n\x15SearchProductsRequest\x12\r\n\x05query\x18\x01 \x01(\t\"?\n\x16SearchProductsResponse\x12%\n\x07results\x18\x01 \x03(\x0b\x32\x14.hipstershop.Product\"^\n\x0fGetQuoteRequest\x12%\n\x07\x61\x64\x64ress\x18\x01 \x01(\x0b\x32\x14.hipstershop.Address\x12$\n\x05items\x18\x02 \x03(\x0b\x32\x15.hipstershop.CartItem\"8\n\x10GetQuoteResponse\x12$\n\x08\x63ost_usd\x18\x01 \x01(\x0b\x32\x12.hipstershop.Money\"_\n\x10ShipOrderRequest\x12%\n\x07\x61\x64\x64ress\x18\x01 \x01(\x0b\x32\x14.hipstershop.Address\x12$\n\x05items\x18\x02 \x03(\x0b\x32\x15.hipstershop.CartItem\"(\n\x11ShipOrderResponse\x12\x13\n\x0btracking_id\x18\x01 \x01(\t\"a\n\x07\x41\x64\x64ress\x12\x16\n\x0estreet_address\x18\x01 \x01(\t\x12\x0c\n\x04\x63ity\x18\x02 \x01(\t\x12\r\n\x05state\x18\x03 \x01(\t\x12\x0f\n\x07\x63ountry\x18\x04 \x01(\t\x12\x10\n\x08zip_code\x18\x05 \x01(\x05\"<\n\x05Money\x12\x15\n\rcurrency_code\x18\x01 \x01(\t\x12\r\n\x05units\x18\x02 \x01(\x03\x12\r\n\x05nanos\x18\x03 \x01(\x05\"8\n\x1eGetSupportedCurrenciesResponse\x12\x16\n\x0e\x63urrency_codes\x18\x01 \x03(\t\"N\n\x19\x43urrencyConversionRequest\x12 \n\x04\x66rom\x18\x01 \x01(\x0b\x32\x12.hipstershop.Money\x12\x0f\n\x07to_code\x18\x02 \x01(\t\"\x90\x01\n\x0e\x43reditCardInfo\x12\x1a\n\x12\x63redit_card_number\x18\x01 \x01(\t\x12\x17\n\x0f\x63redit_card_cvv\x18\x02 \x01(\x05\x12#\n\x1b\x63redit_card_expiration_year\x18\x03 \x01(\x05\x12$\n\x1c\x63redit_card_expiration_month\x18\x04 \x01(\x05\"e\n\rChargeRequest\x12\"\n\x06\x61mount\x18\x01 \x01(\x0b\x32\x12.hipstershop.Money\x12\x30\n\x0b\x63redit_card\x18\x02 \x01(\x0b\x32\x1b.hipstershop.CreditCardInfo\"(\n\x0e\x43hargeResponse\x12\x16\n\x0etransaction_id\x18\x01 \x01(\t\"R\n\tOrderItem\x12#\n\x04item\x18\x01 \x01(\x0b\x32\x15.hipstershop.CartItem\x12 \n\x04\x63ost\x18\x02 \x01(\x0b\x32\x12.hipstershop.Money\"\xbf\x01\n\x0bOrderResult\x12\x10\n\x08order_id\x18\x01 \x01(\t\x12\x1c\n\x14shipping_tracking_id\x18\x02 \x01(\t\x12)\n\rshipping_cost\x18\x03 \x01(\x0b\x32\x12.hipstershop.Money\x12.\n\x10shipping_address\x18\x04 \x01(\x0b\x32\x14.hipstershop.Address\x12%\n\x05items\x18\x05 \x03(\x0b\x32\x16.hipstershop.OrderItem\"V\n\x1cSendOrderConfirmationRequest\x12\r\n\x05\x65mail\x18\x01 \x01(\t\x12\'\n\x05order\x18\x02 \x01(\x0b\x32\x18.hipstershop.OrderResult\"\xa3\x01\n\x11PlaceOrderRequest\x12\x0f\n\x07user_id\x18\x01 \x01(\t\x12\x15\n\ruser_currency\x18\x02 \x01(\t\x12%\n\x07\x61\x64\x64ress\x18\x03 \x01(\x0b\x32\x14.hipstershop.Address\x12\r\n\x05\x65mail\x18\x05 \x01(\t\x12\x30\n\x0b\x63redit_card\x18\x06 \x01(\x0b\x32\x1b.hipstershop.CreditCardInfo\"=\n\x12PlaceOrderResponse\x12\'\n\x05order\x18\x01 \x01(\x0b\x32\x18.hipstershop.OrderResult\"!\n\tAdRequest\x12\x14\n\x0c\x63ontext_keys\x18\x01 \x03(\t\"*\n\nAdResponse\x12\x1c\n\x03\x61\x64s\x18\x01 \x03(\x0b\x32\x0f.hipstershop.Ad\"(\n\x02\x41\x64\x12\x14\n\x0credirect_url\x18\x01 \x01(\t\x12\x0c\n\x04text\x18\x02 \x01(\t2\xca\x01\n\x0b\x43\x61rtService\x12<\n\x07\x41\x64\x64Item\x12\x1b.hipstershop.AddItemRequest\x1a\x12.hipstershop.Empty\"\x00\x12;\n\x07GetCart\x12\x1b.hipstershop.GetCartRequest\x1a\x11.hipstershop.Cart\"\x00\x12@\n\tEmptyCart\x12\x1d.hipstershop.EmptyCartRequest\x1a\x12.hipstershop.Empty\"\x00\x32\x83\x01\n\x15RecommendationService\x12j\n\x13ListRecommendations\x12\'.hipstershop.ListRecommendationsRequest\x1a(.hipstershop.ListRecommendationsResponse\"\x00\x32\x83\x02\n\x15ProductCatalogService\x12G\n\x0cListProducts\x12\x12.hipstershop.Empty\x1a!.hipstershop.ListProductsResponse\"\x00\x12\x44\n\nGetProduct\x12\x1e.hipstershop.GetProductRequest\x1a\x14.hipstershop.Product\"\x00\x12[\n\x0eSearchProducts\x12\".hipstershop.SearchProductsRequest\x1a#.hipstershop.SearchProductsResponse\"\x00\x32\xaa\x01\n\x0fShippingService\x12I\n\x08GetQuote\x12\x1c.hipstershop.GetQuoteRequest\x1a\x1d.hipstershop.GetQuoteResponse\"\x00\x12L\n\tShipOrder\x12\x1d.hipstershop.ShipOrderRequest\x1a\x1e.hipstershop.ShipOrderResponse\"\x00\x32\xb7\x01\n\x0f\x43urrencyService\x12[\n\x16GetSupportedCurrencies\x12\x12.hipstershop.Empty\x1a+.hipstershop.GetSupportedCurrenciesResponse\"\x00\x12G\n\x07\x43onvert\x12&.hipstershop.CurrencyConversionRequest\x1a\x12.hipstershop.Money\"\x00\x32U\n\x0ePaymentService\x12\x43\n\x06\x43harge\x12\x1a.hipstershop.ChargeRequest\x1a\x1b.hipstershop.ChargeResponse\"\x00\x32h\n\x0c\x45mailService\x12X\n\x15SendOrderConfirmation\x12).hipstershop.SendOrderConfirmationRequest\x1a\x12.hipstershop.Empty\"\x00\x32\x62\n\x0f\x43heckoutService\x12O\n\nPlaceOrder\x12\x1e.hipstershop.PlaceOrderRequest\x1a\x1f.hipstershop.PlaceOrderResponse\"\x00\x32H\n\tAdService\x12;\n\x06GetAds\x12\x16.hipstershop.AdRequest\x1a\x17.hipstershop.AdResponse\"\x00\x62\x06proto3') + +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals()) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'demo_pb2', globals()) +if _descriptor._USE_C_DESCRIPTORS == False: + + DESCRIPTOR._options = None + _CARTITEM._serialized_start=27 + _CARTITEM._serialized_end=75 + _ADDITEMREQUEST._serialized_start=77 + _ADDITEMREQUEST._serialized_end=147 + _EMPTYCARTREQUEST._serialized_start=149 + _EMPTYCARTREQUEST._serialized_end=184 + _GETCARTREQUEST._serialized_start=186 + _GETCARTREQUEST._serialized_end=219 + _CART._serialized_start=221 + _CART._serialized_end=282 + _EMPTY._serialized_start=284 + _EMPTY._serialized_end=291 + _LISTRECOMMENDATIONSREQUEST._serialized_start=293 + _LISTRECOMMENDATIONSREQUEST._serialized_end=359 + _LISTRECOMMENDATIONSRESPONSE._serialized_start=361 + _LISTRECOMMENDATIONSRESPONSE._serialized_end=411 + _PRODUCT._serialized_start=414 + _PRODUCT._serialized_end=546 + _LISTPRODUCTSRESPONSE._serialized_start=548 + _LISTPRODUCTSRESPONSE._serialized_end=610 + _GETPRODUCTREQUEST._serialized_start=612 + _GETPRODUCTREQUEST._serialized_end=643 + _SEARCHPRODUCTSREQUEST._serialized_start=645 + _SEARCHPRODUCTSREQUEST._serialized_end=683 + _SEARCHPRODUCTSRESPONSE._serialized_start=685 + _SEARCHPRODUCTSRESPONSE._serialized_end=748 + _GETQUOTEREQUEST._serialized_start=750 + _GETQUOTEREQUEST._serialized_end=844 + _GETQUOTERESPONSE._serialized_start=846 + _GETQUOTERESPONSE._serialized_end=902 + _SHIPORDERREQUEST._serialized_start=904 + _SHIPORDERREQUEST._serialized_end=999 + _SHIPORDERRESPONSE._serialized_start=1001 + _SHIPORDERRESPONSE._serialized_end=1041 + _ADDRESS._serialized_start=1043 + _ADDRESS._serialized_end=1140 + _MONEY._serialized_start=1142 + _MONEY._serialized_end=1202 + _GETSUPPORTEDCURRENCIESRESPONSE._serialized_start=1204 + _GETSUPPORTEDCURRENCIESRESPONSE._serialized_end=1260 + _CURRENCYCONVERSIONREQUEST._serialized_start=1262 + _CURRENCYCONVERSIONREQUEST._serialized_end=1340 + _CREDITCARDINFO._serialized_start=1343 + _CREDITCARDINFO._serialized_end=1487 + _CHARGEREQUEST._serialized_start=1489 + _CHARGEREQUEST._serialized_end=1590 + _CHARGERESPONSE._serialized_start=1592 + _CHARGERESPONSE._serialized_end=1632 + _ORDERITEM._serialized_start=1634 + _ORDERITEM._serialized_end=1716 + _ORDERRESULT._serialized_start=1719 + _ORDERRESULT._serialized_end=1910 + _SENDORDERCONFIRMATIONREQUEST._serialized_start=1912 + _SENDORDERCONFIRMATIONREQUEST._serialized_end=1998 + _PLACEORDERREQUEST._serialized_start=2001 + _PLACEORDERREQUEST._serialized_end=2164 + _PLACEORDERRESPONSE._serialized_start=2166 + _PLACEORDERRESPONSE._serialized_end=2227 + _ADREQUEST._serialized_start=2229 + _ADREQUEST._serialized_end=2262 + _ADRESPONSE._serialized_start=2264 + _ADRESPONSE._serialized_end=2306 + _AD._serialized_start=2308 + _AD._serialized_end=2348 + _CARTSERVICE._serialized_start=2351 + _CARTSERVICE._serialized_end=2553 + _RECOMMENDATIONSERVICE._serialized_start=2556 + _RECOMMENDATIONSERVICE._serialized_end=2687 + _PRODUCTCATALOGSERVICE._serialized_start=2690 + _PRODUCTCATALOGSERVICE._serialized_end=2949 + _SHIPPINGSERVICE._serialized_start=2952 + _SHIPPINGSERVICE._serialized_end=3122 + _CURRENCYSERVICE._serialized_start=3125 + _CURRENCYSERVICE._serialized_end=3308 + _PAYMENTSERVICE._serialized_start=3310 + _PAYMENTSERVICE._serialized_end=3395 + _EMAILSERVICE._serialized_start=3397 + _EMAILSERVICE._serialized_end=3501 + _CHECKOUTSERVICE._serialized_start=3503 + _CHECKOUTSERVICE._serialized_end=3601 + _ADSERVICE._serialized_start=3603 + _ADSERVICE._serialized_end=3675 +# @@protoc_insertion_point(module_scope) diff --git a/src/recommendationservice/demo_pb2_grpc.py b/src/recommendationservice/demo_pb2_grpc.py new file mode 100755 index 0000000..47bbf0a --- /dev/null +++ b/src/recommendationservice/demo_pb2_grpc.py @@ -0,0 +1,822 @@ +#!/usr/bin/python +# +# Copyright 2018 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! +"""Client and server classes corresponding to protobuf-defined services.""" +import grpc + +import demo_pb2 as demo__pb2 + + +class CartServiceStub(object): + """-----------------Cart service----------------- + + """ + + def __init__(self, channel): + """Constructor. + + Args: + channel: A grpc.Channel. + """ + self.AddItem = channel.unary_unary( + '/hipstershop.CartService/AddItem', + request_serializer=demo__pb2.AddItemRequest.SerializeToString, + response_deserializer=demo__pb2.Empty.FromString, + ) + self.GetCart = channel.unary_unary( + '/hipstershop.CartService/GetCart', + request_serializer=demo__pb2.GetCartRequest.SerializeToString, + response_deserializer=demo__pb2.Cart.FromString, + ) + self.EmptyCart = channel.unary_unary( + '/hipstershop.CartService/EmptyCart', + request_serializer=demo__pb2.EmptyCartRequest.SerializeToString, + response_deserializer=demo__pb2.Empty.FromString, + ) + + +class CartServiceServicer(object): + """-----------------Cart service----------------- + + """ + + def AddItem(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def GetCart(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def EmptyCart(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + +def add_CartServiceServicer_to_server(servicer, server): + rpc_method_handlers = { + 'AddItem': grpc.unary_unary_rpc_method_handler( + servicer.AddItem, + request_deserializer=demo__pb2.AddItemRequest.FromString, + response_serializer=demo__pb2.Empty.SerializeToString, + ), + 'GetCart': grpc.unary_unary_rpc_method_handler( + servicer.GetCart, + request_deserializer=demo__pb2.GetCartRequest.FromString, + response_serializer=demo__pb2.Cart.SerializeToString, + ), + 'EmptyCart': grpc.unary_unary_rpc_method_handler( + servicer.EmptyCart, + request_deserializer=demo__pb2.EmptyCartRequest.FromString, + response_serializer=demo__pb2.Empty.SerializeToString, + ), + } + generic_handler = grpc.method_handlers_generic_handler( + 'hipstershop.CartService', rpc_method_handlers) + server.add_generic_rpc_handlers((generic_handler,)) + + + # This class is part of an EXPERIMENTAL API. +class CartService(object): + """-----------------Cart service----------------- + + """ + + @staticmethod + def AddItem(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/hipstershop.CartService/AddItem', + demo__pb2.AddItemRequest.SerializeToString, + demo__pb2.Empty.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def GetCart(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/hipstershop.CartService/GetCart', + demo__pb2.GetCartRequest.SerializeToString, + demo__pb2.Cart.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def EmptyCart(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/hipstershop.CartService/EmptyCart', + demo__pb2.EmptyCartRequest.SerializeToString, + demo__pb2.Empty.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + +class RecommendationServiceStub(object): + """---------------Recommendation service---------- + + """ + + def __init__(self, channel): + """Constructor. + + Args: + channel: A grpc.Channel. + """ + self.ListRecommendations = channel.unary_unary( + '/hipstershop.RecommendationService/ListRecommendations', + request_serializer=demo__pb2.ListRecommendationsRequest.SerializeToString, + response_deserializer=demo__pb2.ListRecommendationsResponse.FromString, + ) + + +class RecommendationServiceServicer(object): + """---------------Recommendation service---------- + + """ + + def ListRecommendations(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + +def add_RecommendationServiceServicer_to_server(servicer, server): + rpc_method_handlers = { + 'ListRecommendations': grpc.unary_unary_rpc_method_handler( + servicer.ListRecommendations, + request_deserializer=demo__pb2.ListRecommendationsRequest.FromString, + response_serializer=demo__pb2.ListRecommendationsResponse.SerializeToString, + ), + } + generic_handler = grpc.method_handlers_generic_handler( + 'hipstershop.RecommendationService', rpc_method_handlers) + server.add_generic_rpc_handlers((generic_handler,)) + + + # This class is part of an EXPERIMENTAL API. +class RecommendationService(object): + """---------------Recommendation service---------- + + """ + + @staticmethod + def ListRecommendations(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/hipstershop.RecommendationService/ListRecommendations', + demo__pb2.ListRecommendationsRequest.SerializeToString, + demo__pb2.ListRecommendationsResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + +class ProductCatalogServiceStub(object): + """---------------Product Catalog---------------- + + """ + + def __init__(self, channel): + """Constructor. + + Args: + channel: A grpc.Channel. + """ + self.ListProducts = channel.unary_unary( + '/hipstershop.ProductCatalogService/ListProducts', + request_serializer=demo__pb2.Empty.SerializeToString, + response_deserializer=demo__pb2.ListProductsResponse.FromString, + ) + self.GetProduct = channel.unary_unary( + '/hipstershop.ProductCatalogService/GetProduct', + request_serializer=demo__pb2.GetProductRequest.SerializeToString, + response_deserializer=demo__pb2.Product.FromString, + ) + self.SearchProducts = channel.unary_unary( + '/hipstershop.ProductCatalogService/SearchProducts', + request_serializer=demo__pb2.SearchProductsRequest.SerializeToString, + response_deserializer=demo__pb2.SearchProductsResponse.FromString, + ) + + +class ProductCatalogServiceServicer(object): + """---------------Product Catalog---------------- + + """ + + def ListProducts(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def GetProduct(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def SearchProducts(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + +def add_ProductCatalogServiceServicer_to_server(servicer, server): + rpc_method_handlers = { + 'ListProducts': grpc.unary_unary_rpc_method_handler( + servicer.ListProducts, + request_deserializer=demo__pb2.Empty.FromString, + response_serializer=demo__pb2.ListProductsResponse.SerializeToString, + ), + 'GetProduct': grpc.unary_unary_rpc_method_handler( + servicer.GetProduct, + request_deserializer=demo__pb2.GetProductRequest.FromString, + response_serializer=demo__pb2.Product.SerializeToString, + ), + 'SearchProducts': grpc.unary_unary_rpc_method_handler( + servicer.SearchProducts, + request_deserializer=demo__pb2.SearchProductsRequest.FromString, + response_serializer=demo__pb2.SearchProductsResponse.SerializeToString, + ), + } + generic_handler = grpc.method_handlers_generic_handler( + 'hipstershop.ProductCatalogService', rpc_method_handlers) + server.add_generic_rpc_handlers((generic_handler,)) + + + # This class is part of an EXPERIMENTAL API. +class ProductCatalogService(object): + """---------------Product Catalog---------------- + + """ + + @staticmethod + def ListProducts(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/hipstershop.ProductCatalogService/ListProducts', + demo__pb2.Empty.SerializeToString, + demo__pb2.ListProductsResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def GetProduct(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/hipstershop.ProductCatalogService/GetProduct', + demo__pb2.GetProductRequest.SerializeToString, + demo__pb2.Product.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def SearchProducts(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/hipstershop.ProductCatalogService/SearchProducts', + demo__pb2.SearchProductsRequest.SerializeToString, + demo__pb2.SearchProductsResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + +class ShippingServiceStub(object): + """---------------Shipping Service---------- + + """ + + def __init__(self, channel): + """Constructor. + + Args: + channel: A grpc.Channel. + """ + self.GetQuote = channel.unary_unary( + '/hipstershop.ShippingService/GetQuote', + request_serializer=demo__pb2.GetQuoteRequest.SerializeToString, + response_deserializer=demo__pb2.GetQuoteResponse.FromString, + ) + self.ShipOrder = channel.unary_unary( + '/hipstershop.ShippingService/ShipOrder', + request_serializer=demo__pb2.ShipOrderRequest.SerializeToString, + response_deserializer=demo__pb2.ShipOrderResponse.FromString, + ) + + +class ShippingServiceServicer(object): + """---------------Shipping Service---------- + + """ + + def GetQuote(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def ShipOrder(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + +def add_ShippingServiceServicer_to_server(servicer, server): + rpc_method_handlers = { + 'GetQuote': grpc.unary_unary_rpc_method_handler( + servicer.GetQuote, + request_deserializer=demo__pb2.GetQuoteRequest.FromString, + response_serializer=demo__pb2.GetQuoteResponse.SerializeToString, + ), + 'ShipOrder': grpc.unary_unary_rpc_method_handler( + servicer.ShipOrder, + request_deserializer=demo__pb2.ShipOrderRequest.FromString, + response_serializer=demo__pb2.ShipOrderResponse.SerializeToString, + ), + } + generic_handler = grpc.method_handlers_generic_handler( + 'hipstershop.ShippingService', rpc_method_handlers) + server.add_generic_rpc_handlers((generic_handler,)) + + + # This class is part of an EXPERIMENTAL API. +class ShippingService(object): + """---------------Shipping Service---------- + + """ + + @staticmethod + def GetQuote(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/hipstershop.ShippingService/GetQuote', + demo__pb2.GetQuoteRequest.SerializeToString, + demo__pb2.GetQuoteResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def ShipOrder(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/hipstershop.ShippingService/ShipOrder', + demo__pb2.ShipOrderRequest.SerializeToString, + demo__pb2.ShipOrderResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + +class CurrencyServiceStub(object): + """-----------------Currency service----------------- + + """ + + def __init__(self, channel): + """Constructor. + + Args: + channel: A grpc.Channel. + """ + self.GetSupportedCurrencies = channel.unary_unary( + '/hipstershop.CurrencyService/GetSupportedCurrencies', + request_serializer=demo__pb2.Empty.SerializeToString, + response_deserializer=demo__pb2.GetSupportedCurrenciesResponse.FromString, + ) + self.Convert = channel.unary_unary( + '/hipstershop.CurrencyService/Convert', + request_serializer=demo__pb2.CurrencyConversionRequest.SerializeToString, + response_deserializer=demo__pb2.Money.FromString, + ) + + +class CurrencyServiceServicer(object): + """-----------------Currency service----------------- + + """ + + def GetSupportedCurrencies(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def Convert(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + +def add_CurrencyServiceServicer_to_server(servicer, server): + rpc_method_handlers = { + 'GetSupportedCurrencies': grpc.unary_unary_rpc_method_handler( + servicer.GetSupportedCurrencies, + request_deserializer=demo__pb2.Empty.FromString, + response_serializer=demo__pb2.GetSupportedCurrenciesResponse.SerializeToString, + ), + 'Convert': grpc.unary_unary_rpc_method_handler( + servicer.Convert, + request_deserializer=demo__pb2.CurrencyConversionRequest.FromString, + response_serializer=demo__pb2.Money.SerializeToString, + ), + } + generic_handler = grpc.method_handlers_generic_handler( + 'hipstershop.CurrencyService', rpc_method_handlers) + server.add_generic_rpc_handlers((generic_handler,)) + + + # This class is part of an EXPERIMENTAL API. +class CurrencyService(object): + """-----------------Currency service----------------- + + """ + + @staticmethod + def GetSupportedCurrencies(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/hipstershop.CurrencyService/GetSupportedCurrencies', + demo__pb2.Empty.SerializeToString, + demo__pb2.GetSupportedCurrenciesResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def Convert(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/hipstershop.CurrencyService/Convert', + demo__pb2.CurrencyConversionRequest.SerializeToString, + demo__pb2.Money.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + +class PaymentServiceStub(object): + """-------------Payment service----------------- + + """ + + def __init__(self, channel): + """Constructor. + + Args: + channel: A grpc.Channel. + """ + self.Charge = channel.unary_unary( + '/hipstershop.PaymentService/Charge', + request_serializer=demo__pb2.ChargeRequest.SerializeToString, + response_deserializer=demo__pb2.ChargeResponse.FromString, + ) + + +class PaymentServiceServicer(object): + """-------------Payment service----------------- + + """ + + def Charge(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + +def add_PaymentServiceServicer_to_server(servicer, server): + rpc_method_handlers = { + 'Charge': grpc.unary_unary_rpc_method_handler( + servicer.Charge, + request_deserializer=demo__pb2.ChargeRequest.FromString, + response_serializer=demo__pb2.ChargeResponse.SerializeToString, + ), + } + generic_handler = grpc.method_handlers_generic_handler( + 'hipstershop.PaymentService', rpc_method_handlers) + server.add_generic_rpc_handlers((generic_handler,)) + + + # This class is part of an EXPERIMENTAL API. +class PaymentService(object): + """-------------Payment service----------------- + + """ + + @staticmethod + def Charge(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/hipstershop.PaymentService/Charge', + demo__pb2.ChargeRequest.SerializeToString, + demo__pb2.ChargeResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + +class EmailServiceStub(object): + """-------------Email service----------------- + + """ + + def __init__(self, channel): + """Constructor. + + Args: + channel: A grpc.Channel. + """ + self.SendOrderConfirmation = channel.unary_unary( + '/hipstershop.EmailService/SendOrderConfirmation', + request_serializer=demo__pb2.SendOrderConfirmationRequest.SerializeToString, + response_deserializer=demo__pb2.Empty.FromString, + ) + + +class EmailServiceServicer(object): + """-------------Email service----------------- + + """ + + def SendOrderConfirmation(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + +def add_EmailServiceServicer_to_server(servicer, server): + rpc_method_handlers = { + 'SendOrderConfirmation': grpc.unary_unary_rpc_method_handler( + servicer.SendOrderConfirmation, + request_deserializer=demo__pb2.SendOrderConfirmationRequest.FromString, + response_serializer=demo__pb2.Empty.SerializeToString, + ), + } + generic_handler = grpc.method_handlers_generic_handler( + 'hipstershop.EmailService', rpc_method_handlers) + server.add_generic_rpc_handlers((generic_handler,)) + + + # This class is part of an EXPERIMENTAL API. +class EmailService(object): + """-------------Email service----------------- + + """ + + @staticmethod + def SendOrderConfirmation(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/hipstershop.EmailService/SendOrderConfirmation', + demo__pb2.SendOrderConfirmationRequest.SerializeToString, + demo__pb2.Empty.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + +class CheckoutServiceStub(object): + """-------------Checkout service----------------- + + """ + + def __init__(self, channel): + """Constructor. + + Args: + channel: A grpc.Channel. + """ + self.PlaceOrder = channel.unary_unary( + '/hipstershop.CheckoutService/PlaceOrder', + request_serializer=demo__pb2.PlaceOrderRequest.SerializeToString, + response_deserializer=demo__pb2.PlaceOrderResponse.FromString, + ) + + +class CheckoutServiceServicer(object): + """-------------Checkout service----------------- + + """ + + def PlaceOrder(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + +def add_CheckoutServiceServicer_to_server(servicer, server): + rpc_method_handlers = { + 'PlaceOrder': grpc.unary_unary_rpc_method_handler( + servicer.PlaceOrder, + request_deserializer=demo__pb2.PlaceOrderRequest.FromString, + response_serializer=demo__pb2.PlaceOrderResponse.SerializeToString, + ), + } + generic_handler = grpc.method_handlers_generic_handler( + 'hipstershop.CheckoutService', rpc_method_handlers) + server.add_generic_rpc_handlers((generic_handler,)) + + + # This class is part of an EXPERIMENTAL API. +class CheckoutService(object): + """-------------Checkout service----------------- + + """ + + @staticmethod + def PlaceOrder(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/hipstershop.CheckoutService/PlaceOrder', + demo__pb2.PlaceOrderRequest.SerializeToString, + demo__pb2.PlaceOrderResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + +class AdServiceStub(object): + """------------Ad service------------------ + + """ + + def __init__(self, channel): + """Constructor. + + Args: + channel: A grpc.Channel. + """ + self.GetAds = channel.unary_unary( + '/hipstershop.AdService/GetAds', + request_serializer=demo__pb2.AdRequest.SerializeToString, + response_deserializer=demo__pb2.AdResponse.FromString, + ) + + +class AdServiceServicer(object): + """------------Ad service------------------ + + """ + + def GetAds(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + +def add_AdServiceServicer_to_server(servicer, server): + rpc_method_handlers = { + 'GetAds': grpc.unary_unary_rpc_method_handler( + servicer.GetAds, + request_deserializer=demo__pb2.AdRequest.FromString, + response_serializer=demo__pb2.AdResponse.SerializeToString, + ), + } + generic_handler = grpc.method_handlers_generic_handler( + 'hipstershop.AdService', rpc_method_handlers) + server.add_generic_rpc_handlers((generic_handler,)) + + + # This class is part of an EXPERIMENTAL API. +class AdService(object): + """------------Ad service------------------ + + """ + + @staticmethod + def GetAds(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/hipstershop.AdService/GetAds', + demo__pb2.AdRequest.SerializeToString, + demo__pb2.AdResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) diff --git a/src/recommendationservice/genproto.sh b/src/recommendationservice/genproto.sh new file mode 100755 index 0000000..695d984 --- /dev/null +++ b/src/recommendationservice/genproto.sh @@ -0,0 +1,26 @@ +#!/bin/bash -eu +# +# Copyright 2018 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# [START gke_recommendationservice_genproto] + +# script to compile python protos +# +# requires gRPC tools: +# pip install -r requirements.txt + +python -m grpc_tools.protoc -I../../protos --python_out=. --grpc_python_out=. ../../protos/demo.proto + +# [END gke_recommendationservice_genproto] \ No newline at end of file diff --git a/src/recommendationservice/logger.py b/src/recommendationservice/logger.py new file mode 100755 index 0000000..dc84910 --- /dev/null +++ b/src/recommendationservice/logger.py @@ -0,0 +1,41 @@ +#!/usr/bin/python +# +# Copyright 2018 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import logging +import sys +from pythonjsonlogger import jsonlogger + +# TODO(yoshifumi) this class is duplicated since other Python services are +# not sharing the modules for logging. +class CustomJsonFormatter(jsonlogger.JsonFormatter): + def add_fields(self, log_record, record, message_dict): + super(CustomJsonFormatter, self).add_fields(log_record, record, message_dict) + if not log_record.get('timestamp'): + log_record['timestamp'] = record.created + if log_record.get('severity'): + log_record['severity'] = log_record['severity'].upper() + else: + log_record['severity'] = record.levelname + +def getJSONLogger(name): + logger = logging.getLogger(name) + handler = logging.StreamHandler(sys.stdout) + formatter = CustomJsonFormatter('%(timestamp)s %(severity)s %(name)s %(message)s') + handler.setFormatter(formatter) + logger.addHandler(handler) + logger.setLevel(logging.INFO) + logger.propagate = False + return logger diff --git a/src/recommendationservice/recommendation_server.py b/src/recommendationservice/recommendation_server.py new file mode 100755 index 0000000..7a8da7d --- /dev/null +++ b/src/recommendationservice/recommendation_server.py @@ -0,0 +1,156 @@ +#!/usr/bin/python +# +# Copyright 2018 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os +import random +import time +import traceback +from concurrent import futures + +# @TODO: Temporarily removed in https://github.com/GoogleCloudPlatform/microservices-demo/pull/3196 +# import googlecloudprofiler + +from google.auth.exceptions import DefaultCredentialsError +import grpc + +import demo_pb2 +import demo_pb2_grpc +from grpc_health.v1 import health_pb2 +from grpc_health.v1 import health_pb2_grpc + +from opentelemetry import trace +from opentelemetry.instrumentation.grpc import GrpcInstrumentorClient, GrpcInstrumentorServer +from opentelemetry.sdk.trace import TracerProvider +from opentelemetry.sdk.trace.export import BatchSpanProcessor +from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter + +from logger import getJSONLogger +logger = getJSONLogger('recommendationservice-server') + +def initStackdriverProfiling(): + project_id = None + try: + project_id = os.environ["GCP_PROJECT_ID"] + except KeyError: + # Environment variable not set + pass + + # @TODO: Temporarily removed in https://github.com/GoogleCloudPlatform/microservices-demo/pull/3196 + # for retry in range(1,4): + # try: + # if project_id: + # googlecloudprofiler.start(service='recommendation_server', service_version='1.0.0', verbose=0, project_id=project_id) + # else: + # googlecloudprofiler.start(service='recommendation_server', service_version='1.0.0', verbose=0) + # logger.info("Successfully started Stackdriver Profiler.") + # return + # except (BaseException) as exc: + # logger.info("Unable to start Stackdriver Profiler Python agent. " + str(exc)) + # if (retry < 4): + # logger.info("Sleeping %d seconds to retry Stackdriver Profiler agent initialization"%(retry*10)) + # time.sleep (1) + # else: + # logger.warning("Could not initialize Stackdriver Profiler after retrying, giving up") + return + +class RecommendationService(demo_pb2_grpc.RecommendationServiceServicer): + def ListRecommendations(self, request, context): + max_responses = 5 + # fetch list of products from product catalog stub + cat_response = product_catalog_stub.ListProducts(demo_pb2.Empty()) + product_ids = [x.id for x in cat_response.products] + filtered_products = list(set(product_ids)-set(request.product_ids)) + num_products = len(filtered_products) + num_return = min(max_responses, num_products) + # sample list of indicies to return + indices = random.sample(range(num_products), num_return) + # fetch product ids from indices + prod_list = [filtered_products[i] for i in indices] + logger.info("[Recv ListRecommendations] product_ids={}".format(prod_list)) + # build and return response + response = demo_pb2.ListRecommendationsResponse() + response.product_ids.extend(prod_list) + return response + + def Check(self, request, context): + return health_pb2.HealthCheckResponse( + status=health_pb2.HealthCheckResponse.SERVING) + + def Watch(self, request, context): + return health_pb2.HealthCheckResponse( + status=health_pb2.HealthCheckResponse.UNIMPLEMENTED) + + +if __name__ == "__main__": + logger.info("initializing recommendationservice") + + try: + if "DISABLE_PROFILER" in os.environ: + raise KeyError() + else: + logger.info("Profiler enabled.") + initStackdriverProfiling() + except KeyError: + logger.info("Profiler disabled.") + + try: + grpc_client_instrumentor = GrpcInstrumentorClient() + grpc_client_instrumentor.instrument() + grpc_server_instrumentor = GrpcInstrumentorServer() + grpc_server_instrumentor.instrument() + if os.environ["ENABLE_TRACING"] == "1": + trace.set_tracer_provider(TracerProvider()) + otel_endpoint = os.getenv("COLLECTOR_SERVICE_ADDR", "localhost:4317") + trace.get_tracer_provider().add_span_processor( + BatchSpanProcessor( + OTLPSpanExporter( + endpoint = otel_endpoint, + insecure = True + ) + ) + ) + except (KeyError, DefaultCredentialsError): + logger.info("Tracing disabled.") + except Exception as e: + logger.warn(f"Exception on Cloud Trace setup: {traceback.format_exc()}, tracing disabled.") + + port = os.environ.get('PORT', "8080") + catalog_addr = os.environ.get('PRODUCT_CATALOG_SERVICE_ADDR', '') + if catalog_addr == "": + raise Exception('PRODUCT_CATALOG_SERVICE_ADDR environment variable not set') + logger.info("product catalog address: " + catalog_addr) + channel = grpc.insecure_channel(catalog_addr) + product_catalog_stub = demo_pb2_grpc.ProductCatalogServiceStub(channel) + + # create gRPC server + server = grpc.server(futures.ThreadPoolExecutor(max_workers=10)) + + # add class to gRPC server + service = RecommendationService() + demo_pb2_grpc.add_RecommendationServiceServicer_to_server(service, server) + health_pb2_grpc.add_HealthServicer_to_server(service, server) + + # start server + logger.info("listening on port: " + port) + server.add_insecure_port('[::]:'+port) + server.start() + + # keep alive + try: + while True: + time.sleep(10000) + except KeyboardInterrupt: + server.stop(0) diff --git a/src/recommendationservice/requirements.in b/src/recommendationservice/requirements.in new file mode 100644 index 0000000..d47ec4d --- /dev/null +++ b/src/recommendationservice/requirements.in @@ -0,0 +1,8 @@ +google-api-core==2.28.1 +grpcio-health-checking==1.76.0 +python-json-logger==4.0.0 +requests==2.32.5 +rsa==4.9.1 +opentelemetry-distro==0.60b1 +opentelemetry-instrumentation-grpc==0.60b1 +opentelemetry-exporter-otlp-proto-grpc==1.39.1 diff --git a/src/recommendationservice/requirements.txt b/src/recommendationservice/requirements.txt new file mode 100644 index 0000000..b5b6a2c --- /dev/null +++ b/src/recommendationservice/requirements.txt @@ -0,0 +1,101 @@ +# This file was autogenerated by uv via the following command: +# uv pip compile requirements.in -o requirements.txt +cachetools==5.3.2 + # via google-auth +certifi==2024.7.4 + # via requests +charset-normalizer==3.3.2 + # via requests +google-api-core==2.28.1 + # via -r requirements.in +google-auth==2.23.4 + # via google-api-core +googleapis-common-protos==1.72.0 + # via + # google-api-core + # opentelemetry-exporter-otlp-proto-grpc +grpcio==1.76.0 + # via + # grpcio-health-checking + # opentelemetry-exporter-otlp-proto-grpc +grpcio-health-checking==1.76.0 + # via -r requirements.in +idna==3.7 + # via requests +importlib-metadata==6.8.0 + # via opentelemetry-api +opentelemetry-api==1.39.1 + # via + # opentelemetry-distro + # opentelemetry-exporter-otlp-proto-grpc + # opentelemetry-instrumentation + # opentelemetry-instrumentation-grpc + # opentelemetry-sdk + # opentelemetry-semantic-conventions +opentelemetry-distro==0.60b1 + # via -r requirements.in +opentelemetry-exporter-otlp-proto-common==1.39.1 + # via opentelemetry-exporter-otlp-proto-grpc +opentelemetry-exporter-otlp-proto-grpc==1.39.1 + # via -r requirements.in +opentelemetry-instrumentation==0.60b1 + # via + # opentelemetry-distro + # opentelemetry-instrumentation-grpc +opentelemetry-instrumentation-grpc==0.60b1 + # via -r requirements.in +opentelemetry-proto==1.39.1 + # via + # opentelemetry-exporter-otlp-proto-common + # opentelemetry-exporter-otlp-proto-grpc +opentelemetry-sdk==1.39.1 + # via + # opentelemetry-distro + # opentelemetry-exporter-otlp-proto-grpc +opentelemetry-semantic-conventions==0.60b1 + # via + # opentelemetry-instrumentation + # opentelemetry-instrumentation-grpc + # opentelemetry-sdk +packaging==25.0 + # via opentelemetry-instrumentation +proto-plus==1.27.0 + # via google-api-core +protobuf==6.33.2 + # via + # google-api-core + # googleapis-common-protos + # grpcio-health-checking + # opentelemetry-proto + # proto-plus +pyasn1==0.5.0 + # via + # pyasn1-modules + # rsa +pyasn1-modules==0.3.0 + # via google-auth +python-json-logger==4.0.0 + # via -r requirements.in +requests==2.32.5 + # via + # -r requirements.in + # google-api-core +rsa==4.9.1 + # via + # -r requirements.in + # google-auth +typing-extensions==4.15.0 + # via + # grpcio + # opentelemetry-api + # opentelemetry-exporter-otlp-proto-grpc + # opentelemetry-sdk + # opentelemetry-semantic-conventions +urllib3==2.6.3 + # via requests +wrapt==1.16.0 + # via + # opentelemetry-instrumentation + # opentelemetry-instrumentation-grpc +zipp==3.19.1 + # via importlib-metadata diff --git a/src/shippingservice/.dockerignore b/src/shippingservice/.dockerignore new file mode 100644 index 0000000..48b8bf9 --- /dev/null +++ b/src/shippingservice/.dockerignore @@ -0,0 +1 @@ +vendor/ diff --git a/src/shippingservice/Dockerfile b/src/shippingservice/Dockerfile new file mode 100644 index 0000000..4a3fb85 --- /dev/null +++ b/src/shippingservice/Dockerfile @@ -0,0 +1,41 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +FROM --platform=$BUILDPLATFORM golang:1.25.6-alpine@sha256:98e6cffc31ccc44c7c15d83df1d69891efee8115a5bb7ede2bf30a38af3e3c92 AS builder +ARG TARGETOS +ARG TARGETARCH +WORKDIR /src + +# restore dependencies +COPY go.mod go.sum ./ +RUN go mod download +COPY . . + +# Skaffold passes in debug-oriented compiler flags +ARG SKAFFOLD_GO_GCFLAGS +RUN GOOS=${TARGETOS} GOARCH=${TARGETARCH} CGO_ENABLED=0 go build -ldflags="-s -w" -gcflags="${SKAFFOLD_GO_GCFLAGS}" -o /go/bin/shippingservice . + +FROM gcr.io/distroless/static + +WORKDIR /src +COPY --from=builder /go/bin/shippingservice /src/shippingservice +ENV APP_PORT=50051 + +# Definition of this variable is used by 'skaffold debug' to identify a golang binary. +# Default behavior - a failure prints a stack trace for the current goroutine. +# See https://golang.org/pkg/runtime/ +ENV GOTRACEBACK=single + +EXPOSE 50051 +ENTRYPOINT ["/src/shippingservice"] diff --git a/src/shippingservice/README.md b/src/shippingservice/README.md new file mode 100644 index 0000000..cbbfdaa --- /dev/null +++ b/src/shippingservice/README.md @@ -0,0 +1,23 @@ +# Shipping Service + +The Shipping service provides price quote, tracking IDs, and the impression of order fulfillment & shipping processes. + +## Local + +Run the following command to restore dependencies to `vendor/` directory: + + dep ensure --vendor-only + +## Build + +From `src/shippingservice`, run: + +``` +docker build ./ +``` + +## Test + +``` +go test . +``` diff --git a/src/shippingservice/genproto.sh b/src/shippingservice/genproto.sh new file mode 100755 index 0000000..b8b2f0c --- /dev/null +++ b/src/shippingservice/genproto.sh @@ -0,0 +1,25 @@ +#!/bin/bash -eu +# +# Copyright 2018 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# [START gke_shippingservice_genproto] + +PATH=$PATH:$(go env GOPATH)/bin +protodir=../../protos +outdir=./genproto + +protoc --proto_path=$protodir --go_out=./$outdir --go_opt=paths=source_relative --go-grpc_out=./$outdir --go-grpc_opt=paths=source_relative $protodir/demo.proto + +# [END gke_shippingservice_genproto] \ No newline at end of file diff --git a/src/shippingservice/genproto/demo.pb.go b/src/shippingservice/genproto/demo.pb.go new file mode 100644 index 0000000..332cb9c --- /dev/null +++ b/src/shippingservice/genproto/demo.pb.go @@ -0,0 +1,2610 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.34.2 +// protoc v3.6.1 +// source: demo.proto + +package hipstershop + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type CartItem struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ProductId string `protobuf:"bytes,1,opt,name=product_id,json=productId,proto3" json:"product_id,omitempty"` + Quantity int32 `protobuf:"varint,2,opt,name=quantity,proto3" json:"quantity,omitempty"` +} + +func (x *CartItem) Reset() { + *x = CartItem{} + if protoimpl.UnsafeEnabled { + mi := &file_demo_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CartItem) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CartItem) ProtoMessage() {} + +func (x *CartItem) ProtoReflect() protoreflect.Message { + mi := &file_demo_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CartItem.ProtoReflect.Descriptor instead. +func (*CartItem) Descriptor() ([]byte, []int) { + return file_demo_proto_rawDescGZIP(), []int{0} +} + +func (x *CartItem) GetProductId() string { + if x != nil { + return x.ProductId + } + return "" +} + +func (x *CartItem) GetQuantity() int32 { + if x != nil { + return x.Quantity + } + return 0 +} + +type AddItemRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + UserId string `protobuf:"bytes,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` + Item *CartItem `protobuf:"bytes,2,opt,name=item,proto3" json:"item,omitempty"` +} + +func (x *AddItemRequest) Reset() { + *x = AddItemRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_demo_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *AddItemRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AddItemRequest) ProtoMessage() {} + +func (x *AddItemRequest) ProtoReflect() protoreflect.Message { + mi := &file_demo_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AddItemRequest.ProtoReflect.Descriptor instead. +func (*AddItemRequest) Descriptor() ([]byte, []int) { + return file_demo_proto_rawDescGZIP(), []int{1} +} + +func (x *AddItemRequest) GetUserId() string { + if x != nil { + return x.UserId + } + return "" +} + +func (x *AddItemRequest) GetItem() *CartItem { + if x != nil { + return x.Item + } + return nil +} + +type EmptyCartRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + UserId string `protobuf:"bytes,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` +} + +func (x *EmptyCartRequest) Reset() { + *x = EmptyCartRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_demo_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *EmptyCartRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*EmptyCartRequest) ProtoMessage() {} + +func (x *EmptyCartRequest) ProtoReflect() protoreflect.Message { + mi := &file_demo_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use EmptyCartRequest.ProtoReflect.Descriptor instead. +func (*EmptyCartRequest) Descriptor() ([]byte, []int) { + return file_demo_proto_rawDescGZIP(), []int{2} +} + +func (x *EmptyCartRequest) GetUserId() string { + if x != nil { + return x.UserId + } + return "" +} + +type GetCartRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + UserId string `protobuf:"bytes,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` +} + +func (x *GetCartRequest) Reset() { + *x = GetCartRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_demo_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetCartRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetCartRequest) ProtoMessage() {} + +func (x *GetCartRequest) ProtoReflect() protoreflect.Message { + mi := &file_demo_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetCartRequest.ProtoReflect.Descriptor instead. +func (*GetCartRequest) Descriptor() ([]byte, []int) { + return file_demo_proto_rawDescGZIP(), []int{3} +} + +func (x *GetCartRequest) GetUserId() string { + if x != nil { + return x.UserId + } + return "" +} + +type Cart struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + UserId string `protobuf:"bytes,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` + Items []*CartItem `protobuf:"bytes,2,rep,name=items,proto3" json:"items,omitempty"` +} + +func (x *Cart) Reset() { + *x = Cart{} + if protoimpl.UnsafeEnabled { + mi := &file_demo_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Cart) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Cart) ProtoMessage() {} + +func (x *Cart) ProtoReflect() protoreflect.Message { + mi := &file_demo_proto_msgTypes[4] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Cart.ProtoReflect.Descriptor instead. +func (*Cart) Descriptor() ([]byte, []int) { + return file_demo_proto_rawDescGZIP(), []int{4} +} + +func (x *Cart) GetUserId() string { + if x != nil { + return x.UserId + } + return "" +} + +func (x *Cart) GetItems() []*CartItem { + if x != nil { + return x.Items + } + return nil +} + +type Empty struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *Empty) Reset() { + *x = Empty{} + if protoimpl.UnsafeEnabled { + mi := &file_demo_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Empty) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Empty) ProtoMessage() {} + +func (x *Empty) ProtoReflect() protoreflect.Message { + mi := &file_demo_proto_msgTypes[5] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Empty.ProtoReflect.Descriptor instead. +func (*Empty) Descriptor() ([]byte, []int) { + return file_demo_proto_rawDescGZIP(), []int{5} +} + +type ListRecommendationsRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + UserId string `protobuf:"bytes,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` + ProductIds []string `protobuf:"bytes,2,rep,name=product_ids,json=productIds,proto3" json:"product_ids,omitempty"` +} + +func (x *ListRecommendationsRequest) Reset() { + *x = ListRecommendationsRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_demo_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListRecommendationsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListRecommendationsRequest) ProtoMessage() {} + +func (x *ListRecommendationsRequest) ProtoReflect() protoreflect.Message { + mi := &file_demo_proto_msgTypes[6] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListRecommendationsRequest.ProtoReflect.Descriptor instead. +func (*ListRecommendationsRequest) Descriptor() ([]byte, []int) { + return file_demo_proto_rawDescGZIP(), []int{6} +} + +func (x *ListRecommendationsRequest) GetUserId() string { + if x != nil { + return x.UserId + } + return "" +} + +func (x *ListRecommendationsRequest) GetProductIds() []string { + if x != nil { + return x.ProductIds + } + return nil +} + +type ListRecommendationsResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ProductIds []string `protobuf:"bytes,1,rep,name=product_ids,json=productIds,proto3" json:"product_ids,omitempty"` +} + +func (x *ListRecommendationsResponse) Reset() { + *x = ListRecommendationsResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_demo_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListRecommendationsResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListRecommendationsResponse) ProtoMessage() {} + +func (x *ListRecommendationsResponse) ProtoReflect() protoreflect.Message { + mi := &file_demo_proto_msgTypes[7] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListRecommendationsResponse.ProtoReflect.Descriptor instead. +func (*ListRecommendationsResponse) Descriptor() ([]byte, []int) { + return file_demo_proto_rawDescGZIP(), []int{7} +} + +func (x *ListRecommendationsResponse) GetProductIds() []string { + if x != nil { + return x.ProductIds + } + return nil +} + +type Product struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` + Description string `protobuf:"bytes,3,opt,name=description,proto3" json:"description,omitempty"` + Picture string `protobuf:"bytes,4,opt,name=picture,proto3" json:"picture,omitempty"` + PriceUsd *Money `protobuf:"bytes,5,opt,name=price_usd,json=priceUsd,proto3" json:"price_usd,omitempty"` + // Categories such as "clothing" or "kitchen" that can be used to look up + // other related products. + Categories []string `protobuf:"bytes,6,rep,name=categories,proto3" json:"categories,omitempty"` +} + +func (x *Product) Reset() { + *x = Product{} + if protoimpl.UnsafeEnabled { + mi := &file_demo_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Product) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Product) ProtoMessage() {} + +func (x *Product) ProtoReflect() protoreflect.Message { + mi := &file_demo_proto_msgTypes[8] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Product.ProtoReflect.Descriptor instead. +func (*Product) Descriptor() ([]byte, []int) { + return file_demo_proto_rawDescGZIP(), []int{8} +} + +func (x *Product) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *Product) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *Product) GetDescription() string { + if x != nil { + return x.Description + } + return "" +} + +func (x *Product) GetPicture() string { + if x != nil { + return x.Picture + } + return "" +} + +func (x *Product) GetPriceUsd() *Money { + if x != nil { + return x.PriceUsd + } + return nil +} + +func (x *Product) GetCategories() []string { + if x != nil { + return x.Categories + } + return nil +} + +type ListProductsResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Products []*Product `protobuf:"bytes,1,rep,name=products,proto3" json:"products,omitempty"` +} + +func (x *ListProductsResponse) Reset() { + *x = ListProductsResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_demo_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListProductsResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListProductsResponse) ProtoMessage() {} + +func (x *ListProductsResponse) ProtoReflect() protoreflect.Message { + mi := &file_demo_proto_msgTypes[9] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListProductsResponse.ProtoReflect.Descriptor instead. +func (*ListProductsResponse) Descriptor() ([]byte, []int) { + return file_demo_proto_rawDescGZIP(), []int{9} +} + +func (x *ListProductsResponse) GetProducts() []*Product { + if x != nil { + return x.Products + } + return nil +} + +type GetProductRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` +} + +func (x *GetProductRequest) Reset() { + *x = GetProductRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_demo_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetProductRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetProductRequest) ProtoMessage() {} + +func (x *GetProductRequest) ProtoReflect() protoreflect.Message { + mi := &file_demo_proto_msgTypes[10] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetProductRequest.ProtoReflect.Descriptor instead. +func (*GetProductRequest) Descriptor() ([]byte, []int) { + return file_demo_proto_rawDescGZIP(), []int{10} +} + +func (x *GetProductRequest) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +type SearchProductsRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Query string `protobuf:"bytes,1,opt,name=query,proto3" json:"query,omitempty"` +} + +func (x *SearchProductsRequest) Reset() { + *x = SearchProductsRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_demo_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SearchProductsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SearchProductsRequest) ProtoMessage() {} + +func (x *SearchProductsRequest) ProtoReflect() protoreflect.Message { + mi := &file_demo_proto_msgTypes[11] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SearchProductsRequest.ProtoReflect.Descriptor instead. +func (*SearchProductsRequest) Descriptor() ([]byte, []int) { + return file_demo_proto_rawDescGZIP(), []int{11} +} + +func (x *SearchProductsRequest) GetQuery() string { + if x != nil { + return x.Query + } + return "" +} + +type SearchProductsResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Results []*Product `protobuf:"bytes,1,rep,name=results,proto3" json:"results,omitempty"` +} + +func (x *SearchProductsResponse) Reset() { + *x = SearchProductsResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_demo_proto_msgTypes[12] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SearchProductsResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SearchProductsResponse) ProtoMessage() {} + +func (x *SearchProductsResponse) ProtoReflect() protoreflect.Message { + mi := &file_demo_proto_msgTypes[12] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SearchProductsResponse.ProtoReflect.Descriptor instead. +func (*SearchProductsResponse) Descriptor() ([]byte, []int) { + return file_demo_proto_rawDescGZIP(), []int{12} +} + +func (x *SearchProductsResponse) GetResults() []*Product { + if x != nil { + return x.Results + } + return nil +} + +type GetQuoteRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Address *Address `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"` + Items []*CartItem `protobuf:"bytes,2,rep,name=items,proto3" json:"items,omitempty"` +} + +func (x *GetQuoteRequest) Reset() { + *x = GetQuoteRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_demo_proto_msgTypes[13] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetQuoteRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetQuoteRequest) ProtoMessage() {} + +func (x *GetQuoteRequest) ProtoReflect() protoreflect.Message { + mi := &file_demo_proto_msgTypes[13] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetQuoteRequest.ProtoReflect.Descriptor instead. +func (*GetQuoteRequest) Descriptor() ([]byte, []int) { + return file_demo_proto_rawDescGZIP(), []int{13} +} + +func (x *GetQuoteRequest) GetAddress() *Address { + if x != nil { + return x.Address + } + return nil +} + +func (x *GetQuoteRequest) GetItems() []*CartItem { + if x != nil { + return x.Items + } + return nil +} + +type GetQuoteResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + CostUsd *Money `protobuf:"bytes,1,opt,name=cost_usd,json=costUsd,proto3" json:"cost_usd,omitempty"` +} + +func (x *GetQuoteResponse) Reset() { + *x = GetQuoteResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_demo_proto_msgTypes[14] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetQuoteResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetQuoteResponse) ProtoMessage() {} + +func (x *GetQuoteResponse) ProtoReflect() protoreflect.Message { + mi := &file_demo_proto_msgTypes[14] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetQuoteResponse.ProtoReflect.Descriptor instead. +func (*GetQuoteResponse) Descriptor() ([]byte, []int) { + return file_demo_proto_rawDescGZIP(), []int{14} +} + +func (x *GetQuoteResponse) GetCostUsd() *Money { + if x != nil { + return x.CostUsd + } + return nil +} + +type ShipOrderRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Address *Address `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"` + Items []*CartItem `protobuf:"bytes,2,rep,name=items,proto3" json:"items,omitempty"` +} + +func (x *ShipOrderRequest) Reset() { + *x = ShipOrderRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_demo_proto_msgTypes[15] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ShipOrderRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ShipOrderRequest) ProtoMessage() {} + +func (x *ShipOrderRequest) ProtoReflect() protoreflect.Message { + mi := &file_demo_proto_msgTypes[15] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ShipOrderRequest.ProtoReflect.Descriptor instead. +func (*ShipOrderRequest) Descriptor() ([]byte, []int) { + return file_demo_proto_rawDescGZIP(), []int{15} +} + +func (x *ShipOrderRequest) GetAddress() *Address { + if x != nil { + return x.Address + } + return nil +} + +func (x *ShipOrderRequest) GetItems() []*CartItem { + if x != nil { + return x.Items + } + return nil +} + +type ShipOrderResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + TrackingId string `protobuf:"bytes,1,opt,name=tracking_id,json=trackingId,proto3" json:"tracking_id,omitempty"` +} + +func (x *ShipOrderResponse) Reset() { + *x = ShipOrderResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_demo_proto_msgTypes[16] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ShipOrderResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ShipOrderResponse) ProtoMessage() {} + +func (x *ShipOrderResponse) ProtoReflect() protoreflect.Message { + mi := &file_demo_proto_msgTypes[16] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ShipOrderResponse.ProtoReflect.Descriptor instead. +func (*ShipOrderResponse) Descriptor() ([]byte, []int) { + return file_demo_proto_rawDescGZIP(), []int{16} +} + +func (x *ShipOrderResponse) GetTrackingId() string { + if x != nil { + return x.TrackingId + } + return "" +} + +type Address struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + StreetAddress string `protobuf:"bytes,1,opt,name=street_address,json=streetAddress,proto3" json:"street_address,omitempty"` + City string `protobuf:"bytes,2,opt,name=city,proto3" json:"city,omitempty"` + State string `protobuf:"bytes,3,opt,name=state,proto3" json:"state,omitempty"` + Country string `protobuf:"bytes,4,opt,name=country,proto3" json:"country,omitempty"` + ZipCode int32 `protobuf:"varint,5,opt,name=zip_code,json=zipCode,proto3" json:"zip_code,omitempty"` +} + +func (x *Address) Reset() { + *x = Address{} + if protoimpl.UnsafeEnabled { + mi := &file_demo_proto_msgTypes[17] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Address) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Address) ProtoMessage() {} + +func (x *Address) ProtoReflect() protoreflect.Message { + mi := &file_demo_proto_msgTypes[17] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Address.ProtoReflect.Descriptor instead. +func (*Address) Descriptor() ([]byte, []int) { + return file_demo_proto_rawDescGZIP(), []int{17} +} + +func (x *Address) GetStreetAddress() string { + if x != nil { + return x.StreetAddress + } + return "" +} + +func (x *Address) GetCity() string { + if x != nil { + return x.City + } + return "" +} + +func (x *Address) GetState() string { + if x != nil { + return x.State + } + return "" +} + +func (x *Address) GetCountry() string { + if x != nil { + return x.Country + } + return "" +} + +func (x *Address) GetZipCode() int32 { + if x != nil { + return x.ZipCode + } + return 0 +} + +// Represents an amount of money with its currency type. +type Money struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The 3-letter currency code defined in ISO 4217. + CurrencyCode string `protobuf:"bytes,1,opt,name=currency_code,json=currencyCode,proto3" json:"currency_code,omitempty"` + // The whole units of the amount. + // For example if `currencyCode` is `"USD"`, then 1 unit is one US dollar. + Units int64 `protobuf:"varint,2,opt,name=units,proto3" json:"units,omitempty"` + // Number of nano (10^-9) units of the amount. + // The value must be between -999,999,999 and +999,999,999 inclusive. + // If `units` is positive, `nanos` must be positive or zero. + // If `units` is zero, `nanos` can be positive, zero, or negative. + // If `units` is negative, `nanos` must be negative or zero. + // For example $-1.75 is represented as `units`=-1 and `nanos`=-750,000,000. + Nanos int32 `protobuf:"varint,3,opt,name=nanos,proto3" json:"nanos,omitempty"` +} + +func (x *Money) Reset() { + *x = Money{} + if protoimpl.UnsafeEnabled { + mi := &file_demo_proto_msgTypes[18] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Money) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Money) ProtoMessage() {} + +func (x *Money) ProtoReflect() protoreflect.Message { + mi := &file_demo_proto_msgTypes[18] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Money.ProtoReflect.Descriptor instead. +func (*Money) Descriptor() ([]byte, []int) { + return file_demo_proto_rawDescGZIP(), []int{18} +} + +func (x *Money) GetCurrencyCode() string { + if x != nil { + return x.CurrencyCode + } + return "" +} + +func (x *Money) GetUnits() int64 { + if x != nil { + return x.Units + } + return 0 +} + +func (x *Money) GetNanos() int32 { + if x != nil { + return x.Nanos + } + return 0 +} + +type GetSupportedCurrenciesResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The 3-letter currency code defined in ISO 4217. + CurrencyCodes []string `protobuf:"bytes,1,rep,name=currency_codes,json=currencyCodes,proto3" json:"currency_codes,omitempty"` +} + +func (x *GetSupportedCurrenciesResponse) Reset() { + *x = GetSupportedCurrenciesResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_demo_proto_msgTypes[19] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetSupportedCurrenciesResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetSupportedCurrenciesResponse) ProtoMessage() {} + +func (x *GetSupportedCurrenciesResponse) ProtoReflect() protoreflect.Message { + mi := &file_demo_proto_msgTypes[19] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetSupportedCurrenciesResponse.ProtoReflect.Descriptor instead. +func (*GetSupportedCurrenciesResponse) Descriptor() ([]byte, []int) { + return file_demo_proto_rawDescGZIP(), []int{19} +} + +func (x *GetSupportedCurrenciesResponse) GetCurrencyCodes() []string { + if x != nil { + return x.CurrencyCodes + } + return nil +} + +type CurrencyConversionRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + From *Money `protobuf:"bytes,1,opt,name=from,proto3" json:"from,omitempty"` + // The 3-letter currency code defined in ISO 4217. + ToCode string `protobuf:"bytes,2,opt,name=to_code,json=toCode,proto3" json:"to_code,omitempty"` +} + +func (x *CurrencyConversionRequest) Reset() { + *x = CurrencyConversionRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_demo_proto_msgTypes[20] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CurrencyConversionRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CurrencyConversionRequest) ProtoMessage() {} + +func (x *CurrencyConversionRequest) ProtoReflect() protoreflect.Message { + mi := &file_demo_proto_msgTypes[20] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CurrencyConversionRequest.ProtoReflect.Descriptor instead. +func (*CurrencyConversionRequest) Descriptor() ([]byte, []int) { + return file_demo_proto_rawDescGZIP(), []int{20} +} + +func (x *CurrencyConversionRequest) GetFrom() *Money { + if x != nil { + return x.From + } + return nil +} + +func (x *CurrencyConversionRequest) GetToCode() string { + if x != nil { + return x.ToCode + } + return "" +} + +type CreditCardInfo struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + CreditCardNumber string `protobuf:"bytes,1,opt,name=credit_card_number,json=creditCardNumber,proto3" json:"credit_card_number,omitempty"` + CreditCardCvv int32 `protobuf:"varint,2,opt,name=credit_card_cvv,json=creditCardCvv,proto3" json:"credit_card_cvv,omitempty"` + CreditCardExpirationYear int32 `protobuf:"varint,3,opt,name=credit_card_expiration_year,json=creditCardExpirationYear,proto3" json:"credit_card_expiration_year,omitempty"` + CreditCardExpirationMonth int32 `protobuf:"varint,4,opt,name=credit_card_expiration_month,json=creditCardExpirationMonth,proto3" json:"credit_card_expiration_month,omitempty"` +} + +func (x *CreditCardInfo) Reset() { + *x = CreditCardInfo{} + if protoimpl.UnsafeEnabled { + mi := &file_demo_proto_msgTypes[21] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CreditCardInfo) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreditCardInfo) ProtoMessage() {} + +func (x *CreditCardInfo) ProtoReflect() protoreflect.Message { + mi := &file_demo_proto_msgTypes[21] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CreditCardInfo.ProtoReflect.Descriptor instead. +func (*CreditCardInfo) Descriptor() ([]byte, []int) { + return file_demo_proto_rawDescGZIP(), []int{21} +} + +func (x *CreditCardInfo) GetCreditCardNumber() string { + if x != nil { + return x.CreditCardNumber + } + return "" +} + +func (x *CreditCardInfo) GetCreditCardCvv() int32 { + if x != nil { + return x.CreditCardCvv + } + return 0 +} + +func (x *CreditCardInfo) GetCreditCardExpirationYear() int32 { + if x != nil { + return x.CreditCardExpirationYear + } + return 0 +} + +func (x *CreditCardInfo) GetCreditCardExpirationMonth() int32 { + if x != nil { + return x.CreditCardExpirationMonth + } + return 0 +} + +type ChargeRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Amount *Money `protobuf:"bytes,1,opt,name=amount,proto3" json:"amount,omitempty"` + CreditCard *CreditCardInfo `protobuf:"bytes,2,opt,name=credit_card,json=creditCard,proto3" json:"credit_card,omitempty"` +} + +func (x *ChargeRequest) Reset() { + *x = ChargeRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_demo_proto_msgTypes[22] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ChargeRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ChargeRequest) ProtoMessage() {} + +func (x *ChargeRequest) ProtoReflect() protoreflect.Message { + mi := &file_demo_proto_msgTypes[22] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ChargeRequest.ProtoReflect.Descriptor instead. +func (*ChargeRequest) Descriptor() ([]byte, []int) { + return file_demo_proto_rawDescGZIP(), []int{22} +} + +func (x *ChargeRequest) GetAmount() *Money { + if x != nil { + return x.Amount + } + return nil +} + +func (x *ChargeRequest) GetCreditCard() *CreditCardInfo { + if x != nil { + return x.CreditCard + } + return nil +} + +type ChargeResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + TransactionId string `protobuf:"bytes,1,opt,name=transaction_id,json=transactionId,proto3" json:"transaction_id,omitempty"` +} + +func (x *ChargeResponse) Reset() { + *x = ChargeResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_demo_proto_msgTypes[23] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ChargeResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ChargeResponse) ProtoMessage() {} + +func (x *ChargeResponse) ProtoReflect() protoreflect.Message { + mi := &file_demo_proto_msgTypes[23] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ChargeResponse.ProtoReflect.Descriptor instead. +func (*ChargeResponse) Descriptor() ([]byte, []int) { + return file_demo_proto_rawDescGZIP(), []int{23} +} + +func (x *ChargeResponse) GetTransactionId() string { + if x != nil { + return x.TransactionId + } + return "" +} + +type OrderItem struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Item *CartItem `protobuf:"bytes,1,opt,name=item,proto3" json:"item,omitempty"` + Cost *Money `protobuf:"bytes,2,opt,name=cost,proto3" json:"cost,omitempty"` +} + +func (x *OrderItem) Reset() { + *x = OrderItem{} + if protoimpl.UnsafeEnabled { + mi := &file_demo_proto_msgTypes[24] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *OrderItem) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*OrderItem) ProtoMessage() {} + +func (x *OrderItem) ProtoReflect() protoreflect.Message { + mi := &file_demo_proto_msgTypes[24] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use OrderItem.ProtoReflect.Descriptor instead. +func (*OrderItem) Descriptor() ([]byte, []int) { + return file_demo_proto_rawDescGZIP(), []int{24} +} + +func (x *OrderItem) GetItem() *CartItem { + if x != nil { + return x.Item + } + return nil +} + +func (x *OrderItem) GetCost() *Money { + if x != nil { + return x.Cost + } + return nil +} + +type OrderResult struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + OrderId string `protobuf:"bytes,1,opt,name=order_id,json=orderId,proto3" json:"order_id,omitempty"` + ShippingTrackingId string `protobuf:"bytes,2,opt,name=shipping_tracking_id,json=shippingTrackingId,proto3" json:"shipping_tracking_id,omitempty"` + ShippingCost *Money `protobuf:"bytes,3,opt,name=shipping_cost,json=shippingCost,proto3" json:"shipping_cost,omitempty"` + ShippingAddress *Address `protobuf:"bytes,4,opt,name=shipping_address,json=shippingAddress,proto3" json:"shipping_address,omitempty"` + Items []*OrderItem `protobuf:"bytes,5,rep,name=items,proto3" json:"items,omitempty"` +} + +func (x *OrderResult) Reset() { + *x = OrderResult{} + if protoimpl.UnsafeEnabled { + mi := &file_demo_proto_msgTypes[25] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *OrderResult) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*OrderResult) ProtoMessage() {} + +func (x *OrderResult) ProtoReflect() protoreflect.Message { + mi := &file_demo_proto_msgTypes[25] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use OrderResult.ProtoReflect.Descriptor instead. +func (*OrderResult) Descriptor() ([]byte, []int) { + return file_demo_proto_rawDescGZIP(), []int{25} +} + +func (x *OrderResult) GetOrderId() string { + if x != nil { + return x.OrderId + } + return "" +} + +func (x *OrderResult) GetShippingTrackingId() string { + if x != nil { + return x.ShippingTrackingId + } + return "" +} + +func (x *OrderResult) GetShippingCost() *Money { + if x != nil { + return x.ShippingCost + } + return nil +} + +func (x *OrderResult) GetShippingAddress() *Address { + if x != nil { + return x.ShippingAddress + } + return nil +} + +func (x *OrderResult) GetItems() []*OrderItem { + if x != nil { + return x.Items + } + return nil +} + +type SendOrderConfirmationRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Email string `protobuf:"bytes,1,opt,name=email,proto3" json:"email,omitempty"` + Order *OrderResult `protobuf:"bytes,2,opt,name=order,proto3" json:"order,omitempty"` +} + +func (x *SendOrderConfirmationRequest) Reset() { + *x = SendOrderConfirmationRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_demo_proto_msgTypes[26] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SendOrderConfirmationRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SendOrderConfirmationRequest) ProtoMessage() {} + +func (x *SendOrderConfirmationRequest) ProtoReflect() protoreflect.Message { + mi := &file_demo_proto_msgTypes[26] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SendOrderConfirmationRequest.ProtoReflect.Descriptor instead. +func (*SendOrderConfirmationRequest) Descriptor() ([]byte, []int) { + return file_demo_proto_rawDescGZIP(), []int{26} +} + +func (x *SendOrderConfirmationRequest) GetEmail() string { + if x != nil { + return x.Email + } + return "" +} + +func (x *SendOrderConfirmationRequest) GetOrder() *OrderResult { + if x != nil { + return x.Order + } + return nil +} + +type PlaceOrderRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + UserId string `protobuf:"bytes,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` + UserCurrency string `protobuf:"bytes,2,opt,name=user_currency,json=userCurrency,proto3" json:"user_currency,omitempty"` + Address *Address `protobuf:"bytes,3,opt,name=address,proto3" json:"address,omitempty"` + Email string `protobuf:"bytes,5,opt,name=email,proto3" json:"email,omitempty"` + CreditCard *CreditCardInfo `protobuf:"bytes,6,opt,name=credit_card,json=creditCard,proto3" json:"credit_card,omitempty"` +} + +func (x *PlaceOrderRequest) Reset() { + *x = PlaceOrderRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_demo_proto_msgTypes[27] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *PlaceOrderRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PlaceOrderRequest) ProtoMessage() {} + +func (x *PlaceOrderRequest) ProtoReflect() protoreflect.Message { + mi := &file_demo_proto_msgTypes[27] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PlaceOrderRequest.ProtoReflect.Descriptor instead. +func (*PlaceOrderRequest) Descriptor() ([]byte, []int) { + return file_demo_proto_rawDescGZIP(), []int{27} +} + +func (x *PlaceOrderRequest) GetUserId() string { + if x != nil { + return x.UserId + } + return "" +} + +func (x *PlaceOrderRequest) GetUserCurrency() string { + if x != nil { + return x.UserCurrency + } + return "" +} + +func (x *PlaceOrderRequest) GetAddress() *Address { + if x != nil { + return x.Address + } + return nil +} + +func (x *PlaceOrderRequest) GetEmail() string { + if x != nil { + return x.Email + } + return "" +} + +func (x *PlaceOrderRequest) GetCreditCard() *CreditCardInfo { + if x != nil { + return x.CreditCard + } + return nil +} + +type PlaceOrderResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Order *OrderResult `protobuf:"bytes,1,opt,name=order,proto3" json:"order,omitempty"` +} + +func (x *PlaceOrderResponse) Reset() { + *x = PlaceOrderResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_demo_proto_msgTypes[28] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *PlaceOrderResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PlaceOrderResponse) ProtoMessage() {} + +func (x *PlaceOrderResponse) ProtoReflect() protoreflect.Message { + mi := &file_demo_proto_msgTypes[28] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PlaceOrderResponse.ProtoReflect.Descriptor instead. +func (*PlaceOrderResponse) Descriptor() ([]byte, []int) { + return file_demo_proto_rawDescGZIP(), []int{28} +} + +func (x *PlaceOrderResponse) GetOrder() *OrderResult { + if x != nil { + return x.Order + } + return nil +} + +type AdRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // List of important key words from the current page describing the context. + ContextKeys []string `protobuf:"bytes,1,rep,name=context_keys,json=contextKeys,proto3" json:"context_keys,omitempty"` +} + +func (x *AdRequest) Reset() { + *x = AdRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_demo_proto_msgTypes[29] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *AdRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AdRequest) ProtoMessage() {} + +func (x *AdRequest) ProtoReflect() protoreflect.Message { + mi := &file_demo_proto_msgTypes[29] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AdRequest.ProtoReflect.Descriptor instead. +func (*AdRequest) Descriptor() ([]byte, []int) { + return file_demo_proto_rawDescGZIP(), []int{29} +} + +func (x *AdRequest) GetContextKeys() []string { + if x != nil { + return x.ContextKeys + } + return nil +} + +type AdResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Ads []*Ad `protobuf:"bytes,1,rep,name=ads,proto3" json:"ads,omitempty"` +} + +func (x *AdResponse) Reset() { + *x = AdResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_demo_proto_msgTypes[30] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *AdResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AdResponse) ProtoMessage() {} + +func (x *AdResponse) ProtoReflect() protoreflect.Message { + mi := &file_demo_proto_msgTypes[30] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AdResponse.ProtoReflect.Descriptor instead. +func (*AdResponse) Descriptor() ([]byte, []int) { + return file_demo_proto_rawDescGZIP(), []int{30} +} + +func (x *AdResponse) GetAds() []*Ad { + if x != nil { + return x.Ads + } + return nil +} + +type Ad struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // url to redirect to when an ad is clicked. + RedirectUrl string `protobuf:"bytes,1,opt,name=redirect_url,json=redirectUrl,proto3" json:"redirect_url,omitempty"` + // short advertisement text to display. + Text string `protobuf:"bytes,2,opt,name=text,proto3" json:"text,omitempty"` +} + +func (x *Ad) Reset() { + *x = Ad{} + if protoimpl.UnsafeEnabled { + mi := &file_demo_proto_msgTypes[31] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Ad) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Ad) ProtoMessage() {} + +func (x *Ad) ProtoReflect() protoreflect.Message { + mi := &file_demo_proto_msgTypes[31] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Ad.ProtoReflect.Descriptor instead. +func (*Ad) Descriptor() ([]byte, []int) { + return file_demo_proto_rawDescGZIP(), []int{31} +} + +func (x *Ad) GetRedirectUrl() string { + if x != nil { + return x.RedirectUrl + } + return "" +} + +func (x *Ad) GetText() string { + if x != nil { + return x.Text + } + return "" +} + +var File_demo_proto protoreflect.FileDescriptor + +var file_demo_proto_rawDesc = []byte{ + 0x0a, 0x0a, 0x64, 0x65, 0x6d, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0b, 0x68, 0x69, + 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x22, 0x45, 0x0a, 0x08, 0x43, 0x61, 0x72, + 0x74, 0x49, 0x74, 0x65, 0x6d, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, + 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x70, 0x72, 0x6f, 0x64, 0x75, + 0x63, 0x74, 0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x71, 0x75, 0x61, 0x6e, 0x74, 0x69, 0x74, 0x79, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x71, 0x75, 0x61, 0x6e, 0x74, 0x69, 0x74, 0x79, + 0x22, 0x54, 0x0a, 0x0e, 0x41, 0x64, 0x64, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x29, 0x0a, 0x04, 0x69, + 0x74, 0x65, 0x6d, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x68, 0x69, 0x70, 0x73, + 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x43, 0x61, 0x72, 0x74, 0x49, 0x74, 0x65, 0x6d, + 0x52, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x22, 0x2b, 0x0a, 0x10, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x43, + 0x61, 0x72, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, + 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, + 0x72, 0x49, 0x64, 0x22, 0x29, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x43, 0x61, 0x72, 0x74, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0x4c, + 0x0a, 0x04, 0x43, 0x61, 0x72, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, + 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, + 0x2b, 0x0a, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, + 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x43, 0x61, 0x72, + 0x74, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x22, 0x07, 0x0a, 0x05, + 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x56, 0x0a, 0x1a, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x63, + 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b, + 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, + 0x09, 0x52, 0x0a, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x49, 0x64, 0x73, 0x22, 0x3e, 0x0a, + 0x1b, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x64, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1f, 0x0a, 0x0b, + 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, + 0x09, 0x52, 0x0a, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x49, 0x64, 0x73, 0x22, 0xba, 0x01, + 0x0a, 0x07, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, + 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x20, 0x0a, + 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, + 0x18, 0x0a, 0x07, 0x70, 0x69, 0x63, 0x74, 0x75, 0x72, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x07, 0x70, 0x69, 0x63, 0x74, 0x75, 0x72, 0x65, 0x12, 0x2f, 0x0a, 0x09, 0x70, 0x72, 0x69, + 0x63, 0x65, 0x5f, 0x75, 0x73, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x68, + 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x4d, 0x6f, 0x6e, 0x65, 0x79, + 0x52, 0x08, 0x70, 0x72, 0x69, 0x63, 0x65, 0x55, 0x73, 0x64, 0x12, 0x1e, 0x0a, 0x0a, 0x63, 0x61, + 0x74, 0x65, 0x67, 0x6f, 0x72, 0x69, 0x65, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, + 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x69, 0x65, 0x73, 0x22, 0x48, 0x0a, 0x14, 0x4c, 0x69, + 0x73, 0x74, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x30, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x73, 0x18, 0x01, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, + 0x6f, 0x70, 0x2e, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x64, + 0x75, 0x63, 0x74, 0x73, 0x22, 0x23, 0x0a, 0x11, 0x47, 0x65, 0x74, 0x50, 0x72, 0x6f, 0x64, 0x75, + 0x63, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x22, 0x2d, 0x0a, 0x15, 0x53, 0x65, 0x61, + 0x72, 0x63, 0x68, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x22, 0x48, 0x0a, 0x16, 0x53, 0x65, 0x61, 0x72, + 0x63, 0x68, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x2e, 0x0a, 0x07, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x18, 0x01, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, + 0x70, 0x2e, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x52, 0x07, 0x72, 0x65, 0x73, 0x75, 0x6c, + 0x74, 0x73, 0x22, 0x6e, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x51, 0x75, 0x6f, 0x74, 0x65, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, + 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, 0x07, 0x61, 0x64, + 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x2b, 0x0a, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x18, 0x02, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, + 0x6f, 0x70, 0x2e, 0x43, 0x61, 0x72, 0x74, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x05, 0x69, 0x74, 0x65, + 0x6d, 0x73, 0x22, 0x41, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x51, 0x75, 0x6f, 0x74, 0x65, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2d, 0x0a, 0x08, 0x63, 0x6f, 0x73, 0x74, 0x5f, 0x75, + 0x73, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, + 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x4d, 0x6f, 0x6e, 0x65, 0x79, 0x52, 0x07, 0x63, 0x6f, + 0x73, 0x74, 0x55, 0x73, 0x64, 0x22, 0x6f, 0x0a, 0x10, 0x53, 0x68, 0x69, 0x70, 0x4f, 0x72, 0x64, + 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x07, 0x61, 0x64, 0x64, + 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x68, 0x69, 0x70, + 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, + 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x2b, 0x0a, 0x05, 0x69, 0x74, 0x65, + 0x6d, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, + 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x43, 0x61, 0x72, 0x74, 0x49, 0x74, 0x65, 0x6d, 0x52, + 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x22, 0x34, 0x0a, 0x11, 0x53, 0x68, 0x69, 0x70, 0x4f, 0x72, + 0x64, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x74, + 0x72, 0x61, 0x63, 0x6b, 0x69, 0x6e, 0x67, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0a, 0x74, 0x72, 0x61, 0x63, 0x6b, 0x69, 0x6e, 0x67, 0x49, 0x64, 0x22, 0x8f, 0x01, 0x0a, + 0x07, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x25, 0x0a, 0x0e, 0x73, 0x74, 0x72, 0x65, + 0x65, 0x74, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0d, 0x73, 0x74, 0x72, 0x65, 0x65, 0x74, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, + 0x12, 0x0a, 0x04, 0x63, 0x69, 0x74, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x63, + 0x69, 0x74, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x75, + 0x6e, 0x74, 0x72, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x6f, 0x75, 0x6e, + 0x74, 0x72, 0x79, 0x12, 0x19, 0x0a, 0x08, 0x7a, 0x69, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, + 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x07, 0x7a, 0x69, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x22, 0x58, + 0x0a, 0x05, 0x4d, 0x6f, 0x6e, 0x65, 0x79, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x75, 0x72, 0x72, 0x65, + 0x6e, 0x63, 0x79, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, + 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x14, 0x0a, 0x05, + 0x75, 0x6e, 0x69, 0x74, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x75, 0x6e, 0x69, + 0x74, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x6e, 0x61, 0x6e, 0x6f, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x05, 0x52, 0x05, 0x6e, 0x61, 0x6e, 0x6f, 0x73, 0x22, 0x47, 0x0a, 0x1e, 0x47, 0x65, 0x74, 0x53, + 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x69, + 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x63, 0x75, + 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, + 0x28, 0x09, 0x52, 0x0d, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x43, 0x6f, 0x64, 0x65, + 0x73, 0x22, 0x5c, 0x0a, 0x19, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x43, 0x6f, 0x6e, + 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x26, + 0x0a, 0x04, 0x66, 0x72, 0x6f, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x68, + 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x4d, 0x6f, 0x6e, 0x65, 0x79, + 0x52, 0x04, 0x66, 0x72, 0x6f, 0x6d, 0x12, 0x17, 0x0a, 0x07, 0x74, 0x6f, 0x5f, 0x63, 0x6f, 0x64, + 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x74, 0x6f, 0x43, 0x6f, 0x64, 0x65, 0x22, + 0xe6, 0x01, 0x0a, 0x0e, 0x43, 0x72, 0x65, 0x64, 0x69, 0x74, 0x43, 0x61, 0x72, 0x64, 0x49, 0x6e, + 0x66, 0x6f, 0x12, 0x2c, 0x0a, 0x12, 0x63, 0x72, 0x65, 0x64, 0x69, 0x74, 0x5f, 0x63, 0x61, 0x72, + 0x64, 0x5f, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, + 0x63, 0x72, 0x65, 0x64, 0x69, 0x74, 0x43, 0x61, 0x72, 0x64, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, + 0x12, 0x26, 0x0a, 0x0f, 0x63, 0x72, 0x65, 0x64, 0x69, 0x74, 0x5f, 0x63, 0x61, 0x72, 0x64, 0x5f, + 0x63, 0x76, 0x76, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0d, 0x63, 0x72, 0x65, 0x64, 0x69, + 0x74, 0x43, 0x61, 0x72, 0x64, 0x43, 0x76, 0x76, 0x12, 0x3d, 0x0a, 0x1b, 0x63, 0x72, 0x65, 0x64, + 0x69, 0x74, 0x5f, 0x63, 0x61, 0x72, 0x64, 0x5f, 0x65, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x5f, 0x79, 0x65, 0x61, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x18, 0x63, + 0x72, 0x65, 0x64, 0x69, 0x74, 0x43, 0x61, 0x72, 0x64, 0x45, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x59, 0x65, 0x61, 0x72, 0x12, 0x3f, 0x0a, 0x1c, 0x63, 0x72, 0x65, 0x64, 0x69, + 0x74, 0x5f, 0x63, 0x61, 0x72, 0x64, 0x5f, 0x65, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x5f, 0x6d, 0x6f, 0x6e, 0x74, 0x68, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x19, 0x63, + 0x72, 0x65, 0x64, 0x69, 0x74, 0x43, 0x61, 0x72, 0x64, 0x45, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x4d, 0x6f, 0x6e, 0x74, 0x68, 0x22, 0x79, 0x0a, 0x0d, 0x43, 0x68, 0x61, 0x72, + 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2a, 0x0a, 0x06, 0x61, 0x6d, 0x6f, + 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x68, 0x69, 0x70, 0x73, + 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x4d, 0x6f, 0x6e, 0x65, 0x79, 0x52, 0x06, 0x61, + 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x3c, 0x0a, 0x0b, 0x63, 0x72, 0x65, 0x64, 0x69, 0x74, 0x5f, + 0x63, 0x61, 0x72, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x68, 0x69, 0x70, + 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x43, 0x72, 0x65, 0x64, 0x69, 0x74, 0x43, + 0x61, 0x72, 0x64, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x0a, 0x63, 0x72, 0x65, 0x64, 0x69, 0x74, 0x43, + 0x61, 0x72, 0x64, 0x22, 0x37, 0x0a, 0x0e, 0x43, 0x68, 0x61, 0x72, 0x67, 0x65, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, + 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x74, + 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x22, 0x5e, 0x0a, 0x09, + 0x4f, 0x72, 0x64, 0x65, 0x72, 0x49, 0x74, 0x65, 0x6d, 0x12, 0x29, 0x0a, 0x04, 0x69, 0x74, 0x65, + 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, + 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x43, 0x61, 0x72, 0x74, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x04, + 0x69, 0x74, 0x65, 0x6d, 0x12, 0x26, 0x0a, 0x04, 0x63, 0x6f, 0x73, 0x74, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, + 0x2e, 0x4d, 0x6f, 0x6e, 0x65, 0x79, 0x52, 0x04, 0x63, 0x6f, 0x73, 0x74, 0x22, 0x82, 0x02, 0x0a, + 0x0b, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x19, 0x0a, 0x08, + 0x6f, 0x72, 0x64, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, + 0x6f, 0x72, 0x64, 0x65, 0x72, 0x49, 0x64, 0x12, 0x30, 0x0a, 0x14, 0x73, 0x68, 0x69, 0x70, 0x70, + 0x69, 0x6e, 0x67, 0x5f, 0x74, 0x72, 0x61, 0x63, 0x6b, 0x69, 0x6e, 0x67, 0x5f, 0x69, 0x64, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x12, 0x73, 0x68, 0x69, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x54, + 0x72, 0x61, 0x63, 0x6b, 0x69, 0x6e, 0x67, 0x49, 0x64, 0x12, 0x37, 0x0a, 0x0d, 0x73, 0x68, 0x69, + 0x70, 0x70, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x6f, 0x73, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x12, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x4d, + 0x6f, 0x6e, 0x65, 0x79, 0x52, 0x0c, 0x73, 0x68, 0x69, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x43, 0x6f, + 0x73, 0x74, 0x12, 0x3f, 0x0a, 0x10, 0x73, 0x68, 0x69, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x5f, 0x61, + 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x68, + 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x41, 0x64, 0x64, 0x72, 0x65, + 0x73, 0x73, 0x52, 0x0f, 0x73, 0x68, 0x69, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x41, 0x64, 0x64, 0x72, + 0x65, 0x73, 0x73, 0x12, 0x2c, 0x0a, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x18, 0x05, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, + 0x2e, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d, + 0x73, 0x22, 0x64, 0x0a, 0x1c, 0x53, 0x65, 0x6e, 0x64, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x43, 0x6f, + 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x12, 0x2e, 0x0a, 0x05, 0x6f, 0x72, 0x64, 0x65, 0x72, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, + 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, + 0x52, 0x05, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x22, 0xd5, 0x01, 0x0a, 0x11, 0x50, 0x6c, 0x61, 0x63, + 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x17, 0x0a, + 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, + 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x63, + 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x75, + 0x73, 0x65, 0x72, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x12, 0x2e, 0x0a, 0x07, 0x61, + 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x68, + 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x41, 0x64, 0x64, 0x72, 0x65, + 0x73, 0x73, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x65, + 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x6d, 0x61, 0x69, + 0x6c, 0x12, 0x3c, 0x0a, 0x0b, 0x63, 0x72, 0x65, 0x64, 0x69, 0x74, 0x5f, 0x63, 0x61, 0x72, 0x64, + 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, + 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x43, 0x72, 0x65, 0x64, 0x69, 0x74, 0x43, 0x61, 0x72, 0x64, 0x49, + 0x6e, 0x66, 0x6f, 0x52, 0x0a, 0x63, 0x72, 0x65, 0x64, 0x69, 0x74, 0x43, 0x61, 0x72, 0x64, 0x22, + 0x44, 0x0a, 0x12, 0x50, 0x6c, 0x61, 0x63, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2e, 0x0a, 0x05, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, + 0x6f, 0x70, 0x2e, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x05, + 0x6f, 0x72, 0x64, 0x65, 0x72, 0x22, 0x2e, 0x0a, 0x09, 0x41, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x5f, 0x6b, 0x65, + 0x79, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, + 0x74, 0x4b, 0x65, 0x79, 0x73, 0x22, 0x2f, 0x0a, 0x0a, 0x41, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x21, 0x0a, 0x03, 0x61, 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x0f, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x41, + 0x64, 0x52, 0x03, 0x61, 0x64, 0x73, 0x22, 0x3b, 0x0a, 0x02, 0x41, 0x64, 0x12, 0x21, 0x0a, 0x0c, + 0x72, 0x65, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0b, 0x72, 0x65, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x55, 0x72, 0x6c, 0x12, + 0x12, 0x0a, 0x04, 0x74, 0x65, 0x78, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, + 0x65, 0x78, 0x74, 0x32, 0xca, 0x01, 0x0a, 0x0b, 0x43, 0x61, 0x72, 0x74, 0x53, 0x65, 0x72, 0x76, + 0x69, 0x63, 0x65, 0x12, 0x3c, 0x0a, 0x07, 0x41, 0x64, 0x64, 0x49, 0x74, 0x65, 0x6d, 0x12, 0x1b, + 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x41, 0x64, 0x64, + 0x49, 0x74, 0x65, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x68, 0x69, + 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, + 0x00, 0x12, 0x3b, 0x0a, 0x07, 0x47, 0x65, 0x74, 0x43, 0x61, 0x72, 0x74, 0x12, 0x1b, 0x2e, 0x68, + 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x61, + 0x72, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x11, 0x2e, 0x68, 0x69, 0x70, 0x73, + 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x43, 0x61, 0x72, 0x74, 0x22, 0x00, 0x12, 0x40, + 0x0a, 0x09, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x43, 0x61, 0x72, 0x74, 0x12, 0x1d, 0x2e, 0x68, 0x69, + 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x43, + 0x61, 0x72, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x68, 0x69, 0x70, + 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, + 0x32, 0x83, 0x01, 0x0a, 0x15, 0x52, 0x65, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x64, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x6a, 0x0a, 0x13, 0x4c, 0x69, + 0x73, 0x74, 0x52, 0x65, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x73, 0x12, 0x27, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, + 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x64, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, 0x68, 0x69, 0x70, + 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x63, + 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x32, 0x83, 0x02, 0x0a, 0x15, 0x50, 0x72, 0x6f, 0x64, 0x75, + 0x63, 0x74, 0x43, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, + 0x12, 0x47, 0x0a, 0x0c, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x73, + 0x12, 0x12, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x45, + 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x21, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, + 0x6f, 0x70, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x73, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x44, 0x0a, 0x0a, 0x47, 0x65, 0x74, + 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x12, 0x1e, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, + 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x47, 0x65, 0x74, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, + 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x22, 0x00, 0x12, + 0x5b, 0x0a, 0x0e, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, + 0x73, 0x12, 0x22, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, + 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x73, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, + 0x68, 0x6f, 0x70, 0x2e, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, + 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x32, 0xaa, 0x01, 0x0a, + 0x0f, 0x53, 0x68, 0x69, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, + 0x12, 0x49, 0x0a, 0x08, 0x47, 0x65, 0x74, 0x51, 0x75, 0x6f, 0x74, 0x65, 0x12, 0x1c, 0x2e, 0x68, + 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x47, 0x65, 0x74, 0x51, 0x75, + 0x6f, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x68, 0x69, 0x70, + 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x47, 0x65, 0x74, 0x51, 0x75, 0x6f, 0x74, + 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x4c, 0x0a, 0x09, 0x53, + 0x68, 0x69, 0x70, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x1d, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, + 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x53, 0x68, 0x69, 0x70, 0x4f, 0x72, 0x64, 0x65, 0x72, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, + 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x53, 0x68, 0x69, 0x70, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x32, 0xb7, 0x01, 0x0a, 0x0f, 0x43, 0x75, + 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x5b, 0x0a, + 0x16, 0x47, 0x65, 0x74, 0x53, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x43, 0x75, 0x72, + 0x72, 0x65, 0x6e, 0x63, 0x69, 0x65, 0x73, 0x12, 0x12, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, + 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x2b, 0x2e, 0x68, 0x69, + 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x75, 0x70, + 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x69, 0x65, 0x73, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x47, 0x0a, 0x07, 0x43, 0x6f, + 0x6e, 0x76, 0x65, 0x72, 0x74, 0x12, 0x26, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, + 0x68, 0x6f, 0x70, 0x2e, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x43, 0x6f, 0x6e, 0x76, + 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, + 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x4d, 0x6f, 0x6e, 0x65, + 0x79, 0x22, 0x00, 0x32, 0x55, 0x0a, 0x0e, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x65, + 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x43, 0x0a, 0x06, 0x43, 0x68, 0x61, 0x72, 0x67, 0x65, 0x12, + 0x1a, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x43, 0x68, + 0x61, 0x72, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x68, 0x69, + 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x43, 0x68, 0x61, 0x72, 0x67, 0x65, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x32, 0x68, 0x0a, 0x0c, 0x45, 0x6d, + 0x61, 0x69, 0x6c, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x58, 0x0a, 0x15, 0x53, 0x65, + 0x6e, 0x64, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x12, 0x29, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, + 0x70, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, + 0x72, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, + 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x45, 0x6d, 0x70, + 0x74, 0x79, 0x22, 0x00, 0x32, 0x62, 0x0a, 0x0f, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x6f, 0x75, 0x74, + 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x4f, 0x0a, 0x0a, 0x50, 0x6c, 0x61, 0x63, 0x65, + 0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x1e, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, + 0x68, 0x6f, 0x70, 0x2e, 0x50, 0x6c, 0x61, 0x63, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, + 0x68, 0x6f, 0x70, 0x2e, 0x50, 0x6c, 0x61, 0x63, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x32, 0x48, 0x0a, 0x09, 0x41, 0x64, 0x53, 0x65, + 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x3b, 0x0a, 0x06, 0x47, 0x65, 0x74, 0x41, 0x64, 0x73, 0x12, + 0x16, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x41, 0x64, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, + 0x72, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x41, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x22, 0x00, 0x42, 0x40, 0x5a, 0x3e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, + 0x2f, 0x47, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x43, 0x6c, 0x6f, 0x75, 0x64, 0x50, 0x6c, 0x61, 0x74, + 0x66, 0x6f, 0x72, 0x6d, 0x2f, 0x6d, 0x69, 0x73, 0x63, 0x72, 0x6f, 0x73, 0x65, 0x72, 0x76, 0x69, + 0x63, 0x65, 0x73, 0x2d, 0x64, 0x65, 0x6d, 0x6f, 0x2f, 0x68, 0x69, 0x70, 0x73, 0x74, 0x65, 0x72, + 0x73, 0x68, 0x6f, 0x70, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_demo_proto_rawDescOnce sync.Once + file_demo_proto_rawDescData = file_demo_proto_rawDesc +) + +func file_demo_proto_rawDescGZIP() []byte { + file_demo_proto_rawDescOnce.Do(func() { + file_demo_proto_rawDescData = protoimpl.X.CompressGZIP(file_demo_proto_rawDescData) + }) + return file_demo_proto_rawDescData +} + +var file_demo_proto_msgTypes = make([]protoimpl.MessageInfo, 32) +var file_demo_proto_goTypes = []any{ + (*CartItem)(nil), // 0: hipstershop.CartItem + (*AddItemRequest)(nil), // 1: hipstershop.AddItemRequest + (*EmptyCartRequest)(nil), // 2: hipstershop.EmptyCartRequest + (*GetCartRequest)(nil), // 3: hipstershop.GetCartRequest + (*Cart)(nil), // 4: hipstershop.Cart + (*Empty)(nil), // 5: hipstershop.Empty + (*ListRecommendationsRequest)(nil), // 6: hipstershop.ListRecommendationsRequest + (*ListRecommendationsResponse)(nil), // 7: hipstershop.ListRecommendationsResponse + (*Product)(nil), // 8: hipstershop.Product + (*ListProductsResponse)(nil), // 9: hipstershop.ListProductsResponse + (*GetProductRequest)(nil), // 10: hipstershop.GetProductRequest + (*SearchProductsRequest)(nil), // 11: hipstershop.SearchProductsRequest + (*SearchProductsResponse)(nil), // 12: hipstershop.SearchProductsResponse + (*GetQuoteRequest)(nil), // 13: hipstershop.GetQuoteRequest + (*GetQuoteResponse)(nil), // 14: hipstershop.GetQuoteResponse + (*ShipOrderRequest)(nil), // 15: hipstershop.ShipOrderRequest + (*ShipOrderResponse)(nil), // 16: hipstershop.ShipOrderResponse + (*Address)(nil), // 17: hipstershop.Address + (*Money)(nil), // 18: hipstershop.Money + (*GetSupportedCurrenciesResponse)(nil), // 19: hipstershop.GetSupportedCurrenciesResponse + (*CurrencyConversionRequest)(nil), // 20: hipstershop.CurrencyConversionRequest + (*CreditCardInfo)(nil), // 21: hipstershop.CreditCardInfo + (*ChargeRequest)(nil), // 22: hipstershop.ChargeRequest + (*ChargeResponse)(nil), // 23: hipstershop.ChargeResponse + (*OrderItem)(nil), // 24: hipstershop.OrderItem + (*OrderResult)(nil), // 25: hipstershop.OrderResult + (*SendOrderConfirmationRequest)(nil), // 26: hipstershop.SendOrderConfirmationRequest + (*PlaceOrderRequest)(nil), // 27: hipstershop.PlaceOrderRequest + (*PlaceOrderResponse)(nil), // 28: hipstershop.PlaceOrderResponse + (*AdRequest)(nil), // 29: hipstershop.AdRequest + (*AdResponse)(nil), // 30: hipstershop.AdResponse + (*Ad)(nil), // 31: hipstershop.Ad +} +var file_demo_proto_depIdxs = []int32{ + 0, // 0: hipstershop.AddItemRequest.item:type_name -> hipstershop.CartItem + 0, // 1: hipstershop.Cart.items:type_name -> hipstershop.CartItem + 18, // 2: hipstershop.Product.price_usd:type_name -> hipstershop.Money + 8, // 3: hipstershop.ListProductsResponse.products:type_name -> hipstershop.Product + 8, // 4: hipstershop.SearchProductsResponse.results:type_name -> hipstershop.Product + 17, // 5: hipstershop.GetQuoteRequest.address:type_name -> hipstershop.Address + 0, // 6: hipstershop.GetQuoteRequest.items:type_name -> hipstershop.CartItem + 18, // 7: hipstershop.GetQuoteResponse.cost_usd:type_name -> hipstershop.Money + 17, // 8: hipstershop.ShipOrderRequest.address:type_name -> hipstershop.Address + 0, // 9: hipstershop.ShipOrderRequest.items:type_name -> hipstershop.CartItem + 18, // 10: hipstershop.CurrencyConversionRequest.from:type_name -> hipstershop.Money + 18, // 11: hipstershop.ChargeRequest.amount:type_name -> hipstershop.Money + 21, // 12: hipstershop.ChargeRequest.credit_card:type_name -> hipstershop.CreditCardInfo + 0, // 13: hipstershop.OrderItem.item:type_name -> hipstershop.CartItem + 18, // 14: hipstershop.OrderItem.cost:type_name -> hipstershop.Money + 18, // 15: hipstershop.OrderResult.shipping_cost:type_name -> hipstershop.Money + 17, // 16: hipstershop.OrderResult.shipping_address:type_name -> hipstershop.Address + 24, // 17: hipstershop.OrderResult.items:type_name -> hipstershop.OrderItem + 25, // 18: hipstershop.SendOrderConfirmationRequest.order:type_name -> hipstershop.OrderResult + 17, // 19: hipstershop.PlaceOrderRequest.address:type_name -> hipstershop.Address + 21, // 20: hipstershop.PlaceOrderRequest.credit_card:type_name -> hipstershop.CreditCardInfo + 25, // 21: hipstershop.PlaceOrderResponse.order:type_name -> hipstershop.OrderResult + 31, // 22: hipstershop.AdResponse.ads:type_name -> hipstershop.Ad + 1, // 23: hipstershop.CartService.AddItem:input_type -> hipstershop.AddItemRequest + 3, // 24: hipstershop.CartService.GetCart:input_type -> hipstershop.GetCartRequest + 2, // 25: hipstershop.CartService.EmptyCart:input_type -> hipstershop.EmptyCartRequest + 6, // 26: hipstershop.RecommendationService.ListRecommendations:input_type -> hipstershop.ListRecommendationsRequest + 5, // 27: hipstershop.ProductCatalogService.ListProducts:input_type -> hipstershop.Empty + 10, // 28: hipstershop.ProductCatalogService.GetProduct:input_type -> hipstershop.GetProductRequest + 11, // 29: hipstershop.ProductCatalogService.SearchProducts:input_type -> hipstershop.SearchProductsRequest + 13, // 30: hipstershop.ShippingService.GetQuote:input_type -> hipstershop.GetQuoteRequest + 15, // 31: hipstershop.ShippingService.ShipOrder:input_type -> hipstershop.ShipOrderRequest + 5, // 32: hipstershop.CurrencyService.GetSupportedCurrencies:input_type -> hipstershop.Empty + 20, // 33: hipstershop.CurrencyService.Convert:input_type -> hipstershop.CurrencyConversionRequest + 22, // 34: hipstershop.PaymentService.Charge:input_type -> hipstershop.ChargeRequest + 26, // 35: hipstershop.EmailService.SendOrderConfirmation:input_type -> hipstershop.SendOrderConfirmationRequest + 27, // 36: hipstershop.CheckoutService.PlaceOrder:input_type -> hipstershop.PlaceOrderRequest + 29, // 37: hipstershop.AdService.GetAds:input_type -> hipstershop.AdRequest + 5, // 38: hipstershop.CartService.AddItem:output_type -> hipstershop.Empty + 4, // 39: hipstershop.CartService.GetCart:output_type -> hipstershop.Cart + 5, // 40: hipstershop.CartService.EmptyCart:output_type -> hipstershop.Empty + 7, // 41: hipstershop.RecommendationService.ListRecommendations:output_type -> hipstershop.ListRecommendationsResponse + 9, // 42: hipstershop.ProductCatalogService.ListProducts:output_type -> hipstershop.ListProductsResponse + 8, // 43: hipstershop.ProductCatalogService.GetProduct:output_type -> hipstershop.Product + 12, // 44: hipstershop.ProductCatalogService.SearchProducts:output_type -> hipstershop.SearchProductsResponse + 14, // 45: hipstershop.ShippingService.GetQuote:output_type -> hipstershop.GetQuoteResponse + 16, // 46: hipstershop.ShippingService.ShipOrder:output_type -> hipstershop.ShipOrderResponse + 19, // 47: hipstershop.CurrencyService.GetSupportedCurrencies:output_type -> hipstershop.GetSupportedCurrenciesResponse + 18, // 48: hipstershop.CurrencyService.Convert:output_type -> hipstershop.Money + 23, // 49: hipstershop.PaymentService.Charge:output_type -> hipstershop.ChargeResponse + 5, // 50: hipstershop.EmailService.SendOrderConfirmation:output_type -> hipstershop.Empty + 28, // 51: hipstershop.CheckoutService.PlaceOrder:output_type -> hipstershop.PlaceOrderResponse + 30, // 52: hipstershop.AdService.GetAds:output_type -> hipstershop.AdResponse + 38, // [38:53] is the sub-list for method output_type + 23, // [23:38] is the sub-list for method input_type + 23, // [23:23] is the sub-list for extension type_name + 23, // [23:23] is the sub-list for extension extendee + 0, // [0:23] is the sub-list for field type_name +} + +func init() { file_demo_proto_init() } +func file_demo_proto_init() { + if File_demo_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_demo_proto_msgTypes[0].Exporter = func(v any, i int) any { + switch v := v.(*CartItem); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_demo_proto_msgTypes[1].Exporter = func(v any, i int) any { + switch v := v.(*AddItemRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_demo_proto_msgTypes[2].Exporter = func(v any, i int) any { + switch v := v.(*EmptyCartRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_demo_proto_msgTypes[3].Exporter = func(v any, i int) any { + switch v := v.(*GetCartRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_demo_proto_msgTypes[4].Exporter = func(v any, i int) any { + switch v := v.(*Cart); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_demo_proto_msgTypes[5].Exporter = func(v any, i int) any { + switch v := v.(*Empty); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_demo_proto_msgTypes[6].Exporter = func(v any, i int) any { + switch v := v.(*ListRecommendationsRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_demo_proto_msgTypes[7].Exporter = func(v any, i int) any { + switch v := v.(*ListRecommendationsResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_demo_proto_msgTypes[8].Exporter = func(v any, i int) any { + switch v := v.(*Product); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_demo_proto_msgTypes[9].Exporter = func(v any, i int) any { + switch v := v.(*ListProductsResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_demo_proto_msgTypes[10].Exporter = func(v any, i int) any { + switch v := v.(*GetProductRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_demo_proto_msgTypes[11].Exporter = func(v any, i int) any { + switch v := v.(*SearchProductsRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_demo_proto_msgTypes[12].Exporter = func(v any, i int) any { + switch v := v.(*SearchProductsResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_demo_proto_msgTypes[13].Exporter = func(v any, i int) any { + switch v := v.(*GetQuoteRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_demo_proto_msgTypes[14].Exporter = func(v any, i int) any { + switch v := v.(*GetQuoteResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_demo_proto_msgTypes[15].Exporter = func(v any, i int) any { + switch v := v.(*ShipOrderRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_demo_proto_msgTypes[16].Exporter = func(v any, i int) any { + switch v := v.(*ShipOrderResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_demo_proto_msgTypes[17].Exporter = func(v any, i int) any { + switch v := v.(*Address); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_demo_proto_msgTypes[18].Exporter = func(v any, i int) any { + switch v := v.(*Money); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_demo_proto_msgTypes[19].Exporter = func(v any, i int) any { + switch v := v.(*GetSupportedCurrenciesResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_demo_proto_msgTypes[20].Exporter = func(v any, i int) any { + switch v := v.(*CurrencyConversionRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_demo_proto_msgTypes[21].Exporter = func(v any, i int) any { + switch v := v.(*CreditCardInfo); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_demo_proto_msgTypes[22].Exporter = func(v any, i int) any { + switch v := v.(*ChargeRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_demo_proto_msgTypes[23].Exporter = func(v any, i int) any { + switch v := v.(*ChargeResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_demo_proto_msgTypes[24].Exporter = func(v any, i int) any { + switch v := v.(*OrderItem); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_demo_proto_msgTypes[25].Exporter = func(v any, i int) any { + switch v := v.(*OrderResult); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_demo_proto_msgTypes[26].Exporter = func(v any, i int) any { + switch v := v.(*SendOrderConfirmationRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_demo_proto_msgTypes[27].Exporter = func(v any, i int) any { + switch v := v.(*PlaceOrderRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_demo_proto_msgTypes[28].Exporter = func(v any, i int) any { + switch v := v.(*PlaceOrderResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_demo_proto_msgTypes[29].Exporter = func(v any, i int) any { + switch v := v.(*AdRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_demo_proto_msgTypes[30].Exporter = func(v any, i int) any { + switch v := v.(*AdResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_demo_proto_msgTypes[31].Exporter = func(v any, i int) any { + switch v := v.(*Ad); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_demo_proto_rawDesc, + NumEnums: 0, + NumMessages: 32, + NumExtensions: 0, + NumServices: 9, + }, + GoTypes: file_demo_proto_goTypes, + DependencyIndexes: file_demo_proto_depIdxs, + MessageInfos: file_demo_proto_msgTypes, + }.Build() + File_demo_proto = out.File + file_demo_proto_rawDesc = nil + file_demo_proto_goTypes = nil + file_demo_proto_depIdxs = nil +} diff --git a/src/shippingservice/genproto/demo_grpc.pb.go b/src/shippingservice/genproto/demo_grpc.pb.go new file mode 100644 index 0000000..e99e6d6 --- /dev/null +++ b/src/shippingservice/genproto/demo_grpc.pb.go @@ -0,0 +1,1179 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.5.1 +// - protoc v3.6.1 +// source: demo.proto + +package hipstershop + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.64.0 or later. +const _ = grpc.SupportPackageIsVersion9 + +const ( + CartService_AddItem_FullMethodName = "/hipstershop.CartService/AddItem" + CartService_GetCart_FullMethodName = "/hipstershop.CartService/GetCart" + CartService_EmptyCart_FullMethodName = "/hipstershop.CartService/EmptyCart" +) + +// CartServiceClient is the client API for CartService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type CartServiceClient interface { + AddItem(ctx context.Context, in *AddItemRequest, opts ...grpc.CallOption) (*Empty, error) + GetCart(ctx context.Context, in *GetCartRequest, opts ...grpc.CallOption) (*Cart, error) + EmptyCart(ctx context.Context, in *EmptyCartRequest, opts ...grpc.CallOption) (*Empty, error) +} + +type cartServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewCartServiceClient(cc grpc.ClientConnInterface) CartServiceClient { + return &cartServiceClient{cc} +} + +func (c *cartServiceClient) AddItem(ctx context.Context, in *AddItemRequest, opts ...grpc.CallOption) (*Empty, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(Empty) + err := c.cc.Invoke(ctx, CartService_AddItem_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *cartServiceClient) GetCart(ctx context.Context, in *GetCartRequest, opts ...grpc.CallOption) (*Cart, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(Cart) + err := c.cc.Invoke(ctx, CartService_GetCart_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *cartServiceClient) EmptyCart(ctx context.Context, in *EmptyCartRequest, opts ...grpc.CallOption) (*Empty, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(Empty) + err := c.cc.Invoke(ctx, CartService_EmptyCart_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +// CartServiceServer is the server API for CartService service. +// All implementations must embed UnimplementedCartServiceServer +// for forward compatibility. +type CartServiceServer interface { + AddItem(context.Context, *AddItemRequest) (*Empty, error) + GetCart(context.Context, *GetCartRequest) (*Cart, error) + EmptyCart(context.Context, *EmptyCartRequest) (*Empty, error) + mustEmbedUnimplementedCartServiceServer() +} + +// UnimplementedCartServiceServer must be embedded to have +// forward compatible implementations. +// +// NOTE: this should be embedded by value instead of pointer to avoid a nil +// pointer dereference when methods are called. +type UnimplementedCartServiceServer struct{} + +func (UnimplementedCartServiceServer) AddItem(context.Context, *AddItemRequest) (*Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method AddItem not implemented") +} +func (UnimplementedCartServiceServer) GetCart(context.Context, *GetCartRequest) (*Cart, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetCart not implemented") +} +func (UnimplementedCartServiceServer) EmptyCart(context.Context, *EmptyCartRequest) (*Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method EmptyCart not implemented") +} +func (UnimplementedCartServiceServer) mustEmbedUnimplementedCartServiceServer() {} +func (UnimplementedCartServiceServer) testEmbeddedByValue() {} + +// UnsafeCartServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to CartServiceServer will +// result in compilation errors. +type UnsafeCartServiceServer interface { + mustEmbedUnimplementedCartServiceServer() +} + +func RegisterCartServiceServer(s grpc.ServiceRegistrar, srv CartServiceServer) { + // If the following call pancis, it indicates UnimplementedCartServiceServer was + // embedded by pointer and is nil. This will cause panics if an + // unimplemented method is ever invoked, so we test this at initialization + // time to prevent it from happening at runtime later due to I/O. + if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { + t.testEmbeddedByValue() + } + s.RegisterService(&CartService_ServiceDesc, srv) +} + +func _CartService_AddItem_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(AddItemRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(CartServiceServer).AddItem(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: CartService_AddItem_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(CartServiceServer).AddItem(ctx, req.(*AddItemRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _CartService_GetCart_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetCartRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(CartServiceServer).GetCart(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: CartService_GetCart_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(CartServiceServer).GetCart(ctx, req.(*GetCartRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _CartService_EmptyCart_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(EmptyCartRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(CartServiceServer).EmptyCart(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: CartService_EmptyCart_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(CartServiceServer).EmptyCart(ctx, req.(*EmptyCartRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// CartService_ServiceDesc is the grpc.ServiceDesc for CartService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var CartService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "hipstershop.CartService", + HandlerType: (*CartServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "AddItem", + Handler: _CartService_AddItem_Handler, + }, + { + MethodName: "GetCart", + Handler: _CartService_GetCart_Handler, + }, + { + MethodName: "EmptyCart", + Handler: _CartService_EmptyCart_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "demo.proto", +} + +const ( + RecommendationService_ListRecommendations_FullMethodName = "/hipstershop.RecommendationService/ListRecommendations" +) + +// RecommendationServiceClient is the client API for RecommendationService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type RecommendationServiceClient interface { + ListRecommendations(ctx context.Context, in *ListRecommendationsRequest, opts ...grpc.CallOption) (*ListRecommendationsResponse, error) +} + +type recommendationServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewRecommendationServiceClient(cc grpc.ClientConnInterface) RecommendationServiceClient { + return &recommendationServiceClient{cc} +} + +func (c *recommendationServiceClient) ListRecommendations(ctx context.Context, in *ListRecommendationsRequest, opts ...grpc.CallOption) (*ListRecommendationsResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ListRecommendationsResponse) + err := c.cc.Invoke(ctx, RecommendationService_ListRecommendations_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +// RecommendationServiceServer is the server API for RecommendationService service. +// All implementations must embed UnimplementedRecommendationServiceServer +// for forward compatibility. +type RecommendationServiceServer interface { + ListRecommendations(context.Context, *ListRecommendationsRequest) (*ListRecommendationsResponse, error) + mustEmbedUnimplementedRecommendationServiceServer() +} + +// UnimplementedRecommendationServiceServer must be embedded to have +// forward compatible implementations. +// +// NOTE: this should be embedded by value instead of pointer to avoid a nil +// pointer dereference when methods are called. +type UnimplementedRecommendationServiceServer struct{} + +func (UnimplementedRecommendationServiceServer) ListRecommendations(context.Context, *ListRecommendationsRequest) (*ListRecommendationsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method ListRecommendations not implemented") +} +func (UnimplementedRecommendationServiceServer) mustEmbedUnimplementedRecommendationServiceServer() {} +func (UnimplementedRecommendationServiceServer) testEmbeddedByValue() {} + +// UnsafeRecommendationServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to RecommendationServiceServer will +// result in compilation errors. +type UnsafeRecommendationServiceServer interface { + mustEmbedUnimplementedRecommendationServiceServer() +} + +func RegisterRecommendationServiceServer(s grpc.ServiceRegistrar, srv RecommendationServiceServer) { + // If the following call pancis, it indicates UnimplementedRecommendationServiceServer was + // embedded by pointer and is nil. This will cause panics if an + // unimplemented method is ever invoked, so we test this at initialization + // time to prevent it from happening at runtime later due to I/O. + if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { + t.testEmbeddedByValue() + } + s.RegisterService(&RecommendationService_ServiceDesc, srv) +} + +func _RecommendationService_ListRecommendations_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ListRecommendationsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(RecommendationServiceServer).ListRecommendations(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: RecommendationService_ListRecommendations_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(RecommendationServiceServer).ListRecommendations(ctx, req.(*ListRecommendationsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// RecommendationService_ServiceDesc is the grpc.ServiceDesc for RecommendationService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var RecommendationService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "hipstershop.RecommendationService", + HandlerType: (*RecommendationServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "ListRecommendations", + Handler: _RecommendationService_ListRecommendations_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "demo.proto", +} + +const ( + ProductCatalogService_ListProducts_FullMethodName = "/hipstershop.ProductCatalogService/ListProducts" + ProductCatalogService_GetProduct_FullMethodName = "/hipstershop.ProductCatalogService/GetProduct" + ProductCatalogService_SearchProducts_FullMethodName = "/hipstershop.ProductCatalogService/SearchProducts" +) + +// ProductCatalogServiceClient is the client API for ProductCatalogService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type ProductCatalogServiceClient interface { + ListProducts(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*ListProductsResponse, error) + GetProduct(ctx context.Context, in *GetProductRequest, opts ...grpc.CallOption) (*Product, error) + SearchProducts(ctx context.Context, in *SearchProductsRequest, opts ...grpc.CallOption) (*SearchProductsResponse, error) +} + +type productCatalogServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewProductCatalogServiceClient(cc grpc.ClientConnInterface) ProductCatalogServiceClient { + return &productCatalogServiceClient{cc} +} + +func (c *productCatalogServiceClient) ListProducts(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*ListProductsResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ListProductsResponse) + err := c.cc.Invoke(ctx, ProductCatalogService_ListProducts_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *productCatalogServiceClient) GetProduct(ctx context.Context, in *GetProductRequest, opts ...grpc.CallOption) (*Product, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(Product) + err := c.cc.Invoke(ctx, ProductCatalogService_GetProduct_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *productCatalogServiceClient) SearchProducts(ctx context.Context, in *SearchProductsRequest, opts ...grpc.CallOption) (*SearchProductsResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(SearchProductsResponse) + err := c.cc.Invoke(ctx, ProductCatalogService_SearchProducts_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +// ProductCatalogServiceServer is the server API for ProductCatalogService service. +// All implementations must embed UnimplementedProductCatalogServiceServer +// for forward compatibility. +type ProductCatalogServiceServer interface { + ListProducts(context.Context, *Empty) (*ListProductsResponse, error) + GetProduct(context.Context, *GetProductRequest) (*Product, error) + SearchProducts(context.Context, *SearchProductsRequest) (*SearchProductsResponse, error) + mustEmbedUnimplementedProductCatalogServiceServer() +} + +// UnimplementedProductCatalogServiceServer must be embedded to have +// forward compatible implementations. +// +// NOTE: this should be embedded by value instead of pointer to avoid a nil +// pointer dereference when methods are called. +type UnimplementedProductCatalogServiceServer struct{} + +func (UnimplementedProductCatalogServiceServer) ListProducts(context.Context, *Empty) (*ListProductsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method ListProducts not implemented") +} +func (UnimplementedProductCatalogServiceServer) GetProduct(context.Context, *GetProductRequest) (*Product, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetProduct not implemented") +} +func (UnimplementedProductCatalogServiceServer) SearchProducts(context.Context, *SearchProductsRequest) (*SearchProductsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method SearchProducts not implemented") +} +func (UnimplementedProductCatalogServiceServer) mustEmbedUnimplementedProductCatalogServiceServer() {} +func (UnimplementedProductCatalogServiceServer) testEmbeddedByValue() {} + +// UnsafeProductCatalogServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to ProductCatalogServiceServer will +// result in compilation errors. +type UnsafeProductCatalogServiceServer interface { + mustEmbedUnimplementedProductCatalogServiceServer() +} + +func RegisterProductCatalogServiceServer(s grpc.ServiceRegistrar, srv ProductCatalogServiceServer) { + // If the following call pancis, it indicates UnimplementedProductCatalogServiceServer was + // embedded by pointer and is nil. This will cause panics if an + // unimplemented method is ever invoked, so we test this at initialization + // time to prevent it from happening at runtime later due to I/O. + if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { + t.testEmbeddedByValue() + } + s.RegisterService(&ProductCatalogService_ServiceDesc, srv) +} + +func _ProductCatalogService_ListProducts_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(Empty) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ProductCatalogServiceServer).ListProducts(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: ProductCatalogService_ListProducts_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ProductCatalogServiceServer).ListProducts(ctx, req.(*Empty)) + } + return interceptor(ctx, in, info, handler) +} + +func _ProductCatalogService_GetProduct_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetProductRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ProductCatalogServiceServer).GetProduct(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: ProductCatalogService_GetProduct_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ProductCatalogServiceServer).GetProduct(ctx, req.(*GetProductRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _ProductCatalogService_SearchProducts_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(SearchProductsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ProductCatalogServiceServer).SearchProducts(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: ProductCatalogService_SearchProducts_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ProductCatalogServiceServer).SearchProducts(ctx, req.(*SearchProductsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// ProductCatalogService_ServiceDesc is the grpc.ServiceDesc for ProductCatalogService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var ProductCatalogService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "hipstershop.ProductCatalogService", + HandlerType: (*ProductCatalogServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "ListProducts", + Handler: _ProductCatalogService_ListProducts_Handler, + }, + { + MethodName: "GetProduct", + Handler: _ProductCatalogService_GetProduct_Handler, + }, + { + MethodName: "SearchProducts", + Handler: _ProductCatalogService_SearchProducts_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "demo.proto", +} + +const ( + ShippingService_GetQuote_FullMethodName = "/hipstershop.ShippingService/GetQuote" + ShippingService_ShipOrder_FullMethodName = "/hipstershop.ShippingService/ShipOrder" +) + +// ShippingServiceClient is the client API for ShippingService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type ShippingServiceClient interface { + GetQuote(ctx context.Context, in *GetQuoteRequest, opts ...grpc.CallOption) (*GetQuoteResponse, error) + ShipOrder(ctx context.Context, in *ShipOrderRequest, opts ...grpc.CallOption) (*ShipOrderResponse, error) +} + +type shippingServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewShippingServiceClient(cc grpc.ClientConnInterface) ShippingServiceClient { + return &shippingServiceClient{cc} +} + +func (c *shippingServiceClient) GetQuote(ctx context.Context, in *GetQuoteRequest, opts ...grpc.CallOption) (*GetQuoteResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(GetQuoteResponse) + err := c.cc.Invoke(ctx, ShippingService_GetQuote_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *shippingServiceClient) ShipOrder(ctx context.Context, in *ShipOrderRequest, opts ...grpc.CallOption) (*ShipOrderResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ShipOrderResponse) + err := c.cc.Invoke(ctx, ShippingService_ShipOrder_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +// ShippingServiceServer is the server API for ShippingService service. +// All implementations must embed UnimplementedShippingServiceServer +// for forward compatibility. +type ShippingServiceServer interface { + GetQuote(context.Context, *GetQuoteRequest) (*GetQuoteResponse, error) + ShipOrder(context.Context, *ShipOrderRequest) (*ShipOrderResponse, error) + mustEmbedUnimplementedShippingServiceServer() +} + +// UnimplementedShippingServiceServer must be embedded to have +// forward compatible implementations. +// +// NOTE: this should be embedded by value instead of pointer to avoid a nil +// pointer dereference when methods are called. +type UnimplementedShippingServiceServer struct{} + +func (UnimplementedShippingServiceServer) GetQuote(context.Context, *GetQuoteRequest) (*GetQuoteResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetQuote not implemented") +} +func (UnimplementedShippingServiceServer) ShipOrder(context.Context, *ShipOrderRequest) (*ShipOrderResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method ShipOrder not implemented") +} +func (UnimplementedShippingServiceServer) mustEmbedUnimplementedShippingServiceServer() {} +func (UnimplementedShippingServiceServer) testEmbeddedByValue() {} + +// UnsafeShippingServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to ShippingServiceServer will +// result in compilation errors. +type UnsafeShippingServiceServer interface { + mustEmbedUnimplementedShippingServiceServer() +} + +func RegisterShippingServiceServer(s grpc.ServiceRegistrar, srv ShippingServiceServer) { + // If the following call pancis, it indicates UnimplementedShippingServiceServer was + // embedded by pointer and is nil. This will cause panics if an + // unimplemented method is ever invoked, so we test this at initialization + // time to prevent it from happening at runtime later due to I/O. + if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { + t.testEmbeddedByValue() + } + s.RegisterService(&ShippingService_ServiceDesc, srv) +} + +func _ShippingService_GetQuote_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetQuoteRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ShippingServiceServer).GetQuote(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: ShippingService_GetQuote_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ShippingServiceServer).GetQuote(ctx, req.(*GetQuoteRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _ShippingService_ShipOrder_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ShipOrderRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ShippingServiceServer).ShipOrder(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: ShippingService_ShipOrder_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ShippingServiceServer).ShipOrder(ctx, req.(*ShipOrderRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// ShippingService_ServiceDesc is the grpc.ServiceDesc for ShippingService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var ShippingService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "hipstershop.ShippingService", + HandlerType: (*ShippingServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "GetQuote", + Handler: _ShippingService_GetQuote_Handler, + }, + { + MethodName: "ShipOrder", + Handler: _ShippingService_ShipOrder_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "demo.proto", +} + +const ( + CurrencyService_GetSupportedCurrencies_FullMethodName = "/hipstershop.CurrencyService/GetSupportedCurrencies" + CurrencyService_Convert_FullMethodName = "/hipstershop.CurrencyService/Convert" +) + +// CurrencyServiceClient is the client API for CurrencyService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type CurrencyServiceClient interface { + GetSupportedCurrencies(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*GetSupportedCurrenciesResponse, error) + Convert(ctx context.Context, in *CurrencyConversionRequest, opts ...grpc.CallOption) (*Money, error) +} + +type currencyServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewCurrencyServiceClient(cc grpc.ClientConnInterface) CurrencyServiceClient { + return ¤cyServiceClient{cc} +} + +func (c *currencyServiceClient) GetSupportedCurrencies(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*GetSupportedCurrenciesResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(GetSupportedCurrenciesResponse) + err := c.cc.Invoke(ctx, CurrencyService_GetSupportedCurrencies_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *currencyServiceClient) Convert(ctx context.Context, in *CurrencyConversionRequest, opts ...grpc.CallOption) (*Money, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(Money) + err := c.cc.Invoke(ctx, CurrencyService_Convert_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +// CurrencyServiceServer is the server API for CurrencyService service. +// All implementations must embed UnimplementedCurrencyServiceServer +// for forward compatibility. +type CurrencyServiceServer interface { + GetSupportedCurrencies(context.Context, *Empty) (*GetSupportedCurrenciesResponse, error) + Convert(context.Context, *CurrencyConversionRequest) (*Money, error) + mustEmbedUnimplementedCurrencyServiceServer() +} + +// UnimplementedCurrencyServiceServer must be embedded to have +// forward compatible implementations. +// +// NOTE: this should be embedded by value instead of pointer to avoid a nil +// pointer dereference when methods are called. +type UnimplementedCurrencyServiceServer struct{} + +func (UnimplementedCurrencyServiceServer) GetSupportedCurrencies(context.Context, *Empty) (*GetSupportedCurrenciesResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetSupportedCurrencies not implemented") +} +func (UnimplementedCurrencyServiceServer) Convert(context.Context, *CurrencyConversionRequest) (*Money, error) { + return nil, status.Errorf(codes.Unimplemented, "method Convert not implemented") +} +func (UnimplementedCurrencyServiceServer) mustEmbedUnimplementedCurrencyServiceServer() {} +func (UnimplementedCurrencyServiceServer) testEmbeddedByValue() {} + +// UnsafeCurrencyServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to CurrencyServiceServer will +// result in compilation errors. +type UnsafeCurrencyServiceServer interface { + mustEmbedUnimplementedCurrencyServiceServer() +} + +func RegisterCurrencyServiceServer(s grpc.ServiceRegistrar, srv CurrencyServiceServer) { + // If the following call pancis, it indicates UnimplementedCurrencyServiceServer was + // embedded by pointer and is nil. This will cause panics if an + // unimplemented method is ever invoked, so we test this at initialization + // time to prevent it from happening at runtime later due to I/O. + if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { + t.testEmbeddedByValue() + } + s.RegisterService(&CurrencyService_ServiceDesc, srv) +} + +func _CurrencyService_GetSupportedCurrencies_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(Empty) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(CurrencyServiceServer).GetSupportedCurrencies(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: CurrencyService_GetSupportedCurrencies_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(CurrencyServiceServer).GetSupportedCurrencies(ctx, req.(*Empty)) + } + return interceptor(ctx, in, info, handler) +} + +func _CurrencyService_Convert_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(CurrencyConversionRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(CurrencyServiceServer).Convert(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: CurrencyService_Convert_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(CurrencyServiceServer).Convert(ctx, req.(*CurrencyConversionRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// CurrencyService_ServiceDesc is the grpc.ServiceDesc for CurrencyService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var CurrencyService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "hipstershop.CurrencyService", + HandlerType: (*CurrencyServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "GetSupportedCurrencies", + Handler: _CurrencyService_GetSupportedCurrencies_Handler, + }, + { + MethodName: "Convert", + Handler: _CurrencyService_Convert_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "demo.proto", +} + +const ( + PaymentService_Charge_FullMethodName = "/hipstershop.PaymentService/Charge" +) + +// PaymentServiceClient is the client API for PaymentService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type PaymentServiceClient interface { + Charge(ctx context.Context, in *ChargeRequest, opts ...grpc.CallOption) (*ChargeResponse, error) +} + +type paymentServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewPaymentServiceClient(cc grpc.ClientConnInterface) PaymentServiceClient { + return &paymentServiceClient{cc} +} + +func (c *paymentServiceClient) Charge(ctx context.Context, in *ChargeRequest, opts ...grpc.CallOption) (*ChargeResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ChargeResponse) + err := c.cc.Invoke(ctx, PaymentService_Charge_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +// PaymentServiceServer is the server API for PaymentService service. +// All implementations must embed UnimplementedPaymentServiceServer +// for forward compatibility. +type PaymentServiceServer interface { + Charge(context.Context, *ChargeRequest) (*ChargeResponse, error) + mustEmbedUnimplementedPaymentServiceServer() +} + +// UnimplementedPaymentServiceServer must be embedded to have +// forward compatible implementations. +// +// NOTE: this should be embedded by value instead of pointer to avoid a nil +// pointer dereference when methods are called. +type UnimplementedPaymentServiceServer struct{} + +func (UnimplementedPaymentServiceServer) Charge(context.Context, *ChargeRequest) (*ChargeResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method Charge not implemented") +} +func (UnimplementedPaymentServiceServer) mustEmbedUnimplementedPaymentServiceServer() {} +func (UnimplementedPaymentServiceServer) testEmbeddedByValue() {} + +// UnsafePaymentServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to PaymentServiceServer will +// result in compilation errors. +type UnsafePaymentServiceServer interface { + mustEmbedUnimplementedPaymentServiceServer() +} + +func RegisterPaymentServiceServer(s grpc.ServiceRegistrar, srv PaymentServiceServer) { + // If the following call pancis, it indicates UnimplementedPaymentServiceServer was + // embedded by pointer and is nil. This will cause panics if an + // unimplemented method is ever invoked, so we test this at initialization + // time to prevent it from happening at runtime later due to I/O. + if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { + t.testEmbeddedByValue() + } + s.RegisterService(&PaymentService_ServiceDesc, srv) +} + +func _PaymentService_Charge_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ChargeRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(PaymentServiceServer).Charge(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: PaymentService_Charge_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(PaymentServiceServer).Charge(ctx, req.(*ChargeRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// PaymentService_ServiceDesc is the grpc.ServiceDesc for PaymentService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var PaymentService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "hipstershop.PaymentService", + HandlerType: (*PaymentServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "Charge", + Handler: _PaymentService_Charge_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "demo.proto", +} + +const ( + EmailService_SendOrderConfirmation_FullMethodName = "/hipstershop.EmailService/SendOrderConfirmation" +) + +// EmailServiceClient is the client API for EmailService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type EmailServiceClient interface { + SendOrderConfirmation(ctx context.Context, in *SendOrderConfirmationRequest, opts ...grpc.CallOption) (*Empty, error) +} + +type emailServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewEmailServiceClient(cc grpc.ClientConnInterface) EmailServiceClient { + return &emailServiceClient{cc} +} + +func (c *emailServiceClient) SendOrderConfirmation(ctx context.Context, in *SendOrderConfirmationRequest, opts ...grpc.CallOption) (*Empty, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(Empty) + err := c.cc.Invoke(ctx, EmailService_SendOrderConfirmation_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +// EmailServiceServer is the server API for EmailService service. +// All implementations must embed UnimplementedEmailServiceServer +// for forward compatibility. +type EmailServiceServer interface { + SendOrderConfirmation(context.Context, *SendOrderConfirmationRequest) (*Empty, error) + mustEmbedUnimplementedEmailServiceServer() +} + +// UnimplementedEmailServiceServer must be embedded to have +// forward compatible implementations. +// +// NOTE: this should be embedded by value instead of pointer to avoid a nil +// pointer dereference when methods are called. +type UnimplementedEmailServiceServer struct{} + +func (UnimplementedEmailServiceServer) SendOrderConfirmation(context.Context, *SendOrderConfirmationRequest) (*Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method SendOrderConfirmation not implemented") +} +func (UnimplementedEmailServiceServer) mustEmbedUnimplementedEmailServiceServer() {} +func (UnimplementedEmailServiceServer) testEmbeddedByValue() {} + +// UnsafeEmailServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to EmailServiceServer will +// result in compilation errors. +type UnsafeEmailServiceServer interface { + mustEmbedUnimplementedEmailServiceServer() +} + +func RegisterEmailServiceServer(s grpc.ServiceRegistrar, srv EmailServiceServer) { + // If the following call pancis, it indicates UnimplementedEmailServiceServer was + // embedded by pointer and is nil. This will cause panics if an + // unimplemented method is ever invoked, so we test this at initialization + // time to prevent it from happening at runtime later due to I/O. + if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { + t.testEmbeddedByValue() + } + s.RegisterService(&EmailService_ServiceDesc, srv) +} + +func _EmailService_SendOrderConfirmation_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(SendOrderConfirmationRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(EmailServiceServer).SendOrderConfirmation(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: EmailService_SendOrderConfirmation_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(EmailServiceServer).SendOrderConfirmation(ctx, req.(*SendOrderConfirmationRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// EmailService_ServiceDesc is the grpc.ServiceDesc for EmailService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var EmailService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "hipstershop.EmailService", + HandlerType: (*EmailServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "SendOrderConfirmation", + Handler: _EmailService_SendOrderConfirmation_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "demo.proto", +} + +const ( + CheckoutService_PlaceOrder_FullMethodName = "/hipstershop.CheckoutService/PlaceOrder" +) + +// CheckoutServiceClient is the client API for CheckoutService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type CheckoutServiceClient interface { + PlaceOrder(ctx context.Context, in *PlaceOrderRequest, opts ...grpc.CallOption) (*PlaceOrderResponse, error) +} + +type checkoutServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewCheckoutServiceClient(cc grpc.ClientConnInterface) CheckoutServiceClient { + return &checkoutServiceClient{cc} +} + +func (c *checkoutServiceClient) PlaceOrder(ctx context.Context, in *PlaceOrderRequest, opts ...grpc.CallOption) (*PlaceOrderResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(PlaceOrderResponse) + err := c.cc.Invoke(ctx, CheckoutService_PlaceOrder_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +// CheckoutServiceServer is the server API for CheckoutService service. +// All implementations must embed UnimplementedCheckoutServiceServer +// for forward compatibility. +type CheckoutServiceServer interface { + PlaceOrder(context.Context, *PlaceOrderRequest) (*PlaceOrderResponse, error) + mustEmbedUnimplementedCheckoutServiceServer() +} + +// UnimplementedCheckoutServiceServer must be embedded to have +// forward compatible implementations. +// +// NOTE: this should be embedded by value instead of pointer to avoid a nil +// pointer dereference when methods are called. +type UnimplementedCheckoutServiceServer struct{} + +func (UnimplementedCheckoutServiceServer) PlaceOrder(context.Context, *PlaceOrderRequest) (*PlaceOrderResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method PlaceOrder not implemented") +} +func (UnimplementedCheckoutServiceServer) mustEmbedUnimplementedCheckoutServiceServer() {} +func (UnimplementedCheckoutServiceServer) testEmbeddedByValue() {} + +// UnsafeCheckoutServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to CheckoutServiceServer will +// result in compilation errors. +type UnsafeCheckoutServiceServer interface { + mustEmbedUnimplementedCheckoutServiceServer() +} + +func RegisterCheckoutServiceServer(s grpc.ServiceRegistrar, srv CheckoutServiceServer) { + // If the following call pancis, it indicates UnimplementedCheckoutServiceServer was + // embedded by pointer and is nil. This will cause panics if an + // unimplemented method is ever invoked, so we test this at initialization + // time to prevent it from happening at runtime later due to I/O. + if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { + t.testEmbeddedByValue() + } + s.RegisterService(&CheckoutService_ServiceDesc, srv) +} + +func _CheckoutService_PlaceOrder_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(PlaceOrderRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(CheckoutServiceServer).PlaceOrder(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: CheckoutService_PlaceOrder_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(CheckoutServiceServer).PlaceOrder(ctx, req.(*PlaceOrderRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// CheckoutService_ServiceDesc is the grpc.ServiceDesc for CheckoutService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var CheckoutService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "hipstershop.CheckoutService", + HandlerType: (*CheckoutServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "PlaceOrder", + Handler: _CheckoutService_PlaceOrder_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "demo.proto", +} + +const ( + AdService_GetAds_FullMethodName = "/hipstershop.AdService/GetAds" +) + +// AdServiceClient is the client API for AdService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type AdServiceClient interface { + GetAds(ctx context.Context, in *AdRequest, opts ...grpc.CallOption) (*AdResponse, error) +} + +type adServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewAdServiceClient(cc grpc.ClientConnInterface) AdServiceClient { + return &adServiceClient{cc} +} + +func (c *adServiceClient) GetAds(ctx context.Context, in *AdRequest, opts ...grpc.CallOption) (*AdResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(AdResponse) + err := c.cc.Invoke(ctx, AdService_GetAds_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +// AdServiceServer is the server API for AdService service. +// All implementations must embed UnimplementedAdServiceServer +// for forward compatibility. +type AdServiceServer interface { + GetAds(context.Context, *AdRequest) (*AdResponse, error) + mustEmbedUnimplementedAdServiceServer() +} + +// UnimplementedAdServiceServer must be embedded to have +// forward compatible implementations. +// +// NOTE: this should be embedded by value instead of pointer to avoid a nil +// pointer dereference when methods are called. +type UnimplementedAdServiceServer struct{} + +func (UnimplementedAdServiceServer) GetAds(context.Context, *AdRequest) (*AdResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetAds not implemented") +} +func (UnimplementedAdServiceServer) mustEmbedUnimplementedAdServiceServer() {} +func (UnimplementedAdServiceServer) testEmbeddedByValue() {} + +// UnsafeAdServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to AdServiceServer will +// result in compilation errors. +type UnsafeAdServiceServer interface { + mustEmbedUnimplementedAdServiceServer() +} + +func RegisterAdServiceServer(s grpc.ServiceRegistrar, srv AdServiceServer) { + // If the following call pancis, it indicates UnimplementedAdServiceServer was + // embedded by pointer and is nil. This will cause panics if an + // unimplemented method is ever invoked, so we test this at initialization + // time to prevent it from happening at runtime later due to I/O. + if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { + t.testEmbeddedByValue() + } + s.RegisterService(&AdService_ServiceDesc, srv) +} + +func _AdService_GetAds_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(AdRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AdServiceServer).GetAds(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: AdService_GetAds_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AdServiceServer).GetAds(ctx, req.(*AdRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// AdService_ServiceDesc is the grpc.ServiceDesc for AdService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var AdService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "hipstershop.AdService", + HandlerType: (*AdServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "GetAds", + Handler: _AdService_GetAds_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "demo.proto", +} diff --git a/src/shippingservice/go.mod b/src/shippingservice/go.mod new file mode 100644 index 0000000..52cdc9d --- /dev/null +++ b/src/shippingservice/go.mod @@ -0,0 +1,41 @@ +module github.com/GoogleCloudPlatform/microservices-demo/src/shippingservice + +go 1.25 + +toolchain go1.25.6 + +require ( + cloud.google.com/go/profiler v0.4.3 + github.com/sirupsen/logrus v1.9.4 + golang.org/x/net v0.49.0 + google.golang.org/grpc v1.78.0 + google.golang.org/protobuf v1.36.11 +) + +require ( + cloud.google.com/go v0.123.0 // indirect + cloud.google.com/go/auth v0.17.0 // indirect + cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect + cloud.google.com/go/compute/metadata v0.9.0 // indirect + github.com/go-logr/logr v1.4.3 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/google/pprof v0.0.0-20251114195745-4902fdda35c8 // indirect + github.com/google/s2a-go v0.1.9 // indirect + github.com/googleapis/enterprise-certificate-proxy v0.3.7 // indirect + github.com/googleapis/gax-go/v2 v2.15.0 // indirect + go.opentelemetry.io/auto/sdk v1.2.1 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0 // indirect + go.opentelemetry.io/otel v1.38.0 // indirect + go.opentelemetry.io/otel/metric v1.38.0 // indirect + go.opentelemetry.io/otel/trace v1.38.0 // indirect + golang.org/x/crypto v0.47.0 // indirect + golang.org/x/oauth2 v0.33.0 // indirect + golang.org/x/sync v0.19.0 // indirect + golang.org/x/sys v0.40.0 // indirect + golang.org/x/text v0.33.0 // indirect + golang.org/x/time v0.14.0 // indirect + google.golang.org/api v0.256.0 // indirect + google.golang.org/genproto v0.0.0-20251124214823-79d6a2a48846 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20251124214823-79d6a2a48846 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20251124214823-79d6a2a48846 // indirect +) diff --git a/src/shippingservice/go.sum b/src/shippingservice/go.sum new file mode 100644 index 0000000..ca29c97 --- /dev/null +++ b/src/shippingservice/go.sum @@ -0,0 +1,148 @@ +cel.dev/expr v0.24.0 h1:56OvJKSH3hDGL0ml5uSxZmz3/3Pq4tJ+fb1unVLAFcY= +cel.dev/expr v0.24.0/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw= +cloud.google.com/go v0.123.0 h1:2NAUJwPR47q+E35uaJeYoNhuNEM9kM8SjgRgdeOJUSE= +cloud.google.com/go v0.123.0/go.mod h1:xBoMV08QcqUGuPW65Qfm1o9Y4zKZBpGS+7bImXLTAZU= +cloud.google.com/go/auth v0.17.0 h1:74yCm7hCj2rUyyAocqnFzsAYXgJhrG26XCFimrc/Kz4= +cloud.google.com/go/auth v0.17.0/go.mod h1:6wv/t5/6rOPAX4fJiRjKkJCvswLwdet7G8+UGXt7nCQ= +cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc= +cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c= +cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs= +cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10= +cloud.google.com/go/iam v1.5.3 h1:+vMINPiDF2ognBJ97ABAYYwRgsaqxPbQDlMnbHMjolc= +cloud.google.com/go/iam v1.5.3/go.mod h1:MR3v9oLkZCTlaqljW6Eb2d3HGDGK5/bDv93jhfISFvU= +cloud.google.com/go/monitoring v1.24.3 h1:dde+gMNc0UhPZD1Azu6at2e79bfdztVDS5lvhOdsgaE= +cloud.google.com/go/monitoring v1.24.3/go.mod h1:nYP6W0tm3N9H/bOw8am7t62YTzZY+zUeQ+Bi6+2eonI= +cloud.google.com/go/profiler v0.4.3 h1:IY3QNKlr8VbXwGWHcZbJQsMA/83ZTH6uAHf8jYyj7OI= +cloud.google.com/go/profiler v0.4.3/go.mod h1:3xFodugWfPIQZWFcXdUmfa+yTiiyQ8fWrdT+d2Sg4J0= +cloud.google.com/go/storage v1.56.0 h1:iixmq2Fse2tqxMbWhLWC9HfBj1qdxqAmiK8/eqtsLxI= +cloud.google.com/go/storage v1.56.0/go.mod h1:Tpuj6t4NweCLzlNbw9Z9iwxEkrSem20AetIeH/shgVU= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0 h1:sBEjpZlNHzK1voKq9695PJSX2o5NEXl7/OL3coiIY0c= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0/go.mod h1:P4WPRUkOhJC13W//jWpyfJNDAIpvRbAUIYLX/4jtlE0= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.53.0 h1:owcC2UnmsZycprQ5RfRgjydWhuoxg71LUfyiQdijZuM= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.53.0/go.mod h1:ZPpqegjbE99EPKsu3iUWV22A04wzGPcAY/ziSIQEEgs= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.53.0 h1:Ron4zCA/yk6U7WOBXhTJcDpsUBG9npumK6xw2auFltQ= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.53.0/go.mod h1:cSgYe11MCNYunTnRXrKiR/tHc0eoKjICUuWpNZoVCOo= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cncf/xds/go v0.0.0-20251022180443-0feb69152e9f h1:Y8xYupdHxryycyPlc9Y+bSQAYZnetRJ70VMVKm5CKI0= +github.com/cncf/xds/go v0.0.0-20251022180443-0feb69152e9f/go.mod h1:HlzOvOjVBOfTGSRXRyY0OiCS/3J1akRGQQpRO/7zyF4= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/envoyproxy/go-control-plane v0.13.5-0.20251024222203-75eaa193e329 h1:K+fnvUM0VZ7ZFJf0n4L/BRlnsb9pL/GuDG6FqaH+PwM= +github.com/envoyproxy/go-control-plane/envoy v1.35.0 h1:ixjkELDE+ru6idPxcHLj8LBVc2bFP7iBytj353BoHUo= +github.com/envoyproxy/go-control-plane/envoy v1.35.0/go.mod h1:09qwbGVuSWWAyN5t/b3iyVfz5+z8QWGrzkoqm/8SbEs= +github.com/envoyproxy/protoc-gen-validate v1.2.1 h1:DEo3O99U8j4hBFwbJfrz9VtgcDfUKS7KJ7spH3d86P8= +github.com/envoyproxy/protoc-gen-validate v1.2.1/go.mod h1:d/C80l/jxXLdfEIhX1W2TmLfsJ31lvEjwamM4DxlWXU= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/go-jose/go-jose/v4 v4.1.3 h1:CVLmWDhDVRa6Mi/IgCgaopNosCaHz7zrMeF9MlZRkrs= +github.com/go-jose/go-jose/v4 v4.1.3/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/golang/mock v1.7.0-rc.1 h1:YojYx61/OLFsiv6Rw1Z96LpldJIy31o+UHmwAUMJ6/U= +github.com/golang/mock v1.7.0-rc.1/go.mod h1:s42URUywIqd+OcERslBJvOjepvNymP31m3q8d/GkuRs= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/pprof v0.0.0-20251114195745-4902fdda35c8 h1:3DsUAV+VNEQa2CUVLxCY3f87278uWfIDhJnbdvDjvmE= +github.com/google/pprof v0.0.0-20251114195745-4902fdda35c8/go.mod h1:I6V7YzU0XDpsHqbsyrghnFZLO1gwK6NPTNvmetQIk9U= +github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0= +github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/enterprise-certificate-proxy v0.3.7 h1:zrn2Ee/nWmHulBx5sAVrGgAa0f2/R35S4DJwfFaUPFQ= +github.com/googleapis/enterprise-certificate-proxy v0.3.7/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA= +github.com/googleapis/gax-go/v2 v2.15.0 h1:SyjDc1mGgZU5LncH8gimWo9lW1DtIfPibOG81vgd/bo= +github.com/googleapis/gax-go/v2 v2.15.0/go.mod h1:zVVkkxAQHa1RQpg9z2AUCMnKhi0Qld9rcmyfL1OZhoc= +github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo= +github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w= +github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g= +github.com/spiffe/go-spiffe/v2 v2.6.0 h1:l+DolpxNWYgruGQVV0xsfeya3CsC7m8iBzDnMpsbLuo= +github.com/spiffe/go-spiffe/v2 v2.6.0/go.mod h1:gm2SeUoMZEtpnzPNs2Csc0D/gX33k1xIx7lEzqblHEs= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= +go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= +go.opentelemetry.io/contrib/detectors/gcp v1.38.0 h1:ZoYbqX7OaA/TAikspPl3ozPI6iY6LiIY9I8cUfm+pJs= +go.opentelemetry.io/contrib/detectors/gcp v1.38.0/go.mod h1:SU+iU7nu5ud4oCb3LQOhIZ3nRLj6FNVrKgtflbaf2ts= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0 h1:YH4g8lQroajqUwWbq/tr2QX1JFmEXaDLgG+ew9bLMWo= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0/go.mod h1:fvPi2qXDqFs8M4B4fmJhE92TyQs9Ydjlg3RvfUp+NbQ= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q= +go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= +go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= +go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= +go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= +go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E= +go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg= +go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM= +go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= +go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= +go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= +golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q= +golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4= +golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU= +golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0= +golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8= +golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A= +golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= +golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= +golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU= +golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY= +golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o= +golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8= +golang.org/x/oauth2 v0.33.0 h1:4Q+qn+E5z8gPRJfmRy7C2gGG3T4jIprK6aSYgTXGRpo= +golang.org/x/oauth2 v0.33.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= +golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= +golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= +golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= +golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= +golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= +golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= +golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= +golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU= +golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY= +golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= +golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= +golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= +golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= +gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= +gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= +google.golang.org/api v0.256.0 h1:u6Khm8+F9sxbCTYNoBHg6/Hwv0N/i+V94MvkOSor6oI= +google.golang.org/api v0.256.0/go.mod h1:KIgPhksXADEKJlnEoRa9qAII4rXcy40vfI8HRqcU964= +google.golang.org/genproto v0.0.0-20251124214823-79d6a2a48846 h1:dDbsTLIK7EzwUq36kCSAsk0slouq/S0tWHeeGi97cD8= +google.golang.org/genproto v0.0.0-20251124214823-79d6a2a48846/go.mod h1:PP0g88Dz3C7hRAfbQCQggeWAXjuqGsNPLE4s7jh0RGU= +google.golang.org/genproto/googleapis/api v0.0.0-20251124214823-79d6a2a48846 h1:ZdyUkS9po3H7G0tuh955QVyyotWvOD4W0aEapeGeUYk= +google.golang.org/genproto/googleapis/api v0.0.0-20251124214823-79d6a2a48846/go.mod h1:Fk4kyraUvqD7i5H6S43sj2W98fbZa75lpZz/eUyhfO0= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251124214823-79d6a2a48846 h1:Wgl1rcDNThT+Zn47YyCXOXyX/COgMTIdhJ717F0l4xk= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251124214823-79d6a2a48846/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= +google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM= +google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig= +google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc= +google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U= +google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= +google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= +google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/src/shippingservice/main.go b/src/shippingservice/main.go new file mode 100644 index 0000000..5ba495a --- /dev/null +++ b/src/shippingservice/main.go @@ -0,0 +1,179 @@ +// Copyright 2018 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +import ( + "fmt" + "net" + "os" + "time" + + "cloud.google.com/go/profiler" + "github.com/sirupsen/logrus" + "golang.org/x/net/context" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/health" + "google.golang.org/grpc/reflection" + "google.golang.org/grpc/status" + + pb "github.com/GoogleCloudPlatform/microservices-demo/src/shippingservice/genproto" + healthpb "google.golang.org/grpc/health/grpc_health_v1" +) + +const ( + defaultPort = "50051" +) + +var log *logrus.Logger + +func init() { + log = logrus.New() + log.Level = logrus.DebugLevel + log.Formatter = &logrus.JSONFormatter{ + FieldMap: logrus.FieldMap{ + logrus.FieldKeyTime: "timestamp", + logrus.FieldKeyLevel: "severity", + logrus.FieldKeyMsg: "message", + }, + TimestampFormat: time.RFC3339Nano, + } + log.Out = os.Stdout +} + +func main() { + if os.Getenv("DISABLE_TRACING") == "" { + log.Info("Tracing enabled, but temporarily unavailable") + log.Info("See https://github.com/GoogleCloudPlatform/microservices-demo/issues/422 for more info.") + go initTracing() + } else { + log.Info("Tracing disabled.") + } + + if os.Getenv("DISABLE_PROFILER") == "" { + log.Info("Profiling enabled.") + go initProfiling("shippingservice", "1.0.0") + } else { + log.Info("Profiling disabled.") + } + + port := defaultPort + if value, ok := os.LookupEnv("PORT"); ok { + port = value + } + port = fmt.Sprintf(":%s", port) + + lis, err := net.Listen("tcp", port) + if err != nil { + log.Fatalf("failed to listen: %v", err) + } + + var srv *grpc.Server + if os.Getenv("DISABLE_STATS") == "" { + log.Info("Stats enabled, but temporarily unavailable") + srv = grpc.NewServer() + } else { + log.Info("Stats disabled.") + srv = grpc.NewServer() + } + svc := &server{} + pb.RegisterShippingServiceServer(srv, svc) + healthcheck := health.NewServer() + healthpb.RegisterHealthServer(srv, healthcheck) + log.Infof("Shipping Service listening on port %s", port) + + // Register reflection service on gRPC server. + reflection.Register(srv) + if err := srv.Serve(lis); err != nil { + log.Fatalf("failed to serve: %v", err) + } +} + +// server controls RPC service responses. +type server struct { + pb.UnimplementedShippingServiceServer +} + +// Check is for health checking. +func (s *server) Check(ctx context.Context, req *healthpb.HealthCheckRequest) (*healthpb.HealthCheckResponse, error) { + return &healthpb.HealthCheckResponse{Status: healthpb.HealthCheckResponse_SERVING}, nil +} + +func (s *server) Watch(req *healthpb.HealthCheckRequest, ws healthpb.Health_WatchServer) error { + return status.Errorf(codes.Unimplemented, "health check via Watch not implemented") +} + +// GetQuote produces a shipping quote (cost) in USD. +func (s *server) GetQuote(ctx context.Context, in *pb.GetQuoteRequest) (*pb.GetQuoteResponse, error) { + log.Info("[GetQuote] received request") + defer log.Info("[GetQuote] completed request") + + // 1. Generate a quote based on the total number of items to be shipped. + quote := CreateQuoteFromCount(0) + + // 2. Generate a response. + return &pb.GetQuoteResponse{ + CostUsd: &pb.Money{ + CurrencyCode: "USD", + Units: int64(quote.Dollars), + Nanos: int32(quote.Cents * 10000000)}, + }, nil + +} + +// ShipOrder mocks that the requested items will be shipped. +// It supplies a tracking ID for notional lookup of shipment delivery status. +func (s *server) ShipOrder(ctx context.Context, in *pb.ShipOrderRequest) (*pb.ShipOrderResponse, error) { + log.Info("[ShipOrder] received request") + defer log.Info("[ShipOrder] completed request") + // 1. Create a Tracking ID + baseAddress := fmt.Sprintf("%s, %s, %s", in.Address.StreetAddress, in.Address.City, in.Address.State) + id := CreateTrackingId(baseAddress) + + // 2. Generate a response. + return &pb.ShipOrderResponse{ + TrackingId: id, + }, nil +} + +func initStats() { + //TODO(arbrown) Implement OpenTelemetry stats +} + +func initTracing() { + // TODO(arbrown) Implement OpenTelemetry tracing +} + +func initProfiling(service, version string) { + // TODO(ahmetb) this method is duplicated in other microservices using Go + // since they are not sharing packages. + for i := 1; i <= 3; i++ { + if err := profiler.Start(profiler.Config{ + Service: service, + ServiceVersion: version, + // ProjectID must be set if not running on GCP. + // ProjectID: "my-project", + }); err != nil { + log.Warnf("failed to start profiler: %+v", err) + } else { + log.Info("started Stackdriver profiler") + return + } + d := time.Second * 10 * time.Duration(i) + log.Infof("sleeping %v to retry initializing Stackdriver profiler", d) + time.Sleep(d) + } + log.Warn("could not initialize Stackdriver profiler after retrying, giving up") +} diff --git a/src/shippingservice/quote.go b/src/shippingservice/quote.go new file mode 100644 index 0000000..46bc39e --- /dev/null +++ b/src/shippingservice/quote.go @@ -0,0 +1,45 @@ +// Copyright 2018 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +import ( + "fmt" + "math" +) + +// Quote represents a currency value. +type Quote struct { + Dollars uint32 + Cents uint32 +} + +// String representation of the Quote. +func (q Quote) String() string { + return fmt.Sprintf("$%d.%d", q.Dollars, q.Cents) +} + +// CreateQuoteFromCount takes a number of items and returns a Price struct. +func CreateQuoteFromCount(count int) Quote { + return CreateQuoteFromFloat(8.99) +} + +// CreateQuoteFromFloat takes a price represented as a float and creates a Price struct. +func CreateQuoteFromFloat(value float64) Quote { + units, fraction := math.Modf(value) + return Quote{ + uint32(units), + uint32(math.Trunc(fraction * 100)), + } +} \ No newline at end of file diff --git a/src/shippingservice/shippingservice_test.go b/src/shippingservice/shippingservice_test.go new file mode 100644 index 0000000..6f22d32 --- /dev/null +++ b/src/shippingservice/shippingservice_test.go @@ -0,0 +1,90 @@ +// Copyright 2018 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +import ( + "testing" + + "golang.org/x/net/context" + + pb "github.com/GoogleCloudPlatform/microservices-demo/src/shippingservice/genproto" +) + +// TestGetQuote is a basic check on the GetQuote RPC service. +func TestGetQuote(t *testing.T) { + s := server{} + + // A basic test case to test logic and protobuf interactions. + req := &pb.GetQuoteRequest{ + Address: &pb.Address{ + StreetAddress: "Muffin Man", + City: "London", + State: "", + Country: "England", + }, + Items: []*pb.CartItem{ + { + ProductId: "23", + Quantity: 1, + }, + { + ProductId: "46", + Quantity: 3, + }, + }, + } + + res, err := s.GetQuote(context.Background(), req) + if err != nil { + t.Errorf("TestGetQuote (%v) failed", err) + } + if res.CostUsd.GetUnits() != 8 || res.CostUsd.GetNanos() != 990000000 { + t.Errorf("TestGetQuote: Quote value '%d.%d' does not match expected '%s'", res.CostUsd.GetUnits(), res.CostUsd.GetNanos(), "11.220000000") + } +} + +// TestShipOrder is a basic check on the ShipOrder RPC service. +func TestShipOrder(t *testing.T) { + s := server{} + + // A basic test case to test logic and protobuf interactions. + req := &pb.ShipOrderRequest{ + Address: &pb.Address{ + StreetAddress: "Muffin Man", + City: "London", + State: "", + Country: "England", + }, + Items: []*pb.CartItem{ + { + ProductId: "23", + Quantity: 1, + }, + { + ProductId: "46", + Quantity: 3, + }, + }, + } + + res, err := s.ShipOrder(context.Background(), req) + if err != nil { + t.Errorf("TestShipOrder (%v) failed", err) + } + // @todo improve quality of this test to check for a pattern such as '[A-Z]{2}-\d+-\d+'. + if len(res.TrackingId) != 18 { + t.Errorf("TestShipOrder: Tracking ID is malformed - has %d characters, %d expected", len(res.TrackingId), 18) + } +} diff --git a/src/shippingservice/tracker.go b/src/shippingservice/tracker.go new file mode 100644 index 0000000..6ba3d93 --- /dev/null +++ b/src/shippingservice/tracker.go @@ -0,0 +1,56 @@ +// Copyright 2018 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +import ( + "fmt" + "math/rand" + "time" +) + +// seeded determines if the random number generator is ready. +var seeded bool = false + +// CreateTrackingId generates a tracking ID. +func CreateTrackingId(salt string) string { + if !seeded { + rand.Seed(time.Now().UnixNano()) + seeded = true + } + + return fmt.Sprintf("%c%c-%d%s-%d%s", + getRandomLetterCode(), + getRandomLetterCode(), + len(salt), + getRandomNumber(3), + len(salt)/2, + getRandomNumber(7), + ) +} + +// getRandomLetterCode generates a code point value for a capital letter. +func getRandomLetterCode() uint32 { + return 65 + uint32(rand.Intn(25)) +} + +// getRandomNumber generates a string representation of a number with the requested number of digits. +func getRandomNumber(digits int) string { + str := "" + for i := 0; i < digits; i++ { + str = fmt.Sprintf("%s%d", str, rand.Intn(10)) + } + + return str +} diff --git a/src/shoppingassistantservice/Dockerfile b/src/shoppingassistantservice/Dockerfile new file mode 100644 index 0000000..5c5adda --- /dev/null +++ b/src/shoppingassistantservice/Dockerfile @@ -0,0 +1,44 @@ +# Copyright 2024 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +FROM --platform=$BUILDPLATFORM python:3.14.2-slim@sha256:1a3c6dbfd2173971abba880c3cc2ec4643690901f6ad6742d0827bae6cefc925 AS base + +FROM base AS builder + +RUN apt-get -qq update \ + && apt-get install -y --no-install-recommends g++ \ + && rm -rf /var/lib/apt/lists/* + +# get packages +COPY requirements.txt . +RUN pip install -r requirements.txt + +FROM base +# Enable unbuffered logging +ENV PYTHONUNBUFFERED=1 + +# get packages +WORKDIR /shoppingassistantservice + +# Grab packages from builder +COPY --from=builder /usr/local/lib/python3.14/ /usr/local/lib/python3.14/ + +# Add the application +COPY . . + +# set listen port +ENV PORT "8080" +EXPOSE 8080 + +ENTRYPOINT ["python", "shoppingassistantservice.py"] diff --git a/src/shoppingassistantservice/requirements.in b/src/shoppingassistantservice/requirements.in new file mode 100644 index 0000000..2c80698 --- /dev/null +++ b/src/shoppingassistantservice/requirements.in @@ -0,0 +1,6 @@ +flask==3.1.2 +langchain-google-genai==4.1.2 +langchain==1.2.0 +pillow==12.0.0 +langchain-google-alloydb-pg==0.13.0 +google-cloud-secret-manager==2.26.0 diff --git a/src/shoppingassistantservice/requirements.txt b/src/shoppingassistantservice/requirements.txt new file mode 100644 index 0000000..5940f1d --- /dev/null +++ b/src/shoppingassistantservice/requirements.txt @@ -0,0 +1,253 @@ +# This file was autogenerated by uv via the following command: +# uv pip compile requirements.in -o requirements.txt +aiofiles==24.1.0 + # via google-cloud-alloydb-connector +annotated-types==0.7.0 + # via pydantic +anyio==4.10.0 + # via + # google-genai + # httpx +asyncpg==0.30.0 + # via + # google-cloud-alloydb-connector + # langchain-postgres +blinker==1.9.0 + # via flask +cachetools==5.5.2 + # via google-auth +certifi==2025.8.3 + # via + # httpcore + # httpx + # requests +cffi==2.0.0 + # via cryptography +charset-normalizer==3.4.3 + # via requests +click==8.3.0 + # via flask +cryptography==46.0.1 + # via google-cloud-alloydb-connector +distro==1.9.0 + # via google-genai +filetype==1.2.0 + # via langchain-google-genai +flask==3.1.2 + # via -r requirements.in +google-api-core[grpc]==2.25.1 + # via + # google-cloud-alloydb + # google-cloud-alloydb-connector + # google-cloud-core + # google-cloud-secret-manager + # google-cloud-storage +google-auth[requests]==2.45.0 + # via + # google-api-core + # google-cloud-alloydb + # google-cloud-alloydb-connector + # google-cloud-core + # google-cloud-secret-manager + # google-cloud-storage + # google-genai +google-cloud-alloydb==0.4.9 + # via google-cloud-alloydb-connector +google-cloud-alloydb-connector[asyncpg]==1.9.1 + # via langchain-google-alloydb-pg +google-cloud-core==2.4.3 + # via google-cloud-storage +google-cloud-secret-manager==2.26.0 + # via -r requirements.in +google-cloud-storage==3.4.0 + # via langchain-google-alloydb-pg +google-crc32c==1.7.1 + # via + # google-cloud-storage + # google-resumable-media +google-genai==1.56.0 + # via langchain-google-genai +google-resumable-media==2.7.2 + # via google-cloud-storage +googleapis-common-protos[grpc]==1.70.0 + # via + # google-api-core + # grpc-google-iam-v1 + # grpcio-status +greenlet==3.3.0 + # via sqlalchemy +grpc-google-iam-v1==0.14.2 + # via + # google-cloud-alloydb + # google-cloud-secret-manager +grpcio==1.76.0 + # via + # google-api-core + # google-cloud-secret-manager + # googleapis-common-protos + # grpc-google-iam-v1 + # grpcio-status +grpcio-status==1.75.0 + # via google-api-core +h11==0.16.0 + # via httpcore +httpcore==1.0.9 + # via httpx +httpx==0.28.1 + # via + # google-genai + # langgraph-sdk + # langsmith +idna==3.10 + # via + # anyio + # httpx + # requests +itsdangerous==2.2.0 + # via flask +jinja2==3.1.6 + # via flask +jsonpatch==1.33 + # via langchain-core +jsonpointer==3.0.0 + # via jsonpatch +langchain==1.2.0 + # via -r requirements.in +langchain-core==1.2.5 + # via + # langchain + # langchain-google-genai + # langchain-postgres + # langgraph + # langgraph-checkpoint + # langgraph-prebuilt +langchain-google-alloydb-pg==0.13.0 + # via -r requirements.in +langchain-google-genai==4.1.2 + # via -r requirements.in +langchain-postgres==0.0.16 + # via langchain-google-alloydb-pg +langgraph==1.0.5 + # via langchain +langgraph-checkpoint==3.0.1 + # via + # langgraph + # langgraph-prebuilt +langgraph-prebuilt==1.0.5 + # via langgraph +langgraph-sdk==0.3.1 + # via langgraph +langsmith==0.4.30 + # via langchain-core +markupsafe==3.0.2 + # via + # flask + # jinja2 + # werkzeug +numpy==2.3.3 + # via + # langchain-google-alloydb-pg + # langchain-postgres + # pgvector +orjson==3.11.3 + # via + # langgraph-sdk + # langsmith +ormsgpack==1.12.1 + # via langgraph-checkpoint +packaging==25.0 + # via + # langchain-core + # langsmith +pgvector==0.3.6 + # via langchain-postgres +pillow==12.0.0 + # via -r requirements.in +proto-plus==1.26.1 + # via + # google-api-core + # google-cloud-alloydb + # google-cloud-secret-manager +protobuf==6.32.1 + # via + # google-api-core + # google-cloud-alloydb + # google-cloud-alloydb-connector + # google-cloud-secret-manager + # googleapis-common-protos + # grpc-google-iam-v1 + # grpcio-status + # proto-plus +psycopg[binary]==3.2.10 + # via langchain-postgres +psycopg-binary==3.2.10 + # via psycopg +psycopg-pool==3.2.6 + # via langchain-postgres +pyasn1==0.6.2 + # via + # pyasn1-modules + # rsa +pyasn1-modules==0.4.2 + # via google-auth +pycparser==2.23 + # via cffi +pydantic==2.12.5 + # via + # google-genai + # langchain + # langchain-core + # langchain-google-genai + # langgraph + # langsmith +pydantic-core==2.41.5 + # via pydantic +pyyaml==6.0.2 + # via langchain-core +requests==2.32.5 + # via + # google-api-core + # google-auth + # google-cloud-alloydb-connector + # google-cloud-storage + # google-genai + # langsmith + # requests-toolbelt +requests-toolbelt==1.0.0 + # via langsmith +rsa==4.9.1 + # via google-auth +sniffio==1.3.1 + # via + # anyio + # google-genai +sqlalchemy[asyncio]==2.0.43 + # via langchain-postgres +tenacity==9.1.2 + # via + # google-genai + # langchain-core +typing-extensions==4.15.0 + # via + # google-genai + # grpcio + # langchain-core + # psycopg-pool + # pydantic + # pydantic-core + # sqlalchemy + # typing-inspection +typing-inspection==0.4.2 + # via pydantic +urllib3==2.6.3 + # via requests +uuid-utils==0.12.0 + # via langchain-core +websockets==15.0.1 + # via google-genai +werkzeug==3.1.5 + # via flask +xxhash==3.6.0 + # via langgraph +zstandard==0.25.0 + # via langsmith diff --git a/src/shoppingassistantservice/shoppingassistantservice.py b/src/shoppingassistantservice/shoppingassistantservice.py new file mode 100755 index 0000000..94fd8c2 --- /dev/null +++ b/src/shoppingassistantservice/shoppingassistantservice.py @@ -0,0 +1,120 @@ +#!/usr/bin/python +# +# Copyright 2024 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os + +from google.cloud import secretmanager_v1 +from urllib.parse import unquote +from langchain_core.messages import HumanMessage +from langchain_google_genai import ChatGoogleGenerativeAI, GoogleGenerativeAIEmbeddings +from flask import Flask, request + +from langchain_google_alloydb_pg import AlloyDBEngine, AlloyDBVectorStore + +PROJECT_ID = os.environ["PROJECT_ID"] +REGION = os.environ["REGION"] +ALLOYDB_DATABASE_NAME = os.environ["ALLOYDB_DATABASE_NAME"] +ALLOYDB_TABLE_NAME = os.environ["ALLOYDB_TABLE_NAME"] +ALLOYDB_CLUSTER_NAME = os.environ["ALLOYDB_CLUSTER_NAME"] +ALLOYDB_INSTANCE_NAME = os.environ["ALLOYDB_INSTANCE_NAME"] +ALLOYDB_SECRET_NAME = os.environ["ALLOYDB_SECRET_NAME"] + +secret_manager_client = secretmanager_v1.SecretManagerServiceClient() +secret_name = secret_manager_client.secret_version_path(project=PROJECT_ID, secret=ALLOYDB_SECRET_NAME, secret_version="latest") +secret_request = secretmanager_v1.AccessSecretVersionRequest(name=secret_name) +secret_response = secret_manager_client.access_secret_version(request=secret_request) +PGPASSWORD = secret_response.payload.data.decode("UTF-8").strip() + +engine = AlloyDBEngine.from_instance( + project_id=PROJECT_ID, + region=REGION, + cluster=ALLOYDB_CLUSTER_NAME, + instance=ALLOYDB_INSTANCE_NAME, + database=ALLOYDB_DATABASE_NAME, + user="postgres", + password=PGPASSWORD +) + +# Create a synchronous connection to our vectorstore +vectorstore = AlloyDBVectorStore.create_sync( + engine=engine, + table_name=ALLOYDB_TABLE_NAME, + embedding_service=GoogleGenerativeAIEmbeddings(model="models/embedding-001"), + id_column="id", + content_column="description", + embedding_column="product_embedding", + metadata_columns=["id", "name", "categories"] +) + +def create_app(): + app = Flask(__name__) + + @app.route("/", methods=['POST']) + def talkToGemini(): + print("Beginning RAG call") + prompt = request.json['message'] + prompt = unquote(prompt) + + # Step 1 – Get a room description from Gemini-vision-pro + llm_vision = ChatGoogleGenerativeAI(model="gemini-1.5-flash") + message = HumanMessage( + content=[ + { + "type": "text", + "text": "You are a professional interior designer, give me a detailed decsription of the style of the room in this image", + }, + {"type": "image_url", "image_url": request.json['image']}, + ] + ) + response = llm_vision.invoke([message]) + print("Description step:") + print(response) + description_response = response.content + + # Step 2 – Similarity search with the description & user prompt + vector_search_prompt = f""" This is the user's request: {prompt} Find the most relevant items for that prompt, while matching style of the room described here: {description_response} """ + print(vector_search_prompt) + + docs = vectorstore.similarity_search(vector_search_prompt) + print(f"Vector search: {description_response}") + print(f"Retrieved documents: {len(docs)}") + #Prepare relevant documents for inclusion in final prompt + relevant_docs = "" + for doc in docs: + doc_details = doc.to_json() + print(f"Adding relevant document to prompt context: {doc_details}") + relevant_docs += str(doc_details) + ", " + + # Step 3 – Tie it all together by augmenting our call to Gemini-pro + llm = ChatGoogleGenerativeAI(model="gemini-1.5-flash") + design_prompt = ( + f" You are an interior designer that works for Online Boutique. You are tasked with providing recommendations to a customer on what they should add to a given room from our catalog. This is the description of the room: \n" + f"{description_response} Here are a list of products that are relevant to it: {relevant_docs} Specifically, this is what the customer has asked for, see if you can accommodate it: {prompt} Start by repeating a brief description of the room's design to the customer, then provide your recommendations. Do your best to pick the most relevant item out of the list of products provided, but if none of them seem relevant, then say that instead of inventing a new product. At the end of the response, add a list of the IDs of the relevant products in the following format for the top 3 results: [], [], [] ") + print("Final design prompt: ") + print(design_prompt) + design_response = llm.invoke( + design_prompt + ) + + data = {'content': design_response.content} + return data + + return app + +if __name__ == "__main__": + # Create an instance of flask server when called directly + app = create_app() + app.run(host='0.0.0.0', port=8080) diff --git a/terraform/README.md b/terraform/README.md new file mode 100644 index 0000000..f6281f8 --- /dev/null +++ b/terraform/README.md @@ -0,0 +1,97 @@ + + +# Use Terraform to deploy Online Boutique on a GKE cluster + +This page walks you through the steps required to deploy the [Online Boutique](https://github.com/GoogleCloudPlatform/microservices-demo) sample application on a [Google Kubernetes Engine (GKE)](https://cloud.google.com/kubernetes-engine) cluster using Terraform. + +## Prerequisites + +1. [Create a new project or use an existing project](https://cloud.google.com/resource-manager/docs/creating-managing-projects#console) on Google Cloud, and ensure [billing is enabled](https://cloud.google.com/billing/docs/how-to/verify-billing-enabled) on the project. + +## Deploy the sample application + +1. Clone the Github repository. + + ```bash + git clone https://github.com/GoogleCloudPlatform/microservices-demo.git + ``` + +1. Move into the `terraform/` directory which contains the Terraform installation scripts. + + ```bash + cd microservices-demo/terraform + ``` + +1. Open the `terraform.tfvars` file and replace `` with the [GCP Project ID](https://cloud.google.com/resource-manager/docs/creating-managing-projects?hl=en#identifying_projects) for the `gcp_project_id` variable. + +1. (Optional) If you want to provision a [Google Cloud Memorystore (Redis)](https://cloud.google.com/memorystore) instance, you can change the value of `memorystore = false` to `memorystore = true` in this `terraform.tfvars` file. + +1. Initialize Terraform. + + ```bash + terraform init + ``` + +1. See what resources will be created. + + ```bash + terraform plan + ``` + +1. Create the resources and deploy the sample. + + ```bash + terraform apply + ``` + + 1. If there is a confirmation prompt, type `yes` and hit Enter/Return. + + Note: This step can take about 10 minutes. Do not interrupt the process. + +Once the Terraform script has finished, you can locate the frontend's external IP address to access the sample application. + +- Option 1: + + ```bash + kubectl get service frontend-external | awk '{print $4}' + ``` + +- Option 2: On Google Cloud Console, navigate to "Kubernetes Engine" and then "Services & Ingress" to locate the Endpoint associated with "frontend-external". + +## Clean up + +To avoid incurring charges to your Google Cloud account for the resources used in this sample application, either delete the project that contains the resources, or keep the project and delete the individual resources. + +To remove the individual resources created for by Terraform without deleting the project: + +1. Navigate to the `terraform/` directory. + +1. Set `deletion_protection` to `false` for the `google_container_cluster` resource (GKE cluster). + + ```bash + # Uncomment the line: "deletion_protection = false" + sed -i "s/# deletion_protection/deletion_protection/g" main.tf + + # Re-apply the Terraform to update the state + terraform apply + ``` + +1. Run the following command: + + ```bash + terraform destroy + ``` + + 1. If there is a confirmation prompt, type `yes` and hit Enter/Return. diff --git a/terraform/main.tf b/terraform/main.tf new file mode 100644 index 0000000..b5ecbbc --- /dev/null +++ b/terraform/main.tf @@ -0,0 +1,100 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Definition of local variables +locals { + base_apis = [ + "container.googleapis.com", + "monitoring.googleapis.com", + "cloudtrace.googleapis.com", + "cloudprofiler.googleapis.com" + ] + memorystore_apis = ["redis.googleapis.com"] + cluster_name = google_container_cluster.my_cluster.name +} + +# Enable Google Cloud APIs +module "enable_google_apis" { + source = "terraform-google-modules/project-factory/google//modules/project_services" + version = "~> 18.0" + + project_id = var.gcp_project_id + disable_services_on_destroy = false + + # activate_apis is the set of base_apis and the APIs required by user-configured deployment options + activate_apis = concat(local.base_apis, var.memorystore ? local.memorystore_apis : []) +} + +# Create GKE cluster +resource "google_container_cluster" "my_cluster" { + + name = var.name + location = var.region + + # Enable autopilot for this cluster + enable_autopilot = true + + # Set an empty ip_allocation_policy to allow autopilot cluster to spin up correctly + ip_allocation_policy { + } + + # Avoid setting deletion_protection to false + # until you're ready (and certain you want) to destroy the cluster. + # deletion_protection = false + + depends_on = [ + module.enable_google_apis + ] +} + +# Get credentials for cluster +module "gcloud" { + source = "terraform-google-modules/gcloud/google" + version = "~> 4.0" + + platform = "linux" + additional_components = ["kubectl", "beta"] + + create_cmd_entrypoint = "gcloud" + # Module does not support explicit dependency + # Enforce implicit dependency through use of local variable + create_cmd_body = "container clusters get-credentials ${local.cluster_name} --zone=${var.region} --project=${var.gcp_project_id}" +} + +# Apply YAML kubernetes-manifest configurations +resource "null_resource" "apply_deployment" { + provisioner "local-exec" { + interpreter = ["bash", "-exc"] + command = "kubectl apply -k ${var.filepath_manifest} -n ${var.namespace}" + } + + depends_on = [ + module.gcloud + ] +} + +# Wait condition for all Pods to be ready before finishing +resource "null_resource" "wait_conditions" { + provisioner "local-exec" { + interpreter = ["bash", "-exc"] + command = <<-EOT + kubectl wait --for=condition=AVAILABLE apiservice/v1beta1.metrics.k8s.io --timeout=180s + kubectl wait --for=condition=ready pods --all -n ${var.namespace} --timeout=280s + EOT + } + + depends_on = [ + resource.null_resource.apply_deployment + ] +} diff --git a/terraform/memorystore.tf b/terraform/memorystore.tf new file mode 100644 index 0000000..f4b80b3 --- /dev/null +++ b/terraform/memorystore.tf @@ -0,0 +1,47 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Create the Memorystore (redis) instance +resource "google_redis_instance" "redis-cart" { + name = "redis-cart" + memory_size_gb = 1 + region = var.region + + # count specifies the number of instances to create; + # if var.memorystore is true then the resource is enabled + count = var.memorystore ? 1 : 0 + + redis_version = "REDIS_7_0" + project = var.gcp_project_id + + depends_on = [ + module.enable_google_apis + ] +} + +# Edit contents of Memorystore kustomization.yaml file to target new Memorystore (redis) instance +resource "null_resource" "kustomization-update" { + provisioner "local-exec" { + interpreter = ["bash", "-exc"] + command = "sed -i \"s/REDIS_CONNECTION_STRING/${google_redis_instance.redis-cart[0].host}:${google_redis_instance.redis-cart[0].port}/g\" ../kustomize/components/memorystore/kustomization.yaml" + } + + # count specifies the number of instances to create; + # if var.memorystore is true then the resource is enabled + count = var.memorystore ? 1 : 0 + + depends_on = [ + resource.google_redis_instance.redis-cart + ] +} diff --git a/terraform/output.tf b/terraform/output.tf new file mode 100644 index 0000000..3425981 --- /dev/null +++ b/terraform/output.tf @@ -0,0 +1,23 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +output "cluster_location" { + description = "Location of the cluster" + value = resource.google_container_cluster.my_cluster.location +} + +output "cluster_name" { + description = "Name of the cluster" + value = resource.google_container_cluster.my_cluster.name +} diff --git a/terraform/providers.tf b/terraform/providers.tf new file mode 100644 index 0000000..502f4cd --- /dev/null +++ b/terraform/providers.tf @@ -0,0 +1,27 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +terraform { + required_providers { + google = { + source = "hashicorp/google" + version = "7.16.0" + } + } +} + +provider "google" { + project = var.gcp_project_id + region = var.region +} diff --git a/terraform/variables.tf b/terraform/variables.tf new file mode 100644 index 0000000..90236ff --- /dev/null +++ b/terraform/variables.tf @@ -0,0 +1,47 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +variable "gcp_project_id" { + type = string + description = "The GCP project ID to apply this config to" +} + +variable "name" { + type = string + description = "Name given to the new GKE cluster" + default = "online-boutique" +} + +variable "region" { + type = string + description = "Region of the new GKE cluster" + default = "us-central1" +} + +variable "namespace" { + type = string + description = "Kubernetes Namespace in which the Online Boutique resources are to be deployed" + default = "default" +} + +variable "filepath_manifest" { + type = string + description = "Path to Online Boutique's Kubernetes resources, written using Kustomize" + default = "../kustomize/" +} + +variable "memorystore" { + type = bool + description = "If true, Online Boutique's in-cluster Redis cache will be replaced with a Google Cloud Memorystore Redis cache" +}