Repository: PacktPublishing/Go-for-DevOps Branch: main Commit: 5861b176be50 Files: 358 Total size: 15.4 MB Directory structure: gitextract_rgfwv7jv/ ├── .gitignore ├── LICENSE ├── README.md ├── chapter/ │ ├── 10/ │ │ ├── .github/ │ │ │ ├── release.yml │ │ │ └── workflows/ │ │ │ ├── action-version.yaml │ │ │ ├── first.yaml │ │ │ ├── image-release.yaml │ │ │ └── tweeter-automation.yaml │ │ ├── .gitignore │ │ ├── Dockerfile │ │ ├── README.md │ │ ├── action.yaml │ │ ├── go.mod │ │ ├── go.sum │ │ ├── main.go │ │ └── pkg/ │ │ └── tweeter/ │ │ ├── tweeter.go │ │ └── tweeter_test.go │ ├── 11/ │ │ ├── Dockerfile │ │ ├── README.md │ │ ├── chatbot/ │ │ │ ├── .gitignore │ │ │ ├── bot/ │ │ │ │ └── bot.go │ │ │ ├── chatbot.go │ │ │ ├── internal/ │ │ │ │ └── handlers/ │ │ │ │ ├── handlers.go │ │ │ │ └── help.go │ │ │ └── slack.manifest │ │ ├── docker-compose.yaml │ │ ├── ops/ │ │ │ ├── README.md │ │ │ ├── client/ │ │ │ │ └── client.go │ │ │ ├── internal/ │ │ │ │ ├── jaeger/ │ │ │ │ │ └── client/ │ │ │ │ │ ├── client.go │ │ │ │ │ └── test/ │ │ │ │ │ ├── README.md │ │ │ │ │ ├── client/ │ │ │ │ │ │ └── client.go │ │ │ │ │ ├── docker-compose.yaml │ │ │ │ │ ├── etoe_test.go │ │ │ │ │ ├── otel-collector-config.yaml │ │ │ │ │ └── server/ │ │ │ │ │ ├── Dockerfile │ │ │ │ │ ├── docker-compose.yaml │ │ │ │ │ ├── go.mod │ │ │ │ │ ├── go.sum │ │ │ │ │ ├── main.go │ │ │ │ │ ├── otel-collector-config.yaml │ │ │ │ │ └── readme.md │ │ │ │ ├── prom/ │ │ │ │ │ └── prom.go │ │ │ │ └── server/ │ │ │ │ └── server.go │ │ │ ├── ops.go │ │ │ └── proto/ │ │ │ ├── buf.gen.yaml │ │ │ ├── buf.yaml │ │ │ ├── jaeger/ │ │ │ │ ├── collector.pb.go │ │ │ │ ├── collector.proto │ │ │ │ ├── collector_grpc.pb.go │ │ │ │ ├── model/ │ │ │ │ │ ├── model.pb.go │ │ │ │ │ └── model.proto │ │ │ │ ├── query.pb.go │ │ │ │ ├── query.proto │ │ │ │ ├── query_grpc.pb.go │ │ │ │ ├── sampling.pb.go │ │ │ │ ├── sampling.proto │ │ │ │ └── sampling_grpc.pb.go │ │ │ ├── ops.pb.go │ │ │ ├── ops.proto │ │ │ └── ops_grpc.pb.go │ │ ├── otel-collector-config.yaml │ │ ├── petstore/ │ │ │ ├── Dockerfile │ │ │ ├── README.md │ │ │ ├── client/ │ │ │ │ ├── cli/ │ │ │ │ │ └── petstore/ │ │ │ │ │ └── petstore.go │ │ │ │ ├── client.go │ │ │ │ └── demo/ │ │ │ │ ├── Dockerfile │ │ │ │ ├── demo.go │ │ │ │ └── names.txt │ │ │ ├── docker-compose.yaml │ │ │ ├── go.mod │ │ │ ├── go.sum │ │ │ ├── internal/ │ │ │ │ └── server/ │ │ │ │ ├── errors/ │ │ │ │ │ └── errors.go │ │ │ │ ├── log/ │ │ │ │ │ └── log.go │ │ │ │ ├── server.go │ │ │ │ ├── storage/ │ │ │ │ │ ├── mem/ │ │ │ │ │ │ ├── mem.go │ │ │ │ │ │ └── mem_test.go │ │ │ │ │ └── storage.go │ │ │ │ └── telemetry/ │ │ │ │ ├── metrics/ │ │ │ │ │ ├── metrics.go │ │ │ │ │ └── start.go │ │ │ │ └── tracing/ │ │ │ │ ├── sampler/ │ │ │ │ │ └── sampler.go │ │ │ │ └── tracing.go │ │ │ ├── otel-collector-config.yaml │ │ │ ├── petstore.go │ │ │ ├── prometheus.yaml │ │ │ └── proto/ │ │ │ ├── buf.gen.yaml │ │ │ ├── buf.yaml │ │ │ ├── petstore.pb.go │ │ │ ├── petstore.proto │ │ │ └── petstore_grpc.pb.go │ │ └── prometheus.yaml │ ├── 12/ │ │ ├── agent.service │ │ ├── goss/ │ │ │ └── allfiles/ │ │ │ └── allfiles.go │ │ └── packer/ │ │ ├── README │ │ ├── amazon.final.pkr.hcl │ │ ├── amazon.goenv.pkr.hcl │ │ └── plugins/ │ │ └── goenv/ │ │ ├── GNUmakefile │ │ ├── LICENSE │ │ ├── goenv.go │ │ └── internal/ │ │ └── config/ │ │ ├── config.go │ │ └── config.hcl2spec.go │ ├── 13/ │ │ └── petstore-provider/ │ │ ├── .gitignore │ │ ├── Makefile │ │ ├── docker-compose.yml │ │ ├── examples/ │ │ │ └── main.tf │ │ ├── go.mod │ │ ├── go.sum │ │ ├── internal/ │ │ │ ├── client/ │ │ │ │ ├── client.go │ │ │ │ ├── internal/ │ │ │ │ │ └── server/ │ │ │ │ │ ├── errors/ │ │ │ │ │ │ └── errors.go │ │ │ │ │ ├── log/ │ │ │ │ │ │ └── log.go │ │ │ │ │ ├── storage/ │ │ │ │ │ │ ├── mem/ │ │ │ │ │ │ │ ├── mem.go │ │ │ │ │ │ │ └── mem_test.go │ │ │ │ │ │ └── storage.go │ │ │ │ │ └── telemetry/ │ │ │ │ │ ├── metrics/ │ │ │ │ │ │ ├── metrics.go │ │ │ │ │ │ └── start.go │ │ │ │ │ └── tracing/ │ │ │ │ │ ├── sampler/ │ │ │ │ │ │ └── sampler.go │ │ │ │ │ └── tracing.go │ │ │ │ └── proto/ │ │ │ │ ├── buf.gen.yaml │ │ │ │ ├── buf.yaml │ │ │ │ ├── petstore.pb.go │ │ │ │ ├── petstore.proto │ │ │ │ └── petstore_grpc.pb.go │ │ │ ├── data_source_pet.go │ │ │ ├── provider.go │ │ │ ├── resource_pets.go │ │ │ └── schema.go │ │ └── main.go │ ├── 14/ │ │ ├── petstore-operator/ │ │ │ ├── .dockerignore │ │ │ ├── .gitignore │ │ │ ├── Dockerfile │ │ │ ├── Makefile │ │ │ ├── PROJECT │ │ │ ├── Tiltfile │ │ │ ├── api/ │ │ │ │ └── v1alpha1/ │ │ │ │ ├── groupversion_info.go │ │ │ │ ├── pet_types.go │ │ │ │ └── zz_generated.deepcopy.go │ │ │ ├── client/ │ │ │ │ ├── client.go │ │ │ │ ├── internal/ │ │ │ │ │ └── server/ │ │ │ │ │ ├── errors/ │ │ │ │ │ │ └── errors.go │ │ │ │ │ ├── log/ │ │ │ │ │ │ └── log.go │ │ │ │ │ ├── storage/ │ │ │ │ │ │ ├── mem/ │ │ │ │ │ │ │ ├── mem.go │ │ │ │ │ │ │ └── mem_test.go │ │ │ │ │ │ └── storage.go │ │ │ │ │ └── telemetry/ │ │ │ │ │ ├── metrics/ │ │ │ │ │ │ ├── metrics.go │ │ │ │ │ │ └── start.go │ │ │ │ │ └── tracing/ │ │ │ │ │ ├── sampler/ │ │ │ │ │ │ └── sampler.go │ │ │ │ │ └── tracing.go │ │ │ │ └── proto/ │ │ │ │ ├── buf.gen.yaml │ │ │ │ ├── buf.yaml │ │ │ │ ├── petstore.pb.go │ │ │ │ ├── petstore.proto │ │ │ │ └── petstore_grpc.pb.go │ │ │ ├── config/ │ │ │ │ ├── crd/ │ │ │ │ │ ├── bases/ │ │ │ │ │ │ └── petstore.example.com_pets.yaml │ │ │ │ │ ├── kustomization.yaml │ │ │ │ │ ├── kustomizeconfig.yaml │ │ │ │ │ └── patches/ │ │ │ │ │ ├── cainjection_in_pets.yaml │ │ │ │ │ └── webhook_in_pets.yaml │ │ │ │ ├── default/ │ │ │ │ │ ├── kustomization.yaml │ │ │ │ │ ├── manager_auth_proxy_patch.yaml │ │ │ │ │ └── manager_config_patch.yaml │ │ │ │ ├── manager/ │ │ │ │ │ ├── controller_manager_config.yaml │ │ │ │ │ ├── kustomization.yaml │ │ │ │ │ └── manager.yaml │ │ │ │ ├── manifests/ │ │ │ │ │ └── kustomization.yaml │ │ │ │ ├── petstore-service/ │ │ │ │ │ └── service.yaml │ │ │ │ ├── prometheus/ │ │ │ │ │ ├── kustomization.yaml │ │ │ │ │ └── monitor.yaml │ │ │ │ ├── rbac/ │ │ │ │ │ ├── auth_proxy_client_clusterrole.yaml │ │ │ │ │ ├── auth_proxy_role.yaml │ │ │ │ │ ├── auth_proxy_role_binding.yaml │ │ │ │ │ ├── auth_proxy_service.yaml │ │ │ │ │ ├── kustomization.yaml │ │ │ │ │ ├── leader_election_role.yaml │ │ │ │ │ ├── leader_election_role_binding.yaml │ │ │ │ │ ├── pet_editor_role.yaml │ │ │ │ │ ├── pet_viewer_role.yaml │ │ │ │ │ ├── role.yaml │ │ │ │ │ ├── role_binding.yaml │ │ │ │ │ └── service_account.yaml │ │ │ │ ├── samples/ │ │ │ │ │ ├── kustomization.yaml │ │ │ │ │ └── petstore_v1alpha1_pet.yaml │ │ │ │ └── scorecard/ │ │ │ │ ├── bases/ │ │ │ │ │ └── config.yaml │ │ │ │ ├── kustomization.yaml │ │ │ │ └── patches/ │ │ │ │ ├── basic.config.yaml │ │ │ │ └── olm.config.yaml │ │ │ ├── controllers/ │ │ │ │ ├── pet_controller.go │ │ │ │ └── suite_test.go │ │ │ ├── go.mod │ │ │ ├── go.sum │ │ │ ├── hack/ │ │ │ │ └── boilerplate.go.txt │ │ │ └── main.go │ │ └── workloads/ │ │ ├── Readme.md │ │ ├── go.mod │ │ ├── go.sum │ │ ├── kind-config.yaml │ │ └── main.go │ ├── 15/ │ │ ├── .gitignore │ │ ├── cloud-init/ │ │ │ └── init.yml │ │ ├── cmd/ │ │ │ ├── compute/ │ │ │ │ └── main.go │ │ │ └── storage/ │ │ │ └── main.go │ │ ├── go.mod │ │ ├── go.sum │ │ ├── pkg/ │ │ │ ├── helpers/ │ │ │ │ └── helpers.go │ │ │ └── mgmt/ │ │ │ ├── compute.go │ │ │ └── storage.go │ │ └── readme.md │ ├── 16/ │ │ └── workflow/ │ │ ├── README.md │ │ ├── client/ │ │ │ └── client.go │ │ ├── configs/ │ │ │ ├── README │ │ │ ├── es.json │ │ │ └── policies.json │ │ ├── data/ │ │ │ ├── README.md │ │ │ ├── generators/ │ │ │ │ ├── README │ │ │ │ └── mk/ │ │ │ │ ├── README │ │ │ │ └── mk.go │ │ │ ├── machines.json │ │ │ ├── packages/ │ │ │ │ └── sites/ │ │ │ │ └── sites.go │ │ │ └── sites.json │ │ ├── internal/ │ │ │ ├── es/ │ │ │ │ └── es.go │ │ │ ├── policy/ │ │ │ │ ├── config/ │ │ │ │ │ └── config.go │ │ │ │ ├── policy.go │ │ │ │ └── register/ │ │ │ │ ├── README │ │ │ │ ├── restrictjobtypes/ │ │ │ │ │ └── restrictjobtypes.go │ │ │ │ ├── sameargs/ │ │ │ │ │ └── sameargs.go │ │ │ │ └── startorend/ │ │ │ │ └── startOrEnd.go │ │ │ ├── service/ │ │ │ │ ├── executor/ │ │ │ │ │ └── executor.go │ │ │ │ ├── jobs/ │ │ │ │ │ ├── jobs.go │ │ │ │ │ └── register/ │ │ │ │ │ ├── diskerase/ │ │ │ │ │ │ └── diskerase.go │ │ │ │ │ ├── sleep/ │ │ │ │ │ │ └── sleep.go │ │ │ │ │ ├── tokenbucket/ │ │ │ │ │ │ └── tokenbucket.go │ │ │ │ │ └── validatedecom/ │ │ │ │ │ └── validatedecom.go │ │ │ │ └── service.go │ │ │ └── token/ │ │ │ └── token.go │ │ ├── proto/ │ │ │ ├── buf.gen.yaml │ │ │ ├── buf.yaml │ │ │ ├── custom.go │ │ │ ├── diskerase.pb.go │ │ │ ├── diskerase.proto │ │ │ └── diskerase_grpc.pb.go │ │ ├── samples/ │ │ │ └── diskerase/ │ │ │ ├── LICENSE │ │ │ ├── cmd/ │ │ │ │ ├── eraseSatellite.go │ │ │ │ ├── protoStatus.go │ │ │ │ ├── root.go │ │ │ │ └── status.go │ │ │ └── diskerase.go │ │ └── workflow.go │ ├── 5/ │ │ └── excel/ │ │ ├── simple/ │ │ │ └── excel.go │ │ └── visualization/ │ │ ├── excel.go │ │ └── internal/ │ │ └── chart/ │ │ └── chart.go │ ├── 6/ │ │ └── grpc/ │ │ ├── buf.work │ │ ├── client/ │ │ │ ├── bin/ │ │ │ │ └── qotd.go │ │ │ └── client.go │ │ ├── proto/ │ │ │ ├── buf.gen.yaml │ │ │ ├── buf.yaml │ │ │ ├── qotd.pb.go │ │ │ ├── qotd.proto │ │ │ └── qotd_grpc.pb.go │ │ ├── qotd.go │ │ └── server/ │ │ └── server.go │ ├── 7/ │ │ ├── cobra/ │ │ │ └── app/ │ │ │ ├── LICENSE │ │ │ ├── cmd/ │ │ │ │ ├── get.go │ │ │ │ └── root.go │ │ │ └── main.go │ │ ├── filter_errors/ │ │ │ └── main.go │ │ └── signals/ │ │ └── main.go │ ├── 8/ │ │ ├── agent/ │ │ │ ├── README.md │ │ │ ├── agent.go │ │ │ ├── bin/ │ │ │ │ ├── README.md │ │ │ │ ├── build.sh │ │ │ │ └── linux_amd64/ │ │ │ │ └── agent │ │ │ ├── buf.work │ │ │ ├── client/ │ │ │ │ ├── cli/ │ │ │ │ │ ├── LICENSE │ │ │ │ │ ├── cmd/ │ │ │ │ │ │ ├── auth.go │ │ │ │ │ │ ├── install.go │ │ │ │ │ │ ├── remove.go │ │ │ │ │ │ └── root.go │ │ │ │ │ ├── main.go │ │ │ │ │ └── sample/ │ │ │ │ │ └── helloweb/ │ │ │ │ │ ├── helloweb │ │ │ │ │ └── helloweb.go │ │ │ │ └── client.go │ │ │ ├── internal/ │ │ │ │ └── service/ │ │ │ │ ├── service.go │ │ │ │ └── unit_file.go │ │ │ └── proto/ │ │ │ ├── agent.pb.go │ │ │ ├── agent.proto │ │ │ ├── agent_grpc.pb.go │ │ │ ├── buf.gen.yaml │ │ │ ├── buf.yaml │ │ │ └── extra.go │ │ ├── rollout/ │ │ │ ├── README.md │ │ │ ├── config.go │ │ │ ├── endstate_string.go │ │ │ ├── lb/ │ │ │ │ ├── README.md │ │ │ │ ├── buf.work │ │ │ │ ├── client/ │ │ │ │ │ ├── cli/ │ │ │ │ │ │ └── cli.go │ │ │ │ │ └── client.go │ │ │ │ ├── lb.go │ │ │ │ ├── proto/ │ │ │ │ │ ├── buf.gen.yaml │ │ │ │ │ ├── buf.yaml │ │ │ │ │ ├── lb.pb.go │ │ │ │ │ ├── lb.proto │ │ │ │ │ └── lb_grpc.pb.go │ │ │ │ ├── sample/ │ │ │ │ │ └── web/ │ │ │ │ │ └── main.go │ │ │ │ └── server/ │ │ │ │ ├── grpc/ │ │ │ │ │ └── server.go │ │ │ │ └── http/ │ │ │ │ ├── p2c.go │ │ │ │ └── server.go │ │ │ ├── rollout.go │ │ │ ├── service.json │ │ │ └── workflow.go │ │ ├── scanner/ │ │ │ └── scanner.go │ │ └── ssh/ │ │ └── client/ │ │ ├── expect/ │ │ │ └── expect.go │ │ └── remotecmd/ │ │ └── remotecmd.go │ ├── 9/ │ │ ├── alerting/ │ │ │ ├── alertmanager.yml │ │ │ ├── client/ │ │ │ │ ├── Dockerfile │ │ │ │ ├── go.mod │ │ │ │ ├── go.sum │ │ │ │ └── main.go │ │ │ ├── docker-compose.yaml │ │ │ ├── otel-collector-config.yaml │ │ │ ├── prometheus.yaml │ │ │ ├── readme.md │ │ │ ├── rules/ │ │ │ │ └── demo-server.yml │ │ │ └── server/ │ │ │ ├── Dockerfile │ │ │ ├── go.mod │ │ │ ├── go.sum │ │ │ └── main.go │ │ ├── logging/ │ │ │ ├── docker-compose.yml │ │ │ ├── otel-collector-config.yml │ │ │ ├── readme.md │ │ │ └── varlogpods/ │ │ │ ├── containerd_logs-0_000011112222333344445555666677778888/ │ │ │ │ └── logs/ │ │ │ │ └── 0.log │ │ │ ├── crio_logs-0_111122223333444455556666777788889999/ │ │ │ │ └── logs/ │ │ │ │ └── 0.log │ │ │ ├── docker_logs-0_222233334444555566667777888899990000/ │ │ │ │ └── logs/ │ │ │ │ └── 0.log │ │ │ └── otel_otel_888877776666555544443333222211110000/ │ │ │ └── otel-collector/ │ │ │ └── 0.log │ │ ├── metrics/ │ │ │ ├── client/ │ │ │ │ ├── Dockerfile │ │ │ │ ├── go.mod │ │ │ │ ├── go.sum │ │ │ │ └── main.go │ │ │ ├── docker-compose.yaml │ │ │ ├── otel-collector-config.yaml │ │ │ ├── prometheus.yaml │ │ │ ├── readme.md │ │ │ └── server/ │ │ │ ├── Dockerfile │ │ │ ├── go.mod │ │ │ ├── go.sum │ │ │ └── main.go │ │ └── tracing/ │ │ ├── client/ │ │ │ ├── Dockerfile │ │ │ ├── go.mod │ │ │ ├── go.sum │ │ │ └── main.go │ │ ├── docker-compose.yaml │ │ ├── otel-collector-config.yaml │ │ ├── readme.md │ │ └── server/ │ │ ├── Dockerfile │ │ ├── go.mod │ │ ├── go.sum │ │ └── main.go │ ├── build_check.sh │ ├── mod_tidy.sh │ └── rename.sh ├── go.mod └── go.sum ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ .idea venv ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2021 Packt Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # Go for DevOps Go for DevOps This is the code repository for [Go for DevOps](https://www.packtpub.com/product/go-for-devops/9781801818896?utm_source=github&utm_medium=repository&utm_campaign=9781801818896), published by Packt. **Learn how to use the Go language to automate servers, the cloud, Kubernetes, GitHub, Packer, and Terraform** ## What is this book about? Go is the go-to language for DevOps libraries and services, and without it, achieving fast and safe automation is a challenge. With the help of Go for DevOps, you'll learn how to deliver services with ease and safety, becoming a better DevOps engineer in the process. This book covers the following exciting features: * Understand the basic structure of the Go language to begin your DevOps journey * Interact with filesystems to read or stream data * Communicate with remote services via REST and gRPC * Explore writing tools that can be used in the DevOps environment * Develop command-line operational software in Go * Work with popular frameworks to deploy production software * Create GitHub actions that streamline your CI/CD process * Write a ChatOps application with Slack to simplify production visibility If you feel this book is for you, get your [copy](https://www.amazon.com/dp/1801818894) today! https://www.packtpub.com/ ## Instructions and Navigations All of the code is organized into folders. For example, Chapter02. The code will look like the following: ``` packer { required_plugins { amazon = { version = ">= 0.0.1" ``` **Following is what you need for this book:** This book is for Ops and DevOps engineers who would like to use Go to develop their own DevOps tooling or integrate custom features with DevOps tools such as Kubernetes, GitHub Actions, HashiCorp Packer, and Terraform. Experience with some type of programming language, but not necessarily Go, is necessary to get started with this book. With the following software and hardware list you can run all code files present in the book (Chapter 1-16). ### Software and Hardware List | Chapter | Software required | OS required | | -------- | ------------------------------------ | ----------------------------------- | | 1-16 | Go 1.18 | Windows, Mac OS X, and Linux (Any) | | 1-16 | Packer | Windows, Mac OS X, and Linux (Any) | | 1-16 | Terraform | Windows, Mac OS X, and Linux (Any) | | 1-16 | Kubernetes | Windows, Mac OS X, and Linux (Any) | | 1-16 | Docker | Windows, Mac OS X, and Linux (Any) | | 1-16 | Tilt | Windows, Mac OS X, and Linux (Any) | | 1-16 | Protocol Buffers | Windows, Mac OS X, and Linux (Any) | | 1-16 | gPRC,ctlptl | Windows, Mac OS X, and Linux (Any) | | 1-16 | But CLI | Windows, Mac OS X, and Linux (Any) | | 1-16 | Operator SDK | Windows, Mac OS X, and Linux (Any) | | 1-16 | Azure CLI, KinD | Windows, Mac OS X, and Linux (Any) | We also provide a PDF file that has color images of the screenshots/diagrams used in this book. [Click here to download it](https://static.packt-cdn.com/downloads/9781801818896_ColorImages.pdf). ### Related products * Learning DevOps - Second Edition [[Packt]](https://www.packtpub.com/product/learning-devops-second-edition/9781801818964?utm_source=github&utm_medium=repository&utm_campaign=9781801818964) [[Amazon]](https://www.amazon.com/dp/1801818967) * The DevOps Career Handbook [[Packt]](https://www.packtpub.com/product/the-devops-career-handbook/9781803230948?utm_source=github&utm_medium=repository&utm_campaign=9781803230948) [[Amazon]](https://www.amazon.com/dp/1803230940) ## Errata * Page xxi (Under to get the most out of this book): **gPRC(https://grpc.io)** _should be_ **gRPC(https://grpc.io)** * Page 29 (Under Returning multiple values and named results): **func divide(num, div int) (res, rem int) { result = num / div remainder = num % div return res, rem }** _should be_ **func divide(num, div int) (res, rem int) {res = num / div rem = num % div return res, rem }** * Page 74, Third paragraph: **we will spin off 10 goroutines to add a number to a sum value** _should be_ **we will spin off 100 goroutines to add a number to a sum value** * Page 77, Code snippet: `if ctx.Err() != nil { return nil, err }` _should be_ ` if err := ctx.Err() != nil { return nil, err }` ## Get to Know the Authors **John Doak** is the principal manager of Layer 1 Reliability Engineering at Microsoft. John led the development of the Azure Data Explorer and Microsoft Authentication Library Go SDKs. Previously, he was a Staff Site Reliability Engineer at Google. As part of network engineering, he created many of their first network automation systems. John led the migration of that group from Python to Go, developing Go training classes that have been taught around the world. He was a pivotal figure in transforming the network team to a network/systems group that integrated with SRE. Prior to that, he worked for Lucasfilm in video games and film. You can find his musings on Go/SRE topics and his Go classes on the web. **David Justice** is the principal software engineer lead for the Azure K8s infrastructure and Steel Thread teams, which maintain a variety of CNCF and Bytecode Alliance projects. He is a maintainer of the Cluster API Provider Azure and a contributor to the Cluster API. Prior to that, David was the technical assistant to the Azure CTO, where he was responsible for Azure cross-group technical strategy and architecture. Early on at Microsoft, he was a program manager leading Azure SDKs and CLIs, where he transitioned all Azure services to describe them using OpenAPI specifications in GitHub and established automations to generate Azure reference docs, SDKs, and CLIs. Prior to working at Microsoft, David was the CTO of a mobile CI/CD SaaS called CISimple. ### Download a free PDF If you have already purchased a print or Kindle version of this book, you can get a DRM-free PDF version at no cost.
Simply click on the link to claim your free PDF.

https://packt.link/free-ebook/9781801818896

================================================ FILE: chapter/10/.github/release.yml ================================================ changelog: exclude: labels: - ignore-for-release categories: - title: Breaking Changes 🛠 labels: - breaking-change - title: New Features 🎉 labels: - enhancement - title: Bug Fixes 🐛 labels: - bug-fix - title: Other Changes labels: - "*" ================================================ FILE: chapter/10/.github/workflows/action-version.yaml ================================================ name: Release new tweeter version on: release: types: [released] workflow_dispatch: inputs: TAG_NAME: description: 'Tag name that the major tag will point to' required: true permissions: contents: write env: TAG_NAME: ${{ github.event.inputs.TAG_NAME || github.event.release.tag_name }} jobs: update_tag: name: Update the major tag to include the ${{ github.event.inputs.TAG_NAME || github.event.release.tag_name }} changes runs-on: ubuntu-latest steps: - name: Update the ${{ env.TAG_NAME }} tag uses: actions/publish-action@v0.1.0 with: source-tag: ${{ env.TAG_NAME }} ================================================ FILE: chapter/10/.github/workflows/first.yaml ================================================ name: first-workflow on: workflow_dispatch jobs: echo: runs-on: ubuntu-latest steps: - name: echo step run: echo 'Hello World!' ================================================ FILE: chapter/10/.github/workflows/image-release.yaml ================================================ name: release image on: push: tags: - 'image-v*' # push events for tags matching image-v for version (image-v1.0, etc) permissions: contents: read packages: write jobs: image: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: set env run: echo "RELEASE_VERSION=${GITHUB_REF:17}" >> $GITHUB_ENV # refs/tags/image-v1.0.0 substring starting at 1.0.0 - name: setup buildx uses: docker/setup-buildx-action@v1 - name: login to GitHub container registry uses: docker/login-action@v1 with: registry: ghcr.io username: ${{ github.repository_owner }} password: ${{ secrets.GITHUB_TOKEN }} - name: build and push uses: docker/build-push-action@v2 with: push: true tags: | ghcr.io/PacktPublishing/tweeter:${{ env.RELEASE_VERSION }} ghcr.io/PacktPublishing/tweeter:latest ================================================ FILE: chapter/10/.github/workflows/tweeter-automation.yaml ================================================ name: tweeter-automation on: push: tags: - 'v[0-9]+.[0-9]+.*' branches: - main pull_request: branches: - main jobs: test: permissions: contents: read strategy: matrix: go-version: [ 1.16.x, 1.17.x ] os: [ ubuntu-latest, macos-latest, windows-latest ] runs-on: ${{ matrix.os }} steps: - name: install go uses: actions/setup-go@v2 with: go-version: ${{ matrix.go-version }} - uses: actions/checkout@v2 - name: lint with golangci-lint uses: golangci/golangci-lint-action@v2 - name: run go test run: go test ./... test-action: permissions: contents: read packages: read runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 # TODO: when the repo is open sourced, remove the login and open the image to the public - name: login to GitHub container registry uses: docker/login-action@v1 with: registry: ghcr.io username: ${{ github.repository_owner }} password: ${{ secrets.GITHUB_TOKEN }} - run: docker pull ghcr.io/PacktPublishing/tweeter:1.0.0 - name: test the tweeter action in DRY_RUN id: tweeterAction env: DRY_RUN: true uses: ./ with: message: hello world! accessToken: fake accessTokenSecret: fake apiKey: fake apiKeySecret: fake - run: echo ${{ steps.tweeterAction.outputs.sentMessage }} from dry run test release: permissions: contents: write needs: test if: startsWith(github.ref, 'refs/tags/v') runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: Set RELEASE_VERSION ENV var run: echo "RELEASE_VERSION=${GITHUB_REF:10}" >> $GITHUB_ENV - uses: actions/setup-go@v2 with: go-version: 1.17.x - name: install gox run: go install github.com/mitchellh/gox@v1.0.1 - name: build cross-platform binaries env: PLATFORMS: darwin/amd64 darwin/arm64 windows/amd64 linux/amd64 linux/arm64 VERSION_INJECT: github.com/PacktPublishing/Go-for-DevOps/chapter/10/pkg/tweeter.Version OUTPUT_PATH_FORMAT: ./bin/${{ env.RELEASE_VERSION }}/{{.OS}}/{{.Arch}}/tweeter run: | gox -osarch="${PLATFORMS}" -ldflags "-X ${VERSION_INJECT}=${RELEASE_VERSION}" -output "${OUTPUT_PATH_FORMAT}" - name: generate release notes env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | gh api -X POST 'repos/{owner}/{repo}/releases/generate-notes' \ -F commitish=${{ env.RELEASE_VERSION }} \ -F tag_name=${{ env.RELEASE_VERSION }} \ > tmp-release-notes.json - name: gzip the bins env: OUT_BASE: ./bin/${{ env.RELEASE_VERSION }} run: | tar -czvf "${OUT_BASE}/darwin/amd64/tweeter_darwin_amd64.tar.gz" -C "${OUT_BASE}/darwin/amd64" tweeter tar -czvf "${OUT_BASE}/darwin/arm64/tweeter_darwin_arm64.tar.gz" -C "${OUT_BASE}/darwin/arm64" tweeter tar -czvf "${OUT_BASE}/windows/amd64/tweeter_windows_amd64.tar.gz" -C "${OUT_BASE}/windows/amd64" tweeter.exe tar -czvf "${OUT_BASE}/linux/amd64/tweeter_linux_amd64.tar.gz" -C "${OUT_BASE}/linux/amd64" tweeter tar -czvf "${OUT_BASE}/linux/arm64/tweeter_linux_arm64.tar.gz" -C "${OUT_BASE}/linux/arm64" tweeter - name: create release env: OUT_BASE: ./bin/${{ env.RELEASE_VERSION }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | jq -r .body tmp-release-notes.json > tmp-release-notes.md gh release create ${{ env.RELEASE_VERSION }} \ -t "$(jq -r .name tmp-release-notes.json)" \ -F tmp-release-notes.md \ "${OUT_BASE}/darwin/amd64/tweeter_darwin_amd64.tar.gz#tweeter_osx_amd64" \ "${OUT_BASE}/darwin/arm64/tweeter_darwin_arm64.tar.gz#tweeter_osx_arm64" \ "${OUT_BASE}/windows/amd64/tweeter_windows_amd64.tar.gz#tweeter_windows_amd64" \ "${OUT_BASE}/linux/amd64/tweeter_linux_amd64.tar.gz#tweeter_linux_amd64" \ "${OUT_BASE}/linux/arm64/tweeter_linux_arm64.tar.gz#tweeter_linux_arm64" ================================================ FILE: chapter/10/.gitignore ================================================ # Binaries for programs and plugins *.exe *.exe~ *.dll *.so *.dylib # Test binary, built with `go test -c` *.test # Output of the go coverage tool, specifically when used with LiteIDE *.out # Dependency directories (remove the comment below to include it) # vendor/ .idea ================================================ FILE: chapter/10/Dockerfile ================================================ FROM golang:1.17 as builder WORKDIR /workspace # Run this with docker build --build_arg $(go env GOPROXY) to override the goproxy ARG goproxy=https://proxy.golang.org ENV GOPROXY=$goproxy # Copy the Go Modules manifests COPY go.mod go.mod COPY go.sum go.sum # Cache deps before building and copying source so that we don't need to re-download as much # and so that source changes don't invalidate our downloaded layer RUN go mod download # Copy the sources COPY ./ ./ RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \ go build -a -ldflags '-extldflags "-static"' \ -o tweeter . # Copy the action into a thin image FROM gcr.io/distroless/static:latest WORKDIR / COPY --from=builder /workspace/tweeter . ENTRYPOINT ["/tweeter"] ================================================ FILE: chapter/10/README.md ================================================ # DevOps for Go Tweeter The tweeter command line tool will send a tweet via Twitter. ## Setup You can use tweeter to send a tweet or to output the message to STDOUT. If you want to send a tweet, you will need to set up a Twitter application. ### Setup With a Twitter Application To send a tweet, you will need to create or use an existing Twitter account, create a Twitter application, and generate API credentials. All of this can be done through the [Twitter Developer Portal](https://developer.twitter.com/en/portal/projects-and-apps). ### Setup Without a Twitter Application Some people may not want to set up a Twitter account. If you would like to use tweeter without sending tweets, use the `--dry-run` argument. This will cause the tool to write the message to STDOUT rather than sending the message to Twitter. ## Inputs - `--message` **Required** the tweet message you would like to send - `--apiKey` the API key under Consumer Keys in the [Twitter developer portal](https://developer.twitter.com/en/portal/projects-and-apps) - `--apiKeySecret` the API key secret under Consumer Keys in the [Twitter developer portal](https://developer.twitter.com/en/portal/projects-and-apps) - `--accessToken` the access token under Authentication Tokens in the [Twitter developer portal](https://developer.twitter.com/en/portal/projects-and-apps) - `--accessTokenSecret` the access token secret under Authentications Tokens in the [Twitter developer portal](https://developer.twitter.com/en/portal/projects-and-apps) - `--dryRun` will skip authentication validation and sending the message to Twitter ## Test ``` $ go test ./... ? github.com/devopsforgo/github-actions [no test files] ok github.com/devopsforgo/github-actions/pkg/tweeter 0.002s ``` ## Run Help To see the command line arguments and descriptions, display the help. ``` $ go run . -h Usage of /tmp/go-build3731631588/b001/exe/github-actions: --accessToken string twitter access token --accessTokenSecret string twitter access token secret --apiKey string twitter api key --apiKeySecret string twitter api key secret --dryRun if true, then a tweet will not be sent --message string message you'd like to send to twitter pflag: help requested exit status 2 ``` ## Run Without Sending a Tweet The `--dryRun` argument will skip validation of the authentication arguments and output the message to STDOUT. ``` $ go run . --dryRun --message foo ``` ## Run Sending a Tweet Without `--dryRun` specified, tweeter will send the `--messsage` argument as a Tweet. ``` $ go run . --message foo --apiKey 123 --apiKeySecret secret --accessToken token --accessTokenSecret secret ``` ================================================ FILE: chapter/10/action.yaml ================================================ name: Tweeter Action author: DevOps for Go description: Simple action to send a tweet via an GitHub Action. inputs: message: description: 'message you want to tweet' required: true apiKey: description: 'api key for Twitter api' required: true apiKeySecret: description: 'api key secret for Twitter api' required: true accessToken: description: 'access token for Twitter api' required: true accessTokenSecret: description: 'access token secret for Twitter api' required: true outputs: errorMessage: description: 'if something went wrong, the error message' sentMessage: description: 'message sent to Twitter' runs: using: docker image: docker://ghcr.io/PacktPublishing/tweeter:1.0.0 args: - --message - "${{ inputs.message }}" - --apiKey - ${{ inputs.apiKey }} - --apiKeySecret - ${{ inputs.apiKeySecret }} - --accessToken - ${{ inputs.accessToken }} - --accessTokenSecret - ${{ inputs.accessTokenSecret }} ================================================ FILE: chapter/10/go.mod ================================================ module github.com/PacktPublishing/Go-for-DevOps/chapter/10 go 1.17 require ( github.com/dghubble/go-twitter v0.0.0-20210609183100-2fdbf421508e github.com/dghubble/oauth1 v0.7.0 github.com/hashicorp/go-multierror v1.1.1 github.com/pkg/errors v0.9.1 github.com/spf13/pflag v1.0.5 ) require ( github.com/cenkalti/backoff v2.1.1+incompatible // indirect github.com/dghubble/sling v1.3.0 // indirect github.com/google/go-querystring v1.0.0 // indirect github.com/hashicorp/errwrap v1.0.0 // indirect ) ================================================ FILE: chapter/10/go.sum ================================================ github.com/cenkalti/backoff v2.1.1+incompatible h1:tKJnvO2kl0zmb/jA5UKAt4VoEVw1qxKWjE/Bpp46npY= github.com/cenkalti/backoff v2.1.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dghubble/go-twitter v0.0.0-20210609183100-2fdbf421508e h1:o0sI/cfhAXdtAbiIVeTd4hmwtwgs4cQFwRBhmbx8AKY= github.com/dghubble/go-twitter v0.0.0-20210609183100-2fdbf421508e/go.mod h1:xfg4uS5LEzOj8PgZV7SQYRHbG7jPUnelEiaAVJxmhJE= github.com/dghubble/oauth1 v0.7.0 h1:AlpZdbRiJM4XGHIlQ8BuJ/wlpGwFEJNnB4Mc+78tA/w= github.com/dghubble/oauth1 v0.7.0/go.mod h1:8pFdfPkv/jr8mkChVbNVuJ0suiHe278BtWI4Tk1ujxk= github.com/dghubble/sling v1.3.0 h1:pZHjCJq4zJvc6qVQ5wN1jo5oNZlNE0+8T/h0XeXBUKU= github.com/dghubble/sling v1.3.0/go.mod h1:XXShWaBWKzNLhu2OxikSNFrlsvowtz4kyRuXUG7oQKY= github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 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/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= ================================================ FILE: chapter/10/main.go ================================================ package main import ( "fmt" "os" "github.com/hashicorp/go-multierror" "github.com/pkg/errors" flag "github.com/spf13/pflag" "github.com/PacktPublishing/Go-for-DevOps/chapter/10/pkg/tweeter" ) var ( message, apiKey, apiKeySecret, accessToken, accessTokenSecret string dryRun, versionFlag bool ) func main() { parseAndValidateInput() if versionFlag { printVersion() return } if dryRun { printOutput("sentMessage", message) return } tweeterClient, err := tweeter.New(tweeter.Config{ ApiKey: apiKey, ApiKeySecret: apiKeySecret, AccessToken: accessToken, AccessTokenSecret: accessTokenSecret, }) if err != nil { err = errors.Wrap(err, "failed creating tweeter client") printOutput("errorMessage", err.Error()) os.Exit(1) } // send the tweet if err := tweeterClient.Tweet(message); err != nil { err = errors.Wrap(err, "status update error") printOutput("errorMessage", err.Error()) os.Exit(1) } printOutput("sentMessage", message) } func parseAndValidateInput() { flag.StringVar(&message, "message", "", "message you'd like to send to twitter") flag.StringVar(&apiKey, "apiKey", "", "twitter api key") flag.StringVar(&apiKeySecret, "apiKeySecret", "", "twitter api key secret") flag.StringVar(&accessToken, "accessToken", "", "twitter access token") flag.StringVar(&accessTokenSecret, "accessTokenSecret", "", "twitter access token secret") flag.BoolVar(&dryRun, "dryRun", false, "if true or if env var DRY_RUN=true, then a tweet will not be sent") flag.BoolVar(&versionFlag, "version", false, "output the version of tweeter") flag.Parse() if os.Getenv("DRY_RUN") == "true" { dryRun = true } if versionFlag { return } var err error if message == "" { err = multierror.Append(err, errors.New("--message can't be empty")) } if !dryRun { if apiKey == "" { err = multierror.Append(err, errors.New("--apiKey can't be empty")) } if apiKeySecret == "" { err = multierror.Append(err, errors.New("--apiKeySecret can't be empty")) } if accessToken == "" { err = multierror.Append(err, errors.New("--accessToken can't be empty")) } if accessTokenSecret == "" { err = multierror.Append(err, errors.New("--accessTokenSecret can't be empty")) } } if err != nil { _, _ = fmt.Fprint(os.Stderr, err.Error()) os.Exit(1) } } func printVersion() { versionStr := "dirty" if tweeter.Version != "" { versionStr = tweeter.Version } fmt.Printf("tweeter version: %s", versionStr) } func printOutput(key, message string) { fmt.Printf("::set-output name=%s::%s\n", key, message) } ================================================ FILE: chapter/10/pkg/tweeter/tweeter.go ================================================ package tweeter import ( "github.com/dghubble/go-twitter/twitter" "github.com/dghubble/oauth1" "github.com/hashicorp/go-multierror" "github.com/pkg/errors" ) var ( // Version is the git reference injected at build Version string ) type ( // Config is the authentication params needed to construct a tweeter.Client Config struct { ApiKey string ApiKeySecret string AccessToken string AccessTokenSecret string } // Client sends tweets to Twitter Client struct { twitterClient *twitter.Client } ) // Validate will check the Config to ensure its field values are valid func (cfg Config) Validate() error { var err error if cfg.ApiKey == "" { err = multierror.Append(err, errors.New("ApiKey is required")) } if cfg.ApiKeySecret == "" { err = multierror.Append(err, errors.New("ApiKeySecret is required")) } if cfg.AccessToken == "" { err = multierror.Append(err, errors.New("AccessToken is required")) } if cfg.AccessTokenSecret == "" { err = multierror.Append(err, errors.New("AccessTokenSecret is required")) } return err } // New creates a new instance of the tweeter.Client ready to send tweets func New(cfg Config) (*Client, error) { if err := cfg.Validate(); err != nil { return nil, errors.Wrap(err, "failed to validate tweeter config") } var ( oauthCfg = oauth1.NewConfig(cfg.ApiKey, cfg.ApiKeySecret) token = oauth1.NewToken(cfg.AccessToken, cfg.AccessTokenSecret) httpClient = oauthCfg.Client(oauth1.NoContext, token) ) return &Client{ twitterClient: twitter.NewClient(httpClient), }, nil } // Tweet sends a tweet func (c *Client) Tweet(message string) error { _, _, err := c.twitterClient.Statuses.Update(message, nil) return errors.Wrap(err, "failed to send tweet") } ================================================ FILE: chapter/10/pkg/tweeter/tweeter_test.go ================================================ package tweeter_test import ( "strings" "testing" "github.com/PacktPublishing/Go-for-DevOps/chapter/10/pkg/tweeter" ) func TestNew(t *testing.T) { subject, err := tweeter.New(tweeter.Config{ ApiKey: "apiKey", ApiKeySecret: "apiKeySecret", AccessToken: "accessToken", AccessTokenSecret: "accessTokenSecret", }) if err != nil { t.Error(err, "config should be valid") } if subject == nil { t.Error("subject should not be nil") } } func TestConfig_Validate(t *testing.T) { testCases := []struct { Name string Cfg tweeter.Config Expect func(t *testing.T, err error) }{ { Name: "All keys are filled", Cfg: tweeter.Config{ ApiKey: "apiKey", ApiKeySecret: "apiKeySecret", AccessToken: "accessToken", AccessTokenSecret: "accessTokenSecret", }, Expect: func(t *testing.T, err error) { if err != nil { t.Error(err, "error should be nil") } }, }, { Name: "ApiKey is empty", Cfg: tweeter.Config{ ApiKey: "", ApiKeySecret: "apiKeySecret", AccessToken: "accessToken", AccessTokenSecret: "accessTokenSecret", }, Expect: func(t *testing.T, err error) { if err == nil { t.Error("error should be non-nil") } if !strings.Contains(err.Error(), "ApiKey is required") { t.Error(err.Error(), "should contain 'ApiKey is required'") } }, }, { Name: "ApiKeySecret is empty", Cfg: tweeter.Config{ ApiKey: "apiKey", ApiKeySecret: "", AccessToken: "accessToken", AccessTokenSecret: "accessTokenSecret", }, Expect: func(t *testing.T, err error) { if err == nil { t.Error("error should be non-nil") } if !strings.Contains(err.Error(), "ApiKeySecret is required") { t.Error(err.Error(), "should contain 'ApiKeySecret is required'") } }, }, { Name: "AccessToken is empty", Cfg: tweeter.Config{ ApiKey: "apiKey", ApiKeySecret: "apiKeySecret", AccessToken: "", AccessTokenSecret: "accessTokenSecret", }, Expect: func(t *testing.T, err error) { if err == nil { t.Error("error should be non-nil") } if !strings.Contains(err.Error(), "AccessToken is required") { t.Error(err.Error(), "should contain 'AccessToken is required'") } }, }, { Name: "AccessTokenSecret is empty", Cfg: tweeter.Config{ ApiKey: "apiKey", ApiKeySecret: "apiKeySecret", AccessToken: "accessToken", AccessTokenSecret: "", }, Expect: func(t *testing.T, err error) { if err == nil { t.Error("error should be non-nil") } if !strings.Contains(err.Error(), "AccessTokenSecret is required") { t.Error(err.Error(), "should contain 'AccessTokenSecret is required'") } }, }, { Name: "ApiKey and AccessTokenSecret are empty", Cfg: tweeter.Config{ ApiKey: "", ApiKeySecret: "apiKeySecret", AccessToken: "accessToken", AccessTokenSecret: "", }, Expect: func(t *testing.T, err error) { if err == nil { t.Error("error should be non-nil") } if !strings.Contains(err.Error(), "AccessTokenSecret is required") || !strings.Contains(err.Error(), "ApiKey is required") { t.Error(err.Error(), "should contain 'AccessTokenSecret is required' and 'ApiKey is required'") } }, }, } for _, tc := range testCases { tc := tc t.Run(tc.Name, func(t *testing.T) { tc.Expect(t, tc.Cfg.Validate()) }) } } ================================================ FILE: chapter/11/Dockerfile ================================================ FROM golang:1.17 COPY . /usr/src/server WORKDIR /usr/src/server RUN go env -w GOPROXY=direct GO111MODULE=on RUN go mod init github.com/PacktPublishing/Go-for-DevOps/chapter/11 RUN go mod tidy WORKDIR /usr/src/server/ops RUN go install CMD ["/go/bin/ops", "--jaegerAddr=jaeger-all-in-one:16685", "--promAddr=prometheus:9000", "--petstoreAddr=petstore:6742"] ================================================ FILE: chapter/11/README.md ================================================ # ChatOps Demonstration This directory contains: * A ChatOps service for Slack * An traceable and metricized Petstore CRUD service * Jaeger for Trace storage * Prometheus for Metric storage ## Turnup the demo In this directory, use the following command: ```bash docker-compose up -d ``` In a another terminal enter the following directory: ``` chapter/11/chatops/ ``` Create a `.env` file with the following content: ```bash AUTH_TOKEN=xoxb-[the rest of the token] APP_TOKEN=xapp-[the rest of the token] ``` These were generated when we setup the Slack app. Please see chapter 11 for more details on generating these. Run the bot with: ```bash go run chatbotops.go ``` In the background, there is a demo client that is adding pets to the petstore and doing searches for pets (some searches will cause errors). The service is set to Float sampling, so not every call will generate a trace. In another terminal, you can interact with the petstore by using the CLI application. This will let you add your own pets, delete pets, search for pets with a filter. That client can be found here: chapter/11/petstore/client/cli/petstore. You can find instructions on its use by running the following: ```bash go run petstore.go --help ``` ## Turndown the demo This consists of: * Cancelling the running chatbot.go binary * Running `docker-compose down` in this directory ================================================ FILE: chapter/11/chatbot/.gitignore ================================================ .env ================================================ FILE: chapter/11/chatbot/bot/bot.go ================================================ // Package bot defines a basic slack bot that can listen for app mention events for our bot // and send the message to a handler to handle the interaction. package bot import ( "context" "encoding/json" "fmt" "log" "regexp" "strings" "github.com/slack-go/slack" "github.com/slack-go/slack/slackevents" "github.com/slack-go/slack/socketmode" ) // HandleFunc receive the user who sent a message and the message. It can then use api or client to respond to said message. type HandleFunc func(ctx context.Context, m Message) // Message details information about a message that was sent in an AppMention event. type Message struct { // User has the user information on who mentioned the bot. User *slack.User // AppMention gives information on the event. AppMention *slackevents.AppMentionEvent // Text gives the text of the message without the @User stuff. If you want the full message, see AppMention. Text string } type register struct { r *regexp.Regexp h HandleFunc } // Bot provides a slack bot for listening to slack channels. type Bot struct { api *slack.Client client *socketmode.Client ctx context.Context cancel context.CancelFunc defaultHandler HandleFunc reg []register } // New creates a new Bot. func New(api *slack.Client, client *socketmode.Client) (*Bot, error) { ctx, cancel := context.WithCancel(context.Background()) b := &Bot{ api: api, client: client, ctx: ctx, cancel: cancel, } return b, nil } // Start starts listening for events from the socket client. This blocks until the client dies // or Stop() is called. func (b *Bot) Start() { go b.loop() b.client.RunContext(b.ctx) } // Stop stops the bot. The bot cannot be reused after this. func (b *Bot) Stop() { b.cancel() } // Register registers a function for handling a message to the bot. The regex is checked in the order // that it is added. A nil regexp is considered the default handler. Only 1 default handler can be added and // is always the choice of last resort. func (b *Bot) Register(r *regexp.Regexp, h HandleFunc) { if h == nil { panic("HandleFunc cannot be nil") } if r == nil { if b.defaultHandler != nil { panic("cannot add two default handles") } b.defaultHandler = h return } b.reg = append(b.reg, register{r, h}) } // loop is the event loop. func (b *Bot) loop() { for { ctx := context.Background() select { case <-b.ctx.Done(): return case evt := <-b.client.Events: switch evt.Type { case socketmode.EventTypeConnecting, socketmode.EventTypeConnected: case socketmode.EventTypeConnectionError: log.Println("connection failed. Retrying later...") case socketmode.EventTypeEventsAPI: data, ok := evt.Data.(slackevents.EventsAPIEvent) if !ok { log.Printf("bug: got %T which should be a slackevents.EventsAPIEvent", evt.Data) continue } b.client.Ack(*evt.Request) go b.appMentioned(ctx, data) } } } } // appMentioned handles an event socketmode.EventTypeEventsAPI that had a .Data that is a slackevents.EventsAPIEvent that eventually // is a AppMentionEvent. This has a crazy amount of freaking event wrapping. func (b *Bot) appMentioned(ctx context.Context, data slackevents.EventsAPIEvent) { switch data.Type { case slackevents.CallbackEvent: callback := data.Data.(*slackevents.EventsAPICallbackEvent) switch ev := data.InnerEvent.Data.(type) { case *slackevents.AppMentionEvent: if ev.BotID != "" { _, _, err := b.api.PostMessage(ev.Channel, slack.MsgOptionText("I don't talk to other bots", false)) if err != nil { log.Printf("failed posting message: %v", err) } return } msg, err := b.makeMsg(callback, ev) if err != nil { log.Println(err) return } for _, reg := range b.reg { if reg.r.MatchString(msg.Text) { reg.h(ctx, msg) return } } if b.defaultHandler != nil { b.defaultHandler(ctx, msg) } } default: b.client.Debugf("unsupported Events API event received") } } // makeMsg extracts the user and text from an event and callback into a Message type. func (b *Bot) makeMsg(callback *slackevents.EventsAPICallbackEvent, event *slackevents.AppMentionEvent) (Message, error) { user, err := b.api.GetUserInfo(event.User) if err != nil { return Message{}, fmt.Errorf("could not get user data: %w", err) } rm := rawMessage{} if err := json.Unmarshal(*callback.InnerEvent, &rm); err != nil { return Message{}, fmt.Errorf("bot received a callback with no InnerEvent: %w", err) } return Message{User: user, AppMention: event, Text: rm.getText()}, nil } // rawMessage is used to covert a slackevents.EventsAPICallbackEvent.InnerEvent, which is the raw JSON, into // a form in which I can abstract the message sent by the user without things like @user in it. This is // not carried into the exposed Go type and I want to use Slack's pre-filtering instead of doing it myself. type rawMessage struct { Blocks []interface{} } // getText gets the text without all the extra @user stuff. func (r rawMessage) getText() string { for _, block := range r.Blocks { blockReal := block.(map[string]interface{}) if blockReal["type"] != "rich_text" { continue } elements := blockReal["elements"].([]interface{}) for _, el := range elements { elReal := el.(map[string]interface{}) if elReal["type"].(string) != "rich_text_section" { continue } subElements := elReal["elements"].([]interface{}) for _, subEl := range subElements { subElReal := subEl.(map[string]interface{}) if subElReal["type"] != "text" { continue } return strings.TrimSpace(subElReal["text"].(string)) } } } return "" } ================================================ FILE: chapter/11/chatbot/chatbot.go ================================================ package main import ( "flag" "log" "os" "github.com/PacktPublishing/Go-for-DevOps/chapter/11/chatbot/bot" "github.com/PacktPublishing/Go-for-DevOps/chapter/11/chatbot/internal/handlers" "github.com/PacktPublishing/Go-for-DevOps/chapter/11/ops/client" "github.com/joho/godotenv" "github.com/slack-go/slack" "github.com/slack-go/slack/socketmode" ) var ( opsAddr = flag.String("opsAddr", "127.0.0.1:7000", "The address the Ops service runs on.") debug = flag.Bool("debug", false, "If turned on will log debug information to the screen.") ) func main() { flag.Parse() err := godotenv.Load() if err != nil { panic("could not load .env file") } api := slack.New( os.Getenv("AUTH_TOKEN"), slack.OptionAppLevelToken(os.Getenv("APP_TOKEN")), slack.OptionDebug(*debug), ) smClient := socketmode.New( api, socketmode.OptionDebug(*debug), socketmode.OptionLog( log.New( os.Stdout, "socketmode: ", log.Lshortfile|log.LstdFlags, ), ), ) opsClient, err := client.New(*opsAddr) if err != nil { panic(err) } b, err := bot.New(api, smClient) if err != nil { panic(err) } h := handlers.Ops{OpsClient: opsClient, API: api, SMClient: smClient} h.Register(b) log.Println("Bot started") b.Start() panic("Bot stopped functioning") } ================================================ FILE: chapter/11/chatbot/internal/handlers/handlers.go ================================================ // Package handlers provides an Ops type that has methods that implement bot.HandleFunc for various commands that could be sent to a bot. package handlers import ( "context" "errors" "fmt" "log" "regexp" "sort" "strconv" "strings" "time" "github.com/PacktPublishing/Go-for-DevOps/chapter/11/chatbot/bot" "github.com/PacktPublishing/Go-for-DevOps/chapter/11/ops/client" "github.com/olekukonko/tablewriter" "github.com/slack-go/slack" "github.com/slack-go/slack/socketmode" pb "github.com/PacktPublishing/Go-for-DevOps/chapter/11/ops/proto" ) // Ops provides bot.HandleFunc methods that can reuse the connections to the Ops service. type Ops struct { OpsClient *client.Ops API *slack.Client SMClient *socketmode.Client } // write writes a formatted string to the event output in the bot.Message. func (o Ops) write(m bot.Message, s string, i ...interface{}) error { _, _, err := o.API.PostMessage( m.AppMention.Channel, slack.MsgOptionText(fmt.Sprintf(s, i...), false), ) return err } // Register registers all the commands held in Ops with the bot. func (o Ops) Register(b *bot.Bot) { b.Register(regexp.MustCompile(`^\s*help`), o.Help) b.Register(regexp.MustCompile(`^\s*list traces`), o.ListTraces) b.Register(regexp.MustCompile(`^\s*show trace`), o.ShowTrace) b.Register(regexp.MustCompile(`^\s*change sampling`), o.ChangeSampling) b.Register(regexp.MustCompile(`^\s*show logs`), o.ShowLogs) b.Register(nil, o.lastResort) } // opt stores the key/value pair for an option to a command. type opt struct { key string val string } // listTracesRE teases the options from a `list traces` command. var listTracesRE = regexp.MustCompile(`(\S+)=(?:(\S+))`) // ListTraces lists all the traces requested in a table that is output to the user. func (o Ops) ListTraces(ctx context.Context, m bot.Message) { sp := strings.Split(m.Text, "list traces") if len(sp) != 2 { o.write(m, "The 'list traces' command is malformed") return } t := strings.TrimSpace(sp[1]) kvOpts := []opt{} for _, match := range listTracesRE.FindAllStringSubmatch(t, -1) { kvOpts = append(kvOpts, opt{strings.TrimSpace(match[1]), strings.TrimSpace(match[2])}) } options := []client.CallOption{} for _, opt := range kvOpts { switch opt.key { case "operation": options = append(options, client.WithOperation(opt.val)) case "start": t, err := time.Parse(`01/02/2006-15:04:05`, opt.val) if err != nil { o.write(m, "The start option must be in the form `01/02/2006-15:04:05` for UTC") return } options = append(options, client.WithStart(t)) case "end": if opt.val == "now" { continue } t, err := time.Parse(`01/02/2006-15:04:05`, opt.val) if err != nil { o.write(m, "The end option must be in the form `01/02/2006-15:04:05` for UTC") return } options = append(options, client.WithEnd(t)) case "limit": i, err := strconv.Atoi(opt.val) if err != nil { o.write(m, "The limit option must be an integer") return } if i > 100 { o.write(m, "Cannot request more than 100 traces") return } options = append(options, client.WithLimit(int32(i))) case "tags": tags, err := convertList(opt.val) if err != nil { o.write(m, "tags: must enclosed in [], like tags=[tag,tag2]") return } options = append(options, client.WithLabels(tags)) default: o.write(m, "don't understand an option type(%s)", opt.key) return } } traces, err := o.OpsClient.ListTraces(ctx, options...) if err != nil { o.write(m, "Ops server had an error: %s", err) return } b := strings.Builder{} b.WriteString("Here are the traces you requested:\n") table := tablewriter.NewWriter(&b) table.SetHeader([]string{"Start Time(UTC)", "Trace ID"}) for _, item := range traces { table.Append( []string{ item.Start.Format("01/02/2006 04:05"), "http://127.0.0.1:16686/trace/" + item.ID, }, ) } table.Render() o.write(m, b.String()) } // ShowTrace gives the URL to a trace ID. func (o Ops) ShowTrace(ctx context.Context, m bot.Message) { sp := strings.Split(m.Text, "show trace") if len(sp) != 2 { o.write(m, `show trace command should be in form: show trace `) return } id := strings.TrimSpace(sp[1]) trace, err := o.OpsClient.ShowTrace(ctx, id) if err != nil { o.write(m, "Ops server had an error: %s", err) return } b := strings.Builder{} table := tablewriter.NewWriter(&b) b.WriteString("Here is some basic trace data:\n") table.Append([]string{"ID", trace.Id}) table.Append([]string{"Duration", trace.Duration.AsDuration().String()}) table.Append([]string{"Jaeger URL", "http://127.0.0.1:16686/trace/" + trace.Id}) if len(trace.Errors) > 0 { table.Append([]string{"Had Errors", "true"}) } else { table.Append([]string{"Had Errors", "false"}) } table.Render() b.WriteString("\n") if len(trace.Errors) > 0 { table = tablewriter.NewWriter(&b) b.WriteString("Here are the errors from the trace:\n") for _, err := range trace.Errors { table.Append([]string{err}) } table.Render() b.WriteString("\n") } b.WriteString("Here are the operations in the trace:\n") table = tablewriter.NewWriter(&b) for _, op := range trace.Operations { table.Append([]string{op}) } table.Render() b.WriteString("\n") o.write(m, "%s,\nHere is the trace info you requested:\n\n%s", m.User.Name, b.String()) } // ShowLogs outputs the logs given a trace ID. func (o Ops) ShowLogs(ctx context.Context, m bot.Message) { sp := strings.Split(m.Text, "show logs") if len(sp) != 2 { o.write(m, `show logs command should be in form: show logs `) return } id := strings.TrimSpace(sp[1]) log.Println("show logs id==", id) logs, err := o.OpsClient.ShowLogs(ctx, id) if err != nil { o.write(m, "Ops server had an error: %s", err) return } b := strings.Builder{} n := time.Now().UTC() for _, l := range logs { var t string if l.Time.Year() == n.Year() && l.Time.Month() == n.Month() && l.Time.Day() == n.Day() { t = l.Time.Format(`15:04:05`) } else { t = l.Time.Format(`a01/02/2006 15:04:05`) } b.WriteString(fmt.Sprintf("%s: %s: %s\n", t, l.Key, l.Value)) } o.write(m, "%s,\nHere are the logs you requested for trace %s:\n\n%s", m.User.Name, id, b.String()) } var sampleTypeRE = regexp.MustCompile(`^\s*(never|always|float)`) // ChangeSampling changes the sampling type/rate on the server. func (o Ops) ChangeSampling(ctx context.Context, m bot.Message) { sp := strings.Split(m.Text, "change sampling") if len(sp) != 2 { o.write(m, `change sampling command should be in form: change sampling `) return } t := strings.TrimSpace((sp[1])) sub := sampleTypeRE.FindStringSubmatch(t) if len(sub) == 0 { o.write(m, `I don't have support for the samplling type you requested, sorry...`) return } req := &pb.ChangeSamplingReq{} switch sub[1] { case "never": req.Type = pb.SamplerType_STNever case "always": req.Type = pb.SamplerType_STAlways case "float": req.Type = pb.SamplerType_STFloat sp := strings.Split(t, "float") if len(sp) != 2 { o.write(m, `'change sampling float' must be followed by a float that is > 0 and <= 1`) return } f, err := strconv.ParseFloat(strings.TrimSpace(sp[1]), 64) if err != nil { o.write(m, `'change sampling float' had an invalid float option: %q`, strings.TrimSpace(sp[1])) return } if f <= 0 || f > 1 { o.write(m, `'change sampling float' must be followed by a float that is > 0 and <= 1`) return } req.FloatValue = f default: o.write(m, `sorry, I hit a bug, I kinda understand %q, so you need to talk to my creator`, m.Text) return } err := o.OpsClient.ChangeSampling(ctx, req) if err != nil { o.write(m, "Ops server gave an error on changing the sampling: %s", err) return } } var cmdList string func init() { cmds := []string{} for k := range help { cmds = append(cmds, k) } sort.Strings(cmds) b := strings.Builder{} for _, cmd := range cmds { b.WriteString(cmd + "\n") } b.WriteString("You can get more help by saying `help ` with a command from above.\n") cmdList = b.String() } // Help returns help about various commands. func (o Ops) Help(ctx context.Context, m bot.Message) { sp := strings.Split(m.Text, "help") if len(sp) < 2 { o.write(m, "%s,\nYou have to give me a command you want help with", m.User.Name) return } cmd := strings.TrimSpace(strings.Join(sp[1:], "")) if cmd == "" { o.write(m, "Here are all the commands that I can help you with:\n%s", cmdList) return } if v, ok := help[cmd]; ok { o.write(m, "I can help you waith that:\n%s", v) return } o.write(m, "%s,\nI don't know what %q is to give you help", m.User.Name, cmd) } func (o Ops) lastResort(ctx context.Context, m bot.Message) { o.write(m, "%s,\nI don't have anything that handles what you sent", m.User.Name) } func convertList(s string) ([]string, error) { if string(s[0]) != `[` || string(s[len(s)-1]) != `]` { return nil, errors.New("must enclosed in [], like [tag,tag2] comma deliminated with no spaces") } s = strings.TrimPrefix(s, `[`) s = strings.TrimSuffix(s, `]`) sp := strings.Split(s, ",") tags := []string{} for _, t := range sp { tags = append(tags, strings.TrimSpace(t)) } return tags, nil } ================================================ FILE: chapter/11/chatbot/internal/handlers/help.go ================================================ package handlers var help = map[string]string{ "list traces": ` list traces Ex: list traces operation=AddPets() limit=5 list traces returns a list of Open Telemetry traces. Various options are provided to allow for filtering what traces you see. Options: operation Desc: Filter the traces that include this operation Ex: operation=server.AddPets() start Desc: Filter the trace by when in the past the trace started Ex: start=01/02/2021-15:04:05 end: Desc: Filter the trace by when the trace ends Ex: end=01/02/2021-16:00:00 limit: Desc: Limit the number of traces returned (default is 20) Ex: limit=5 tags: Desc: Only include traces with these tags Ex: tags=[tag,tag2] Note: no spaces are allowed in the tag list `, "show trace": ` show trace Ex: show trace 17b4f65b0d9f038e2a7bc5ea84309af2 show trace returns information about a particular Open Telemetry trace. This command has no options. `, "change sampling": ` change sampling Ex: change sampling float .1 Sampling types: never Desc: Never sample unless another service or the RPC requests a trace always Desc: Sample very incoming RPC float Desc: Sample at a specific rate Required arg: : Must be > 0 and <= 1 Ex: change sampling float .1 `, "show logs": ` show logs Ex: show logs 17b4f65b0d9f038e2a7bc5ea84309af2 show logs returns all logs contained in a Open Telemetry trace. This command has no options. `, } ================================================ FILE: chapter/11/chatbot/slack.manifest ================================================ display_information: name: PetStore features: app_home: home_tab_enabled: true messages_tab_enabled: false messages_tab_read_only_enabled: false bot_user: display_name: PetStore always_online: false oauth_config: scopes: bot: - app_mentions:read - channels:history - channels:join - chat:write - chat:write.public - commands - files:write - groups:read - incoming-webhook - links:write - usergroups:read - users.profile:read - users:read - chat:write.customize - im:read settings: event_subscriptions: bot_events: - app_mention interactivity: is_enabled: true org_deploy_enabled: false socket_mode_enabled: true token_rotation_enabled: false ================================================ FILE: chapter/11/docker-compose.yaml ================================================ version: "2" services: # Jaeger jaeger-all-in-one: image: jaegertracing/all-in-one:latest ports: - "16685:16685" # Query service port - "16686:16686" - "14268:14268" - "14250:14250" # Collector otel-collector: image: ${OTELCOL_IMG} command: ["--config=/etc/otel-collector-config.yaml", "${OTELCOL_ARGS}"] volumes: - ./otel-collector-config.yaml:/etc/otel-collector-config.yaml ports: - "1888:1888" # pprof extension - "13133:13133" # health_check extension - "4317:4317" # OTLP gRPC receiver - "55670:55679" # zpages extension depends_on: - jaeger-all-in-one prometheus: container_name: prometheus image: prom/prometheus:latest volumes: - ./prometheus.yaml:/etc/prometheus/prometheus.yml ports: - "9090:9090" petstore: build: dockerfile: Dockerfile context: ./petstore environment: - OTEL_EXPORTER_OTLP_ENDPOINT=otel-collector:4317 ports: - "6742:6742" depends_on: - otel-collector - prometheus ops: build: dockerfile: Dockerfile context: ./ environment: - OTEL_EXPORTER_OTLP_ENDPOINT=otel-collector:4317 ports: - "7000:7000" depends_on: - petstore petstore-client-demo: build: dockerfile: ./client/demo/Dockerfile context: ./petstore environment: - OTEL_EXPORTER_OTLP_ENDPOINT=otel-collector:4317 depends_on: - petstore ================================================ FILE: chapter/11/ops/README.md ================================================ # Ops Service The Ops service provides API access to various operational information that we want to allow various tool access to. While this could be built into the chatbot.go binary that implements the ChatOps interface, this abstraction allows us to have multiple programs that can access these API calls. In addition, if we want to migrate to another chat service instead of Slack (like Microsoft Teams), we can easily do so without impacting users during a migration. ## Basic Architecture This is your standard gRPC service with a nice Go client ready made to access the service. We stick the important parts in `internal` to keep anyone from using the packages that are just for the service. `proto/` contains the protocol buffer messages we have for the client/server communication. The file directory layout is as follows (with some highlighted files, but not all files): ``` ├── client │ └── client.go ├── internal │ ├── jaeger │ │ └── client │ │ ├── client.go │ │ └── test │ ├── prom │ │ └── prom.go │ └── server │ └── server.go ├── ops.go └── proto ├── jaeger │ ├── model ├── ops.pb.go ├── ops.proto └── ops_grpc.pb.go ``` * `ops.go` is the main file for the Ops service * `client/ provides` a client library for accessing our Ops service using Go * `internal/jaeger` provides a client wrapper for accessing Jaeger and some end to end testing * `internal/prom` provides a client wrapper for accessing prometheus * `proto/` contains protocol buffer messages and services for accessing the Ops service via gRPC * `proto/jaeger` provides various protocol buffers required to access Jaeger ================================================ FILE: chapter/11/ops/client/client.go ================================================ package client import ( "context" "fmt" "strconv" "time" "google.golang.org/grpc" pb "github.com/PacktPublishing/Go-for-DevOps/chapter/11/ops/proto" mpb "github.com/PacktPublishing/Go-for-DevOps/chapter/11/ops/proto/jaeger/model" ) // Ops is a client for interacting with the Ops service. type Ops struct { client pb.OpsClient conn *grpc.ClientConn } // New is the constructor for Client. addr is the server's [host]:[port]. func New(addr string) (*Ops, error) { conn, err := grpc.Dial(addr, grpc.WithInsecure()) if err != nil { return nil, err } return &Ops{ client: pb.NewOpsClient(conn), conn: conn, }, nil } type callOptions struct { l *listTracesOpts a *alertsOpts } type listTracesOpts struct { operation string tags []string start time.Time end time.Time limit int32 } type alertsOpts struct { labels []string activeAt time.Time states []string } func (l *listTracesOpts) defaults() { l.limit = 10 l.end = time.Now().Add(5 * time.Second) } // CallOption is an option for method. type CallOption func(o *callOptions) error // WithStart sets the minimum time a trace had to start in order to be included. func WithStart(t time.Time) CallOption { return func(o *callOptions) error { if o.l == nil { return fmt.Errorf("WithStart can only be used on ListTraces()") } o.l.start = t return nil } } // Withend sets the maximum time a trace had to start in order to be included. func WithEnd(t time.Time) CallOption { return func(o *callOptions) error { if o.l == nil { return fmt.Errorf("WithEnd can only be used on ListTraces()") } o.l.end = t return nil } } // WithLimt sets the maximum amount of return values. When using Cassandra, this is // not a limit, it is some weird value where increasing it can increase how deep // cassandra searches for traces. In that case, setting 20 might return 10 results // but setting 100 might return 20. func WithLimit(i int32) CallOption { return func(o *callOptions) error { if o.l == nil { return fmt.Errorf("WithLimit can only be used on ListTraces()") } o.l.limit = i return nil } } // WithOperation restricts traces to ones that have this operation. func WithOperation(s string) CallOption { return func(o *callOptions) error { if o.l == nil { return fmt.Errorf("WithOperation can only be used on ListTraces()") } o.l.operation = s return nil } } // WithTags restricts resuts to ones that have all these tags. func WithTags(tags []string) CallOption { return func(o *callOptions) error { if o.l == nil { return fmt.Errorf("WithTags can only be used on ListTraces()") } o.l.tags = tags return nil } } // TraceItem details information on a trace. type TraceItem struct { // ID is the ID of the trace in hex form. ID string // Start is the start time of the trace. Start time.Time } // ListTraces lists traces for the Petstore. By default it pulls the latest 10 items. func (o *Ops) ListTraces(ctx context.Context, options ...CallOption) ([]TraceItem, error) { opts := callOptions{l: &listTracesOpts{}} opts.l.defaults() for _, o := range options { o(&opts) } req := &pb.ListTracesReq{ Service: "petstore", Operation: opts.l.operation, Tags: opts.l.tags, Start: opts.l.start.UnixNano(), End: opts.l.end.UnixNano(), SearchDepth: opts.l.limit, } resp, err := o.client.ListTraces(ctx, req) if err != nil { return nil, err } items := make([]TraceItem, 0, len(resp.Traces)) for _, ti := range resp.Traces { items = append(items, TraceItem{ID: ti.Id, Start: time.Unix(0, ti.Start)}) } return items, nil } type TraceData struct { ID string Operations []string Errors []string Tags []string Duration time.Time } // ShowTrace returns the Jaeger URL that is going to have the trace. func (o *Ops) ShowTrace(ctx context.Context, id string) (*pb.ShowTraceResp, error) { resp, err := o.client.ShowTrace(ctx, &pb.ShowTraceReq{Id: id}) if err != nil { return nil, err } return resp, nil } type Log struct { Time time.Time Key string Value string } func (o *Ops) ShowLogs(ctx context.Context, id string) ([]Log, error) { resp, err := o.client.ShowLogs(ctx, &pb.ShowLogsReq{Id: id}) if err != nil { return nil, err } if resp.Id == "" { return nil, fmt.Errorf("no trace with ID(%s) was found", id) } logs := make([]Log, 0, len(resp.Logs)) for _, l := range resp.Logs { t := l.Timestamp.AsTime().UTC() var v string for _, f := range l.Fields { switch f.VType { case mpb.ValueType_BINARY: v = string(f.VBinary) case mpb.ValueType_BOOL: if f.VBool { v = "true" } else { v = "false" } case mpb.ValueType_FLOAT64: v = strconv.FormatFloat(f.VFloat64, 'e', 2, 64) case mpb.ValueType_INT64: v = strconv.FormatInt(f.VInt64, 10) case mpb.ValueType_STRING: v = f.VStr default: v = fmt.Sprintf("unsupported type: %T", f.VType) } logs = append(logs, Log{Time: t, Key: f.Key, Value: v}) } } return logs, nil } // ChangeSampling is used to change the sampling rate of the service. func (o *Ops) ChangeSampling(ctx context.Context, sampler *pb.ChangeSamplingReq) error { _, err := o.client.ChangeSampling(ctx, sampler) if err != nil { return err } return nil } // DeployedVersion will return the deployed version of the application according // to Prometheus. func (o *Ops) DeployedVersion(ctx context.Context) (string, error) { resp, err := o.client.DeployedVersion(ctx, &pb.DeployedVersionReq{}) if err != nil { return "", err } return resp.Version, nil } // WithLabels restricts alerts to ones that have all these labels. func WithLabels(labels []string) CallOption { return func(o *callOptions) error { if o.a == nil { return fmt.Errorf("WithLabels can only be used on Alerts()") } o.a.labels = labels return nil } } // WithActiveAt restrics alerts to ones from this time to now. func WithActiveAt(t time.Time) CallOption { return func(o *callOptions) error { if o.a == nil { return fmt.Errorf("WithActiveAt can only be used on Alerts()") } o.a.activeAt = t return nil } } // WithStates restrics alerts to ones that have one of these states. func WithStates(states []string) CallOption { return func(o *callOptions) error { if o.a == nil { return fmt.Errorf("WithStates can only be used on Alerts()") } o.a.states = states return nil } } // Alert represents a Prometheus alert. type Alert struct { // State is the state of the alert. State string // Value is the value of the alert. Value string // ActiveAt was when the alert started. ActiveAt time.Time } func (a *Alert) fromProto(p *pb.Alert) { a.State = p.State a.Value = p.Value a.ActiveAt = time.Unix(0, p.ActiveAt) } // Alerts returns a list of Prometheus alerts that are firing. func (o *Ops) Alerts(ctx context.Context, options ...CallOption) ([]Alert, error) { opts := callOptions{a: &alertsOpts{}} for _, o := range options { o(&opts) } req := &pb.AlertsReq{ Labels: opts.a.labels, ActiveAt: opts.a.activeAt.UnixNano(), States: opts.a.states, } resp, err := o.client.Alerts(ctx, req) if err != nil { return nil, err } alerts := make([]Alert, 0, len(resp.Alerts)) for _, p := range resp.Alerts { a := Alert{} a.fromProto(p) alerts = append(alerts, a) } return alerts, nil } ================================================ FILE: chapter/11/ops/internal/jaeger/client/client.go ================================================ // Package client provides a Jaegar client for grabbing traces from the Jaegar service. // It is a wrapper around the undocumented Jaegar gRPC client. package client import ( "context" "errors" "fmt" "io" "time" otelTrace "go.opentelemetry.io/otel/trace" "google.golang.org/grpc" duration "google.golang.org/protobuf/types/known/durationpb" timestamp "google.golang.org/protobuf/types/known/timestamppb" pb "github.com/PacktPublishing/Go-for-DevOps/chapter/11/ops/proto/jaeger" mpb "github.com/PacktPublishing/Go-for-DevOps/chapter/11/ops/proto/jaeger/model" ) var ( // ErrNotFound indicates that no trace with an ID was found. ErrNotFound = errors.New("trace with that ID was not found") ) // Trace represents a OTEL trace that was stored in Jaegar. type Trace struct { // ID is the identity of the trace. ID string // Spans are the spans that make up the trace. Spans []Span // Err indicates if there was a error in the trace stream if the // Trace is being returned in a channel. If not, this will always be nil. Err error } // Span is a convienence wrapper around *mpb.Span. type Span struct { *mpb.Span } // Proto returns the encapsulated proto. Remember that these are generated with // gogo proto and not Google/Buf.build proto engine. func (s Span) Proto() *mpb.Span { return s.Span } // TraceID returns the converted human readable Trace ID. func (s Span) TraceID() string { if len(s.Span.TraceId) < 16 { return "" } // This is a go 1.17 conversion of a slice to an array. t := (*otelTrace.TraceID)(s.Span.TraceId[0:16]) return t.String() } // SpanID returns the converted human readable Span ID. func (s Span) SpanID() string { if len(s.Span.SpanId) < 16 { return "" } t := (*otelTrace.SpanID)(s.Span.SpanId[0:16]) return t.String() } // Jaeger provides a client for interacting with Jaeger to retrieve traces. type Jaeger struct { client pb.QueryServiceClient conn *grpc.ClientConn addr string } // New creates a new Jaeger client that connects to addr. func New(addr string) (*Jaeger, error) { conn, err := grpc.Dial(addr, grpc.WithInsecure()) if err != nil { return nil, err } return &Jaeger{ client: pb.NewQueryServiceClient(conn), conn: conn, addr: addr, }, nil } // Addr returns the address of the Jaeger server we are connected to. func (j *Jaeger) Addr() string { return j.addr } // SearchParams are parameters used to filter a search for trace data. type SearchParams struct { // Service is the name of the service you are querying. Required. Service string // Operation is the name of the operation you want to filter by. Operation string // Tag values you want to filter by. Tags []string // Start is the lower bounds (inclusive) that a trace can start at. Start time.Time // End is the uppper bounds (exclusive) a trace can end at. End time.Time // DurationMin is the minimum duration a trace (inclusive) must have. DurationMin time.Duration // DurationMax is the maximum duration a trace (exclusive) can have. DurationMax time.Duration // SearchDepth is a quirky setting. It is kinda tells the data store how hard to search. // On data stores like Cassandra, settting this to a higher number will cause it // to search deeper in its trees for the data. So a setting of 20 might get 8 results, but 200 will get // 15. This looks to have only a limit like effect on other storage systems. This defaults to 200. SearchDepth int32 } func (s SearchParams) validate() error { if s.Service == "" { return errors.New("Service field must not be an empty string") } return nil } func (s SearchParams) proto() *pb.FindTracesRequest { if s.SearchDepth == 0 { s.SearchDepth = 20 } var t map[string]string if len(s.Tags) > 0 { t = make(map[string]string, len(s.Tags)) for _, tag := range s.Tags { t[tag] = "" } } return &pb.FindTracesRequest{ Query: &pb.TraceQueryParameters{ ServiceName: s.Service, OperationName: s.Operation, Tags: t, StartTimeMin: timestamp.New(s.Start), StartTimeMax: timestamp.New(s.End), DurationMin: duration.New(s.DurationMin), DurationMax: duration.New(s.DurationMax), SearchDepth: s.SearchDepth, }, } } // Search searches Jaeger for traces that match the SearchParams. Each result is the set of spans that make up a trace. func (j *Jaeger) Search(ctx context.Context, params SearchParams) (chan Trace, error) { if err := params.validate(); err != nil { return nil, err } stream, err := j.client.FindTraces(ctx, params.proto()) if err != nil { return nil, err } // Traces come in chunks of spans. So we look at the chunks that come in and combine spans with the same IDs into traces. return unwind(ctx, stream), nil } // Trace allows retreival of a specific trace from Jaegar by its ID. func (j *Jaeger) Trace(ctx context.Context, id string) (Trace, error) { tid, err := otelTrace.TraceIDFromHex(id) if err != nil { return Trace{}, fmt.Errorf("trace ID was invalid: %w", err) } req := &pb.GetTraceRequest{ TraceId: tid[0:16], } stream, err := j.client.GetTrace(ctx, req) if err != nil { return Trace{}, err } ch := unwind(ctx, stream) traces := make([]Trace, 0, 1) for trace := range ch { traces = append(traces, trace) } switch len(traces) { case 0: return Trace{}, ErrNotFound case 1: return traces[0], nil } return Trace{}, fmt.Errorf("bug: received more that a single Trace") } type receiver interface { Recv() (*pb.SpansResponseChunk, error) grpc.ClientStream } // unwind unwinds traces that come in chunks of spans. So we look at the chunks that come in and combine spans with the same IDs into traces. func unwind(ctx context.Context, stream receiver) chan Trace { ch := make(chan Trace, 1) go func() { defer close(ch) var lastTrace Trace for { if ctx.Err() != nil { ch <- Trace{Err: ctx.Err()} return } chunk, err := stream.Recv() if err == io.EOF { if lastTrace.ID != "" { ch <- lastTrace } return } if err != nil { ch <- Trace{Err: err} return } spans := chunkToSpan(chunk) if len(spans) == 0 { continue } if spans[0].TraceID() != lastTrace.ID { if lastTrace.ID != "" { ch <- lastTrace } lastTrace = Trace{ID: spans[0].TraceID(), Spans: spans} } else { lastTrace.Spans = append(lastTrace.Spans, spans...) } } }() return ch } func chunkToSpan(chunk *pb.SpansResponseChunk) []Span { if len(chunk.Spans) == 0 { return nil } var spans []Span for _, s := range chunk.Spans { spans = append(spans, Span{Span: s}) } return spans } ================================================ FILE: chapter/11/ops/internal/jaeger/client/test/README.md ================================================ This provides an end to end test using docker to turn up Jaeger and an http server using Jaeger. We kick off some traces using the client and then use our jaeger client to find the traces. The test will kick off the docker-compose environment on its own. ================================================ FILE: chapter/11/ops/internal/jaeger/client/test/client/client.go ================================================ package client import ( "context" "log" "net/http" "strconv" "sync" "time" "github.com/google/uuid" "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/exporters/otlp/otlptrace" "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc" "go.opentelemetry.io/otel/propagation" "go.opentelemetry.io/otel/sdk/resource" sdktrace "go.opentelemetry.io/otel/sdk/trace" semconv "go.opentelemetry.io/otel/semconv/v1.4.0" "go.opentelemetry.io/otel/trace" "google.golang.org/grpc" ) // NestedSpans is the number of nested spans to create. Ths is set to 2560, because for some // reason that is the maximum number of spans. That may be due to some byte limit or a fixed number. // I could find no documentation to indicate why, thought I didn't look throughthe source. const NestedSpans = 2560 // Initializes an OTLP exporter, and configures the corresponding trace providers. func initProvider() func() { ctx := context.Background() traceExp := initTracer(ctx, "127.0.0.1:4317") log.Println("intTracer done") return func() { ctx, cancel := context.WithTimeout(ctx, time.Second) defer cancel() if err := traceExp.Shutdown(ctx); err != nil { otel.Handle(err) } } } func initTracer(ctx context.Context, otelAgentAddr string) *otlptrace.Exporter { traceClient := otlptracegrpc.NewClient( otlptracegrpc.WithInsecure(), otlptracegrpc.WithEndpoint(otelAgentAddr), otlptracegrpc.WithDialOption(grpc.WithBlock(), grpc.WithTimeout(time.Second))) traceExp, err := otlptrace.New(ctx, traceClient) handleErr(err, "Failed to create the collector trace exporter") res, err := resource.New(ctx, resource.WithFromEnv(), resource.WithProcess(), resource.WithTelemetrySDK(), resource.WithHost(), resource.WithAttributes( // the service name used to display traces in backends semconv.ServiceNameKey.String("demo-client"), ), ) handleErr(err, "failed to create resource") bsp := sdktrace.NewBatchSpanProcessor(traceExp) tracerProvider := sdktrace.NewTracerProvider( sdktrace.WithSampler(sdktrace.AlwaysSample()), sdktrace.WithResource(res), sdktrace.WithSpanProcessor(bsp), ) // set global propagator to tracecontext (the default is no-op). otel.SetTextMapPropagator(propagation.TraceContext{}) otel.SetTracerProvider(tracerProvider) return traceExp } func handleErr(err error, message string) { if err != nil { log.Fatalf("%s: %v", message, err) } } var once sync.Once var Shutdown func() = func() {} type HTTP struct { addr string client http.Client } func New(addr string) (*HTTP, error) { once.Do( func() { log.Println("before initProvider") Shutdown = initProvider() log.Println("after initProvider") }, ) h := &HTTP{client: http.Client{Transport: otelhttp.NewTransport(http.DefaultTransport)}} log.Println("after *HTTP client") return h, nil } func (h *HTTP) Call(ctx context.Context) (traceID string, err error) { tracer := otel.Tracer("demo-client-tracer") ctx, span := tracer.Start(ctx, "ExecuteRequest") defer span.End() ctx = h.makeNestedSpans(ctx, tracer) h.makeRequest(ctx) log.Println("trace says: ", span.SpanContext().TraceID().String()) log.Println("convert says: ", convertTraceID(span.SpanContext().TraceID().String())) return span.SpanContext().TraceID().String(), nil } func (h *HTTP) makeRequest(ctx context.Context) error { // Make sure we pass the context to the request to avoid broken traces. req, err := http.NewRequestWithContext(ctx, "GET", h.addr, nil) if err != nil { return err } // All requests made with this client will create spans. res, err := h.client.Do(req) if err != nil { return err } res.Body.Close() return nil } func (h *HTTP) makeNestedSpans(ctx context.Context, tracer trace.Tracer) context.Context { spans := []trace.Span{} for i := 0; i < NestedSpans; i++ { var span trace.Span ctx, span = tracer.Start(ctx, uuid.New().String()) spans = append(spans, span) } for i := NestedSpans - 1; i > -1; i-- { spans[i].End() } return ctx } func convertTraceID(id string) string { if len(id) < 16 { return "" } if len(id) > 16 { id = id[16:] } intValue, err := strconv.ParseUint(id, 16, 64) if err != nil { return "" } return strconv.FormatUint(intValue, 10) } ================================================ FILE: chapter/11/ops/internal/jaeger/client/test/docker-compose.yaml ================================================ version: "2" services: # Jaeger jaeger-all-in-one: image: jaegertracing/all-in-one:latest ports: - "16685:16685" - "16686:16686" - "14268:14268" - "14250:14250" # Collector otel-collector: image: ${OTELCOL_IMG} command: ["--config=/etc/otel-collector-config.yaml", "${OTELCOL_ARGS}"] volumes: - ./otel-collector-config.yaml:/etc/otel-collector-config.yaml ports: - "1888:1888" # pprof extension - "13133:13133" # health_check extension - "4317:4317" # OTLP gRPC receiver - "55670:55679" # zpages extension depends_on: - jaeger-all-in-one demo-server: build: dockerfile: Dockerfile context: ./server environment: - OTEL_EXPORTER_OTLP_ENDPOINT=otel-collector:4317 ports: - "7080:7080" depends_on: - otel-collector ================================================ FILE: chapter/11/ops/internal/jaeger/client/test/etoe_test.go ================================================ package etoe import ( "context" "log" "os/exec" "testing" "time" "github.com/PacktPublishing/Go-for-DevOps/chapter/11/ops/internal/jaeger/client" httpClient "github.com/PacktPublishing/Go-for-DevOps/chapter/11/ops/internal/jaeger/client/test/client" ) func TestTrace(t *testing.T) { start := exec.Command("docker-compose", "up", "-d") b, err := start.CombinedOutput() if err != nil { panic(string(b)) } time.Sleep(5 * time.Second) end := exec.Command("docker-compose", "down") defer func() { b, err = end.CombinedOutput() if err != nil { panic(string(b)) } }() c, err := client.New("127.0.0.1:16685") if err != nil { panic(err) } h, err := httpClient.New("127.0.0.1:7080") if err != nil { panic(err) } ids := []string{} for i := 0; i < 10; i++ { callCtx, callCancel := context.WithTimeout(context.Background(), 2*time.Second) id, err := h.Call(callCtx) if err != nil { panic(err) } callCancel() ids = append(ids, id) } log.Println("sleeping to let trace get exported") time.Sleep(20 * time.Second) traceCtx, traceCancel := context.WithTimeout(context.Background(), 10*time.Second) defer traceCancel() trace, err := c.Trace(traceCtx, ids[0]) if err != nil { panic(err) } if len(trace.Spans) != httpClient.NestedSpans { t.Errorf("TestTrace(number of Spans): got %d, want %d", len(trace.Spans), httpClient.NestedSpans) } for _, id := range ids { _, err := c.Trace(traceCtx, id) if err != nil { panic(err) } } // So, I don't know what the deal is. /* ch, err := c.Search(traceCtx, client.SearchParams{Service: "demo-client", SearchDepth: 100}) if err != nil { panic(err) } found := []string{} for e := range ch { found = append(found, e.ID) } sort.Strings(found) sort.Strings(ids) if len(ids) != len(found){ t.Fatalf("TestTrace(Search): number of IDs: got %d, want %d", len(found), len(ids)) } for i, id := range ids { if id != found[i] { t.Fatalf("TestTrace(Search): trace ids[%d]: got %s, want %s", i, found[i], id) } } */ } ================================================ FILE: chapter/11/ops/internal/jaeger/client/test/otel-collector-config.yaml ================================================ receivers: otlp: protocols: grpc: exporters: jaeger: endpoint: jaeger-all-in-one:14250 tls: insecure: true processors: batch: service: pipelines: traces: receivers: [otlp] processors: [batch] exporters: [jaeger] ================================================ FILE: chapter/11/ops/internal/jaeger/client/test/server/Dockerfile ================================================ FROM golang:1.17 COPY . /usr/src/server/ WORKDIR /usr/src/server/ RUN go env -w GOPROXY=direct RUN go install ./main.go CMD ["/go/bin/main"] ================================================ FILE: chapter/11/ops/internal/jaeger/client/test/server/docker-compose.yaml ================================================ version: "2" services: # Jaeger jaeger-all-in-one: image: jaegertracing/all-in-one:latest ports: - "16686:16686" - "14268" - "14250" # Collector otel-collector: image: ${OTELCOL_IMG} command: ["--config=/etc/otel-collector-config.yaml", "${OTELCOL_ARGS}"] volumes: - ./otel-collector-config.yaml:/etc/otel-collector-config.yaml ports: - "1888:1888" # pprof extension - "13133:13133" # health_check extension - "4317" # OTLP gRPC receiver - "55670:55679" # zpages extension depends_on: - jaeger-all-in-one demo-client: build: dockerfile: Dockerfile context: ./client environment: - OTEL_EXPORTER_OTLP_ENDPOINT=otel-collector:4317 - DEMO_SERVER_ENDPOINT=http://demo-server:7080/hello depends_on: - demo-server demo-server: build: dockerfile: Dockerfile context: ./server environment: - OTEL_EXPORTER_OTLP_ENDPOINT=otel-collector:4317 ports: - "7080" depends_on: - otel-collector ================================================ FILE: chapter/11/ops/internal/jaeger/client/test/server/go.mod ================================================ module github.com/PacktPublishing/Go-for-DevOps/chapter/9/tracing/demo/server go 1.17 require ( go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.28.0 go.opentelemetry.io/otel v1.3.0 go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.3.0 go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.3.0 go.opentelemetry.io/otel/sdk v1.3.0 go.opentelemetry.io/otel/trace v1.3.0 google.golang.org/grpc v1.43.0 ) require ( github.com/cenkalti/backoff/v4 v4.1.2 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/felixge/httpsnoop v1.0.2 // indirect github.com/go-logr/logr v1.2.1 // indirect github.com/go-logr/stdr v1.2.0 // indirect github.com/golang/protobuf v1.5.2 // indirect github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.3.0 // indirect go.opentelemetry.io/otel/internal/metric v0.26.0 // indirect go.opentelemetry.io/otel/metric v0.26.0 // indirect go.opentelemetry.io/proto/otlp v0.11.0 // indirect golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 // indirect golang.org/x/sys v0.0.0-20210510120138-977fb7262007 // indirect golang.org/x/text v0.3.3 // indirect google.golang.org/genproto v0.0.0-20200527145253-8367513e4ece // indirect google.golang.org/protobuf v1.27.1 // indirect ) ================================================ FILE: chapter/11/ops/internal/jaeger/client/test/server/go.sum ================================================ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/cenkalti/backoff/v4 v4.1.2 h1:6Yo7N8UP2K6LWZnW94DLVSSrbobcWdVzAYOisuDPIFo= github.com/cenkalti/backoff/v4 v4.1.2/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.1.1/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/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= 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.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.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/felixge/httpsnoop v1.0.2 h1:+nS9g82KMXccJ/wp0zyRW9ZBHFETmMGtkk+2CTTrW4o= github.com/felixge/httpsnoop v1.0.2/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.1 h1:DX7uPQ4WgAWfoh+NGGlbJQswnYIVvz0SRlLS3rPZQDA= github.com/go-logr/logr v1.2.1/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/stdr v1.2.0 h1:j4LrlVXgrbIWO83mmQUnK0Hi+YnbD+vzrE1z/EphbFE= github.com/go-logr/stdr v1.2.0/go.mod h1:YkVgnZu1ZjjL7xTxrfm/LLZBfkhTqSR1ydtm6jTKKwI= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 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.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 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.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 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.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 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/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.28.0 h1:hpEoMBvKLC6CqFZogJypr9IHwwSNF3ayEkNzD502QAM= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.28.0/go.mod h1:Ihno+mNBfZlT0Qot3XyRTdZ/9U/Cg2Pfgj75DTdIfq4= go.opentelemetry.io/otel v1.3.0 h1:APxLf0eiBwLl+SOXiJJCVYzA1OOJNyAoV8C5RNRyy7Y= go.opentelemetry.io/otel v1.3.0/go.mod h1:PWIKzi6JCp7sM0k9yZ43VX+T345uNbAkDKwHVjb2PTs= go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.3.0 h1:R/OBkMoGgfy2fLhs2QhkCI1w4HLEQX92GCcJB6SSdNk= go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.3.0/go.mod h1:VpP4/RMn8bv8gNo9uK7/IMY4mtWLELsS+JIP0inH0h4= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.3.0 h1:giGm8w67Ja7amYNfYMdme7xSp2pIxThWopw8+QP51Yk= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.3.0/go.mod h1:hO1KLR7jcKaDDKDkvI9dP/FIhpmna5lkqPUQdEjFAM8= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.3.0 h1:VQbUHoJqytHHSJ1OZodPH9tvZZSVzUHjPHpkO85sT6k= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.3.0/go.mod h1:keUU7UfnwWTWpJ+FWnyqmogPa82nuU5VUANFq49hlMY= go.opentelemetry.io/otel/internal/metric v0.26.0 h1:dlrvawyd/A+X8Jp0EBT4wWEe4k5avYaXsXrBr4dbfnY= go.opentelemetry.io/otel/internal/metric v0.26.0/go.mod h1:CbBP6AxKynRs3QCbhklyLUtpfzbqCLiafV9oY2Zj1Jk= go.opentelemetry.io/otel/metric v0.26.0 h1:VaPYBTvA13h/FsiWfxa3yZnZEm15BhStD8JZQSA773M= go.opentelemetry.io/otel/metric v0.26.0/go.mod h1:c6YL0fhRo4YVoNs6GoByzUgBp36hBL523rECoZA5UWg= go.opentelemetry.io/otel/sdk v1.3.0 h1:3278edCoH89MEJ0Ky8WQXVmDQv3FX4ZJ3Pp+9fJreAI= go.opentelemetry.io/otel/sdk v1.3.0/go.mod h1:rIo4suHNhQwBIPg9axF8V9CA72Wz2mKF1teNrup8yzs= go.opentelemetry.io/otel/trace v1.3.0 h1:doy8Hzb1RJ+I3yFhtDmwNc7tIyw1tNMOIsyPzp1NOGY= go.opentelemetry.io/otel/trace v1.3.0/go.mod h1:c/VDhno8888bvQYmbYLqe41/Ldmr/KKunbvWM4/fEjk= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.opentelemetry.io/proto/otlp v0.11.0 h1:cLDgIBTf4lLOlztkhzAEdQsJ4Lj+i5Wc9k6Nn0K1VyU= go.opentelemetry.io/proto/otlp v0.11.0/go.mod h1:QpEjXPrNQzrFDZgoTo49dgHR9RYRSrg3NAKnUGl9YpQ= go.uber.org/goleak v1.1.12 h1:gZAh5/EyT/HQwlpkCy6wTpqfH9H8Lz8zbm3dZh+OyzA= go.uber.org/goleak v1.1.12/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 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/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 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-20190108225652-1e06a53dbb7e/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-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 h1:4nGaVu0QrbjT/AK2PRLuQfQuh6DJve+pELhqTdAj3x0= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 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-20181221193216-37e7f081c4d4/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.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 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-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007 h1:gG67DSER+11cZvqIMb8S8bt0vZtiN6xWYARwirrOSfE= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 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/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 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-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20200527145253-8367513e4ece h1:1YM0uhfumvoDu9sx8+RyWwTI63zoCQvI23IYFRlvte0= google.golang.org/genproto v0.0.0-20200527145253-8367513e4ece/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= 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.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= google.golang.org/grpc v1.43.0 h1:Eeu7bZtDZ2DpRCsLhUlcrLnvYaMK1Gz86a+hMVvELmM= google.golang.org/grpc v1.43.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= 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.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= ================================================ FILE: chapter/11/ops/internal/jaeger/client/test/server/main.go ================================================ package main import ( "context" "log" "net/http" "os" "time" "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/exporters/otlp/otlptrace" "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc" "go.opentelemetry.io/otel/propagation" "go.opentelemetry.io/otel/sdk/resource" sdktrace "go.opentelemetry.io/otel/sdk/trace" semconv "go.opentelemetry.io/otel/semconv/v1.4.0" "go.opentelemetry.io/otel/trace" "google.golang.org/grpc" ) // Initializes an OTLP exporter, and configures the corresponding trace providers. func initProvider() func() { ctx := context.Background() otelAgentAddr, ok := os.LookupEnv("OTEL_EXPORTER_OTLP_ENDPOINT") if !ok { otelAgentAddr = "0.0.0.0:4317" } traceExp := initTracer(ctx, otelAgentAddr) return func() { cxt, cancel := context.WithTimeout(ctx, time.Second) defer cancel() if err := traceExp.Shutdown(cxt); err != nil { otel.Handle(err) } } } func initTracer(ctx context.Context, otelAgentAddr string) *otlptrace.Exporter { traceClient := otlptracegrpc.NewClient( otlptracegrpc.WithInsecure(), otlptracegrpc.WithEndpoint(otelAgentAddr), otlptracegrpc.WithDialOption(grpc.WithBlock())) traceExp, err := otlptrace.New(ctx, traceClient) handleErr(err, "Failed to create the collector trace exporter") res, err := resource.New(ctx, resource.WithFromEnv(), resource.WithProcess(), resource.WithTelemetrySDK(), resource.WithHost(), resource.WithAttributes( // the service name used to display traces in backends semconv.ServiceNameKey.String("demo-server"), ), ) handleErr(err, "failed to create resource") bsp := sdktrace.NewBatchSpanProcessor(traceExp) tracerProvider := sdktrace.NewTracerProvider( sdktrace.WithSampler(sdktrace.AlwaysSample()), sdktrace.WithResource(res), sdktrace.WithSpanProcessor(bsp), ) // set global propagator to tracecontext (the default is no-op). otel.SetTextMapPropagator(propagation.TraceContext{}) otel.SetTracerProvider(tracerProvider) return traceExp } func handleErr(err error, message string) { if err != nil { log.Fatalf("%s: %v", message, err) } } func main() { shutdown := initProvider() defer shutdown() serverAttribute := attribute.String("server-attribute", "foo") // create a handler wrapped in OpenTelemetry instrumentation handler := http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { ctx := req.Context() span := trace.SpanFromContext(ctx) span.SetAttributes(serverAttribute) w.Write([]byte("Hello World")) log.Println("got called") }) wrappedHandler := otelhttp.NewHandler(handler, "/hello") // serve up the wrapped handler http.Handle("/hello", wrappedHandler) log.Println("server started") http.ListenAndServe(":7080", nil) } ================================================ FILE: chapter/11/ops/internal/jaeger/client/test/server/otel-collector-config.yaml ================================================ receivers: otlp: protocols: grpc: exporters: jaeger: endpoint: jaeger-all-in-one:14250 tls: insecure: true processors: batch: service: pipelines: traces: receivers: [otlp] processors: [batch] exporters: [jaeger] ================================================ FILE: chapter/11/ops/internal/jaeger/client/test/server/readme.md ================================================ # Tracing with OpenTelemetry and Jaeger TODO: fill in the walk through ## Running this example - `docker-compose up -d` - Once started the client application will periodically send requests to the server. Distributed traces will be collected for the requests and responses, then exported for analysis in Jaeger. To view the traces in Jaeger, open http://localhost:16686. If you see something like: ```bash docker-compose up -d Traceback (most recent call last): File "urllib3/connectionpool.py", line 670, in urlopen File "urllib3/connectionpool.py", line 392, in _make_request ``` This indicates that you aren't running docker. Make sure you have docker installed and it is running. ## Tearing down this example - `docker-compose down` ## Influences / Credit The code in this demo was heavily influenced from the example application in https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/examples/demo which carries the following license. ``` // Copyright The OpenTelemetry 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. ``` See also: [OTEL License](https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/10cfdaac1387b4df7a525c3050ce18ec8f8068be/LICENSE ================================================ FILE: chapter/11/ops/internal/prom/prom.go ================================================ package prom import ( "context" "fmt" "regexp" "runtime" "strings" "sync" "time" "github.com/prometheus/client_golang/api" v1 "github.com/prometheus/client_golang/api/prometheus/v1" "github.com/prometheus/common/model" //"github.com/prometheus/common/config" ) // Client is a wrapper around the prometheus Go client for our needs. type Client struct { client v1.API } // New creates a new client connecting to the HTTP address provided. This should be in the form of http[s]://[domain,host,ip]:[port] . func New(httpAddr string) (*Client, error) { client, err := api.NewClient( api.Config{ Address: httpAddr, }, ) if err != nil { return nil, err } return &Client{client: v1.NewAPI(client)}, nil } // Metric returns the metrics current value. func (c *Client) Metric(ctx context.Context, metric string) (model.Value, v1.Warnings, error) { return c.client.Query(ctx, metric, time.Now()) } // Range does a query within a range of time and returns the result. A query might be something like: // "rate(prometheus_tsdb_head_samples_appended_total[5m])". func (c *Client) Range(ctx context.Context, query string, r v1.Range) (model.Value, v1.Warnings, error) { return c.client.QueryRange(ctx, query, r) } // AlertFilter represents a filter you can use to filter out alerts. type AlertFilter struct { // Labels filters alerts by attached labels. If only a key is provided, we only match on the key. // Values that start with "regexp/" are regexp compiled and then matched against a value stored at that key. // Defaults to all labels. Labels map[string]string labelRegexes map[string]*regexp.Regexp // ActiveAt filters out any alerts that are before this time. Defaults to all alerts. ActiveAt time.Time // States filters alerts to only ones in these states. Defaults to all states. States []string states map[string]bool // Value filters out all values that don't match the regex. Value *regexp.Regexp compiled bool } // Compile compiles the AlertFilter. If this is a one off query, no need to do this. If you plan // to reuse the filter, this will increase the speed. Changing a filter after Compile() is called // will not give you the desired result, create a new filter instead. func (a *AlertFilter) Compile() error { if a.compiled { return nil } if len(a.Labels) > 0 { regexes := make(map[string]*regexp.Regexp, len(a.Labels)) for k, v := range a.Labels { if strings.TrimSpace(k) == "" { return fmt.Errorf("cannot have a empty string label key") } if strings.HasPrefix(v, "regexp/") { sp := strings.Split(v, "regexp/") if len(sp) == 1 { return fmt.Errorf("label value with regexp/ must have more content") } if len(sp) > 2 { return fmt.Errorf("regexp/ can only be at the beginning of a label value") } r, err := regexp.Compile(sp[1]) if err != nil { return fmt.Errorf("label with value(%s) cannot be regexp compiled: %w", v, err) } regexes[k] = r } } a.labelRegexes = regexes } if len(a.States) > 0 { a.states = map[string]bool{} for _, s := range a.States { a.states[s] = true } } a.compiled = true return nil } func (a *AlertFilter) filter(items []v1.Alert) (chan v1.Alert, error) { if !a.compiled { if err := a.Compile(); err != nil { return nil, err } } ch := make(chan v1.Alert, runtime.NumCPU()) limit := make(chan struct{}, runtime.NumCPU()) wg := sync.WaitGroup{} wg.Add(1) go func() { defer wg.Done() for _, item := range items { item := item limit <- struct{}{} wg.Add(1) func() { defer wg.Done() defer func() { <-limit }() go a.pipeline(item, ch) }() } }() go func() { wg.Wait() close(ch) }() return ch, nil } func (a *AlertFilter) pipeline(item v1.Alert, out chan v1.Alert) { if len(a.Labels) > 0 { if !a.matchLabel(item) { return } } if !a.ActiveAt.IsZero() { if item.ActiveAt.Before(a.ActiveAt) { return } } if len(a.States) > 0 { if !a.states[string(item.State)] { return } } if a.Value != nil { if !a.Value.MatchString(item.Value) { return } } out <- item } func (a *AlertFilter) matchLabel(item v1.Alert) bool { for k, v := range item.Labels { matched, ok := a.Labels[string(k)] if !ok { continue } // Exact match or we aren't matching on values. switch string(v) { case "", matched: return true } // Let's see if this was a regex match. r := a.labelRegexes[string(k)] // It wasn't, so return false because we didn't have an exact match. if r == nil { continue } if r.MatchString(string(v)) { return true } } return false } // Alerts will return all the alerts that match the filter. func (c *Client) Alerts(ctx context.Context, filter AlertFilter) (chan v1.Alert, error) { r, err := c.client.Alerts(ctx) if err != nil { return nil, err } return filter.filter(r.Alerts) } ================================================ FILE: chapter/11/ops/internal/server/server.go ================================================ // Package server contains our gRPC server implementation for the ops server. package server import ( "context" "errors" "fmt" "log" "net" "sort" "sync" "time" jaeger "github.com/PacktPublishing/Go-for-DevOps/chapter/11/ops/internal/jaeger/client" "github.com/PacktPublishing/Go-for-DevOps/chapter/11/ops/internal/prom" "github.com/PacktPublishing/Go-for-DevOps/chapter/11/petstore/client" "google.golang.org/grpc" "google.golang.org/grpc/reflection" "google.golang.org/protobuf/types/known/durationpb" pb "github.com/PacktPublishing/Go-for-DevOps/chapter/11/ops/proto" mpb "github.com/PacktPublishing/Go-for-DevOps/chapter/11/ops/proto/jaeger/model" ) // API implements our gRPC server's API. type API struct { pb.UnimplementedOpsServer addr string grpcServer *grpc.Server gOpts []grpc.ServerOption mu sync.Mutex clients Clients } // Clients holds the remote clients requires to do ops. type Clients struct { // Jaeger provides access to traces. Jaeger *jaeger.Jaeger // Prom provides access to metrics. Prom *prom.Client // Petstore provides access to the petstore. Petstore *client.Client } func (c Clients) validate() error { if c.Jaeger == nil { return errors.New("Jaeger cannot be nil") } if c.Prom == nil { return errors.New("Prom cannot be nil") } if c.Petstore == nil { return errors.New("PetStore cannot be nil") } return nil } // Option is an optional arguments to New(). type Option func(a *API) // WithGRPCOpts creates the gRPC server with the options passed. func WithGRPCOpts(opts ...grpc.ServerOption) Option { return func(a *API) { a.gOpts = append(a.gOpts, opts...) } } // New is the constructore for API. func New(addr string, clients Clients, options ...Option) (*API, error) { if err := clients.validate(); err != nil { return nil, err } a := &API{addr: addr, clients: clients} for _, o := range options { o(a) } a.grpcServer = grpc.NewServer(a.gOpts...) a.grpcServer.RegisterService(&pb.Ops_ServiceDesc, a) reflection.Register(a.grpcServer) return a, nil } // Start starts the server. This blocks until Stop() is called. func (a *API) Start() error { a.mu.Lock() defer a.mu.Unlock() lis, err := net.Listen("tcp", a.addr) if err != nil { return err } return a.grpcServer.Serve(lis) } // Stop stops the server. func (a *API) Stop() { a.mu.Lock() defer a.mu.Unlock() a.grpcServer.Stop() } // ListTraces lists recent traces from Jaeger for the petstore service. func (a *API) ListTraces(ctx context.Context, req *pb.ListTracesReq) (*pb.ListTracesResp, error) { log.Println("tags:", req.Tags) params := jaeger.SearchParams{ Service: "petstore", Operation: req.Operation, Tags: req.Tags, DurationMin: time.Duration(req.DurationMin), DurationMax: time.Duration(req.DurationMax), SearchDepth: req.SearchDepth, } if req.Start > 0 { params.Start = time.Unix(0, req.Start) } if req.End > 0 { params.End = time.Unix(0, req.End) } ch, err := a.clients.Jaeger.Search(ctx, params) if err != nil { return nil, err } resp := &pb.ListTracesResp{} for trace := range ch { if len(trace.Spans) < 0 { continue } if trace.Err != nil { return nil, trace.Err } start := trace.Spans[0].Span.StartTime t := time.Unix(int64(start.Seconds), int64(start.Nanos)).UTC() resp.Traces = append(resp.Traces, &pb.TraceItem{Start: t.UnixNano(), Id: trace.ID}) } sort.Slice( resp.Traces, func(i, j int) bool { if resp.Traces[i].Start > resp.Traces[j].Start { return true } return false }, ) return resp, nil } func (a *API) ShowLogs(ctx context.Context, req *pb.ShowLogsReq) (*pb.ShowLogsResp, error) { t, err := a.clients.Jaeger.Trace(ctx, req.Id) if err != nil { if err == jaeger.ErrNotFound { return &pb.ShowLogsResp{}, nil } return nil, err } logs := []*mpb.Log{} for _, span := range t.Spans { logs = append(logs, span.Logs...) } /* sort.SliceStable( logs, func(i, j int) bool { if logs[i].Timestamp.AsTime().Before(logs[j].Timestamp.AsTime()) { return true } return false }, ) */ return &pb.ShowLogsResp{ Id: t.ID, Logs: logs, }, nil } func (a *API) ShowTrace(ctx context.Context, req *pb.ShowTraceReq) (*pb.ShowTraceResp, error) { t, err := a.clients.Jaeger.Trace(ctx, req.Id) if err != nil { if err == jaeger.ErrNotFound { return &pb.ShowTraceResp{}, nil } return nil, err } var ( ops []string errors []string tags []string dur *durationpb.Duration ) for _, span := range t.Spans { ops = append(ops, span.OperationName) for _, kv := range span.Tags { if kv.Key == "error" { errors = append(errors, kv.VStr) } tags = append(tags, kv.Key) } if span.Duration.AsDuration() > dur.AsDuration() { dur = span.Duration } } return &pb.ShowTraceResp{ Id: t.ID, Operations: ops, Errors: errors, Tags: tags, Duration: dur, }, nil } // ChangeSampling changes the sampling type and rate for the Petstore. func (a *API) ChangeSampling(ctx context.Context, req *pb.ChangeSamplingReq) (*pb.ChangeSamplingResp, error) { sc := client.Sampler{ Type: client.SamplerType(req.Type), Rate: req.FloatValue, } if err := a.clients.Petstore.ChangeSampler(ctx, sc); err != nil { return nil, err } return &pb.ChangeSamplingResp{}, nil } // DeployedVersion returns the version of the Petstore that prometheus says is current. func (a *API) DeployedVersion(ctx context.Context, req *pb.DeployedVersionReq) (*pb.DeployedVersionResp, error) { mv, _, err := a.clients.Prom.Metric(ctx, "deployedVersion") if err != nil { return nil, fmt.Errorf("problem getting metric: %w", err) } return &pb.DeployedVersionResp{Version: mv.String()}, nil } // Alerts grabs all currnetly firing alerts. func (a *API) Alerts(ctx context.Context, req *pb.AlertsReq) (*pb.AlertsResp, error) { labels := map[string]string{} for _, l := range req.Labels { labels[l] = "" } filter := prom.AlertFilter{ Labels: labels, ActiveAt: time.Unix(0, req.ActiveAt), States: req.States, } ch, err := a.clients.Prom.Alerts(ctx, filter) if err != nil { return nil, err } resp := &pb.AlertsResp{} for a := range ch { resp.Alerts = append( resp.Alerts, &pb.Alert{ State: string(a.State), Value: a.Value, ActiveAt: a.ActiveAt.UnixNano(), }, ) } return resp, nil } ================================================ FILE: chapter/11/ops/ops.go ================================================ package main import ( "flag" "log" jaegerClient "github.com/PacktPublishing/Go-for-DevOps/chapter/11/ops/internal/jaeger/client" "github.com/PacktPublishing/Go-for-DevOps/chapter/11/ops/internal/prom" "github.com/PacktPublishing/Go-for-DevOps/chapter/11/ops/internal/server" "github.com/PacktPublishing/Go-for-DevOps/chapter/11/petstore/client" ) var ( addr = flag.String("addr", "0.0.0.0:7000", "The address to run the service on.") jaegerAddr = flag.String("jaegerAddr", "127.0.0.1:16685", "The address of the Jaeger query service.") promAddr = flag.String("promAddr", "127.0.0.1:9000", "The address of the Prometheus service.") petstoreAddr = flag.String("petstoreAddr", "127.0.0.1:6742", "The address of the Petstore.") ) func main() { flag.Parse() j, err := jaegerClient.New(*jaegerAddr) if err != nil { log.Fatalf("could not connect to Jaeger: %s", err) } p, err := prom.New("http://" + *promAddr) if err != nil { log.Fatalf("could not connect to Prometheus: %s", err) } ps, err := client.New(*petstoreAddr) if err != nil { log.Fatalf("could not connecto the Petstore: %s", err) } clients := server.Clients{ Jaeger: j, Prom: p, Petstore: ps, } serv, err := server.New(*addr, clients) if err != nil { panic(err) } log.Println("serving starting on: ", *addr) serv.Start() } ================================================ FILE: chapter/11/ops/proto/buf.gen.yaml ================================================ version: v1 plugins: - name: go out: ./ opt: - paths=source_relative - name: go-grpc out: ./ opt: - paths=source_relative ================================================ FILE: chapter/11/ops/proto/buf.yaml ================================================ version: v1 deps: - buf.build/googleapis/googleapis - buf.build/gogo/protobuf lint: use: - DEFAULT breaking: use: - FILE ================================================ FILE: chapter/11/ops/proto/jaeger/collector.pb.go ================================================ // Copyright (c) 2019 The Jaeger Authors. // Copyright (c) 2018 Uber Technologies, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.25.0-devel // protoc v3.18.0 // source: jaeger/collector.proto package jaeger import ( model "github.com/PacktPublishing/Go-for-DevOps/chapter/11/ops/proto/jaeger/model" _ "github.com/gogo/protobuf/gogoproto" _ "google.golang.org/genproto/googleapis/api/annotations" 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 PostSpansRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Batch *model.Batch `protobuf:"bytes,1,opt,name=batch,proto3" json:"batch,omitempty"` } func (x *PostSpansRequest) Reset() { *x = PostSpansRequest{} if protoimpl.UnsafeEnabled { mi := &file_jaeger_collector_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *PostSpansRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*PostSpansRequest) ProtoMessage() {} func (x *PostSpansRequest) ProtoReflect() protoreflect.Message { mi := &file_jaeger_collector_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 PostSpansRequest.ProtoReflect.Descriptor instead. func (*PostSpansRequest) Descriptor() ([]byte, []int) { return file_jaeger_collector_proto_rawDescGZIP(), []int{0} } func (x *PostSpansRequest) GetBatch() *model.Batch { if x != nil { return x.Batch } return nil } type PostSpansResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields } func (x *PostSpansResponse) Reset() { *x = PostSpansResponse{} if protoimpl.UnsafeEnabled { mi := &file_jaeger_collector_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *PostSpansResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*PostSpansResponse) ProtoMessage() {} func (x *PostSpansResponse) ProtoReflect() protoreflect.Message { mi := &file_jaeger_collector_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 PostSpansResponse.ProtoReflect.Descriptor instead. func (*PostSpansResponse) Descriptor() ([]byte, []int) { return file_jaeger_collector_proto_rawDescGZIP(), []int{1} } var File_jaeger_collector_proto protoreflect.FileDescriptor var file_jaeger_collector_proto_rawDesc = []byte{ 0x0a, 0x16, 0x6a, 0x61, 0x65, 0x67, 0x65, 0x72, 0x2f, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0d, 0x6a, 0x61, 0x65, 0x67, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x5f, 0x76, 0x32, 0x1a, 0x18, 0x6a, 0x61, 0x65, 0x67, 0x65, 0x72, 0x2f, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x2f, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x14, 0x67, 0x6f, 0x67, 0x6f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x67, 0x6f, 0x67, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1c, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x44, 0x0a, 0x10, 0x50, 0x6f, 0x73, 0x74, 0x53, 0x70, 0x61, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x30, 0x0a, 0x05, 0x62, 0x61, 0x74, 0x63, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x6a, 0x61, 0x65, 0x67, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x5f, 0x76, 0x32, 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x42, 0x04, 0xc8, 0xde, 0x1f, 0x00, 0x52, 0x05, 0x62, 0x61, 0x74, 0x63, 0x68, 0x22, 0x13, 0x0a, 0x11, 0x50, 0x6f, 0x73, 0x74, 0x53, 0x70, 0x61, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0x7c, 0x0a, 0x10, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x68, 0x0a, 0x09, 0x50, 0x6f, 0x73, 0x74, 0x53, 0x70, 0x61, 0x6e, 0x73, 0x12, 0x1f, 0x2e, 0x6a, 0x61, 0x65, 0x67, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x5f, 0x76, 0x32, 0x2e, 0x50, 0x6f, 0x73, 0x74, 0x53, 0x70, 0x61, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x6a, 0x61, 0x65, 0x67, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x5f, 0x76, 0x32, 0x2e, 0x50, 0x6f, 0x73, 0x74, 0x53, 0x70, 0x61, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x18, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x12, 0x22, 0x0d, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x32, 0x2f, 0x73, 0x70, 0x61, 0x6e, 0x73, 0x3a, 0x01, 0x2a, 0x42, 0x6b, 0x0a, 0x17, 0x69, 0x6f, 0x2e, 0x6a, 0x61, 0x65, 0x67, 0x65, 0x72, 0x74, 0x72, 0x61, 0x63, 0x69, 0x6e, 0x67, 0x2e, 0x61, 0x70, 0x69, 0x5f, 0x76, 0x32, 0x5a, 0x44, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x50, 0x61, 0x63, 0x6b, 0x74, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x69, 0x6e, 0x67, 0x2f, 0x47, 0x6f, 0x2d, 0x66, 0x6f, 0x72, 0x2d, 0x44, 0x65, 0x76, 0x4f, 0x70, 0x73, 0x2f, 0x63, 0x68, 0x61, 0x70, 0x74, 0x65, 0x72, 0x2f, 0x31, 0x30, 0x2f, 0x6f, 0x70, 0x73, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x6a, 0x61, 0x65, 0x67, 0x65, 0x72, 0xc8, 0xe2, 0x1e, 0x01, 0xd0, 0xe2, 0x1e, 0x01, 0xe0, 0xe2, 0x1e, 0x01, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( file_jaeger_collector_proto_rawDescOnce sync.Once file_jaeger_collector_proto_rawDescData = file_jaeger_collector_proto_rawDesc ) func file_jaeger_collector_proto_rawDescGZIP() []byte { file_jaeger_collector_proto_rawDescOnce.Do(func() { file_jaeger_collector_proto_rawDescData = protoimpl.X.CompressGZIP(file_jaeger_collector_proto_rawDescData) }) return file_jaeger_collector_proto_rawDescData } var file_jaeger_collector_proto_msgTypes = make([]protoimpl.MessageInfo, 2) var file_jaeger_collector_proto_goTypes = []interface{}{ (*PostSpansRequest)(nil), // 0: jaeger.api_v2.PostSpansRequest (*PostSpansResponse)(nil), // 1: jaeger.api_v2.PostSpansResponse (*model.Batch)(nil), // 2: jaeger.api_v2.Batch } var file_jaeger_collector_proto_depIdxs = []int32{ 2, // 0: jaeger.api_v2.PostSpansRequest.batch:type_name -> jaeger.api_v2.Batch 0, // 1: jaeger.api_v2.CollectorService.PostSpans:input_type -> jaeger.api_v2.PostSpansRequest 1, // 2: jaeger.api_v2.CollectorService.PostSpans:output_type -> jaeger.api_v2.PostSpansResponse 2, // [2:3] is the sub-list for method output_type 1, // [1:2] is the sub-list for method input_type 1, // [1:1] is the sub-list for extension type_name 1, // [1:1] is the sub-list for extension extendee 0, // [0:1] is the sub-list for field type_name } func init() { file_jaeger_collector_proto_init() } func file_jaeger_collector_proto_init() { if File_jaeger_collector_proto != nil { return } if !protoimpl.UnsafeEnabled { file_jaeger_collector_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*PostSpansRequest); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_jaeger_collector_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*PostSpansResponse); 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_jaeger_collector_proto_rawDesc, NumEnums: 0, NumMessages: 2, NumExtensions: 0, NumServices: 1, }, GoTypes: file_jaeger_collector_proto_goTypes, DependencyIndexes: file_jaeger_collector_proto_depIdxs, MessageInfos: file_jaeger_collector_proto_msgTypes, }.Build() File_jaeger_collector_proto = out.File file_jaeger_collector_proto_rawDesc = nil file_jaeger_collector_proto_goTypes = nil file_jaeger_collector_proto_depIdxs = nil } ================================================ FILE: chapter/11/ops/proto/jaeger/collector.proto ================================================ // Copyright (c) 2019 The Jaeger Authors. // Copyright (c) 2018 Uber Technologies, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. syntax="proto3"; package jaeger.api_v2; import "jaeger/model/model.proto"; import "gogoproto/gogo.proto"; import "google/api/annotations.proto"; option go_package = "github.com/PacktPublishing/Go-for-DevOps/chapter/10/ops/proto/jaeger"; option java_package = "io.jaegertracing.api_v2"; // Enable gogoprotobuf extensions (https://github.com/gogo/protobuf/blob/master/extensions.md). // Enable custom Marshal method. option (gogoproto.marshaler_all) = true; // Enable custom Unmarshal method. option (gogoproto.unmarshaler_all) = true; // Enable custom Size method (Required by Marshal and Unmarshal). option (gogoproto.sizer_all) = true; message PostSpansRequest { Batch batch = 1 [ (gogoproto.nullable) = false ]; } message PostSpansResponse { } service CollectorService { rpc PostSpans(PostSpansRequest) returns (PostSpansResponse) { option (google.api.http) = { post: "/api/v2/spans" body: "*" }; } } ================================================ FILE: chapter/11/ops/proto/jaeger/collector_grpc.pb.go ================================================ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. package jaeger 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.32.0 or later. const _ = grpc.SupportPackageIsVersion7 // CollectorServiceClient is the client API for CollectorService 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 CollectorServiceClient interface { PostSpans(ctx context.Context, in *PostSpansRequest, opts ...grpc.CallOption) (*PostSpansResponse, error) } type collectorServiceClient struct { cc grpc.ClientConnInterface } func NewCollectorServiceClient(cc grpc.ClientConnInterface) CollectorServiceClient { return &collectorServiceClient{cc} } func (c *collectorServiceClient) PostSpans(ctx context.Context, in *PostSpansRequest, opts ...grpc.CallOption) (*PostSpansResponse, error) { out := new(PostSpansResponse) err := c.cc.Invoke(ctx, "/jaeger.api_v2.CollectorService/PostSpans", in, out, opts...) if err != nil { return nil, err } return out, nil } // CollectorServiceServer is the server API for CollectorService service. // All implementations must embed UnimplementedCollectorServiceServer // for forward compatibility type CollectorServiceServer interface { PostSpans(context.Context, *PostSpansRequest) (*PostSpansResponse, error) mustEmbedUnimplementedCollectorServiceServer() } // UnimplementedCollectorServiceServer must be embedded to have forward compatible implementations. type UnimplementedCollectorServiceServer struct { } func (UnimplementedCollectorServiceServer) PostSpans(context.Context, *PostSpansRequest) (*PostSpansResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method PostSpans not implemented") } func (UnimplementedCollectorServiceServer) mustEmbedUnimplementedCollectorServiceServer() {} // UnsafeCollectorServiceServer may be embedded to opt out of forward compatibility for this service. // Use of this interface is not recommended, as added methods to CollectorServiceServer will // result in compilation errors. type UnsafeCollectorServiceServer interface { mustEmbedUnimplementedCollectorServiceServer() } func RegisterCollectorServiceServer(s grpc.ServiceRegistrar, srv CollectorServiceServer) { s.RegisterService(&CollectorService_ServiceDesc, srv) } func _CollectorService_PostSpans_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(PostSpansRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(CollectorServiceServer).PostSpans(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: "/jaeger.api_v2.CollectorService/PostSpans", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(CollectorServiceServer).PostSpans(ctx, req.(*PostSpansRequest)) } return interceptor(ctx, in, info, handler) } // CollectorService_ServiceDesc is the grpc.ServiceDesc for CollectorService service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) var CollectorService_ServiceDesc = grpc.ServiceDesc{ ServiceName: "jaeger.api_v2.CollectorService", HandlerType: (*CollectorServiceServer)(nil), Methods: []grpc.MethodDesc{ { MethodName: "PostSpans", Handler: _CollectorService_PostSpans_Handler, }, }, Streams: []grpc.StreamDesc{}, Metadata: "jaeger/collector.proto", } ================================================ FILE: chapter/11/ops/proto/jaeger/model/model.pb.go ================================================ // Copyright (c) 2018 Uber Technologies, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.25.0-devel // protoc v3.18.0 // source: jaeger/model/model.proto package model import ( _ "github.com/gogo/protobuf/gogoproto" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" durationpb "google.golang.org/protobuf/types/known/durationpb" timestamppb "google.golang.org/protobuf/types/known/timestamppb" 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 ValueType int32 const ( ValueType_STRING ValueType = 0 ValueType_BOOL ValueType = 1 ValueType_INT64 ValueType = 2 ValueType_FLOAT64 ValueType = 3 ValueType_BINARY ValueType = 4 ) // Enum value maps for ValueType. var ( ValueType_name = map[int32]string{ 0: "STRING", 1: "BOOL", 2: "INT64", 3: "FLOAT64", 4: "BINARY", } ValueType_value = map[string]int32{ "STRING": 0, "BOOL": 1, "INT64": 2, "FLOAT64": 3, "BINARY": 4, } ) func (x ValueType) Enum() *ValueType { p := new(ValueType) *p = x return p } func (x ValueType) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } func (ValueType) Descriptor() protoreflect.EnumDescriptor { return file_jaeger_model_model_proto_enumTypes[0].Descriptor() } func (ValueType) Type() protoreflect.EnumType { return &file_jaeger_model_model_proto_enumTypes[0] } func (x ValueType) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } // Deprecated: Use ValueType.Descriptor instead. func (ValueType) EnumDescriptor() ([]byte, []int) { return file_jaeger_model_model_proto_rawDescGZIP(), []int{0} } type SpanRefType int32 const ( SpanRefType_CHILD_OF SpanRefType = 0 SpanRefType_FOLLOWS_FROM SpanRefType = 1 ) // Enum value maps for SpanRefType. var ( SpanRefType_name = map[int32]string{ 0: "CHILD_OF", 1: "FOLLOWS_FROM", } SpanRefType_value = map[string]int32{ "CHILD_OF": 0, "FOLLOWS_FROM": 1, } ) func (x SpanRefType) Enum() *SpanRefType { p := new(SpanRefType) *p = x return p } func (x SpanRefType) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } func (SpanRefType) Descriptor() protoreflect.EnumDescriptor { return file_jaeger_model_model_proto_enumTypes[1].Descriptor() } func (SpanRefType) Type() protoreflect.EnumType { return &file_jaeger_model_model_proto_enumTypes[1] } func (x SpanRefType) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } // Deprecated: Use SpanRefType.Descriptor instead. func (SpanRefType) EnumDescriptor() ([]byte, []int) { return file_jaeger_model_model_proto_rawDescGZIP(), []int{1} } type KeyValue struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Key string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` VType ValueType `protobuf:"varint,2,opt,name=v_type,json=vType,proto3,enum=jaeger.api_v2.ValueType" json:"v_type,omitempty"` VStr string `protobuf:"bytes,3,opt,name=v_str,json=vStr,proto3" json:"v_str,omitempty"` VBool bool `protobuf:"varint,4,opt,name=v_bool,json=vBool,proto3" json:"v_bool,omitempty"` VInt64 int64 `protobuf:"varint,5,opt,name=v_int64,json=vInt64,proto3" json:"v_int64,omitempty"` VFloat64 float64 `protobuf:"fixed64,6,opt,name=v_float64,json=vFloat64,proto3" json:"v_float64,omitempty"` VBinary []byte `protobuf:"bytes,7,opt,name=v_binary,json=vBinary,proto3" json:"v_binary,omitempty"` } func (x *KeyValue) Reset() { *x = KeyValue{} if protoimpl.UnsafeEnabled { mi := &file_jaeger_model_model_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *KeyValue) String() string { return protoimpl.X.MessageStringOf(x) } func (*KeyValue) ProtoMessage() {} func (x *KeyValue) ProtoReflect() protoreflect.Message { mi := &file_jaeger_model_model_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 KeyValue.ProtoReflect.Descriptor instead. func (*KeyValue) Descriptor() ([]byte, []int) { return file_jaeger_model_model_proto_rawDescGZIP(), []int{0} } func (x *KeyValue) GetKey() string { if x != nil { return x.Key } return "" } func (x *KeyValue) GetVType() ValueType { if x != nil { return x.VType } return ValueType_STRING } func (x *KeyValue) GetVStr() string { if x != nil { return x.VStr } return "" } func (x *KeyValue) GetVBool() bool { if x != nil { return x.VBool } return false } func (x *KeyValue) GetVInt64() int64 { if x != nil { return x.VInt64 } return 0 } func (x *KeyValue) GetVFloat64() float64 { if x != nil { return x.VFloat64 } return 0 } func (x *KeyValue) GetVBinary() []byte { if x != nil { return x.VBinary } return nil } type Log struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Timestamp *timestamppb.Timestamp `protobuf:"bytes,1,opt,name=timestamp,proto3" json:"timestamp,omitempty"` Fields []*KeyValue `protobuf:"bytes,2,rep,name=fields,proto3" json:"fields,omitempty"` } func (x *Log) Reset() { *x = Log{} if protoimpl.UnsafeEnabled { mi := &file_jaeger_model_model_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Log) String() string { return protoimpl.X.MessageStringOf(x) } func (*Log) ProtoMessage() {} func (x *Log) ProtoReflect() protoreflect.Message { mi := &file_jaeger_model_model_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 Log.ProtoReflect.Descriptor instead. func (*Log) Descriptor() ([]byte, []int) { return file_jaeger_model_model_proto_rawDescGZIP(), []int{1} } func (x *Log) GetTimestamp() *timestamppb.Timestamp { if x != nil { return x.Timestamp } return nil } func (x *Log) GetFields() []*KeyValue { if x != nil { return x.Fields } return nil } type SpanRef struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields TraceId []byte `protobuf:"bytes,1,opt,name=trace_id,json=traceId,proto3" json:"trace_id,omitempty"` SpanId []byte `protobuf:"bytes,2,opt,name=span_id,json=spanId,proto3" json:"span_id,omitempty"` RefType SpanRefType `protobuf:"varint,3,opt,name=ref_type,json=refType,proto3,enum=jaeger.api_v2.SpanRefType" json:"ref_type,omitempty"` } func (x *SpanRef) Reset() { *x = SpanRef{} if protoimpl.UnsafeEnabled { mi := &file_jaeger_model_model_proto_msgTypes[2] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *SpanRef) String() string { return protoimpl.X.MessageStringOf(x) } func (*SpanRef) ProtoMessage() {} func (x *SpanRef) ProtoReflect() protoreflect.Message { mi := &file_jaeger_model_model_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 SpanRef.ProtoReflect.Descriptor instead. func (*SpanRef) Descriptor() ([]byte, []int) { return file_jaeger_model_model_proto_rawDescGZIP(), []int{2} } func (x *SpanRef) GetTraceId() []byte { if x != nil { return x.TraceId } return nil } func (x *SpanRef) GetSpanId() []byte { if x != nil { return x.SpanId } return nil } func (x *SpanRef) GetRefType() SpanRefType { if x != nil { return x.RefType } return SpanRefType_CHILD_OF } type Process struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields ServiceName string `protobuf:"bytes,1,opt,name=service_name,json=serviceName,proto3" json:"service_name,omitempty"` Tags []*KeyValue `protobuf:"bytes,2,rep,name=tags,proto3" json:"tags,omitempty"` } func (x *Process) Reset() { *x = Process{} if protoimpl.UnsafeEnabled { mi := &file_jaeger_model_model_proto_msgTypes[3] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Process) String() string { return protoimpl.X.MessageStringOf(x) } func (*Process) ProtoMessage() {} func (x *Process) ProtoReflect() protoreflect.Message { mi := &file_jaeger_model_model_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 Process.ProtoReflect.Descriptor instead. func (*Process) Descriptor() ([]byte, []int) { return file_jaeger_model_model_proto_rawDescGZIP(), []int{3} } func (x *Process) GetServiceName() string { if x != nil { return x.ServiceName } return "" } func (x *Process) GetTags() []*KeyValue { if x != nil { return x.Tags } return nil } type Span struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields TraceId []byte `protobuf:"bytes,1,opt,name=trace_id,json=traceId,proto3" json:"trace_id,omitempty"` SpanId []byte `protobuf:"bytes,2,opt,name=span_id,json=spanId,proto3" json:"span_id,omitempty"` OperationName string `protobuf:"bytes,3,opt,name=operation_name,json=operationName,proto3" json:"operation_name,omitempty"` References []*SpanRef `protobuf:"bytes,4,rep,name=references,proto3" json:"references,omitempty"` Flags uint32 `protobuf:"varint,5,opt,name=flags,proto3" json:"flags,omitempty"` StartTime *timestamppb.Timestamp `protobuf:"bytes,6,opt,name=start_time,json=startTime,proto3" json:"start_time,omitempty"` Duration *durationpb.Duration `protobuf:"bytes,7,opt,name=duration,proto3" json:"duration,omitempty"` Tags []*KeyValue `protobuf:"bytes,8,rep,name=tags,proto3" json:"tags,omitempty"` Logs []*Log `protobuf:"bytes,9,rep,name=logs,proto3" json:"logs,omitempty"` Process *Process `protobuf:"bytes,10,opt,name=process,proto3" json:"process,omitempty"` ProcessId string `protobuf:"bytes,11,opt,name=process_id,json=processId,proto3" json:"process_id,omitempty"` Warnings []string `protobuf:"bytes,12,rep,name=warnings,proto3" json:"warnings,omitempty"` } func (x *Span) Reset() { *x = Span{} if protoimpl.UnsafeEnabled { mi := &file_jaeger_model_model_proto_msgTypes[4] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Span) String() string { return protoimpl.X.MessageStringOf(x) } func (*Span) ProtoMessage() {} func (x *Span) ProtoReflect() protoreflect.Message { mi := &file_jaeger_model_model_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 Span.ProtoReflect.Descriptor instead. func (*Span) Descriptor() ([]byte, []int) { return file_jaeger_model_model_proto_rawDescGZIP(), []int{4} } func (x *Span) GetTraceId() []byte { if x != nil { return x.TraceId } return nil } func (x *Span) GetSpanId() []byte { if x != nil { return x.SpanId } return nil } func (x *Span) GetOperationName() string { if x != nil { return x.OperationName } return "" } func (x *Span) GetReferences() []*SpanRef { if x != nil { return x.References } return nil } func (x *Span) GetFlags() uint32 { if x != nil { return x.Flags } return 0 } func (x *Span) GetStartTime() *timestamppb.Timestamp { if x != nil { return x.StartTime } return nil } func (x *Span) GetDuration() *durationpb.Duration { if x != nil { return x.Duration } return nil } func (x *Span) GetTags() []*KeyValue { if x != nil { return x.Tags } return nil } func (x *Span) GetLogs() []*Log { if x != nil { return x.Logs } return nil } func (x *Span) GetProcess() *Process { if x != nil { return x.Process } return nil } func (x *Span) GetProcessId() string { if x != nil { return x.ProcessId } return "" } func (x *Span) GetWarnings() []string { if x != nil { return x.Warnings } return nil } type Trace struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Spans []*Span `protobuf:"bytes,1,rep,name=spans,proto3" json:"spans,omitempty"` ProcessMap []*Trace_ProcessMapping `protobuf:"bytes,2,rep,name=process_map,json=processMap,proto3" json:"process_map,omitempty"` Warnings []string `protobuf:"bytes,3,rep,name=warnings,proto3" json:"warnings,omitempty"` } func (x *Trace) Reset() { *x = Trace{} if protoimpl.UnsafeEnabled { mi := &file_jaeger_model_model_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Trace) String() string { return protoimpl.X.MessageStringOf(x) } func (*Trace) ProtoMessage() {} func (x *Trace) ProtoReflect() protoreflect.Message { mi := &file_jaeger_model_model_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 Trace.ProtoReflect.Descriptor instead. func (*Trace) Descriptor() ([]byte, []int) { return file_jaeger_model_model_proto_rawDescGZIP(), []int{5} } func (x *Trace) GetSpans() []*Span { if x != nil { return x.Spans } return nil } func (x *Trace) GetProcessMap() []*Trace_ProcessMapping { if x != nil { return x.ProcessMap } return nil } func (x *Trace) GetWarnings() []string { if x != nil { return x.Warnings } return nil } // Note that both Span and Batch may contain a Process. // This is different from the Thrift model which was only used // for transport, because Proto model is also used by the backend // as the domain model, where once a batch is received it is split // into individual spans which are all processed independently, // and therefore they all need a Process. As far as on-the-wire // semantics, both Batch and Spans in the same message may contain // their own instances of Process, with span.Process taking priority // over batch.Process. type Batch struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Spans []*Span `protobuf:"bytes,1,rep,name=spans,proto3" json:"spans,omitempty"` Process *Process `protobuf:"bytes,2,opt,name=process,proto3" json:"process,omitempty"` } func (x *Batch) Reset() { *x = Batch{} if protoimpl.UnsafeEnabled { mi := &file_jaeger_model_model_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Batch) String() string { return protoimpl.X.MessageStringOf(x) } func (*Batch) ProtoMessage() {} func (x *Batch) ProtoReflect() protoreflect.Message { mi := &file_jaeger_model_model_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 Batch.ProtoReflect.Descriptor instead. func (*Batch) Descriptor() ([]byte, []int) { return file_jaeger_model_model_proto_rawDescGZIP(), []int{6} } func (x *Batch) GetSpans() []*Span { if x != nil { return x.Spans } return nil } func (x *Batch) GetProcess() *Process { if x != nil { return x.Process } return nil } type DependencyLink struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Parent string `protobuf:"bytes,1,opt,name=parent,proto3" json:"parent,omitempty"` Child string `protobuf:"bytes,2,opt,name=child,proto3" json:"child,omitempty"` CallCount uint64 `protobuf:"varint,3,opt,name=call_count,json=callCount,proto3" json:"call_count,omitempty"` Source string `protobuf:"bytes,4,opt,name=source,proto3" json:"source,omitempty"` } func (x *DependencyLink) Reset() { *x = DependencyLink{} if protoimpl.UnsafeEnabled { mi := &file_jaeger_model_model_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *DependencyLink) String() string { return protoimpl.X.MessageStringOf(x) } func (*DependencyLink) ProtoMessage() {} func (x *DependencyLink) ProtoReflect() protoreflect.Message { mi := &file_jaeger_model_model_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 DependencyLink.ProtoReflect.Descriptor instead. func (*DependencyLink) Descriptor() ([]byte, []int) { return file_jaeger_model_model_proto_rawDescGZIP(), []int{7} } func (x *DependencyLink) GetParent() string { if x != nil { return x.Parent } return "" } func (x *DependencyLink) GetChild() string { if x != nil { return x.Child } return "" } func (x *DependencyLink) GetCallCount() uint64 { if x != nil { return x.CallCount } return 0 } func (x *DependencyLink) GetSource() string { if x != nil { return x.Source } return "" } type Trace_ProcessMapping struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields ProcessId string `protobuf:"bytes,1,opt,name=process_id,json=processId,proto3" json:"process_id,omitempty"` Process *Process `protobuf:"bytes,2,opt,name=process,proto3" json:"process,omitempty"` } func (x *Trace_ProcessMapping) Reset() { *x = Trace_ProcessMapping{} if protoimpl.UnsafeEnabled { mi := &file_jaeger_model_model_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Trace_ProcessMapping) String() string { return protoimpl.X.MessageStringOf(x) } func (*Trace_ProcessMapping) ProtoMessage() {} func (x *Trace_ProcessMapping) ProtoReflect() protoreflect.Message { mi := &file_jaeger_model_model_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 Trace_ProcessMapping.ProtoReflect.Descriptor instead. func (*Trace_ProcessMapping) Descriptor() ([]byte, []int) { return file_jaeger_model_model_proto_rawDescGZIP(), []int{5, 0} } func (x *Trace_ProcessMapping) GetProcessId() string { if x != nil { return x.ProcessId } return "" } func (x *Trace_ProcessMapping) GetProcess() *Process { if x != nil { return x.Process } return nil } var File_jaeger_model_model_proto protoreflect.FileDescriptor var file_jaeger_model_model_proto_rawDesc = []byte{ 0x0a, 0x18, 0x6a, 0x61, 0x65, 0x67, 0x65, 0x72, 0x2f, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x2f, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0d, 0x6a, 0x61, 0x65, 0x67, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x5f, 0x76, 0x32, 0x1a, 0x14, 0x67, 0x6f, 0x67, 0x6f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x67, 0x6f, 0x67, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xd4, 0x01, 0x0a, 0x08, 0x4b, 0x65, 0x79, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x2f, 0x0a, 0x06, 0x76, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x18, 0x2e, 0x6a, 0x61, 0x65, 0x67, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x5f, 0x76, 0x32, 0x2e, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x54, 0x79, 0x70, 0x65, 0x52, 0x05, 0x76, 0x54, 0x79, 0x70, 0x65, 0x12, 0x13, 0x0a, 0x05, 0x76, 0x5f, 0x73, 0x74, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x76, 0x53, 0x74, 0x72, 0x12, 0x15, 0x0a, 0x06, 0x76, 0x5f, 0x62, 0x6f, 0x6f, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x76, 0x42, 0x6f, 0x6f, 0x6c, 0x12, 0x17, 0x0a, 0x07, 0x76, 0x5f, 0x69, 0x6e, 0x74, 0x36, 0x34, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x76, 0x49, 0x6e, 0x74, 0x36, 0x34, 0x12, 0x1b, 0x0a, 0x09, 0x76, 0x5f, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x36, 0x34, 0x18, 0x06, 0x20, 0x01, 0x28, 0x01, 0x52, 0x08, 0x76, 0x46, 0x6c, 0x6f, 0x61, 0x74, 0x36, 0x34, 0x12, 0x19, 0x0a, 0x08, 0x76, 0x5f, 0x62, 0x69, 0x6e, 0x61, 0x72, 0x79, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x76, 0x42, 0x69, 0x6e, 0x61, 0x72, 0x79, 0x3a, 0x08, 0xe8, 0xa0, 0x1f, 0x01, 0xe8, 0xa1, 0x1f, 0x01, 0x22, 0x80, 0x01, 0x0a, 0x03, 0x4c, 0x6f, 0x67, 0x12, 0x42, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x42, 0x08, 0xc8, 0xde, 0x1f, 0x00, 0x90, 0xdf, 0x1f, 0x01, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x35, 0x0a, 0x06, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x6a, 0x61, 0x65, 0x67, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x5f, 0x76, 0x32, 0x2e, 0x4b, 0x65, 0x79, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x42, 0x04, 0xc8, 0xde, 0x1f, 0x00, 0x52, 0x06, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x22, 0xaa, 0x01, 0x0a, 0x07, 0x53, 0x70, 0x61, 0x6e, 0x52, 0x65, 0x66, 0x12, 0x35, 0x0a, 0x08, 0x74, 0x72, 0x61, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x42, 0x1a, 0xc8, 0xde, 0x1f, 0x00, 0xda, 0xde, 0x1f, 0x07, 0x54, 0x72, 0x61, 0x63, 0x65, 0x49, 0x44, 0xe2, 0xde, 0x1f, 0x07, 0x54, 0x72, 0x61, 0x63, 0x65, 0x49, 0x44, 0x52, 0x07, 0x74, 0x72, 0x61, 0x63, 0x65, 0x49, 0x64, 0x12, 0x31, 0x0a, 0x07, 0x73, 0x70, 0x61, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x42, 0x18, 0xc8, 0xde, 0x1f, 0x00, 0xda, 0xde, 0x1f, 0x06, 0x53, 0x70, 0x61, 0x6e, 0x49, 0x44, 0xe2, 0xde, 0x1f, 0x06, 0x53, 0x70, 0x61, 0x6e, 0x49, 0x44, 0x52, 0x06, 0x73, 0x70, 0x61, 0x6e, 0x49, 0x64, 0x12, 0x35, 0x0a, 0x08, 0x72, 0x65, 0x66, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1a, 0x2e, 0x6a, 0x61, 0x65, 0x67, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x5f, 0x76, 0x32, 0x2e, 0x53, 0x70, 0x61, 0x6e, 0x52, 0x65, 0x66, 0x54, 0x79, 0x70, 0x65, 0x52, 0x07, 0x72, 0x65, 0x66, 0x54, 0x79, 0x70, 0x65, 0x22, 0x5f, 0x0a, 0x07, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x31, 0x0a, 0x04, 0x74, 0x61, 0x67, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x6a, 0x61, 0x65, 0x67, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x5f, 0x76, 0x32, 0x2e, 0x4b, 0x65, 0x79, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x42, 0x04, 0xc8, 0xde, 0x1f, 0x00, 0x52, 0x04, 0x74, 0x61, 0x67, 0x73, 0x22, 0xdd, 0x04, 0x0a, 0x04, 0x53, 0x70, 0x61, 0x6e, 0x12, 0x35, 0x0a, 0x08, 0x74, 0x72, 0x61, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x42, 0x1a, 0xc8, 0xde, 0x1f, 0x00, 0xda, 0xde, 0x1f, 0x07, 0x54, 0x72, 0x61, 0x63, 0x65, 0x49, 0x44, 0xe2, 0xde, 0x1f, 0x07, 0x54, 0x72, 0x61, 0x63, 0x65, 0x49, 0x44, 0x52, 0x07, 0x74, 0x72, 0x61, 0x63, 0x65, 0x49, 0x64, 0x12, 0x31, 0x0a, 0x07, 0x73, 0x70, 0x61, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x42, 0x18, 0xc8, 0xde, 0x1f, 0x00, 0xda, 0xde, 0x1f, 0x06, 0x53, 0x70, 0x61, 0x6e, 0x49, 0x44, 0xe2, 0xde, 0x1f, 0x06, 0x53, 0x70, 0x61, 0x6e, 0x49, 0x44, 0x52, 0x06, 0x73, 0x70, 0x61, 0x6e, 0x49, 0x64, 0x12, 0x25, 0x0a, 0x0e, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x3c, 0x0a, 0x0a, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x6a, 0x61, 0x65, 0x67, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x5f, 0x76, 0x32, 0x2e, 0x53, 0x70, 0x61, 0x6e, 0x52, 0x65, 0x66, 0x42, 0x04, 0xc8, 0xde, 0x1f, 0x00, 0x52, 0x0a, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x12, 0x23, 0x0a, 0x05, 0x66, 0x6c, 0x61, 0x67, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x42, 0x0d, 0xc8, 0xde, 0x1f, 0x00, 0xda, 0xde, 0x1f, 0x05, 0x46, 0x6c, 0x61, 0x67, 0x73, 0x52, 0x05, 0x66, 0x6c, 0x61, 0x67, 0x73, 0x12, 0x43, 0x0a, 0x0a, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x42, 0x08, 0xc8, 0xde, 0x1f, 0x00, 0x90, 0xdf, 0x1f, 0x01, 0x52, 0x09, 0x73, 0x74, 0x61, 0x72, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x3f, 0x0a, 0x08, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x08, 0xc8, 0xde, 0x1f, 0x00, 0x98, 0xdf, 0x1f, 0x01, 0x52, 0x08, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x31, 0x0a, 0x04, 0x74, 0x61, 0x67, 0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x6a, 0x61, 0x65, 0x67, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x5f, 0x76, 0x32, 0x2e, 0x4b, 0x65, 0x79, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x42, 0x04, 0xc8, 0xde, 0x1f, 0x00, 0x52, 0x04, 0x74, 0x61, 0x67, 0x73, 0x12, 0x2c, 0x0a, 0x04, 0x6c, 0x6f, 0x67, 0x73, 0x18, 0x09, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x6a, 0x61, 0x65, 0x67, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x5f, 0x76, 0x32, 0x2e, 0x4c, 0x6f, 0x67, 0x42, 0x04, 0xc8, 0xde, 0x1f, 0x00, 0x52, 0x04, 0x6c, 0x6f, 0x67, 0x73, 0x12, 0x30, 0x0a, 0x07, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x6a, 0x61, 0x65, 0x67, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x5f, 0x76, 0x32, 0x2e, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x52, 0x07, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x12, 0x2c, 0x0a, 0x0a, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x69, 0x64, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x42, 0x0d, 0xe2, 0xde, 0x1f, 0x09, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x49, 0x44, 0x52, 0x09, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x77, 0x61, 0x72, 0x6e, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x0c, 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x77, 0x61, 0x72, 0x6e, 0x69, 0x6e, 0x67, 0x73, 0x22, 0x92, 0x02, 0x0a, 0x05, 0x54, 0x72, 0x61, 0x63, 0x65, 0x12, 0x29, 0x0a, 0x05, 0x73, 0x70, 0x61, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x6a, 0x61, 0x65, 0x67, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x5f, 0x76, 0x32, 0x2e, 0x53, 0x70, 0x61, 0x6e, 0x52, 0x05, 0x73, 0x70, 0x61, 0x6e, 0x73, 0x12, 0x4a, 0x0a, 0x0b, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x6d, 0x61, 0x70, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x6a, 0x61, 0x65, 0x67, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x5f, 0x76, 0x32, 0x2e, 0x54, 0x72, 0x61, 0x63, 0x65, 0x2e, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x4d, 0x61, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x42, 0x04, 0xc8, 0xde, 0x1f, 0x00, 0x52, 0x0a, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x4d, 0x61, 0x70, 0x12, 0x1a, 0x0a, 0x08, 0x77, 0x61, 0x72, 0x6e, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x77, 0x61, 0x72, 0x6e, 0x69, 0x6e, 0x67, 0x73, 0x1a, 0x76, 0x0a, 0x0e, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x4d, 0x61, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x12, 0x2c, 0x0a, 0x0a, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x0d, 0xe2, 0xde, 0x1f, 0x09, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x49, 0x44, 0x52, 0x09, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x49, 0x64, 0x12, 0x36, 0x0a, 0x07, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x6a, 0x61, 0x65, 0x67, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x5f, 0x76, 0x32, 0x2e, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x42, 0x04, 0xc8, 0xde, 0x1f, 0x00, 0x52, 0x07, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x22, 0x6a, 0x0a, 0x05, 0x42, 0x61, 0x74, 0x63, 0x68, 0x12, 0x29, 0x0a, 0x05, 0x73, 0x70, 0x61, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x6a, 0x61, 0x65, 0x67, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x5f, 0x76, 0x32, 0x2e, 0x53, 0x70, 0x61, 0x6e, 0x52, 0x05, 0x73, 0x70, 0x61, 0x6e, 0x73, 0x12, 0x36, 0x0a, 0x07, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x6a, 0x61, 0x65, 0x67, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x5f, 0x76, 0x32, 0x2e, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x42, 0x04, 0xc8, 0xde, 0x1f, 0x01, 0x52, 0x07, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x22, 0x75, 0x0a, 0x0e, 0x44, 0x65, 0x70, 0x65, 0x6e, 0x64, 0x65, 0x6e, 0x63, 0x79, 0x4c, 0x69, 0x6e, 0x6b, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x68, 0x69, 0x6c, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x63, 0x68, 0x69, 0x6c, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x61, 0x6c, 0x6c, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x63, 0x61, 0x6c, 0x6c, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2a, 0x45, 0x0a, 0x09, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0a, 0x0a, 0x06, 0x53, 0x54, 0x52, 0x49, 0x4e, 0x47, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x42, 0x4f, 0x4f, 0x4c, 0x10, 0x01, 0x12, 0x09, 0x0a, 0x05, 0x49, 0x4e, 0x54, 0x36, 0x34, 0x10, 0x02, 0x12, 0x0b, 0x0a, 0x07, 0x46, 0x4c, 0x4f, 0x41, 0x54, 0x36, 0x34, 0x10, 0x03, 0x12, 0x0a, 0x0a, 0x06, 0x42, 0x49, 0x4e, 0x41, 0x52, 0x59, 0x10, 0x04, 0x2a, 0x2d, 0x0a, 0x0b, 0x53, 0x70, 0x61, 0x6e, 0x52, 0x65, 0x66, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0c, 0x0a, 0x08, 0x43, 0x48, 0x49, 0x4c, 0x44, 0x5f, 0x4f, 0x46, 0x10, 0x00, 0x12, 0x10, 0x0a, 0x0c, 0x46, 0x4f, 0x4c, 0x4c, 0x4f, 0x57, 0x53, 0x5f, 0x46, 0x52, 0x4f, 0x4d, 0x10, 0x01, 0x42, 0x71, 0x0a, 0x17, 0x69, 0x6f, 0x2e, 0x6a, 0x61, 0x65, 0x67, 0x65, 0x72, 0x74, 0x72, 0x61, 0x63, 0x69, 0x6e, 0x67, 0x2e, 0x61, 0x70, 0x69, 0x5f, 0x76, 0x32, 0x5a, 0x4a, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x50, 0x61, 0x63, 0x6b, 0x74, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x69, 0x6e, 0x67, 0x2f, 0x47, 0x6f, 0x2d, 0x66, 0x6f, 0x72, 0x2d, 0x44, 0x65, 0x76, 0x4f, 0x70, 0x73, 0x2f, 0x63, 0x68, 0x61, 0x70, 0x74, 0x65, 0x72, 0x2f, 0x31, 0x30, 0x2f, 0x6f, 0x70, 0x73, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x6a, 0x61, 0x65, 0x67, 0x65, 0x72, 0x2f, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0xc8, 0xe2, 0x1e, 0x01, 0xd0, 0xe2, 0x1e, 0x01, 0xe0, 0xe2, 0x1e, 0x01, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( file_jaeger_model_model_proto_rawDescOnce sync.Once file_jaeger_model_model_proto_rawDescData = file_jaeger_model_model_proto_rawDesc ) func file_jaeger_model_model_proto_rawDescGZIP() []byte { file_jaeger_model_model_proto_rawDescOnce.Do(func() { file_jaeger_model_model_proto_rawDescData = protoimpl.X.CompressGZIP(file_jaeger_model_model_proto_rawDescData) }) return file_jaeger_model_model_proto_rawDescData } var file_jaeger_model_model_proto_enumTypes = make([]protoimpl.EnumInfo, 2) var file_jaeger_model_model_proto_msgTypes = make([]protoimpl.MessageInfo, 9) var file_jaeger_model_model_proto_goTypes = []interface{}{ (ValueType)(0), // 0: jaeger.api_v2.ValueType (SpanRefType)(0), // 1: jaeger.api_v2.SpanRefType (*KeyValue)(nil), // 2: jaeger.api_v2.KeyValue (*Log)(nil), // 3: jaeger.api_v2.Log (*SpanRef)(nil), // 4: jaeger.api_v2.SpanRef (*Process)(nil), // 5: jaeger.api_v2.Process (*Span)(nil), // 6: jaeger.api_v2.Span (*Trace)(nil), // 7: jaeger.api_v2.Trace (*Batch)(nil), // 8: jaeger.api_v2.Batch (*DependencyLink)(nil), // 9: jaeger.api_v2.DependencyLink (*Trace_ProcessMapping)(nil), // 10: jaeger.api_v2.Trace.ProcessMapping (*timestamppb.Timestamp)(nil), // 11: google.protobuf.Timestamp (*durationpb.Duration)(nil), // 12: google.protobuf.Duration } var file_jaeger_model_model_proto_depIdxs = []int32{ 0, // 0: jaeger.api_v2.KeyValue.v_type:type_name -> jaeger.api_v2.ValueType 11, // 1: jaeger.api_v2.Log.timestamp:type_name -> google.protobuf.Timestamp 2, // 2: jaeger.api_v2.Log.fields:type_name -> jaeger.api_v2.KeyValue 1, // 3: jaeger.api_v2.SpanRef.ref_type:type_name -> jaeger.api_v2.SpanRefType 2, // 4: jaeger.api_v2.Process.tags:type_name -> jaeger.api_v2.KeyValue 4, // 5: jaeger.api_v2.Span.references:type_name -> jaeger.api_v2.SpanRef 11, // 6: jaeger.api_v2.Span.start_time:type_name -> google.protobuf.Timestamp 12, // 7: jaeger.api_v2.Span.duration:type_name -> google.protobuf.Duration 2, // 8: jaeger.api_v2.Span.tags:type_name -> jaeger.api_v2.KeyValue 3, // 9: jaeger.api_v2.Span.logs:type_name -> jaeger.api_v2.Log 5, // 10: jaeger.api_v2.Span.process:type_name -> jaeger.api_v2.Process 6, // 11: jaeger.api_v2.Trace.spans:type_name -> jaeger.api_v2.Span 10, // 12: jaeger.api_v2.Trace.process_map:type_name -> jaeger.api_v2.Trace.ProcessMapping 6, // 13: jaeger.api_v2.Batch.spans:type_name -> jaeger.api_v2.Span 5, // 14: jaeger.api_v2.Batch.process:type_name -> jaeger.api_v2.Process 5, // 15: jaeger.api_v2.Trace.ProcessMapping.process:type_name -> jaeger.api_v2.Process 16, // [16:16] is the sub-list for method output_type 16, // [16:16] is the sub-list for method input_type 16, // [16:16] is the sub-list for extension type_name 16, // [16:16] is the sub-list for extension extendee 0, // [0:16] is the sub-list for field type_name } func init() { file_jaeger_model_model_proto_init() } func file_jaeger_model_model_proto_init() { if File_jaeger_model_model_proto != nil { return } if !protoimpl.UnsafeEnabled { file_jaeger_model_model_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*KeyValue); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_jaeger_model_model_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Log); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_jaeger_model_model_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*SpanRef); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_jaeger_model_model_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Process); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_jaeger_model_model_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Span); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_jaeger_model_model_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Trace); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_jaeger_model_model_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Batch); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_jaeger_model_model_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*DependencyLink); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_jaeger_model_model_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Trace_ProcessMapping); 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_jaeger_model_model_proto_rawDesc, NumEnums: 2, NumMessages: 9, NumExtensions: 0, NumServices: 0, }, GoTypes: file_jaeger_model_model_proto_goTypes, DependencyIndexes: file_jaeger_model_model_proto_depIdxs, EnumInfos: file_jaeger_model_model_proto_enumTypes, MessageInfos: file_jaeger_model_model_proto_msgTypes, }.Build() File_jaeger_model_model_proto = out.File file_jaeger_model_model_proto_rawDesc = nil file_jaeger_model_model_proto_goTypes = nil file_jaeger_model_model_proto_depIdxs = nil } ================================================ FILE: chapter/11/ops/proto/jaeger/model/model.proto ================================================ // Copyright (c) 2018 Uber Technologies, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. syntax="proto3"; package jaeger.api_v2; import "gogoproto/gogo.proto"; import "google/protobuf/timestamp.proto"; import "google/protobuf/duration.proto"; // TODO: document all types and fields // TODO: once this moves to jaeger-idl repo, we may want to change Go pkg to api_v2 // and rewrite it to model only in this repo. That should make it easier to generate // classes in other languages. option go_package = "github.com/PacktPublishing/Go-for-DevOps/chapter/10/ops/proto/jaeger/model"; option java_package = "io.jaegertracing.api_v2"; // Enable gogoprotobuf extensions (https://github.com/gogo/protobuf/blob/master/extensions.md). // Enable custom Marshal method. option (gogoproto.marshaler_all) = true; // Enable custom Unmarshal method. option (gogoproto.unmarshaler_all) = true; // Enable custom Size method (Required by Marshal and Unmarshal). option (gogoproto.sizer_all) = true; enum ValueType { STRING = 0; BOOL = 1; INT64 = 2; FLOAT64 = 3; BINARY = 4; }; message KeyValue { option (gogoproto.equal) = true; option (gogoproto.compare) = true; string key = 1; ValueType v_type = 2; string v_str = 3; bool v_bool = 4; int64 v_int64 = 5; double v_float64 = 6; bytes v_binary = 7; } message Log { google.protobuf.Timestamp timestamp = 1 [ (gogoproto.stdtime) = true, (gogoproto.nullable) = false ]; repeated KeyValue fields = 2 [ (gogoproto.nullable) = false ]; } enum SpanRefType { CHILD_OF = 0; FOLLOWS_FROM = 1; }; message SpanRef { bytes trace_id = 1 [ (gogoproto.nullable) = false, (gogoproto.customtype) = "TraceID", (gogoproto.customname) = "TraceID" ]; bytes span_id = 2 [ (gogoproto.nullable) = false, (gogoproto.customtype) = "SpanID", (gogoproto.customname) = "SpanID" ]; SpanRefType ref_type = 3; } message Process { string service_name = 1; repeated KeyValue tags = 2 [ (gogoproto.nullable) = false ]; } message Span { bytes trace_id = 1 [ (gogoproto.nullable) = false, (gogoproto.customtype) = "TraceID", (gogoproto.customname) = "TraceID" ]; bytes span_id = 2 [ (gogoproto.nullable) = false, (gogoproto.customtype) = "SpanID", (gogoproto.customname) = "SpanID" ]; string operation_name = 3; repeated SpanRef references = 4 [ (gogoproto.nullable) = false ]; uint32 flags = 5 [ (gogoproto.nullable) = false, (gogoproto.customtype) = "Flags" ]; google.protobuf.Timestamp start_time = 6 [ (gogoproto.stdtime) = true, (gogoproto.nullable) = false ]; google.protobuf.Duration duration = 7 [ (gogoproto.stdduration) = true, (gogoproto.nullable) = false ]; repeated KeyValue tags = 8 [ (gogoproto.nullable) = false ]; repeated Log logs = 9 [ (gogoproto.nullable) = false ]; Process process = 10; string process_id = 11 [ (gogoproto.customname) = "ProcessID" ]; repeated string warnings = 12; } message Trace { message ProcessMapping { string process_id = 1 [ (gogoproto.customname) = "ProcessID" ]; Process process = 2 [ (gogoproto.nullable) = false ]; } repeated Span spans = 1; repeated ProcessMapping process_map = 2 [ (gogoproto.nullable) = false ]; repeated string warnings = 3; } // Note that both Span and Batch may contain a Process. // This is different from the Thrift model which was only used // for transport, because Proto model is also used by the backend // as the domain model, where once a batch is received it is split // into individual spans which are all processed independently, // and therefore they all need a Process. As far as on-the-wire // semantics, both Batch and Spans in the same message may contain // their own instances of Process, with span.Process taking priority // over batch.Process. message Batch { repeated Span spans = 1; Process process = 2 [ (gogoproto.nullable) = true ]; } message DependencyLink { string parent = 1; string child = 2; uint64 call_count = 3; string source = 4; } ================================================ FILE: chapter/11/ops/proto/jaeger/query.pb.go ================================================ // Copyright (c) 2019 The Jaeger Authors. // Copyright (c) 2018 Uber Technologies, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.25.0-devel // protoc v3.18.0 // source: jaeger/query.proto package jaeger import ( model "github.com/PacktPublishing/Go-for-DevOps/chapter/11/ops/proto/jaeger/model" _ "github.com/gogo/protobuf/gogoproto" _ "google.golang.org/genproto/googleapis/api/annotations" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" durationpb "google.golang.org/protobuf/types/known/durationpb" timestamppb "google.golang.org/protobuf/types/known/timestamppb" 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 GetTraceRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields TraceId []byte `protobuf:"bytes,1,opt,name=trace_id,json=traceId,proto3" json:"trace_id,omitempty"` } func (x *GetTraceRequest) Reset() { *x = GetTraceRequest{} if protoimpl.UnsafeEnabled { mi := &file_jaeger_query_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *GetTraceRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*GetTraceRequest) ProtoMessage() {} func (x *GetTraceRequest) ProtoReflect() protoreflect.Message { mi := &file_jaeger_query_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 GetTraceRequest.ProtoReflect.Descriptor instead. func (*GetTraceRequest) Descriptor() ([]byte, []int) { return file_jaeger_query_proto_rawDescGZIP(), []int{0} } func (x *GetTraceRequest) GetTraceId() []byte { if x != nil { return x.TraceId } return nil } type SpansResponseChunk struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Spans []*model.Span `protobuf:"bytes,1,rep,name=spans,proto3" json:"spans,omitempty"` } func (x *SpansResponseChunk) Reset() { *x = SpansResponseChunk{} if protoimpl.UnsafeEnabled { mi := &file_jaeger_query_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *SpansResponseChunk) String() string { return protoimpl.X.MessageStringOf(x) } func (*SpansResponseChunk) ProtoMessage() {} func (x *SpansResponseChunk) ProtoReflect() protoreflect.Message { mi := &file_jaeger_query_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 SpansResponseChunk.ProtoReflect.Descriptor instead. func (*SpansResponseChunk) Descriptor() ([]byte, []int) { return file_jaeger_query_proto_rawDescGZIP(), []int{1} } func (x *SpansResponseChunk) GetSpans() []*model.Span { if x != nil { return x.Spans } return nil } type ArchiveTraceRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields TraceId []byte `protobuf:"bytes,1,opt,name=trace_id,json=traceId,proto3" json:"trace_id,omitempty"` } func (x *ArchiveTraceRequest) Reset() { *x = ArchiveTraceRequest{} if protoimpl.UnsafeEnabled { mi := &file_jaeger_query_proto_msgTypes[2] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *ArchiveTraceRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*ArchiveTraceRequest) ProtoMessage() {} func (x *ArchiveTraceRequest) ProtoReflect() protoreflect.Message { mi := &file_jaeger_query_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 ArchiveTraceRequest.ProtoReflect.Descriptor instead. func (*ArchiveTraceRequest) Descriptor() ([]byte, []int) { return file_jaeger_query_proto_rawDescGZIP(), []int{2} } func (x *ArchiveTraceRequest) GetTraceId() []byte { if x != nil { return x.TraceId } return nil } type ArchiveTraceResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields } func (x *ArchiveTraceResponse) Reset() { *x = ArchiveTraceResponse{} if protoimpl.UnsafeEnabled { mi := &file_jaeger_query_proto_msgTypes[3] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *ArchiveTraceResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*ArchiveTraceResponse) ProtoMessage() {} func (x *ArchiveTraceResponse) ProtoReflect() protoreflect.Message { mi := &file_jaeger_query_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 ArchiveTraceResponse.ProtoReflect.Descriptor instead. func (*ArchiveTraceResponse) Descriptor() ([]byte, []int) { return file_jaeger_query_proto_rawDescGZIP(), []int{3} } type TraceQueryParameters struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields ServiceName string `protobuf:"bytes,1,opt,name=service_name,json=serviceName,proto3" json:"service_name,omitempty"` OperationName string `protobuf:"bytes,2,opt,name=operation_name,json=operationName,proto3" json:"operation_name,omitempty"` Tags map[string]string `protobuf:"bytes,3,rep,name=tags,proto3" json:"tags,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` StartTimeMin *timestamppb.Timestamp `protobuf:"bytes,4,opt,name=start_time_min,json=startTimeMin,proto3" json:"start_time_min,omitempty"` StartTimeMax *timestamppb.Timestamp `protobuf:"bytes,5,opt,name=start_time_max,json=startTimeMax,proto3" json:"start_time_max,omitempty"` DurationMin *durationpb.Duration `protobuf:"bytes,6,opt,name=duration_min,json=durationMin,proto3" json:"duration_min,omitempty"` DurationMax *durationpb.Duration `protobuf:"bytes,7,opt,name=duration_max,json=durationMax,proto3" json:"duration_max,omitempty"` SearchDepth int32 `protobuf:"varint,8,opt,name=search_depth,json=searchDepth,proto3" json:"search_depth,omitempty"` } func (x *TraceQueryParameters) Reset() { *x = TraceQueryParameters{} if protoimpl.UnsafeEnabled { mi := &file_jaeger_query_proto_msgTypes[4] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *TraceQueryParameters) String() string { return protoimpl.X.MessageStringOf(x) } func (*TraceQueryParameters) ProtoMessage() {} func (x *TraceQueryParameters) ProtoReflect() protoreflect.Message { mi := &file_jaeger_query_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 TraceQueryParameters.ProtoReflect.Descriptor instead. func (*TraceQueryParameters) Descriptor() ([]byte, []int) { return file_jaeger_query_proto_rawDescGZIP(), []int{4} } func (x *TraceQueryParameters) GetServiceName() string { if x != nil { return x.ServiceName } return "" } func (x *TraceQueryParameters) GetOperationName() string { if x != nil { return x.OperationName } return "" } func (x *TraceQueryParameters) GetTags() map[string]string { if x != nil { return x.Tags } return nil } func (x *TraceQueryParameters) GetStartTimeMin() *timestamppb.Timestamp { if x != nil { return x.StartTimeMin } return nil } func (x *TraceQueryParameters) GetStartTimeMax() *timestamppb.Timestamp { if x != nil { return x.StartTimeMax } return nil } func (x *TraceQueryParameters) GetDurationMin() *durationpb.Duration { if x != nil { return x.DurationMin } return nil } func (x *TraceQueryParameters) GetDurationMax() *durationpb.Duration { if x != nil { return x.DurationMax } return nil } func (x *TraceQueryParameters) GetSearchDepth() int32 { if x != nil { return x.SearchDepth } return 0 } type FindTracesRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Query *TraceQueryParameters `protobuf:"bytes,1,opt,name=query,proto3" json:"query,omitempty"` } func (x *FindTracesRequest) Reset() { *x = FindTracesRequest{} if protoimpl.UnsafeEnabled { mi := &file_jaeger_query_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *FindTracesRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*FindTracesRequest) ProtoMessage() {} func (x *FindTracesRequest) ProtoReflect() protoreflect.Message { mi := &file_jaeger_query_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 FindTracesRequest.ProtoReflect.Descriptor instead. func (*FindTracesRequest) Descriptor() ([]byte, []int) { return file_jaeger_query_proto_rawDescGZIP(), []int{5} } func (x *FindTracesRequest) GetQuery() *TraceQueryParameters { if x != nil { return x.Query } return nil } type GetServicesRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields } func (x *GetServicesRequest) Reset() { *x = GetServicesRequest{} if protoimpl.UnsafeEnabled { mi := &file_jaeger_query_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *GetServicesRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*GetServicesRequest) ProtoMessage() {} func (x *GetServicesRequest) ProtoReflect() protoreflect.Message { mi := &file_jaeger_query_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 GetServicesRequest.ProtoReflect.Descriptor instead. func (*GetServicesRequest) Descriptor() ([]byte, []int) { return file_jaeger_query_proto_rawDescGZIP(), []int{6} } type GetServicesResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Services []string `protobuf:"bytes,1,rep,name=services,proto3" json:"services,omitempty"` } func (x *GetServicesResponse) Reset() { *x = GetServicesResponse{} if protoimpl.UnsafeEnabled { mi := &file_jaeger_query_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *GetServicesResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*GetServicesResponse) ProtoMessage() {} func (x *GetServicesResponse) ProtoReflect() protoreflect.Message { mi := &file_jaeger_query_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 GetServicesResponse.ProtoReflect.Descriptor instead. func (*GetServicesResponse) Descriptor() ([]byte, []int) { return file_jaeger_query_proto_rawDescGZIP(), []int{7} } func (x *GetServicesResponse) GetServices() []string { if x != nil { return x.Services } return nil } type GetOperationsRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Service string `protobuf:"bytes,1,opt,name=service,proto3" json:"service,omitempty"` SpanKind string `protobuf:"bytes,2,opt,name=span_kind,json=spanKind,proto3" json:"span_kind,omitempty"` } func (x *GetOperationsRequest) Reset() { *x = GetOperationsRequest{} if protoimpl.UnsafeEnabled { mi := &file_jaeger_query_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *GetOperationsRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*GetOperationsRequest) ProtoMessage() {} func (x *GetOperationsRequest) ProtoReflect() protoreflect.Message { mi := &file_jaeger_query_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 GetOperationsRequest.ProtoReflect.Descriptor instead. func (*GetOperationsRequest) Descriptor() ([]byte, []int) { return file_jaeger_query_proto_rawDescGZIP(), []int{8} } func (x *GetOperationsRequest) GetService() string { if x != nil { return x.Service } return "" } func (x *GetOperationsRequest) GetSpanKind() string { if x != nil { return x.SpanKind } return "" } type Operation struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` SpanKind string `protobuf:"bytes,2,opt,name=span_kind,json=spanKind,proto3" json:"span_kind,omitempty"` } func (x *Operation) Reset() { *x = Operation{} if protoimpl.UnsafeEnabled { mi := &file_jaeger_query_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Operation) String() string { return protoimpl.X.MessageStringOf(x) } func (*Operation) ProtoMessage() {} func (x *Operation) ProtoReflect() protoreflect.Message { mi := &file_jaeger_query_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 Operation.ProtoReflect.Descriptor instead. func (*Operation) Descriptor() ([]byte, []int) { return file_jaeger_query_proto_rawDescGZIP(), []int{9} } func (x *Operation) GetName() string { if x != nil { return x.Name } return "" } func (x *Operation) GetSpanKind() string { if x != nil { return x.SpanKind } return "" } type GetOperationsResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields OperationNames []string `protobuf:"bytes,1,rep,name=operationNames,proto3" json:"operationNames,omitempty"` //deprecated Operations []*Operation `protobuf:"bytes,2,rep,name=operations,proto3" json:"operations,omitempty"` } func (x *GetOperationsResponse) Reset() { *x = GetOperationsResponse{} if protoimpl.UnsafeEnabled { mi := &file_jaeger_query_proto_msgTypes[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *GetOperationsResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*GetOperationsResponse) ProtoMessage() {} func (x *GetOperationsResponse) ProtoReflect() protoreflect.Message { mi := &file_jaeger_query_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 GetOperationsResponse.ProtoReflect.Descriptor instead. func (*GetOperationsResponse) Descriptor() ([]byte, []int) { return file_jaeger_query_proto_rawDescGZIP(), []int{10} } func (x *GetOperationsResponse) GetOperationNames() []string { if x != nil { return x.OperationNames } return nil } func (x *GetOperationsResponse) GetOperations() []*Operation { if x != nil { return x.Operations } return nil } type GetDependenciesRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields StartTime *timestamppb.Timestamp `protobuf:"bytes,1,opt,name=start_time,json=startTime,proto3" json:"start_time,omitempty"` EndTime *timestamppb.Timestamp `protobuf:"bytes,2,opt,name=end_time,json=endTime,proto3" json:"end_time,omitempty"` } func (x *GetDependenciesRequest) Reset() { *x = GetDependenciesRequest{} if protoimpl.UnsafeEnabled { mi := &file_jaeger_query_proto_msgTypes[11] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *GetDependenciesRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*GetDependenciesRequest) ProtoMessage() {} func (x *GetDependenciesRequest) ProtoReflect() protoreflect.Message { mi := &file_jaeger_query_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 GetDependenciesRequest.ProtoReflect.Descriptor instead. func (*GetDependenciesRequest) Descriptor() ([]byte, []int) { return file_jaeger_query_proto_rawDescGZIP(), []int{11} } func (x *GetDependenciesRequest) GetStartTime() *timestamppb.Timestamp { if x != nil { return x.StartTime } return nil } func (x *GetDependenciesRequest) GetEndTime() *timestamppb.Timestamp { if x != nil { return x.EndTime } return nil } type GetDependenciesResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Dependencies []*model.DependencyLink `protobuf:"bytes,1,rep,name=dependencies,proto3" json:"dependencies,omitempty"` } func (x *GetDependenciesResponse) Reset() { *x = GetDependenciesResponse{} if protoimpl.UnsafeEnabled { mi := &file_jaeger_query_proto_msgTypes[12] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *GetDependenciesResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*GetDependenciesResponse) ProtoMessage() {} func (x *GetDependenciesResponse) ProtoReflect() protoreflect.Message { mi := &file_jaeger_query_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 GetDependenciesResponse.ProtoReflect.Descriptor instead. func (*GetDependenciesResponse) Descriptor() ([]byte, []int) { return file_jaeger_query_proto_rawDescGZIP(), []int{12} } func (x *GetDependenciesResponse) GetDependencies() []*model.DependencyLink { if x != nil { return x.Dependencies } return nil } var File_jaeger_query_proto protoreflect.FileDescriptor var file_jaeger_query_proto_rawDesc = []byte{ 0x0a, 0x12, 0x6a, 0x61, 0x65, 0x67, 0x65, 0x72, 0x2f, 0x71, 0x75, 0x65, 0x72, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0d, 0x6a, 0x61, 0x65, 0x67, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x5f, 0x76, 0x32, 0x1a, 0x18, 0x6a, 0x61, 0x65, 0x67, 0x65, 0x72, 0x2f, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x2f, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x14, 0x67, 0x6f, 0x67, 0x6f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x67, 0x6f, 0x67, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1c, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x6e, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x54, 0x72, 0x61, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x5b, 0x0a, 0x08, 0x74, 0x72, 0x61, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x42, 0x40, 0xc8, 0xde, 0x1f, 0x00, 0xda, 0xde, 0x1f, 0x2d, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6a, 0x61, 0x65, 0x67, 0x65, 0x72, 0x74, 0x72, 0x61, 0x63, 0x69, 0x6e, 0x67, 0x2f, 0x6a, 0x61, 0x65, 0x67, 0x65, 0x72, 0x2f, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x2e, 0x54, 0x72, 0x61, 0x63, 0x65, 0x49, 0x44, 0xe2, 0xde, 0x1f, 0x07, 0x54, 0x72, 0x61, 0x63, 0x65, 0x49, 0x44, 0x52, 0x07, 0x74, 0x72, 0x61, 0x63, 0x65, 0x49, 0x64, 0x22, 0x45, 0x0a, 0x12, 0x53, 0x70, 0x61, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x43, 0x68, 0x75, 0x6e, 0x6b, 0x12, 0x2f, 0x0a, 0x05, 0x73, 0x70, 0x61, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x6a, 0x61, 0x65, 0x67, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x5f, 0x76, 0x32, 0x2e, 0x53, 0x70, 0x61, 0x6e, 0x42, 0x04, 0xc8, 0xde, 0x1f, 0x00, 0x52, 0x05, 0x73, 0x70, 0x61, 0x6e, 0x73, 0x22, 0x72, 0x0a, 0x13, 0x41, 0x72, 0x63, 0x68, 0x69, 0x76, 0x65, 0x54, 0x72, 0x61, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x5b, 0x0a, 0x08, 0x74, 0x72, 0x61, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x42, 0x40, 0xc8, 0xde, 0x1f, 0x00, 0xda, 0xde, 0x1f, 0x2d, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6a, 0x61, 0x65, 0x67, 0x65, 0x72, 0x74, 0x72, 0x61, 0x63, 0x69, 0x6e, 0x67, 0x2f, 0x6a, 0x61, 0x65, 0x67, 0x65, 0x72, 0x2f, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x2e, 0x54, 0x72, 0x61, 0x63, 0x65, 0x49, 0x44, 0xe2, 0xde, 0x1f, 0x07, 0x54, 0x72, 0x61, 0x63, 0x65, 0x49, 0x44, 0x52, 0x07, 0x74, 0x72, 0x61, 0x63, 0x65, 0x49, 0x64, 0x22, 0x16, 0x0a, 0x14, 0x41, 0x72, 0x63, 0x68, 0x69, 0x76, 0x65, 0x54, 0x72, 0x61, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xa7, 0x04, 0x0a, 0x14, 0x54, 0x72, 0x61, 0x63, 0x65, 0x51, 0x75, 0x65, 0x72, 0x79, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x41, 0x0a, 0x04, 0x74, 0x61, 0x67, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2d, 0x2e, 0x6a, 0x61, 0x65, 0x67, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x5f, 0x76, 0x32, 0x2e, 0x54, 0x72, 0x61, 0x63, 0x65, 0x51, 0x75, 0x65, 0x72, 0x79, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x73, 0x2e, 0x54, 0x61, 0x67, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x04, 0x74, 0x61, 0x67, 0x73, 0x12, 0x4a, 0x0a, 0x0e, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6d, 0x69, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x42, 0x08, 0xc8, 0xde, 0x1f, 0x00, 0x90, 0xdf, 0x1f, 0x01, 0x52, 0x0c, 0x73, 0x74, 0x61, 0x72, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x4d, 0x69, 0x6e, 0x12, 0x4a, 0x0a, 0x0e, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6d, 0x61, 0x78, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x42, 0x08, 0xc8, 0xde, 0x1f, 0x00, 0x90, 0xdf, 0x1f, 0x01, 0x52, 0x0c, 0x73, 0x74, 0x61, 0x72, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x4d, 0x61, 0x78, 0x12, 0x46, 0x0a, 0x0c, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6d, 0x69, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x08, 0xc8, 0xde, 0x1f, 0x00, 0x98, 0xdf, 0x1f, 0x01, 0x52, 0x0b, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x69, 0x6e, 0x12, 0x46, 0x0a, 0x0c, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6d, 0x61, 0x78, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x08, 0xc8, 0xde, 0x1f, 0x00, 0x98, 0xdf, 0x1f, 0x01, 0x52, 0x0b, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x61, 0x78, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x5f, 0x64, 0x65, 0x70, 0x74, 0x68, 0x18, 0x08, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0b, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x44, 0x65, 0x70, 0x74, 0x68, 0x1a, 0x37, 0x0a, 0x09, 0x54, 0x61, 0x67, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x4e, 0x0a, 0x11, 0x46, 0x69, 0x6e, 0x64, 0x54, 0x72, 0x61, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x39, 0x0a, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x6a, 0x61, 0x65, 0x67, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x5f, 0x76, 0x32, 0x2e, 0x54, 0x72, 0x61, 0x63, 0x65, 0x51, 0x75, 0x65, 0x72, 0x79, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x73, 0x52, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x22, 0x14, 0x0a, 0x12, 0x47, 0x65, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x31, 0x0a, 0x13, 0x47, 0x65, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x22, 0x4d, 0x0a, 0x14, 0x47, 0x65, 0x74, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x73, 0x70, 0x61, 0x6e, 0x5f, 0x6b, 0x69, 0x6e, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x73, 0x70, 0x61, 0x6e, 0x4b, 0x69, 0x6e, 0x64, 0x22, 0x3c, 0x0a, 0x09, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x73, 0x70, 0x61, 0x6e, 0x5f, 0x6b, 0x69, 0x6e, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x73, 0x70, 0x61, 0x6e, 0x4b, 0x69, 0x6e, 0x64, 0x22, 0x79, 0x0a, 0x15, 0x47, 0x65, 0x74, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x26, 0x0a, 0x0e, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0e, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x12, 0x38, 0x0a, 0x0a, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x6a, 0x61, 0x65, 0x67, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x5f, 0x76, 0x32, 0x2e, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0a, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x9e, 0x01, 0x0a, 0x16, 0x47, 0x65, 0x74, 0x44, 0x65, 0x70, 0x65, 0x6e, 0x64, 0x65, 0x6e, 0x63, 0x69, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x43, 0x0a, 0x0a, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x42, 0x08, 0xc8, 0xde, 0x1f, 0x00, 0x90, 0xdf, 0x1f, 0x01, 0x52, 0x09, 0x73, 0x74, 0x61, 0x72, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x3f, 0x0a, 0x08, 0x65, 0x6e, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x42, 0x08, 0xc8, 0xde, 0x1f, 0x00, 0x90, 0xdf, 0x1f, 0x01, 0x52, 0x07, 0x65, 0x6e, 0x64, 0x54, 0x69, 0x6d, 0x65, 0x22, 0x62, 0x0a, 0x17, 0x47, 0x65, 0x74, 0x44, 0x65, 0x70, 0x65, 0x6e, 0x64, 0x65, 0x6e, 0x63, 0x69, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x47, 0x0a, 0x0c, 0x64, 0x65, 0x70, 0x65, 0x6e, 0x64, 0x65, 0x6e, 0x63, 0x69, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x6a, 0x61, 0x65, 0x67, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x5f, 0x76, 0x32, 0x2e, 0x44, 0x65, 0x70, 0x65, 0x6e, 0x64, 0x65, 0x6e, 0x63, 0x79, 0x4c, 0x69, 0x6e, 0x6b, 0x42, 0x04, 0xc8, 0xde, 0x1f, 0x00, 0x52, 0x0c, 0x64, 0x65, 0x70, 0x65, 0x6e, 0x64, 0x65, 0x6e, 0x63, 0x69, 0x65, 0x73, 0x32, 0xad, 0x05, 0x0a, 0x0c, 0x51, 0x75, 0x65, 0x72, 0x79, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x6b, 0x0a, 0x08, 0x47, 0x65, 0x74, 0x54, 0x72, 0x61, 0x63, 0x65, 0x12, 0x1e, 0x2e, 0x6a, 0x61, 0x65, 0x67, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x5f, 0x76, 0x32, 0x2e, 0x47, 0x65, 0x74, 0x54, 0x72, 0x61, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x6a, 0x61, 0x65, 0x67, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x5f, 0x76, 0x32, 0x2e, 0x53, 0x70, 0x61, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x43, 0x68, 0x75, 0x6e, 0x6b, 0x22, 0x1a, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x14, 0x12, 0x12, 0x2f, 0x74, 0x72, 0x61, 0x63, 0x65, 0x73, 0x2f, 0x7b, 0x74, 0x72, 0x61, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x7d, 0x30, 0x01, 0x12, 0x74, 0x0a, 0x0c, 0x41, 0x72, 0x63, 0x68, 0x69, 0x76, 0x65, 0x54, 0x72, 0x61, 0x63, 0x65, 0x12, 0x22, 0x2e, 0x6a, 0x61, 0x65, 0x67, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x5f, 0x76, 0x32, 0x2e, 0x41, 0x72, 0x63, 0x68, 0x69, 0x76, 0x65, 0x54, 0x72, 0x61, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x6a, 0x61, 0x65, 0x67, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x5f, 0x76, 0x32, 0x2e, 0x41, 0x72, 0x63, 0x68, 0x69, 0x76, 0x65, 0x54, 0x72, 0x61, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1b, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x15, 0x22, 0x13, 0x2f, 0x61, 0x72, 0x63, 0x68, 0x69, 0x76, 0x65, 0x2f, 0x7b, 0x74, 0x72, 0x61, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x7d, 0x12, 0x67, 0x0a, 0x0a, 0x46, 0x69, 0x6e, 0x64, 0x54, 0x72, 0x61, 0x63, 0x65, 0x73, 0x12, 0x20, 0x2e, 0x6a, 0x61, 0x65, 0x67, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x5f, 0x76, 0x32, 0x2e, 0x46, 0x69, 0x6e, 0x64, 0x54, 0x72, 0x61, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x6a, 0x61, 0x65, 0x67, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x5f, 0x76, 0x32, 0x2e, 0x53, 0x70, 0x61, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x43, 0x68, 0x75, 0x6e, 0x6b, 0x22, 0x12, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x0c, 0x22, 0x07, 0x2f, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x3a, 0x01, 0x2a, 0x30, 0x01, 0x12, 0x67, 0x0a, 0x0b, 0x47, 0x65, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x12, 0x21, 0x2e, 0x6a, 0x61, 0x65, 0x67, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x5f, 0x76, 0x32, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x6a, 0x61, 0x65, 0x67, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x5f, 0x76, 0x32, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x11, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x0b, 0x12, 0x09, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x12, 0x6f, 0x0a, 0x0d, 0x47, 0x65, 0x74, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x23, 0x2e, 0x6a, 0x61, 0x65, 0x67, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x5f, 0x76, 0x32, 0x2e, 0x47, 0x65, 0x74, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x6a, 0x61, 0x65, 0x67, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x5f, 0x76, 0x32, 0x2e, 0x47, 0x65, 0x74, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x13, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x0d, 0x12, 0x0b, 0x2f, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x77, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x44, 0x65, 0x70, 0x65, 0x6e, 0x64, 0x65, 0x6e, 0x63, 0x69, 0x65, 0x73, 0x12, 0x25, 0x2e, 0x6a, 0x61, 0x65, 0x67, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x5f, 0x76, 0x32, 0x2e, 0x47, 0x65, 0x74, 0x44, 0x65, 0x70, 0x65, 0x6e, 0x64, 0x65, 0x6e, 0x63, 0x69, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x6a, 0x61, 0x65, 0x67, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x5f, 0x76, 0x32, 0x2e, 0x47, 0x65, 0x74, 0x44, 0x65, 0x70, 0x65, 0x6e, 0x64, 0x65, 0x6e, 0x63, 0x69, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x15, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x0f, 0x12, 0x0d, 0x2f, 0x64, 0x65, 0x70, 0x65, 0x6e, 0x64, 0x65, 0x6e, 0x63, 0x69, 0x65, 0x73, 0x42, 0x6b, 0x0a, 0x17, 0x69, 0x6f, 0x2e, 0x6a, 0x61, 0x65, 0x67, 0x65, 0x72, 0x74, 0x72, 0x61, 0x63, 0x69, 0x6e, 0x67, 0x2e, 0x61, 0x70, 0x69, 0x5f, 0x76, 0x32, 0x5a, 0x44, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x50, 0x61, 0x63, 0x6b, 0x74, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x69, 0x6e, 0x67, 0x2f, 0x47, 0x6f, 0x2d, 0x66, 0x6f, 0x72, 0x2d, 0x44, 0x65, 0x76, 0x4f, 0x70, 0x73, 0x2f, 0x63, 0x68, 0x61, 0x70, 0x74, 0x65, 0x72, 0x2f, 0x31, 0x30, 0x2f, 0x6f, 0x70, 0x73, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x6a, 0x61, 0x65, 0x67, 0x65, 0x72, 0xc8, 0xe2, 0x1e, 0x01, 0xd0, 0xe2, 0x1e, 0x01, 0xe0, 0xe2, 0x1e, 0x01, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( file_jaeger_query_proto_rawDescOnce sync.Once file_jaeger_query_proto_rawDescData = file_jaeger_query_proto_rawDesc ) func file_jaeger_query_proto_rawDescGZIP() []byte { file_jaeger_query_proto_rawDescOnce.Do(func() { file_jaeger_query_proto_rawDescData = protoimpl.X.CompressGZIP(file_jaeger_query_proto_rawDescData) }) return file_jaeger_query_proto_rawDescData } var file_jaeger_query_proto_msgTypes = make([]protoimpl.MessageInfo, 14) var file_jaeger_query_proto_goTypes = []interface{}{ (*GetTraceRequest)(nil), // 0: jaeger.api_v2.GetTraceRequest (*SpansResponseChunk)(nil), // 1: jaeger.api_v2.SpansResponseChunk (*ArchiveTraceRequest)(nil), // 2: jaeger.api_v2.ArchiveTraceRequest (*ArchiveTraceResponse)(nil), // 3: jaeger.api_v2.ArchiveTraceResponse (*TraceQueryParameters)(nil), // 4: jaeger.api_v2.TraceQueryParameters (*FindTracesRequest)(nil), // 5: jaeger.api_v2.FindTracesRequest (*GetServicesRequest)(nil), // 6: jaeger.api_v2.GetServicesRequest (*GetServicesResponse)(nil), // 7: jaeger.api_v2.GetServicesResponse (*GetOperationsRequest)(nil), // 8: jaeger.api_v2.GetOperationsRequest (*Operation)(nil), // 9: jaeger.api_v2.Operation (*GetOperationsResponse)(nil), // 10: jaeger.api_v2.GetOperationsResponse (*GetDependenciesRequest)(nil), // 11: jaeger.api_v2.GetDependenciesRequest (*GetDependenciesResponse)(nil), // 12: jaeger.api_v2.GetDependenciesResponse nil, // 13: jaeger.api_v2.TraceQueryParameters.TagsEntry (*model.Span)(nil), // 14: jaeger.api_v2.Span (*timestamppb.Timestamp)(nil), // 15: google.protobuf.Timestamp (*durationpb.Duration)(nil), // 16: google.protobuf.Duration (*model.DependencyLink)(nil), // 17: jaeger.api_v2.DependencyLink } var file_jaeger_query_proto_depIdxs = []int32{ 14, // 0: jaeger.api_v2.SpansResponseChunk.spans:type_name -> jaeger.api_v2.Span 13, // 1: jaeger.api_v2.TraceQueryParameters.tags:type_name -> jaeger.api_v2.TraceQueryParameters.TagsEntry 15, // 2: jaeger.api_v2.TraceQueryParameters.start_time_min:type_name -> google.protobuf.Timestamp 15, // 3: jaeger.api_v2.TraceQueryParameters.start_time_max:type_name -> google.protobuf.Timestamp 16, // 4: jaeger.api_v2.TraceQueryParameters.duration_min:type_name -> google.protobuf.Duration 16, // 5: jaeger.api_v2.TraceQueryParameters.duration_max:type_name -> google.protobuf.Duration 4, // 6: jaeger.api_v2.FindTracesRequest.query:type_name -> jaeger.api_v2.TraceQueryParameters 9, // 7: jaeger.api_v2.GetOperationsResponse.operations:type_name -> jaeger.api_v2.Operation 15, // 8: jaeger.api_v2.GetDependenciesRequest.start_time:type_name -> google.protobuf.Timestamp 15, // 9: jaeger.api_v2.GetDependenciesRequest.end_time:type_name -> google.protobuf.Timestamp 17, // 10: jaeger.api_v2.GetDependenciesResponse.dependencies:type_name -> jaeger.api_v2.DependencyLink 0, // 11: jaeger.api_v2.QueryService.GetTrace:input_type -> jaeger.api_v2.GetTraceRequest 2, // 12: jaeger.api_v2.QueryService.ArchiveTrace:input_type -> jaeger.api_v2.ArchiveTraceRequest 5, // 13: jaeger.api_v2.QueryService.FindTraces:input_type -> jaeger.api_v2.FindTracesRequest 6, // 14: jaeger.api_v2.QueryService.GetServices:input_type -> jaeger.api_v2.GetServicesRequest 8, // 15: jaeger.api_v2.QueryService.GetOperations:input_type -> jaeger.api_v2.GetOperationsRequest 11, // 16: jaeger.api_v2.QueryService.GetDependencies:input_type -> jaeger.api_v2.GetDependenciesRequest 1, // 17: jaeger.api_v2.QueryService.GetTrace:output_type -> jaeger.api_v2.SpansResponseChunk 3, // 18: jaeger.api_v2.QueryService.ArchiveTrace:output_type -> jaeger.api_v2.ArchiveTraceResponse 1, // 19: jaeger.api_v2.QueryService.FindTraces:output_type -> jaeger.api_v2.SpansResponseChunk 7, // 20: jaeger.api_v2.QueryService.GetServices:output_type -> jaeger.api_v2.GetServicesResponse 10, // 21: jaeger.api_v2.QueryService.GetOperations:output_type -> jaeger.api_v2.GetOperationsResponse 12, // 22: jaeger.api_v2.QueryService.GetDependencies:output_type -> jaeger.api_v2.GetDependenciesResponse 17, // [17:23] is the sub-list for method output_type 11, // [11:17] is the sub-list for method input_type 11, // [11:11] is the sub-list for extension type_name 11, // [11:11] is the sub-list for extension extendee 0, // [0:11] is the sub-list for field type_name } func init() { file_jaeger_query_proto_init() } func file_jaeger_query_proto_init() { if File_jaeger_query_proto != nil { return } if !protoimpl.UnsafeEnabled { file_jaeger_query_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*GetTraceRequest); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_jaeger_query_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*SpansResponseChunk); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_jaeger_query_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*ArchiveTraceRequest); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_jaeger_query_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*ArchiveTraceResponse); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_jaeger_query_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*TraceQueryParameters); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_jaeger_query_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*FindTracesRequest); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_jaeger_query_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*GetServicesRequest); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_jaeger_query_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*GetServicesResponse); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_jaeger_query_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*GetOperationsRequest); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_jaeger_query_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Operation); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_jaeger_query_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*GetOperationsResponse); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_jaeger_query_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*GetDependenciesRequest); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_jaeger_query_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*GetDependenciesResponse); 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_jaeger_query_proto_rawDesc, NumEnums: 0, NumMessages: 14, NumExtensions: 0, NumServices: 1, }, GoTypes: file_jaeger_query_proto_goTypes, DependencyIndexes: file_jaeger_query_proto_depIdxs, MessageInfos: file_jaeger_query_proto_msgTypes, }.Build() File_jaeger_query_proto = out.File file_jaeger_query_proto_rawDesc = nil file_jaeger_query_proto_goTypes = nil file_jaeger_query_proto_depIdxs = nil } ================================================ FILE: chapter/11/ops/proto/jaeger/query.proto ================================================ // Copyright (c) 2019 The Jaeger Authors. // Copyright (c) 2018 Uber Technologies, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. syntax="proto3"; package jaeger.api_v2; import "jaeger/model/model.proto"; import "gogoproto/gogo.proto"; import "google/api/annotations.proto"; import "google/protobuf/timestamp.proto"; import "google/protobuf/duration.proto"; option go_package = "github.com/PacktPublishing/Go-for-DevOps/chapter/10/ops/proto/jaeger"; option java_package = "io.jaegertracing.api_v2"; // Enable gogoprotobuf extensions (https://github.com/gogo/protobuf/blob/master/extensions.md). // Enable custom Marshal method. option (gogoproto.marshaler_all) = true; // Enable custom Unmarshal method. option (gogoproto.unmarshaler_all) = true; // Enable custom Size method (Required by Marshal and Unmarshal). option (gogoproto.sizer_all) = true; message GetTraceRequest { bytes trace_id = 1 [ (gogoproto.nullable) = false, (gogoproto.customtype) = "github.com/jaegertracing/jaeger/model.TraceID", (gogoproto.customname) = "TraceID" ]; } message SpansResponseChunk { repeated jaeger.api_v2.Span spans = 1 [ (gogoproto.nullable) = false ]; } message ArchiveTraceRequest { bytes trace_id = 1 [ (gogoproto.nullable) = false, (gogoproto.customtype) = "github.com/jaegertracing/jaeger/model.TraceID", (gogoproto.customname) = "TraceID" ]; } message ArchiveTraceResponse { } message TraceQueryParameters { string service_name = 1; string operation_name = 2; map tags = 3; google.protobuf.Timestamp start_time_min = 4 [ (gogoproto.stdtime) = true, (gogoproto.nullable) = false ]; google.protobuf.Timestamp start_time_max = 5 [ (gogoproto.stdtime) = true, (gogoproto.nullable) = false ]; google.protobuf.Duration duration_min = 6 [ (gogoproto.stdduration) = true, (gogoproto.nullable) = false ]; google.protobuf.Duration duration_max = 7 [ (gogoproto.stdduration) = true, (gogoproto.nullable) = false ]; int32 search_depth = 8; } message FindTracesRequest { TraceQueryParameters query = 1; } message GetServicesRequest {} message GetServicesResponse { repeated string services = 1; } message GetOperationsRequest { string service = 1; string span_kind = 2; } message Operation { string name = 1; string span_kind = 2; } message GetOperationsResponse { repeated string operationNames = 1; //deprecated repeated Operation operations = 2; } message GetDependenciesRequest { google.protobuf.Timestamp start_time = 1 [ (gogoproto.stdtime) = true, (gogoproto.nullable) = false ]; google.protobuf.Timestamp end_time = 2 [ (gogoproto.stdtime) = true, (gogoproto.nullable) = false ]; } message GetDependenciesResponse { repeated jaeger.api_v2.DependencyLink dependencies = 1 [ (gogoproto.nullable) = false ]; } service QueryService { rpc GetTrace(GetTraceRequest) returns (stream SpansResponseChunk) { option (google.api.http) = { get: "/traces/{trace_id}" }; } rpc ArchiveTrace(ArchiveTraceRequest) returns (ArchiveTraceResponse) { option (google.api.http) = { post: "/archive/{trace_id}" }; } rpc FindTraces(FindTracesRequest) returns (stream SpansResponseChunk) { option (google.api.http) = { post: "/search" body: "*" }; } rpc GetServices(GetServicesRequest) returns (GetServicesResponse) { option (google.api.http) = { get: "/services" }; } rpc GetOperations(GetOperationsRequest) returns (GetOperationsResponse) { option (google.api.http) = { get: "/operations" }; } rpc GetDependencies(GetDependenciesRequest) returns (GetDependenciesResponse) { option (google.api.http) = { get: "/dependencies" }; } } ================================================ FILE: chapter/11/ops/proto/jaeger/query_grpc.pb.go ================================================ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. package jaeger 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.32.0 or later. const _ = grpc.SupportPackageIsVersion7 // QueryServiceClient is the client API for QueryService 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 QueryServiceClient interface { GetTrace(ctx context.Context, in *GetTraceRequest, opts ...grpc.CallOption) (QueryService_GetTraceClient, error) ArchiveTrace(ctx context.Context, in *ArchiveTraceRequest, opts ...grpc.CallOption) (*ArchiveTraceResponse, error) FindTraces(ctx context.Context, in *FindTracesRequest, opts ...grpc.CallOption) (QueryService_FindTracesClient, error) GetServices(ctx context.Context, in *GetServicesRequest, opts ...grpc.CallOption) (*GetServicesResponse, error) GetOperations(ctx context.Context, in *GetOperationsRequest, opts ...grpc.CallOption) (*GetOperationsResponse, error) GetDependencies(ctx context.Context, in *GetDependenciesRequest, opts ...grpc.CallOption) (*GetDependenciesResponse, error) } type queryServiceClient struct { cc grpc.ClientConnInterface } func NewQueryServiceClient(cc grpc.ClientConnInterface) QueryServiceClient { return &queryServiceClient{cc} } func (c *queryServiceClient) GetTrace(ctx context.Context, in *GetTraceRequest, opts ...grpc.CallOption) (QueryService_GetTraceClient, error) { stream, err := c.cc.NewStream(ctx, &QueryService_ServiceDesc.Streams[0], "/jaeger.api_v2.QueryService/GetTrace", opts...) if err != nil { return nil, err } x := &queryServiceGetTraceClient{stream} if err := x.ClientStream.SendMsg(in); err != nil { return nil, err } if err := x.ClientStream.CloseSend(); err != nil { return nil, err } return x, nil } type QueryService_GetTraceClient interface { Recv() (*SpansResponseChunk, error) grpc.ClientStream } type queryServiceGetTraceClient struct { grpc.ClientStream } func (x *queryServiceGetTraceClient) Recv() (*SpansResponseChunk, error) { m := new(SpansResponseChunk) if err := x.ClientStream.RecvMsg(m); err != nil { return nil, err } return m, nil } func (c *queryServiceClient) ArchiveTrace(ctx context.Context, in *ArchiveTraceRequest, opts ...grpc.CallOption) (*ArchiveTraceResponse, error) { out := new(ArchiveTraceResponse) err := c.cc.Invoke(ctx, "/jaeger.api_v2.QueryService/ArchiveTrace", in, out, opts...) if err != nil { return nil, err } return out, nil } func (c *queryServiceClient) FindTraces(ctx context.Context, in *FindTracesRequest, opts ...grpc.CallOption) (QueryService_FindTracesClient, error) { stream, err := c.cc.NewStream(ctx, &QueryService_ServiceDesc.Streams[1], "/jaeger.api_v2.QueryService/FindTraces", opts...) if err != nil { return nil, err } x := &queryServiceFindTracesClient{stream} if err := x.ClientStream.SendMsg(in); err != nil { return nil, err } if err := x.ClientStream.CloseSend(); err != nil { return nil, err } return x, nil } type QueryService_FindTracesClient interface { Recv() (*SpansResponseChunk, error) grpc.ClientStream } type queryServiceFindTracesClient struct { grpc.ClientStream } func (x *queryServiceFindTracesClient) Recv() (*SpansResponseChunk, error) { m := new(SpansResponseChunk) if err := x.ClientStream.RecvMsg(m); err != nil { return nil, err } return m, nil } func (c *queryServiceClient) GetServices(ctx context.Context, in *GetServicesRequest, opts ...grpc.CallOption) (*GetServicesResponse, error) { out := new(GetServicesResponse) err := c.cc.Invoke(ctx, "/jaeger.api_v2.QueryService/GetServices", in, out, opts...) if err != nil { return nil, err } return out, nil } func (c *queryServiceClient) GetOperations(ctx context.Context, in *GetOperationsRequest, opts ...grpc.CallOption) (*GetOperationsResponse, error) { out := new(GetOperationsResponse) err := c.cc.Invoke(ctx, "/jaeger.api_v2.QueryService/GetOperations", in, out, opts...) if err != nil { return nil, err } return out, nil } func (c *queryServiceClient) GetDependencies(ctx context.Context, in *GetDependenciesRequest, opts ...grpc.CallOption) (*GetDependenciesResponse, error) { out := new(GetDependenciesResponse) err := c.cc.Invoke(ctx, "/jaeger.api_v2.QueryService/GetDependencies", in, out, opts...) if err != nil { return nil, err } return out, nil } // QueryServiceServer is the server API for QueryService service. // All implementations must embed UnimplementedQueryServiceServer // for forward compatibility type QueryServiceServer interface { GetTrace(*GetTraceRequest, QueryService_GetTraceServer) error ArchiveTrace(context.Context, *ArchiveTraceRequest) (*ArchiveTraceResponse, error) FindTraces(*FindTracesRequest, QueryService_FindTracesServer) error GetServices(context.Context, *GetServicesRequest) (*GetServicesResponse, error) GetOperations(context.Context, *GetOperationsRequest) (*GetOperationsResponse, error) GetDependencies(context.Context, *GetDependenciesRequest) (*GetDependenciesResponse, error) mustEmbedUnimplementedQueryServiceServer() } // UnimplementedQueryServiceServer must be embedded to have forward compatible implementations. type UnimplementedQueryServiceServer struct { } func (UnimplementedQueryServiceServer) GetTrace(*GetTraceRequest, QueryService_GetTraceServer) error { return status.Errorf(codes.Unimplemented, "method GetTrace not implemented") } func (UnimplementedQueryServiceServer) ArchiveTrace(context.Context, *ArchiveTraceRequest) (*ArchiveTraceResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method ArchiveTrace not implemented") } func (UnimplementedQueryServiceServer) FindTraces(*FindTracesRequest, QueryService_FindTracesServer) error { return status.Errorf(codes.Unimplemented, "method FindTraces not implemented") } func (UnimplementedQueryServiceServer) GetServices(context.Context, *GetServicesRequest) (*GetServicesResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method GetServices not implemented") } func (UnimplementedQueryServiceServer) GetOperations(context.Context, *GetOperationsRequest) (*GetOperationsResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method GetOperations not implemented") } func (UnimplementedQueryServiceServer) GetDependencies(context.Context, *GetDependenciesRequest) (*GetDependenciesResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method GetDependencies not implemented") } func (UnimplementedQueryServiceServer) mustEmbedUnimplementedQueryServiceServer() {} // UnsafeQueryServiceServer may be embedded to opt out of forward compatibility for this service. // Use of this interface is not recommended, as added methods to QueryServiceServer will // result in compilation errors. type UnsafeQueryServiceServer interface { mustEmbedUnimplementedQueryServiceServer() } func RegisterQueryServiceServer(s grpc.ServiceRegistrar, srv QueryServiceServer) { s.RegisterService(&QueryService_ServiceDesc, srv) } func _QueryService_GetTrace_Handler(srv interface{}, stream grpc.ServerStream) error { m := new(GetTraceRequest) if err := stream.RecvMsg(m); err != nil { return err } return srv.(QueryServiceServer).GetTrace(m, &queryServiceGetTraceServer{stream}) } type QueryService_GetTraceServer interface { Send(*SpansResponseChunk) error grpc.ServerStream } type queryServiceGetTraceServer struct { grpc.ServerStream } func (x *queryServiceGetTraceServer) Send(m *SpansResponseChunk) error { return x.ServerStream.SendMsg(m) } func _QueryService_ArchiveTrace_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(ArchiveTraceRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(QueryServiceServer).ArchiveTrace(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: "/jaeger.api_v2.QueryService/ArchiveTrace", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(QueryServiceServer).ArchiveTrace(ctx, req.(*ArchiveTraceRequest)) } return interceptor(ctx, in, info, handler) } func _QueryService_FindTraces_Handler(srv interface{}, stream grpc.ServerStream) error { m := new(FindTracesRequest) if err := stream.RecvMsg(m); err != nil { return err } return srv.(QueryServiceServer).FindTraces(m, &queryServiceFindTracesServer{stream}) } type QueryService_FindTracesServer interface { Send(*SpansResponseChunk) error grpc.ServerStream } type queryServiceFindTracesServer struct { grpc.ServerStream } func (x *queryServiceFindTracesServer) Send(m *SpansResponseChunk) error { return x.ServerStream.SendMsg(m) } func _QueryService_GetServices_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(GetServicesRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(QueryServiceServer).GetServices(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: "/jaeger.api_v2.QueryService/GetServices", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(QueryServiceServer).GetServices(ctx, req.(*GetServicesRequest)) } return interceptor(ctx, in, info, handler) } func _QueryService_GetOperations_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(GetOperationsRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(QueryServiceServer).GetOperations(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: "/jaeger.api_v2.QueryService/GetOperations", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(QueryServiceServer).GetOperations(ctx, req.(*GetOperationsRequest)) } return interceptor(ctx, in, info, handler) } func _QueryService_GetDependencies_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(GetDependenciesRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(QueryServiceServer).GetDependencies(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: "/jaeger.api_v2.QueryService/GetDependencies", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(QueryServiceServer).GetDependencies(ctx, req.(*GetDependenciesRequest)) } return interceptor(ctx, in, info, handler) } // QueryService_ServiceDesc is the grpc.ServiceDesc for QueryService service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) var QueryService_ServiceDesc = grpc.ServiceDesc{ ServiceName: "jaeger.api_v2.QueryService", HandlerType: (*QueryServiceServer)(nil), Methods: []grpc.MethodDesc{ { MethodName: "ArchiveTrace", Handler: _QueryService_ArchiveTrace_Handler, }, { MethodName: "GetServices", Handler: _QueryService_GetServices_Handler, }, { MethodName: "GetOperations", Handler: _QueryService_GetOperations_Handler, }, { MethodName: "GetDependencies", Handler: _QueryService_GetDependencies_Handler, }, }, Streams: []grpc.StreamDesc{ { StreamName: "GetTrace", Handler: _QueryService_GetTrace_Handler, ServerStreams: true, }, { StreamName: "FindTraces", Handler: _QueryService_FindTraces_Handler, ServerStreams: true, }, }, Metadata: "jaeger/query.proto", } ================================================ FILE: chapter/11/ops/proto/jaeger/sampling.pb.go ================================================ // Copyright (c) 2019 The Jaeger Authors. // Copyright (c) 2018 Uber Technologies, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.25.0-devel // protoc v3.18.0 // source: jaeger/sampling.proto package jaeger import ( _ "github.com/gogo/protobuf/gogoproto" _ "google.golang.org/genproto/googleapis/api/annotations" 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) ) // See description of the SamplingStrategyResponse.strategyType field. type SamplingStrategyType int32 const ( SamplingStrategyType_PROBABILISTIC SamplingStrategyType = 0 SamplingStrategyType_RATE_LIMITING SamplingStrategyType = 1 ) // Enum value maps for SamplingStrategyType. var ( SamplingStrategyType_name = map[int32]string{ 0: "PROBABILISTIC", 1: "RATE_LIMITING", } SamplingStrategyType_value = map[string]int32{ "PROBABILISTIC": 0, "RATE_LIMITING": 1, } ) func (x SamplingStrategyType) Enum() *SamplingStrategyType { p := new(SamplingStrategyType) *p = x return p } func (x SamplingStrategyType) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } func (SamplingStrategyType) Descriptor() protoreflect.EnumDescriptor { return file_jaeger_sampling_proto_enumTypes[0].Descriptor() } func (SamplingStrategyType) Type() protoreflect.EnumType { return &file_jaeger_sampling_proto_enumTypes[0] } func (x SamplingStrategyType) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } // Deprecated: Use SamplingStrategyType.Descriptor instead. func (SamplingStrategyType) EnumDescriptor() ([]byte, []int) { return file_jaeger_sampling_proto_rawDescGZIP(), []int{0} } // ProbabilisticSamplingStrategy samples traces with a fixed probability. type ProbabilisticSamplingStrategy struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // samplingRate is the sampling probability in the range [0.0, 1.0]. SamplingRate float64 `protobuf:"fixed64,1,opt,name=samplingRate,proto3" json:"samplingRate,omitempty"` } func (x *ProbabilisticSamplingStrategy) Reset() { *x = ProbabilisticSamplingStrategy{} if protoimpl.UnsafeEnabled { mi := &file_jaeger_sampling_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *ProbabilisticSamplingStrategy) String() string { return protoimpl.X.MessageStringOf(x) } func (*ProbabilisticSamplingStrategy) ProtoMessage() {} func (x *ProbabilisticSamplingStrategy) ProtoReflect() protoreflect.Message { mi := &file_jaeger_sampling_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 ProbabilisticSamplingStrategy.ProtoReflect.Descriptor instead. func (*ProbabilisticSamplingStrategy) Descriptor() ([]byte, []int) { return file_jaeger_sampling_proto_rawDescGZIP(), []int{0} } func (x *ProbabilisticSamplingStrategy) GetSamplingRate() float64 { if x != nil { return x.SamplingRate } return 0 } // RateLimitingSamplingStrategy samples a fixed number of traces per time interval. // The typical implementations use the leaky bucket algorithm. type RateLimitingSamplingStrategy struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // TODO this field type should be changed to double, to support rates like 1 per minute. MaxTracesPerSecond int32 `protobuf:"varint,1,opt,name=maxTracesPerSecond,proto3" json:"maxTracesPerSecond,omitempty"` } func (x *RateLimitingSamplingStrategy) Reset() { *x = RateLimitingSamplingStrategy{} if protoimpl.UnsafeEnabled { mi := &file_jaeger_sampling_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *RateLimitingSamplingStrategy) String() string { return protoimpl.X.MessageStringOf(x) } func (*RateLimitingSamplingStrategy) ProtoMessage() {} func (x *RateLimitingSamplingStrategy) ProtoReflect() protoreflect.Message { mi := &file_jaeger_sampling_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 RateLimitingSamplingStrategy.ProtoReflect.Descriptor instead. func (*RateLimitingSamplingStrategy) Descriptor() ([]byte, []int) { return file_jaeger_sampling_proto_rawDescGZIP(), []int{1} } func (x *RateLimitingSamplingStrategy) GetMaxTracesPerSecond() int32 { if x != nil { return x.MaxTracesPerSecond } return 0 } // OperationSamplingStrategy is a sampling strategy for a given operation // (aka endpoint, span name). Only probabilistic sampling is currently supported. type OperationSamplingStrategy struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Operation string `protobuf:"bytes,1,opt,name=operation,proto3" json:"operation,omitempty"` ProbabilisticSampling *ProbabilisticSamplingStrategy `protobuf:"bytes,2,opt,name=probabilisticSampling,proto3" json:"probabilisticSampling,omitempty"` } func (x *OperationSamplingStrategy) Reset() { *x = OperationSamplingStrategy{} if protoimpl.UnsafeEnabled { mi := &file_jaeger_sampling_proto_msgTypes[2] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *OperationSamplingStrategy) String() string { return protoimpl.X.MessageStringOf(x) } func (*OperationSamplingStrategy) ProtoMessage() {} func (x *OperationSamplingStrategy) ProtoReflect() protoreflect.Message { mi := &file_jaeger_sampling_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 OperationSamplingStrategy.ProtoReflect.Descriptor instead. func (*OperationSamplingStrategy) Descriptor() ([]byte, []int) { return file_jaeger_sampling_proto_rawDescGZIP(), []int{2} } func (x *OperationSamplingStrategy) GetOperation() string { if x != nil { return x.Operation } return "" } func (x *OperationSamplingStrategy) GetProbabilisticSampling() *ProbabilisticSamplingStrategy { if x != nil { return x.ProbabilisticSampling } return nil } // PerOperationSamplingStrategies is a combination of strategies for different endpoints // as well as some service-wide defaults. It is particularly useful for services whose // endpoints receive vastly different traffic, so that any single rate of sampling would // result in either too much data for some endpoints or almost no data for other endpoints. type PerOperationSamplingStrategies struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // defaultSamplingProbability is the sampling probability for spans that do not match // any of the perOperationStrategies. DefaultSamplingProbability float64 `protobuf:"fixed64,1,opt,name=defaultSamplingProbability,proto3" json:"defaultSamplingProbability,omitempty"` // defaultLowerBoundTracesPerSecond defines a lower-bound rate limit used to ensure that // there is some minimal amount of traces sampled for an endpoint that might otherwise // be never sampled via probabilistic strategies. The limit is local to a service instance, // so if a service is deployed with many (N) instances, the effective minimum rate of sampling // will be N times higher. This setting applies to ALL operations, whether or not they match // one of the perOperationStrategies. DefaultLowerBoundTracesPerSecond float64 `protobuf:"fixed64,2,opt,name=defaultLowerBoundTracesPerSecond,proto3" json:"defaultLowerBoundTracesPerSecond,omitempty"` // perOperationStrategies describes sampling strategiesf for individual operations within // a given service. PerOperationStrategies []*OperationSamplingStrategy `protobuf:"bytes,3,rep,name=perOperationStrategies,proto3" json:"perOperationStrategies,omitempty"` // defaultUpperBoundTracesPerSecond defines an upper bound rate limit. // However, almost no Jaeger SDKs support this parameter. DefaultUpperBoundTracesPerSecond float64 `protobuf:"fixed64,4,opt,name=defaultUpperBoundTracesPerSecond,proto3" json:"defaultUpperBoundTracesPerSecond,omitempty"` } func (x *PerOperationSamplingStrategies) Reset() { *x = PerOperationSamplingStrategies{} if protoimpl.UnsafeEnabled { mi := &file_jaeger_sampling_proto_msgTypes[3] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *PerOperationSamplingStrategies) String() string { return protoimpl.X.MessageStringOf(x) } func (*PerOperationSamplingStrategies) ProtoMessage() {} func (x *PerOperationSamplingStrategies) ProtoReflect() protoreflect.Message { mi := &file_jaeger_sampling_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 PerOperationSamplingStrategies.ProtoReflect.Descriptor instead. func (*PerOperationSamplingStrategies) Descriptor() ([]byte, []int) { return file_jaeger_sampling_proto_rawDescGZIP(), []int{3} } func (x *PerOperationSamplingStrategies) GetDefaultSamplingProbability() float64 { if x != nil { return x.DefaultSamplingProbability } return 0 } func (x *PerOperationSamplingStrategies) GetDefaultLowerBoundTracesPerSecond() float64 { if x != nil { return x.DefaultLowerBoundTracesPerSecond } return 0 } func (x *PerOperationSamplingStrategies) GetPerOperationStrategies() []*OperationSamplingStrategy { if x != nil { return x.PerOperationStrategies } return nil } func (x *PerOperationSamplingStrategies) GetDefaultUpperBoundTracesPerSecond() float64 { if x != nil { return x.DefaultUpperBoundTracesPerSecond } return 0 } // SamplingStrategyResponse contains an overall sampling strategy for a given service. // This type should be treated as a union where only one of the strategy field is present. type SamplingStrategyResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // Legacy field that was meant to indicate which one of the strategy fields // below is present. This enum was not extended when per-operation strategy // was introduced, because extending enum has backwards compatiblity issues. // The recommended approach for consumers is to ignore this field and instead // checks the other fields being not null (starting with operationSampling). // For producers, it is recommended to set this field correctly for probabilistic // and rate-limiting strategies, but if per-operation strategy is returned, // the enum can be set to 0 (probabilistic). StrategyType SamplingStrategyType `protobuf:"varint,1,opt,name=strategyType,proto3,enum=jaeger.api_v2.SamplingStrategyType" json:"strategyType,omitempty"` ProbabilisticSampling *ProbabilisticSamplingStrategy `protobuf:"bytes,2,opt,name=probabilisticSampling,proto3" json:"probabilisticSampling,omitempty"` RateLimitingSampling *RateLimitingSamplingStrategy `protobuf:"bytes,3,opt,name=rateLimitingSampling,proto3" json:"rateLimitingSampling,omitempty"` OperationSampling *PerOperationSamplingStrategies `protobuf:"bytes,4,opt,name=operationSampling,proto3" json:"operationSampling,omitempty"` } func (x *SamplingStrategyResponse) Reset() { *x = SamplingStrategyResponse{} if protoimpl.UnsafeEnabled { mi := &file_jaeger_sampling_proto_msgTypes[4] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *SamplingStrategyResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*SamplingStrategyResponse) ProtoMessage() {} func (x *SamplingStrategyResponse) ProtoReflect() protoreflect.Message { mi := &file_jaeger_sampling_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 SamplingStrategyResponse.ProtoReflect.Descriptor instead. func (*SamplingStrategyResponse) Descriptor() ([]byte, []int) { return file_jaeger_sampling_proto_rawDescGZIP(), []int{4} } func (x *SamplingStrategyResponse) GetStrategyType() SamplingStrategyType { if x != nil { return x.StrategyType } return SamplingStrategyType_PROBABILISTIC } func (x *SamplingStrategyResponse) GetProbabilisticSampling() *ProbabilisticSamplingStrategy { if x != nil { return x.ProbabilisticSampling } return nil } func (x *SamplingStrategyResponse) GetRateLimitingSampling() *RateLimitingSamplingStrategy { if x != nil { return x.RateLimitingSampling } return nil } func (x *SamplingStrategyResponse) GetOperationSampling() *PerOperationSamplingStrategies { if x != nil { return x.OperationSampling } return nil } // SamplingStrategyParameters defines request parameters for remote sampler. type SamplingStrategyParameters struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // serviceName is a required argument. ServiceName string `protobuf:"bytes,1,opt,name=serviceName,proto3" json:"serviceName,omitempty"` } func (x *SamplingStrategyParameters) Reset() { *x = SamplingStrategyParameters{} if protoimpl.UnsafeEnabled { mi := &file_jaeger_sampling_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *SamplingStrategyParameters) String() string { return protoimpl.X.MessageStringOf(x) } func (*SamplingStrategyParameters) ProtoMessage() {} func (x *SamplingStrategyParameters) ProtoReflect() protoreflect.Message { mi := &file_jaeger_sampling_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 SamplingStrategyParameters.ProtoReflect.Descriptor instead. func (*SamplingStrategyParameters) Descriptor() ([]byte, []int) { return file_jaeger_sampling_proto_rawDescGZIP(), []int{5} } func (x *SamplingStrategyParameters) GetServiceName() string { if x != nil { return x.ServiceName } return "" } var File_jaeger_sampling_proto protoreflect.FileDescriptor var file_jaeger_sampling_proto_rawDesc = []byte{ 0x0a, 0x15, 0x6a, 0x61, 0x65, 0x67, 0x65, 0x72, 0x2f, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x69, 0x6e, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0d, 0x6a, 0x61, 0x65, 0x67, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x5f, 0x76, 0x32, 0x1a, 0x14, 0x67, 0x6f, 0x67, 0x6f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x67, 0x6f, 0x67, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1c, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x43, 0x0a, 0x1d, 0x50, 0x72, 0x6f, 0x62, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x73, 0x74, 0x69, 0x63, 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x69, 0x6e, 0x67, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x12, 0x22, 0x0a, 0x0c, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x69, 0x6e, 0x67, 0x52, 0x61, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x01, 0x52, 0x0c, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x69, 0x6e, 0x67, 0x52, 0x61, 0x74, 0x65, 0x22, 0x4e, 0x0a, 0x1c, 0x52, 0x61, 0x74, 0x65, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x69, 0x6e, 0x67, 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x69, 0x6e, 0x67, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x12, 0x2e, 0x0a, 0x12, 0x6d, 0x61, 0x78, 0x54, 0x72, 0x61, 0x63, 0x65, 0x73, 0x50, 0x65, 0x72, 0x53, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x12, 0x6d, 0x61, 0x78, 0x54, 0x72, 0x61, 0x63, 0x65, 0x73, 0x50, 0x65, 0x72, 0x53, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x22, 0x9d, 0x01, 0x0a, 0x19, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x69, 0x6e, 0x67, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x12, 0x1c, 0x0a, 0x09, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x62, 0x0a, 0x15, 0x70, 0x72, 0x6f, 0x62, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x73, 0x74, 0x69, 0x63, 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x69, 0x6e, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2c, 0x2e, 0x6a, 0x61, 0x65, 0x67, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x5f, 0x76, 0x32, 0x2e, 0x50, 0x72, 0x6f, 0x62, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x73, 0x74, 0x69, 0x63, 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x69, 0x6e, 0x67, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x52, 0x15, 0x70, 0x72, 0x6f, 0x62, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x73, 0x74, 0x69, 0x63, 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x69, 0x6e, 0x67, 0x22, 0xda, 0x02, 0x0a, 0x1e, 0x50, 0x65, 0x72, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x69, 0x6e, 0x67, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x69, 0x65, 0x73, 0x12, 0x3e, 0x0a, 0x1a, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x69, 0x6e, 0x67, 0x50, 0x72, 0x6f, 0x62, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x01, 0x52, 0x1a, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x69, 0x6e, 0x67, 0x50, 0x72, 0x6f, 0x62, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x12, 0x4a, 0x0a, 0x20, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x4c, 0x6f, 0x77, 0x65, 0x72, 0x42, 0x6f, 0x75, 0x6e, 0x64, 0x54, 0x72, 0x61, 0x63, 0x65, 0x73, 0x50, 0x65, 0x72, 0x53, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x01, 0x52, 0x20, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x4c, 0x6f, 0x77, 0x65, 0x72, 0x42, 0x6f, 0x75, 0x6e, 0x64, 0x54, 0x72, 0x61, 0x63, 0x65, 0x73, 0x50, 0x65, 0x72, 0x53, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x12, 0x60, 0x0a, 0x16, 0x70, 0x65, 0x72, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x69, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x6a, 0x61, 0x65, 0x67, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x5f, 0x76, 0x32, 0x2e, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x69, 0x6e, 0x67, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x52, 0x16, 0x70, 0x65, 0x72, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x69, 0x65, 0x73, 0x12, 0x4a, 0x0a, 0x20, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x55, 0x70, 0x70, 0x65, 0x72, 0x42, 0x6f, 0x75, 0x6e, 0x64, 0x54, 0x72, 0x61, 0x63, 0x65, 0x73, 0x50, 0x65, 0x72, 0x53, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x01, 0x52, 0x20, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x55, 0x70, 0x70, 0x65, 0x72, 0x42, 0x6f, 0x75, 0x6e, 0x64, 0x54, 0x72, 0x61, 0x63, 0x65, 0x73, 0x50, 0x65, 0x72, 0x53, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x22, 0x85, 0x03, 0x0a, 0x18, 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x69, 0x6e, 0x67, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x47, 0x0a, 0x0c, 0x73, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x54, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x23, 0x2e, 0x6a, 0x61, 0x65, 0x67, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x5f, 0x76, 0x32, 0x2e, 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x69, 0x6e, 0x67, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0c, 0x73, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x54, 0x79, 0x70, 0x65, 0x12, 0x62, 0x0a, 0x15, 0x70, 0x72, 0x6f, 0x62, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x73, 0x74, 0x69, 0x63, 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x69, 0x6e, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2c, 0x2e, 0x6a, 0x61, 0x65, 0x67, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x5f, 0x76, 0x32, 0x2e, 0x50, 0x72, 0x6f, 0x62, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x73, 0x74, 0x69, 0x63, 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x69, 0x6e, 0x67, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x52, 0x15, 0x70, 0x72, 0x6f, 0x62, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x73, 0x74, 0x69, 0x63, 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x69, 0x6e, 0x67, 0x12, 0x5f, 0x0a, 0x14, 0x72, 0x61, 0x74, 0x65, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x69, 0x6e, 0x67, 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x69, 0x6e, 0x67, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x6a, 0x61, 0x65, 0x67, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x5f, 0x76, 0x32, 0x2e, 0x52, 0x61, 0x74, 0x65, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x69, 0x6e, 0x67, 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x69, 0x6e, 0x67, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x52, 0x14, 0x72, 0x61, 0x74, 0x65, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x69, 0x6e, 0x67, 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x69, 0x6e, 0x67, 0x12, 0x5b, 0x0a, 0x11, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x69, 0x6e, 0x67, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2d, 0x2e, 0x6a, 0x61, 0x65, 0x67, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x5f, 0x76, 0x32, 0x2e, 0x50, 0x65, 0x72, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x69, 0x6e, 0x67, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x69, 0x65, 0x73, 0x52, 0x11, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x69, 0x6e, 0x67, 0x22, 0x3e, 0x0a, 0x1a, 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x69, 0x6e, 0x67, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x73, 0x12, 0x20, 0x0a, 0x0b, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x2a, 0x3c, 0x0a, 0x14, 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x69, 0x6e, 0x67, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x54, 0x79, 0x70, 0x65, 0x12, 0x11, 0x0a, 0x0d, 0x50, 0x52, 0x4f, 0x42, 0x41, 0x42, 0x49, 0x4c, 0x49, 0x53, 0x54, 0x49, 0x43, 0x10, 0x00, 0x12, 0x11, 0x0a, 0x0d, 0x52, 0x41, 0x54, 0x45, 0x5f, 0x4c, 0x49, 0x4d, 0x49, 0x54, 0x49, 0x4e, 0x47, 0x10, 0x01, 0x32, 0xa2, 0x01, 0x0a, 0x0f, 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x69, 0x6e, 0x67, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x12, 0x8e, 0x01, 0x0a, 0x13, 0x47, 0x65, 0x74, 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x69, 0x6e, 0x67, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x12, 0x29, 0x2e, 0x6a, 0x61, 0x65, 0x67, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x5f, 0x76, 0x32, 0x2e, 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x69, 0x6e, 0x67, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x73, 0x1a, 0x27, 0x2e, 0x6a, 0x61, 0x65, 0x67, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x5f, 0x76, 0x32, 0x2e, 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x69, 0x6e, 0x67, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x23, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1d, 0x22, 0x18, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x32, 0x2f, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x69, 0x6e, 0x67, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x3a, 0x01, 0x2a, 0x42, 0x6b, 0x0a, 0x17, 0x69, 0x6f, 0x2e, 0x6a, 0x61, 0x65, 0x67, 0x65, 0x72, 0x74, 0x72, 0x61, 0x63, 0x69, 0x6e, 0x67, 0x2e, 0x61, 0x70, 0x69, 0x5f, 0x76, 0x32, 0x5a, 0x44, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x50, 0x61, 0x63, 0x6b, 0x74, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x69, 0x6e, 0x67, 0x2f, 0x47, 0x6f, 0x2d, 0x66, 0x6f, 0x72, 0x2d, 0x44, 0x65, 0x76, 0x4f, 0x70, 0x73, 0x2f, 0x63, 0x68, 0x61, 0x70, 0x74, 0x65, 0x72, 0x2f, 0x31, 0x30, 0x2f, 0x6f, 0x70, 0x73, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x6a, 0x61, 0x65, 0x67, 0x65, 0x72, 0xc8, 0xe2, 0x1e, 0x01, 0xd0, 0xe2, 0x1e, 0x01, 0xe0, 0xe2, 0x1e, 0x01, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( file_jaeger_sampling_proto_rawDescOnce sync.Once file_jaeger_sampling_proto_rawDescData = file_jaeger_sampling_proto_rawDesc ) func file_jaeger_sampling_proto_rawDescGZIP() []byte { file_jaeger_sampling_proto_rawDescOnce.Do(func() { file_jaeger_sampling_proto_rawDescData = protoimpl.X.CompressGZIP(file_jaeger_sampling_proto_rawDescData) }) return file_jaeger_sampling_proto_rawDescData } var file_jaeger_sampling_proto_enumTypes = make([]protoimpl.EnumInfo, 1) var file_jaeger_sampling_proto_msgTypes = make([]protoimpl.MessageInfo, 6) var file_jaeger_sampling_proto_goTypes = []interface{}{ (SamplingStrategyType)(0), // 0: jaeger.api_v2.SamplingStrategyType (*ProbabilisticSamplingStrategy)(nil), // 1: jaeger.api_v2.ProbabilisticSamplingStrategy (*RateLimitingSamplingStrategy)(nil), // 2: jaeger.api_v2.RateLimitingSamplingStrategy (*OperationSamplingStrategy)(nil), // 3: jaeger.api_v2.OperationSamplingStrategy (*PerOperationSamplingStrategies)(nil), // 4: jaeger.api_v2.PerOperationSamplingStrategies (*SamplingStrategyResponse)(nil), // 5: jaeger.api_v2.SamplingStrategyResponse (*SamplingStrategyParameters)(nil), // 6: jaeger.api_v2.SamplingStrategyParameters } var file_jaeger_sampling_proto_depIdxs = []int32{ 1, // 0: jaeger.api_v2.OperationSamplingStrategy.probabilisticSampling:type_name -> jaeger.api_v2.ProbabilisticSamplingStrategy 3, // 1: jaeger.api_v2.PerOperationSamplingStrategies.perOperationStrategies:type_name -> jaeger.api_v2.OperationSamplingStrategy 0, // 2: jaeger.api_v2.SamplingStrategyResponse.strategyType:type_name -> jaeger.api_v2.SamplingStrategyType 1, // 3: jaeger.api_v2.SamplingStrategyResponse.probabilisticSampling:type_name -> jaeger.api_v2.ProbabilisticSamplingStrategy 2, // 4: jaeger.api_v2.SamplingStrategyResponse.rateLimitingSampling:type_name -> jaeger.api_v2.RateLimitingSamplingStrategy 4, // 5: jaeger.api_v2.SamplingStrategyResponse.operationSampling:type_name -> jaeger.api_v2.PerOperationSamplingStrategies 6, // 6: jaeger.api_v2.SamplingManager.GetSamplingStrategy:input_type -> jaeger.api_v2.SamplingStrategyParameters 5, // 7: jaeger.api_v2.SamplingManager.GetSamplingStrategy:output_type -> jaeger.api_v2.SamplingStrategyResponse 7, // [7:8] is the sub-list for method output_type 6, // [6:7] is the sub-list for method input_type 6, // [6:6] is the sub-list for extension type_name 6, // [6:6] is the sub-list for extension extendee 0, // [0:6] is the sub-list for field type_name } func init() { file_jaeger_sampling_proto_init() } func file_jaeger_sampling_proto_init() { if File_jaeger_sampling_proto != nil { return } if !protoimpl.UnsafeEnabled { file_jaeger_sampling_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*ProbabilisticSamplingStrategy); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_jaeger_sampling_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*RateLimitingSamplingStrategy); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_jaeger_sampling_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*OperationSamplingStrategy); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_jaeger_sampling_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*PerOperationSamplingStrategies); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_jaeger_sampling_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*SamplingStrategyResponse); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_jaeger_sampling_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*SamplingStrategyParameters); 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_jaeger_sampling_proto_rawDesc, NumEnums: 1, NumMessages: 6, NumExtensions: 0, NumServices: 1, }, GoTypes: file_jaeger_sampling_proto_goTypes, DependencyIndexes: file_jaeger_sampling_proto_depIdxs, EnumInfos: file_jaeger_sampling_proto_enumTypes, MessageInfos: file_jaeger_sampling_proto_msgTypes, }.Build() File_jaeger_sampling_proto = out.File file_jaeger_sampling_proto_rawDesc = nil file_jaeger_sampling_proto_goTypes = nil file_jaeger_sampling_proto_depIdxs = nil } ================================================ FILE: chapter/11/ops/proto/jaeger/sampling.proto ================================================ // Copyright (c) 2019 The Jaeger Authors. // Copyright (c) 2018 Uber Technologies, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. syntax="proto3"; package jaeger.api_v2; import "gogoproto/gogo.proto"; import "google/api/annotations.proto"; option go_package = "github.com/PacktPublishing/Go-for-DevOps/chapter/10/ops/proto/jaeger"; option java_package = "io.jaegertracing.api_v2"; // Enable gogoprotobuf extensions (https://github.com/gogo/protobuf/blob/master/extensions.md). // Enable custom Marshal method. option (gogoproto.marshaler_all) = true; // Enable custom Unmarshal method. option (gogoproto.unmarshaler_all) = true; // Enable custom Size method (Required by Marshal and Unmarshal). option (gogoproto.sizer_all) = true; // See description of the SamplingStrategyResponse.strategyType field. enum SamplingStrategyType { PROBABILISTIC = 0; RATE_LIMITING = 1; }; // ProbabilisticSamplingStrategy samples traces with a fixed probability. message ProbabilisticSamplingStrategy { // samplingRate is the sampling probability in the range [0.0, 1.0]. double samplingRate = 1; } // RateLimitingSamplingStrategy samples a fixed number of traces per time interval. // The typical implementations use the leaky bucket algorithm. message RateLimitingSamplingStrategy { // TODO this field type should be changed to double, to support rates like 1 per minute. int32 maxTracesPerSecond = 1; } // OperationSamplingStrategy is a sampling strategy for a given operation // (aka endpoint, span name). Only probabilistic sampling is currently supported. message OperationSamplingStrategy { string operation = 1; ProbabilisticSamplingStrategy probabilisticSampling = 2; } // PerOperationSamplingStrategies is a combination of strategies for different endpoints // as well as some service-wide defaults. It is particularly useful for services whose // endpoints receive vastly different traffic, so that any single rate of sampling would // result in either too much data for some endpoints or almost no data for other endpoints. message PerOperationSamplingStrategies { // defaultSamplingProbability is the sampling probability for spans that do not match // any of the perOperationStrategies. double defaultSamplingProbability = 1; // defaultLowerBoundTracesPerSecond defines a lower-bound rate limit used to ensure that // there is some minimal amount of traces sampled for an endpoint that might otherwise // be never sampled via probabilistic strategies. The limit is local to a service instance, // so if a service is deployed with many (N) instances, the effective minimum rate of sampling // will be N times higher. This setting applies to ALL operations, whether or not they match // one of the perOperationStrategies. double defaultLowerBoundTracesPerSecond = 2; // perOperationStrategies describes sampling strategiesf for individual operations within // a given service. repeated OperationSamplingStrategy perOperationStrategies = 3; // defaultUpperBoundTracesPerSecond defines an upper bound rate limit. // However, almost no Jaeger SDKs support this parameter. double defaultUpperBoundTracesPerSecond = 4; } // SamplingStrategyResponse contains an overall sampling strategy for a given service. // This type should be treated as a union where only one of the strategy field is present. message SamplingStrategyResponse { // Legacy field that was meant to indicate which one of the strategy fields // below is present. This enum was not extended when per-operation strategy // was introduced, because extending enum has backwards compatiblity issues. // The recommended approach for consumers is to ignore this field and instead // checks the other fields being not null (starting with operationSampling). // For producers, it is recommended to set this field correctly for probabilistic // and rate-limiting strategies, but if per-operation strategy is returned, // the enum can be set to 0 (probabilistic). SamplingStrategyType strategyType = 1; ProbabilisticSamplingStrategy probabilisticSampling = 2; RateLimitingSamplingStrategy rateLimitingSampling = 3; PerOperationSamplingStrategies operationSampling = 4; } // SamplingStrategyParameters defines request parameters for remote sampler. message SamplingStrategyParameters { // serviceName is a required argument. string serviceName = 1; } // SamplingManager defines service for the remote sampler. service SamplingManager { rpc GetSamplingStrategy(SamplingStrategyParameters) returns (SamplingStrategyResponse) { option (google.api.http) = { post: "/api/v2/samplingStrategy" body: "*" }; } } ================================================ FILE: chapter/11/ops/proto/jaeger/sampling_grpc.pb.go ================================================ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. package jaeger 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.32.0 or later. const _ = grpc.SupportPackageIsVersion7 // SamplingManagerClient is the client API for SamplingManager 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 SamplingManagerClient interface { GetSamplingStrategy(ctx context.Context, in *SamplingStrategyParameters, opts ...grpc.CallOption) (*SamplingStrategyResponse, error) } type samplingManagerClient struct { cc grpc.ClientConnInterface } func NewSamplingManagerClient(cc grpc.ClientConnInterface) SamplingManagerClient { return &samplingManagerClient{cc} } func (c *samplingManagerClient) GetSamplingStrategy(ctx context.Context, in *SamplingStrategyParameters, opts ...grpc.CallOption) (*SamplingStrategyResponse, error) { out := new(SamplingStrategyResponse) err := c.cc.Invoke(ctx, "/jaeger.api_v2.SamplingManager/GetSamplingStrategy", in, out, opts...) if err != nil { return nil, err } return out, nil } // SamplingManagerServer is the server API for SamplingManager service. // All implementations must embed UnimplementedSamplingManagerServer // for forward compatibility type SamplingManagerServer interface { GetSamplingStrategy(context.Context, *SamplingStrategyParameters) (*SamplingStrategyResponse, error) mustEmbedUnimplementedSamplingManagerServer() } // UnimplementedSamplingManagerServer must be embedded to have forward compatible implementations. type UnimplementedSamplingManagerServer struct { } func (UnimplementedSamplingManagerServer) GetSamplingStrategy(context.Context, *SamplingStrategyParameters) (*SamplingStrategyResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method GetSamplingStrategy not implemented") } func (UnimplementedSamplingManagerServer) mustEmbedUnimplementedSamplingManagerServer() {} // UnsafeSamplingManagerServer may be embedded to opt out of forward compatibility for this service. // Use of this interface is not recommended, as added methods to SamplingManagerServer will // result in compilation errors. type UnsafeSamplingManagerServer interface { mustEmbedUnimplementedSamplingManagerServer() } func RegisterSamplingManagerServer(s grpc.ServiceRegistrar, srv SamplingManagerServer) { s.RegisterService(&SamplingManager_ServiceDesc, srv) } func _SamplingManager_GetSamplingStrategy_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(SamplingStrategyParameters) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(SamplingManagerServer).GetSamplingStrategy(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: "/jaeger.api_v2.SamplingManager/GetSamplingStrategy", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(SamplingManagerServer).GetSamplingStrategy(ctx, req.(*SamplingStrategyParameters)) } return interceptor(ctx, in, info, handler) } // SamplingManager_ServiceDesc is the grpc.ServiceDesc for SamplingManager service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) var SamplingManager_ServiceDesc = grpc.ServiceDesc{ ServiceName: "jaeger.api_v2.SamplingManager", HandlerType: (*SamplingManagerServer)(nil), Methods: []grpc.MethodDesc{ { MethodName: "GetSamplingStrategy", Handler: _SamplingManager_GetSamplingStrategy_Handler, }, }, Streams: []grpc.StreamDesc{}, Metadata: "jaeger/sampling.proto", } ================================================ FILE: chapter/11/ops/proto/ops.pb.go ================================================ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.25.0-devel // protoc v3.18.0 // source: ops.proto package proto import ( model "github.com/PacktPublishing/Go-for-DevOps/chapter/11/ops/proto/jaeger/model" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" durationpb "google.golang.org/protobuf/types/known/durationpb" 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 SamplerType int32 const ( SamplerType_STUnknown SamplerType = 0 SamplerType_STNever SamplerType = 1 SamplerType_STAlways SamplerType = 2 SamplerType_STFloat SamplerType = 3 ) // Enum value maps for SamplerType. var ( SamplerType_name = map[int32]string{ 0: "STUnknown", 1: "STNever", 2: "STAlways", 3: "STFloat", } SamplerType_value = map[string]int32{ "STUnknown": 0, "STNever": 1, "STAlways": 2, "STFloat": 3, } ) func (x SamplerType) Enum() *SamplerType { p := new(SamplerType) *p = x return p } func (x SamplerType) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } func (SamplerType) Descriptor() protoreflect.EnumDescriptor { return file_ops_proto_enumTypes[0].Descriptor() } func (SamplerType) Type() protoreflect.EnumType { return &file_ops_proto_enumTypes[0] } func (x SamplerType) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } // Deprecated: Use SamplerType.Descriptor instead. func (SamplerType) EnumDescriptor() ([]byte, []int) { return file_ops_proto_rawDescGZIP(), []int{0} } // The request to get traces from Jaeger. type ListTracesReq struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // The name of the service to find traces for. Service string `protobuf:"bytes,1,opt,name=service,proto3" json:"service,omitempty"` // Filter the traces for this operation. Operation string `protobuf:"bytes,2,opt,name=operation,proto3" json:"operation,omitempty"` // Filter the traces for only traces with these all these tags. Tags []string `protobuf:"bytes,3,rep,name=tags,proto3" json:"tags,omitempty"` // Traces must start after the time in unix nanoseconds. Start int64 `protobuf:"varint,4,opt,name=start,proto3" json:"start,omitempty"` // Traces must end before this time in unix nanoseconds. End int64 `protobuf:"varint,5,opt,name=end,proto3" json:"end,omitempty"` // The minimum duration of a matched trace. DurationMin int64 `protobuf:"varint,6,opt,name=duration_min,json=durationMin,proto3" json:"duration_min,omitempty"` // The maximum duration of a matched trace. DurationMax int64 `protobuf:"varint,7,opt,name=duration_max,json=durationMax,proto3" json:"duration_max,omitempty"` // The number of traces to return. SearchDepth int32 `protobuf:"varint,8,opt,name=search_depth,json=searchDepth,proto3" json:"search_depth,omitempty"` } func (x *ListTracesReq) Reset() { *x = ListTracesReq{} if protoimpl.UnsafeEnabled { mi := &file_ops_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *ListTracesReq) String() string { return protoimpl.X.MessageStringOf(x) } func (*ListTracesReq) ProtoMessage() {} func (x *ListTracesReq) ProtoReflect() protoreflect.Message { mi := &file_ops_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 ListTracesReq.ProtoReflect.Descriptor instead. func (*ListTracesReq) Descriptor() ([]byte, []int) { return file_ops_proto_rawDescGZIP(), []int{0} } func (x *ListTracesReq) GetService() string { if x != nil { return x.Service } return "" } func (x *ListTracesReq) GetOperation() string { if x != nil { return x.Operation } return "" } func (x *ListTracesReq) GetTags() []string { if x != nil { return x.Tags } return nil } func (x *ListTracesReq) GetStart() int64 { if x != nil { return x.Start } return 0 } func (x *ListTracesReq) GetEnd() int64 { if x != nil { return x.End } return 0 } func (x *ListTracesReq) GetDurationMin() int64 { if x != nil { return x.DurationMin } return 0 } func (x *ListTracesReq) GetDurationMax() int64 { if x != nil { return x.DurationMax } return 0 } func (x *ListTracesReq) GetSearchDepth() int32 { if x != nil { return x.SearchDepth } return 0 } // This represents a trace identifier and when the trace started. type TraceItem struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // The trace identifier in hex form. Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` // The time the trace started in unix nanosecods. Start int64 `protobuf:"varint,2,opt,name=start,proto3" json:"start,omitempty"` } func (x *TraceItem) Reset() { *x = TraceItem{} if protoimpl.UnsafeEnabled { mi := &file_ops_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *TraceItem) String() string { return protoimpl.X.MessageStringOf(x) } func (*TraceItem) ProtoMessage() {} func (x *TraceItem) ProtoReflect() protoreflect.Message { mi := &file_ops_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 TraceItem.ProtoReflect.Descriptor instead. func (*TraceItem) Descriptor() ([]byte, []int) { return file_ops_proto_rawDescGZIP(), []int{1} } func (x *TraceItem) GetId() string { if x != nil { return x.Id } return "" } func (x *TraceItem) GetStart() int64 { if x != nil { return x.Start } return 0 } // A response to ListTraces(). type ListTracesResp struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // A list of traces that met the search criteria. Traces []*TraceItem `protobuf:"bytes,1,rep,name=traces,proto3" json:"traces,omitempty"` } func (x *ListTracesResp) Reset() { *x = ListTracesResp{} if protoimpl.UnsafeEnabled { mi := &file_ops_proto_msgTypes[2] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *ListTracesResp) String() string { return protoimpl.X.MessageStringOf(x) } func (*ListTracesResp) ProtoMessage() {} func (x *ListTracesResp) ProtoReflect() protoreflect.Message { mi := &file_ops_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 ListTracesResp.ProtoReflect.Descriptor instead. func (*ListTracesResp) Descriptor() ([]byte, []int) { return file_ops_proto_rawDescGZIP(), []int{2} } func (x *ListTracesResp) GetTraces() []*TraceItem { if x != nil { return x.Traces } return nil } // The request to get a URL showing the trace information for a trace id. type ShowTraceReq struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // The ID of the trace in hex. Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` } func (x *ShowTraceReq) Reset() { *x = ShowTraceReq{} if protoimpl.UnsafeEnabled { mi := &file_ops_proto_msgTypes[3] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *ShowTraceReq) String() string { return protoimpl.X.MessageStringOf(x) } func (*ShowTraceReq) ProtoMessage() {} func (x *ShowTraceReq) ProtoReflect() protoreflect.Message { mi := &file_ops_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 ShowTraceReq.ProtoReflect.Descriptor instead. func (*ShowTraceReq) Descriptor() ([]byte, []int) { return file_ops_proto_rawDescGZIP(), []int{3} } func (x *ShowTraceReq) GetId() string { if x != nil { return x.Id } return "" } // The resonse to ShowTrace(). type ShowTraceResp struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // The URL to view the trace. Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` // The name of the operations being performed. Operations []string `protobuf:"bytes,2,rep,name=operations,proto3" json:"operations,omitempty"` // A list of tag values in spans labelled "error". Errors []string `protobuf:"bytes,3,rep,name=errors,proto3" json:"errors,omitempty"` // A list of all tags in the spans. Tags []string `protobuf:"bytes,4,rep,name=tags,proto3" json:"tags,omitempty"` // The longest duration found in any span. Duration *durationpb.Duration `protobuf:"bytes,5,opt,name=duration,proto3" json:"duration,omitempty"` } func (x *ShowTraceResp) Reset() { *x = ShowTraceResp{} if protoimpl.UnsafeEnabled { mi := &file_ops_proto_msgTypes[4] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *ShowTraceResp) String() string { return protoimpl.X.MessageStringOf(x) } func (*ShowTraceResp) ProtoMessage() {} func (x *ShowTraceResp) ProtoReflect() protoreflect.Message { mi := &file_ops_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 ShowTraceResp.ProtoReflect.Descriptor instead. func (*ShowTraceResp) Descriptor() ([]byte, []int) { return file_ops_proto_rawDescGZIP(), []int{4} } func (x *ShowTraceResp) GetId() string { if x != nil { return x.Id } return "" } func (x *ShowTraceResp) GetOperations() []string { if x != nil { return x.Operations } return nil } func (x *ShowTraceResp) GetErrors() []string { if x != nil { return x.Errors } return nil } func (x *ShowTraceResp) GetTags() []string { if x != nil { return x.Tags } return nil } func (x *ShowTraceResp) GetDuration() *durationpb.Duration { if x != nil { return x.Duration } return nil } type ShowLogsReq struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // The hex ID of the trace. Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` } func (x *ShowLogsReq) Reset() { *x = ShowLogsReq{} if protoimpl.UnsafeEnabled { mi := &file_ops_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *ShowLogsReq) String() string { return protoimpl.X.MessageStringOf(x) } func (*ShowLogsReq) ProtoMessage() {} func (x *ShowLogsReq) ProtoReflect() protoreflect.Message { mi := &file_ops_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 ShowLogsReq.ProtoReflect.Descriptor instead. func (*ShowLogsReq) Descriptor() ([]byte, []int) { return file_ops_proto_rawDescGZIP(), []int{5} } func (x *ShowLogsReq) GetId() string { if x != nil { return x.Id } return "" } type ShowLogsResp struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` Logs []*model.Log `protobuf:"bytes,2,rep,name=logs,proto3" json:"logs,omitempty"` } func (x *ShowLogsResp) Reset() { *x = ShowLogsResp{} if protoimpl.UnsafeEnabled { mi := &file_ops_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *ShowLogsResp) String() string { return protoimpl.X.MessageStringOf(x) } func (*ShowLogsResp) ProtoMessage() {} func (x *ShowLogsResp) ProtoReflect() protoreflect.Message { mi := &file_ops_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 ShowLogsResp.ProtoReflect.Descriptor instead. func (*ShowLogsResp) Descriptor() ([]byte, []int) { return file_ops_proto_rawDescGZIP(), []int{6} } func (x *ShowLogsResp) GetId() string { if x != nil { return x.Id } return "" } func (x *ShowLogsResp) GetLogs() []*model.Log { if x != nil { return x.Logs } return nil } // Used to request we change the OTEL sampling. type ChangeSamplingReq struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // The type of sampling to change to. Type SamplerType `protobuf:"varint,1,opt,name=type,proto3,enum=ops.SamplerType" json:"type,omitempty"` // This is the sampling rate if type == STFloat. Values must be // > 0 and <= 1.0 . FloatValue float64 `protobuf:"fixed64,2,opt,name=float_value,json=floatValue,proto3" json:"float_value,omitempty"` } func (x *ChangeSamplingReq) Reset() { *x = ChangeSamplingReq{} if protoimpl.UnsafeEnabled { mi := &file_ops_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *ChangeSamplingReq) String() string { return protoimpl.X.MessageStringOf(x) } func (*ChangeSamplingReq) ProtoMessage() {} func (x *ChangeSamplingReq) ProtoReflect() protoreflect.Message { mi := &file_ops_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 ChangeSamplingReq.ProtoReflect.Descriptor instead. func (*ChangeSamplingReq) Descriptor() ([]byte, []int) { return file_ops_proto_rawDescGZIP(), []int{7} } func (x *ChangeSamplingReq) GetType() SamplerType { if x != nil { return x.Type } return SamplerType_STUnknown } func (x *ChangeSamplingReq) GetFloatValue() float64 { if x != nil { return x.FloatValue } return 0 } // The response to a sampling change. type ChangeSamplingResp struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields } func (x *ChangeSamplingResp) Reset() { *x = ChangeSamplingResp{} if protoimpl.UnsafeEnabled { mi := &file_ops_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *ChangeSamplingResp) String() string { return protoimpl.X.MessageStringOf(x) } func (*ChangeSamplingResp) ProtoMessage() {} func (x *ChangeSamplingResp) ProtoReflect() protoreflect.Message { mi := &file_ops_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 ChangeSamplingResp.ProtoReflect.Descriptor instead. func (*ChangeSamplingResp) Descriptor() ([]byte, []int) { return file_ops_proto_rawDescGZIP(), []int{8} } // The request to get the deployed version of the service. type DeployedVersionReq struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields } func (x *DeployedVersionReq) Reset() { *x = DeployedVersionReq{} if protoimpl.UnsafeEnabled { mi := &file_ops_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *DeployedVersionReq) String() string { return protoimpl.X.MessageStringOf(x) } func (*DeployedVersionReq) ProtoMessage() {} func (x *DeployedVersionReq) ProtoReflect() protoreflect.Message { mi := &file_ops_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 DeployedVersionReq.ProtoReflect.Descriptor instead. func (*DeployedVersionReq) Descriptor() ([]byte, []int) { return file_ops_proto_rawDescGZIP(), []int{9} } // The response to DeployedVersion(). type DeployedVersionResp struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // The version running according to prometheus metrics. Version string `protobuf:"bytes,1,opt,name=version,proto3" json:"version,omitempty"` } func (x *DeployedVersionResp) Reset() { *x = DeployedVersionResp{} if protoimpl.UnsafeEnabled { mi := &file_ops_proto_msgTypes[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *DeployedVersionResp) String() string { return protoimpl.X.MessageStringOf(x) } func (*DeployedVersionResp) ProtoMessage() {} func (x *DeployedVersionResp) ProtoReflect() protoreflect.Message { mi := &file_ops_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 DeployedVersionResp.ProtoReflect.Descriptor instead. func (*DeployedVersionResp) Descriptor() ([]byte, []int) { return file_ops_proto_rawDescGZIP(), []int{10} } func (x *DeployedVersionResp) GetVersion() string { if x != nil { return x.Version } return "" } // Alert describes a Prometheus alert. type Alert struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // This is the current state of the alert. State string `protobuf:"bytes,1,opt,name=state,proto3" json:"state,omitempty"` // This is the current value of the alert. Value string `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"` // This is how long it has been active in unix nanoseconds. ActiveAt int64 `protobuf:"varint,3,opt,name=active_at,json=activeAt,proto3" json:"active_at,omitempty"` } func (x *Alert) Reset() { *x = Alert{} if protoimpl.UnsafeEnabled { mi := &file_ops_proto_msgTypes[11] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Alert) String() string { return protoimpl.X.MessageStringOf(x) } func (*Alert) ProtoMessage() {} func (x *Alert) ProtoReflect() protoreflect.Message { mi := &file_ops_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 Alert.ProtoReflect.Descriptor instead. func (*Alert) Descriptor() ([]byte, []int) { return file_ops_proto_rawDescGZIP(), []int{11} } func (x *Alert) GetState() string { if x != nil { return x.State } return "" } func (x *Alert) GetValue() string { if x != nil { return x.Value } return "" } func (x *Alert) GetActiveAt() int64 { if x != nil { return x.ActiveAt } return 0 } // This requests an set of active alerts in the system. type AlertsReq struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // Labels that the alert must match. Must have all labels. None indicates all alerts. Labels []string `protobuf:"bytes,1,rep,name=labels,proto3" json:"labels,omitempty"` // It must be an alert that is active since this time in unix nanoseconds. 0 indicates any alive alert. ActiveAt int64 `protobuf:"varint,2,opt,name=active_at,json=activeAt,proto3" json:"active_at,omitempty"` // It must have one of these states. None indicates all states. States []string `protobuf:"bytes,3,rep,name=states,proto3" json:"states,omitempty"` } func (x *AlertsReq) Reset() { *x = AlertsReq{} if protoimpl.UnsafeEnabled { mi := &file_ops_proto_msgTypes[12] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *AlertsReq) String() string { return protoimpl.X.MessageStringOf(x) } func (*AlertsReq) ProtoMessage() {} func (x *AlertsReq) ProtoReflect() protoreflect.Message { mi := &file_ops_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 AlertsReq.ProtoReflect.Descriptor instead. func (*AlertsReq) Descriptor() ([]byte, []int) { return file_ops_proto_rawDescGZIP(), []int{12} } func (x *AlertsReq) GetLabels() []string { if x != nil { return x.Labels } return nil } func (x *AlertsReq) GetActiveAt() int64 { if x != nil { return x.ActiveAt } return 0 } func (x *AlertsReq) GetStates() []string { if x != nil { return x.States } return nil } // The response to Alerts(). type AlertsResp struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // A list of alerts that matched the filter. Alerts []*Alert `protobuf:"bytes,1,rep,name=alerts,proto3" json:"alerts,omitempty"` } func (x *AlertsResp) Reset() { *x = AlertsResp{} if protoimpl.UnsafeEnabled { mi := &file_ops_proto_msgTypes[13] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *AlertsResp) String() string { return protoimpl.X.MessageStringOf(x) } func (*AlertsResp) ProtoMessage() {} func (x *AlertsResp) ProtoReflect() protoreflect.Message { mi := &file_ops_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 AlertsResp.ProtoReflect.Descriptor instead. func (*AlertsResp) Descriptor() ([]byte, []int) { return file_ops_proto_rawDescGZIP(), []int{13} } func (x *AlertsResp) GetAlerts() []*Alert { if x != nil { return x.Alerts } return nil } var File_ops_proto protoreflect.FileDescriptor var file_ops_proto_rawDesc = []byte{ 0x0a, 0x09, 0x6f, 0x70, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x03, 0x6f, 0x70, 0x73, 0x1a, 0x1e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x18, 0x6a, 0x61, 0x65, 0x67, 0x65, 0x72, 0x2f, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x2f, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xec, 0x01, 0x0a, 0x0d, 0x4c, 0x69, 0x73, 0x74, 0x54, 0x72, 0x61, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x61, 0x67, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x04, 0x74, 0x61, 0x67, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x65, 0x6e, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x03, 0x65, 0x6e, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6d, 0x69, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x69, 0x6e, 0x12, 0x21, 0x0a, 0x0c, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6d, 0x61, 0x78, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x61, 0x78, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x5f, 0x64, 0x65, 0x70, 0x74, 0x68, 0x18, 0x08, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0b, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x44, 0x65, 0x70, 0x74, 0x68, 0x22, 0x31, 0x0a, 0x09, 0x54, 0x72, 0x61, 0x63, 0x65, 0x49, 0x74, 0x65, 0x6d, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x22, 0x38, 0x0a, 0x0e, 0x4c, 0x69, 0x73, 0x74, 0x54, 0x72, 0x61, 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x12, 0x26, 0x0a, 0x06, 0x74, 0x72, 0x61, 0x63, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x6f, 0x70, 0x73, 0x2e, 0x54, 0x72, 0x61, 0x63, 0x65, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x06, 0x74, 0x72, 0x61, 0x63, 0x65, 0x73, 0x22, 0x1e, 0x0a, 0x0c, 0x53, 0x68, 0x6f, 0x77, 0x54, 0x72, 0x61, 0x63, 0x65, 0x52, 0x65, 0x71, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x22, 0xa2, 0x01, 0x0a, 0x0d, 0x53, 0x68, 0x6f, 0x77, 0x54, 0x72, 0x61, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x1e, 0x0a, 0x0a, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x06, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x61, 0x67, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x04, 0x74, 0x61, 0x67, 0x73, 0x12, 0x35, 0x0a, 0x08, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x08, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x1d, 0x0a, 0x0b, 0x53, 0x68, 0x6f, 0x77, 0x4c, 0x6f, 0x67, 0x73, 0x52, 0x65, 0x71, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x22, 0x46, 0x0a, 0x0c, 0x53, 0x68, 0x6f, 0x77, 0x4c, 0x6f, 0x67, 0x73, 0x52, 0x65, 0x73, 0x70, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x26, 0x0a, 0x04, 0x6c, 0x6f, 0x67, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x6a, 0x61, 0x65, 0x67, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x5f, 0x76, 0x32, 0x2e, 0x4c, 0x6f, 0x67, 0x52, 0x04, 0x6c, 0x6f, 0x67, 0x73, 0x22, 0x5a, 0x0a, 0x11, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x71, 0x12, 0x24, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x10, 0x2e, 0x6f, 0x70, 0x73, 0x2e, 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x72, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x01, 0x52, 0x0a, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x14, 0x0a, 0x12, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x73, 0x70, 0x22, 0x14, 0x0a, 0x12, 0x44, 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x65, 0x64, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x22, 0x2f, 0x0a, 0x13, 0x44, 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x65, 0x64, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x50, 0x0a, 0x05, 0x41, 0x6c, 0x65, 0x72, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x61, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x41, 0x74, 0x22, 0x58, 0x0a, 0x09, 0x41, 0x6c, 0x65, 0x72, 0x74, 0x73, 0x52, 0x65, 0x71, 0x12, 0x16, 0x0a, 0x06, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x06, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x61, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x41, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x65, 0x73, 0x22, 0x30, 0x0a, 0x0a, 0x41, 0x6c, 0x65, 0x72, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x12, 0x22, 0x0a, 0x06, 0x61, 0x6c, 0x65, 0x72, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0a, 0x2e, 0x6f, 0x70, 0x73, 0x2e, 0x41, 0x6c, 0x65, 0x72, 0x74, 0x52, 0x06, 0x61, 0x6c, 0x65, 0x72, 0x74, 0x73, 0x2a, 0x44, 0x0a, 0x0b, 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x72, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0d, 0x0a, 0x09, 0x53, 0x54, 0x55, 0x6e, 0x6b, 0x6e, 0x6f, 0x77, 0x6e, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x53, 0x54, 0x4e, 0x65, 0x76, 0x65, 0x72, 0x10, 0x01, 0x12, 0x0c, 0x0a, 0x08, 0x53, 0x54, 0x41, 0x6c, 0x77, 0x61, 0x79, 0x73, 0x10, 0x02, 0x12, 0x0b, 0x0a, 0x07, 0x53, 0x54, 0x46, 0x6c, 0x6f, 0x61, 0x74, 0x10, 0x03, 0x32, 0xe1, 0x02, 0x0a, 0x03, 0x4f, 0x70, 0x73, 0x12, 0x37, 0x0a, 0x0a, 0x4c, 0x69, 0x73, 0x74, 0x54, 0x72, 0x61, 0x63, 0x65, 0x73, 0x12, 0x12, 0x2e, 0x6f, 0x70, 0x73, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x54, 0x72, 0x61, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x1a, 0x13, 0x2e, 0x6f, 0x70, 0x73, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x54, 0x72, 0x61, 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x12, 0x34, 0x0a, 0x09, 0x53, 0x68, 0x6f, 0x77, 0x54, 0x72, 0x61, 0x63, 0x65, 0x12, 0x11, 0x2e, 0x6f, 0x70, 0x73, 0x2e, 0x53, 0x68, 0x6f, 0x77, 0x54, 0x72, 0x61, 0x63, 0x65, 0x52, 0x65, 0x71, 0x1a, 0x12, 0x2e, 0x6f, 0x70, 0x73, 0x2e, 0x53, 0x68, 0x6f, 0x77, 0x54, 0x72, 0x61, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x12, 0x31, 0x0a, 0x08, 0x53, 0x68, 0x6f, 0x77, 0x4c, 0x6f, 0x67, 0x73, 0x12, 0x10, 0x2e, 0x6f, 0x70, 0x73, 0x2e, 0x53, 0x68, 0x6f, 0x77, 0x4c, 0x6f, 0x67, 0x73, 0x52, 0x65, 0x71, 0x1a, 0x11, 0x2e, 0x6f, 0x70, 0x73, 0x2e, 0x53, 0x68, 0x6f, 0x77, 0x4c, 0x6f, 0x67, 0x73, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x12, 0x43, 0x0a, 0x0e, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x69, 0x6e, 0x67, 0x12, 0x16, 0x2e, 0x6f, 0x70, 0x73, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x71, 0x1a, 0x17, 0x2e, 0x6f, 0x70, 0x73, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x12, 0x46, 0x0a, 0x0f, 0x44, 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x65, 0x64, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x17, 0x2e, 0x6f, 0x70, 0x73, 0x2e, 0x44, 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x65, 0x64, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x1a, 0x18, 0x2e, 0x6f, 0x70, 0x73, 0x2e, 0x44, 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x65, 0x64, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x12, 0x2b, 0x0a, 0x06, 0x41, 0x6c, 0x65, 0x72, 0x74, 0x73, 0x12, 0x0e, 0x2e, 0x6f, 0x70, 0x73, 0x2e, 0x41, 0x6c, 0x65, 0x72, 0x74, 0x73, 0x52, 0x65, 0x71, 0x1a, 0x0f, 0x2e, 0x6f, 0x70, 0x73, 0x2e, 0x41, 0x6c, 0x65, 0x72, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x42, 0x3f, 0x5a, 0x3d, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x50, 0x61, 0x63, 0x6b, 0x74, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x69, 0x6e, 0x67, 0x2f, 0x47, 0x6f, 0x2d, 0x66, 0x6f, 0x72, 0x2d, 0x44, 0x65, 0x76, 0x4f, 0x70, 0x73, 0x2f, 0x63, 0x68, 0x61, 0x70, 0x74, 0x65, 0x72, 0x2f, 0x31, 0x30, 0x2f, 0x6f, 0x70, 0x73, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( file_ops_proto_rawDescOnce sync.Once file_ops_proto_rawDescData = file_ops_proto_rawDesc ) func file_ops_proto_rawDescGZIP() []byte { file_ops_proto_rawDescOnce.Do(func() { file_ops_proto_rawDescData = protoimpl.X.CompressGZIP(file_ops_proto_rawDescData) }) return file_ops_proto_rawDescData } var file_ops_proto_enumTypes = make([]protoimpl.EnumInfo, 1) var file_ops_proto_msgTypes = make([]protoimpl.MessageInfo, 14) var file_ops_proto_goTypes = []interface{}{ (SamplerType)(0), // 0: ops.SamplerType (*ListTracesReq)(nil), // 1: ops.ListTracesReq (*TraceItem)(nil), // 2: ops.TraceItem (*ListTracesResp)(nil), // 3: ops.ListTracesResp (*ShowTraceReq)(nil), // 4: ops.ShowTraceReq (*ShowTraceResp)(nil), // 5: ops.ShowTraceResp (*ShowLogsReq)(nil), // 6: ops.ShowLogsReq (*ShowLogsResp)(nil), // 7: ops.ShowLogsResp (*ChangeSamplingReq)(nil), // 8: ops.ChangeSamplingReq (*ChangeSamplingResp)(nil), // 9: ops.ChangeSamplingResp (*DeployedVersionReq)(nil), // 10: ops.DeployedVersionReq (*DeployedVersionResp)(nil), // 11: ops.DeployedVersionResp (*Alert)(nil), // 12: ops.Alert (*AlertsReq)(nil), // 13: ops.AlertsReq (*AlertsResp)(nil), // 14: ops.AlertsResp (*durationpb.Duration)(nil), // 15: google.protobuf.Duration (*model.Log)(nil), // 16: jaeger.api_v2.Log } var file_ops_proto_depIdxs = []int32{ 2, // 0: ops.ListTracesResp.traces:type_name -> ops.TraceItem 15, // 1: ops.ShowTraceResp.duration:type_name -> google.protobuf.Duration 16, // 2: ops.ShowLogsResp.logs:type_name -> jaeger.api_v2.Log 0, // 3: ops.ChangeSamplingReq.type:type_name -> ops.SamplerType 12, // 4: ops.AlertsResp.alerts:type_name -> ops.Alert 1, // 5: ops.Ops.ListTraces:input_type -> ops.ListTracesReq 4, // 6: ops.Ops.ShowTrace:input_type -> ops.ShowTraceReq 6, // 7: ops.Ops.ShowLogs:input_type -> ops.ShowLogsReq 8, // 8: ops.Ops.ChangeSampling:input_type -> ops.ChangeSamplingReq 10, // 9: ops.Ops.DeployedVersion:input_type -> ops.DeployedVersionReq 13, // 10: ops.Ops.Alerts:input_type -> ops.AlertsReq 3, // 11: ops.Ops.ListTraces:output_type -> ops.ListTracesResp 5, // 12: ops.Ops.ShowTrace:output_type -> ops.ShowTraceResp 7, // 13: ops.Ops.ShowLogs:output_type -> ops.ShowLogsResp 9, // 14: ops.Ops.ChangeSampling:output_type -> ops.ChangeSamplingResp 11, // 15: ops.Ops.DeployedVersion:output_type -> ops.DeployedVersionResp 14, // 16: ops.Ops.Alerts:output_type -> ops.AlertsResp 11, // [11:17] is the sub-list for method output_type 5, // [5:11] is the sub-list for method input_type 5, // [5:5] is the sub-list for extension type_name 5, // [5:5] is the sub-list for extension extendee 0, // [0:5] is the sub-list for field type_name } func init() { file_ops_proto_init() } func file_ops_proto_init() { if File_ops_proto != nil { return } if !protoimpl.UnsafeEnabled { file_ops_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*ListTracesReq); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_ops_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*TraceItem); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_ops_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*ListTracesResp); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_ops_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*ShowTraceReq); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_ops_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*ShowTraceResp); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_ops_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*ShowLogsReq); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_ops_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*ShowLogsResp); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_ops_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*ChangeSamplingReq); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_ops_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*ChangeSamplingResp); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_ops_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*DeployedVersionReq); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_ops_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*DeployedVersionResp); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_ops_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Alert); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_ops_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*AlertsReq); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_ops_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*AlertsResp); 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_ops_proto_rawDesc, NumEnums: 1, NumMessages: 14, NumExtensions: 0, NumServices: 1, }, GoTypes: file_ops_proto_goTypes, DependencyIndexes: file_ops_proto_depIdxs, EnumInfos: file_ops_proto_enumTypes, MessageInfos: file_ops_proto_msgTypes, }.Build() File_ops_proto = out.File file_ops_proto_rawDesc = nil file_ops_proto_goTypes = nil file_ops_proto_depIdxs = nil } ================================================ FILE: chapter/11/ops/proto/ops.proto ================================================ syntax = "proto3"; package ops; import "google/protobuf/duration.proto"; import "jaeger/model/model.proto"; option go_package = "github.com/PacktPublishing/Go-for-DevOps/chapter/10/ops/proto"; // The request to get traces from Jaeger. message ListTracesReq { // The name of the service to find traces for. string service = 1; // Filter the traces for this operation. string operation = 2; // Filter the traces for only traces with these all these tags. repeated string tags = 3; // Traces must start after the time in unix nanoseconds. int64 start = 4; // Traces must end before this time in unix nanoseconds. int64 end = 5; // The minimum duration of a matched trace. int64 duration_min = 6; // The maximum duration of a matched trace. int64 duration_max = 7; // The number of traces to return. int32 search_depth = 8; } // This represents a trace identifier and when the trace started. message TraceItem { // The trace identifier in hex form. string id = 1; // The time the trace started in unix nanosecods. int64 start = 2; } // A response to ListTraces(). message ListTracesResp { // A list of traces that met the search criteria. repeated TraceItem traces = 1; } // The request to get a URL showing the trace information for a trace id. message ShowTraceReq { // The ID of the trace in hex. string id = 1; } // The resonse to ShowTrace(). message ShowTraceResp { // The URL to view the trace. string id = 1; // The name of the operations being performed. repeated string operations = 2; // A list of tag values in spans labelled "error". repeated string errors = 3; // A list of all tags in the spans. repeated string tags = 4; // The longest duration found in any span. google.protobuf.Duration duration = 5; } message ShowLogsReq { // The hex ID of the trace. string id = 1; } message ShowLogsResp{ string id = 1; repeated jaeger.api_v2.Log logs = 2; } enum SamplerType { STUnknown = 0; STNever = 1; STAlways = 2; STFloat = 3; } // Used to request we change the OTEL sampling. message ChangeSamplingReq { // The type of sampling to change to. SamplerType type = 1; // This is the sampling rate if type == STFloat. Values must be // > 0 and <= 1.0 . double float_value = 2; } // The response to a sampling change. message ChangeSamplingResp{} // The request to get the deployed version of the service. message DeployedVersionReq {} // The response to DeployedVersion(). message DeployedVersionResp { // The version running according to prometheus metrics. string version = 1; } // Alert describes a Prometheus alert. message Alert { // This is the current state of the alert. string state = 1; // This is the current value of the alert. string value = 2; // This is how long it has been active in unix nanoseconds. int64 active_at = 3; } // This requests an set of active alerts in the system. message AlertsReq{ // Labels that the alert must match. Must have all labels. None indicates all alerts. repeated string labels = 1; // It must be an alert that is active since this time in unix nanoseconds. 0 indicates any alive alert. int64 active_at = 2; // It must have one of these states. None indicates all states. repeated string states = 3; } // The response to Alerts(). message AlertsResp{ // A list of alerts that matched the filter. repeated Alert alerts = 1; } service Ops { // ListTraces that the Jaeger has for our application. rpc ListTraces(ListTracesReq) returns (ListTracesResp) {}; // ShowTrace returns the URL of a trace you ask for. rpc ShowTrace(ShowTraceReq) returns (ShowTraceResp) {}; // ShowLogs extracts the logs from a trace. rpc ShowLogs(ShowLogsReq) returns (ShowLogsResp) {}; // ChangeSampling changes the sampling the service is currently using for its traces. rpc ChangeSampling(ChangeSamplingReq) returns (ChangeSamplingResp) {}; // DeployedVersion returns the currently deployed version of the application. rpc DeployedVersion(DeployedVersionReq) returns (DeployedVersionResp) {}; // Alerts returns the currently firing alerts. rpc Alerts(AlertsReq) returns (AlertsResp) {}; } ================================================ FILE: chapter/11/ops/proto/ops_grpc.pb.go ================================================ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. package proto 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.32.0 or later. const _ = grpc.SupportPackageIsVersion7 // OpsClient is the client API for Ops 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 OpsClient interface { // ListTraces that the Jaeger has for our application. ListTraces(ctx context.Context, in *ListTracesReq, opts ...grpc.CallOption) (*ListTracesResp, error) // ShowTrace returns the URL of a trace you ask for. ShowTrace(ctx context.Context, in *ShowTraceReq, opts ...grpc.CallOption) (*ShowTraceResp, error) // ShowLogs extracts the logs from a trace. ShowLogs(ctx context.Context, in *ShowLogsReq, opts ...grpc.CallOption) (*ShowLogsResp, error) // ChangeSampling changes the sampling the service is currently using for its traces. ChangeSampling(ctx context.Context, in *ChangeSamplingReq, opts ...grpc.CallOption) (*ChangeSamplingResp, error) // DeployedVersion returns the currently deployed version of the application. DeployedVersion(ctx context.Context, in *DeployedVersionReq, opts ...grpc.CallOption) (*DeployedVersionResp, error) // Alerts returns the currently firing alerts. Alerts(ctx context.Context, in *AlertsReq, opts ...grpc.CallOption) (*AlertsResp, error) } type opsClient struct { cc grpc.ClientConnInterface } func NewOpsClient(cc grpc.ClientConnInterface) OpsClient { return &opsClient{cc} } func (c *opsClient) ListTraces(ctx context.Context, in *ListTracesReq, opts ...grpc.CallOption) (*ListTracesResp, error) { out := new(ListTracesResp) err := c.cc.Invoke(ctx, "/ops.Ops/ListTraces", in, out, opts...) if err != nil { return nil, err } return out, nil } func (c *opsClient) ShowTrace(ctx context.Context, in *ShowTraceReq, opts ...grpc.CallOption) (*ShowTraceResp, error) { out := new(ShowTraceResp) err := c.cc.Invoke(ctx, "/ops.Ops/ShowTrace", in, out, opts...) if err != nil { return nil, err } return out, nil } func (c *opsClient) ShowLogs(ctx context.Context, in *ShowLogsReq, opts ...grpc.CallOption) (*ShowLogsResp, error) { out := new(ShowLogsResp) err := c.cc.Invoke(ctx, "/ops.Ops/ShowLogs", in, out, opts...) if err != nil { return nil, err } return out, nil } func (c *opsClient) ChangeSampling(ctx context.Context, in *ChangeSamplingReq, opts ...grpc.CallOption) (*ChangeSamplingResp, error) { out := new(ChangeSamplingResp) err := c.cc.Invoke(ctx, "/ops.Ops/ChangeSampling", in, out, opts...) if err != nil { return nil, err } return out, nil } func (c *opsClient) DeployedVersion(ctx context.Context, in *DeployedVersionReq, opts ...grpc.CallOption) (*DeployedVersionResp, error) { out := new(DeployedVersionResp) err := c.cc.Invoke(ctx, "/ops.Ops/DeployedVersion", in, out, opts...) if err != nil { return nil, err } return out, nil } func (c *opsClient) Alerts(ctx context.Context, in *AlertsReq, opts ...grpc.CallOption) (*AlertsResp, error) { out := new(AlertsResp) err := c.cc.Invoke(ctx, "/ops.Ops/Alerts", in, out, opts...) if err != nil { return nil, err } return out, nil } // OpsServer is the server API for Ops service. // All implementations must embed UnimplementedOpsServer // for forward compatibility type OpsServer interface { // ListTraces that the Jaeger has for our application. ListTraces(context.Context, *ListTracesReq) (*ListTracesResp, error) // ShowTrace returns the URL of a trace you ask for. ShowTrace(context.Context, *ShowTraceReq) (*ShowTraceResp, error) // ShowLogs extracts the logs from a trace. ShowLogs(context.Context, *ShowLogsReq) (*ShowLogsResp, error) // ChangeSampling changes the sampling the service is currently using for its traces. ChangeSampling(context.Context, *ChangeSamplingReq) (*ChangeSamplingResp, error) // DeployedVersion returns the currently deployed version of the application. DeployedVersion(context.Context, *DeployedVersionReq) (*DeployedVersionResp, error) // Alerts returns the currently firing alerts. Alerts(context.Context, *AlertsReq) (*AlertsResp, error) mustEmbedUnimplementedOpsServer() } // UnimplementedOpsServer must be embedded to have forward compatible implementations. type UnimplementedOpsServer struct { } func (UnimplementedOpsServer) ListTraces(context.Context, *ListTracesReq) (*ListTracesResp, error) { return nil, status.Errorf(codes.Unimplemented, "method ListTraces not implemented") } func (UnimplementedOpsServer) ShowTrace(context.Context, *ShowTraceReq) (*ShowTraceResp, error) { return nil, status.Errorf(codes.Unimplemented, "method ShowTrace not implemented") } func (UnimplementedOpsServer) ShowLogs(context.Context, *ShowLogsReq) (*ShowLogsResp, error) { return nil, status.Errorf(codes.Unimplemented, "method ShowLogs not implemented") } func (UnimplementedOpsServer) ChangeSampling(context.Context, *ChangeSamplingReq) (*ChangeSamplingResp, error) { return nil, status.Errorf(codes.Unimplemented, "method ChangeSampling not implemented") } func (UnimplementedOpsServer) DeployedVersion(context.Context, *DeployedVersionReq) (*DeployedVersionResp, error) { return nil, status.Errorf(codes.Unimplemented, "method DeployedVersion not implemented") } func (UnimplementedOpsServer) Alerts(context.Context, *AlertsReq) (*AlertsResp, error) { return nil, status.Errorf(codes.Unimplemented, "method Alerts not implemented") } func (UnimplementedOpsServer) mustEmbedUnimplementedOpsServer() {} // UnsafeOpsServer may be embedded to opt out of forward compatibility for this service. // Use of this interface is not recommended, as added methods to OpsServer will // result in compilation errors. type UnsafeOpsServer interface { mustEmbedUnimplementedOpsServer() } func RegisterOpsServer(s grpc.ServiceRegistrar, srv OpsServer) { s.RegisterService(&Ops_ServiceDesc, srv) } func _Ops_ListTraces_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(ListTracesReq) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(OpsServer).ListTraces(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: "/ops.Ops/ListTraces", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(OpsServer).ListTraces(ctx, req.(*ListTracesReq)) } return interceptor(ctx, in, info, handler) } func _Ops_ShowTrace_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(ShowTraceReq) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(OpsServer).ShowTrace(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: "/ops.Ops/ShowTrace", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(OpsServer).ShowTrace(ctx, req.(*ShowTraceReq)) } return interceptor(ctx, in, info, handler) } func _Ops_ShowLogs_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(ShowLogsReq) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(OpsServer).ShowLogs(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: "/ops.Ops/ShowLogs", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(OpsServer).ShowLogs(ctx, req.(*ShowLogsReq)) } return interceptor(ctx, in, info, handler) } func _Ops_ChangeSampling_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(ChangeSamplingReq) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(OpsServer).ChangeSampling(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: "/ops.Ops/ChangeSampling", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(OpsServer).ChangeSampling(ctx, req.(*ChangeSamplingReq)) } return interceptor(ctx, in, info, handler) } func _Ops_DeployedVersion_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(DeployedVersionReq) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(OpsServer).DeployedVersion(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: "/ops.Ops/DeployedVersion", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(OpsServer).DeployedVersion(ctx, req.(*DeployedVersionReq)) } return interceptor(ctx, in, info, handler) } func _Ops_Alerts_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(AlertsReq) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(OpsServer).Alerts(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: "/ops.Ops/Alerts", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(OpsServer).Alerts(ctx, req.(*AlertsReq)) } return interceptor(ctx, in, info, handler) } // Ops_ServiceDesc is the grpc.ServiceDesc for Ops service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) var Ops_ServiceDesc = grpc.ServiceDesc{ ServiceName: "ops.Ops", HandlerType: (*OpsServer)(nil), Methods: []grpc.MethodDesc{ { MethodName: "ListTraces", Handler: _Ops_ListTraces_Handler, }, { MethodName: "ShowTrace", Handler: _Ops_ShowTrace_Handler, }, { MethodName: "ShowLogs", Handler: _Ops_ShowLogs_Handler, }, { MethodName: "ChangeSampling", Handler: _Ops_ChangeSampling_Handler, }, { MethodName: "DeployedVersion", Handler: _Ops_DeployedVersion_Handler, }, { MethodName: "Alerts", Handler: _Ops_Alerts_Handler, }, }, Streams: []grpc.StreamDesc{}, Metadata: "ops.proto", } ================================================ FILE: chapter/11/otel-collector-config.yaml ================================================ receivers: otlp: protocols: grpc: exporters: prometheus: endpoint: "0.0.0.0:8889" const_labels: label1: value1 logging: jaeger: endpoint: jaeger-all-in-one:14250 tls: insecure: true processors: batch: service: pipelines: traces: receivers: [otlp] processors: [batch] exporters: [jaeger] metrics: receivers: [otlp] processors: [batch] exporters: [prometheus] ================================================ FILE: chapter/11/petstore/Dockerfile ================================================ FROM golang:1.17 COPY . /usr/src/server/ WORKDIR /usr/src/server/ RUN go install CMD ["/go/bin/petstore", "--grpcTraces", "--traceSampling=.1"] ================================================ FILE: chapter/11/petstore/README.md ================================================ # Petstore Application ## Introduction In chapter 8, you have seen an example application that did metrics and tracing using Open Telemetry (OTEL). This application was designed to be simple and easy to understand. This application is how a traditional application would be instrumented. This application is based on gRPC platform that we have used before. We track more useful metrics to allow us to detect problems with our application. We have removed most standard logging mechansisms (and no use of zero logger), instead we provide our own log package that writes our log message into the current span. That allows us to narrow our logging noise and avoid all the correlation required if you use standard logging. We only log startup information that is relevant before out service is ready (what ports we ran on, we can't start OTEL, startup args, ...). We have spun off our own error package that logs our errors to the span and generates the error type. This means we don't have to figure out when to log errors, they are always logged to spans. We only use standard errors when the error is not being generated in a span path. We have moved our metrics and tracing constructors to their own packages and out of the main package. This lets us offer multiple places to put our traces or metrics. In the case of tracing, we offer a stderr tracing provider or provider that traces to a file. Finally, we provide our own tracing sampler which wraps one of the standard samplers. This allows us to trace whenever an RPC has the "trace" key in the gRPC request metadata or we receive one with a TraceID set. Otherwise we can do sampling at some rate, for ever RPC or not trace at all. Our sampler can be dialed up or down and this can be down with a management RPC we provide to allow changing our sampling. ## Running - `docker-compose up -d` (if you remove -d, you will see all the logs from the docker jobs in stdout, ^c to make it stop) - Once started the client application will periodically add pets to the server until it runs out of names to add - Metrics will be collected for various things - Traces happen at 10% sampling rate - You can use the cli/petstore application to query the service yourself A sample query for all felines that have birthday's after Jan 1, 2004: `machine:.../client/petstore$ go run petstore.go search types="PTFeline" birthdayStart='{"month":1, "day":1, "year":2004}' If you want to force the query to do a trace, you can add `--trace` after `petstore.go`. Prometheus has metrics at: http://localhost:9090 Traces are in Jaegar at: http://localhost:16686 If you see something like: ```bash docker-compose up -d Traceback (most recent call last): File "urllib3/connectionpool.py", line 670, in urlopen File "urllib3/connectionpool.py", line 392, in _make_request ``` This indicates that you aren't running docker. Make sure you have docker installed and it is running. ## Teardown Simple run; `docker-compose down` ## Structure Here is a breakdown of how the petstore application is made. This is not required for the chapter's use, but looking at the code will give you a deeper insight into how you might want to structure a real application. ``` ├── client │   ├── cli │   │   └── petstore │   │   └── petstore.go │   └── client.go ├── internal │   └── server │   ├── errors │   ├── log │   ├── server.go │   ├── storage │   │   ├── mem │   │   └── storage.go │   └── telemetry │   ├── metrics │   └── tracing │   ├── sampler │   └── tracing.go ├── petstore.go └── proto ``` * client/ Has an RPC client for the service * client/cli/petstore Is a CLI client to send RPCs to the petstore * petstore/ Is the main package * internal/server Is the gRPC service implementation * internal/server/errors The app's error package, works similar to the "errors" package from stdlib * internal/server/log The app's logging pacakge, similar to "log" from the stdlib * storage/ Defines the storage abstraction for the service * storage/mem Defines an in-memory storage implementation of storage.Data * telemetry/metrics Defines all the OpenTelemetry(OTEL) metrics for the application * telemetry/tracing Defines the Opentelemetry(OTEL) tracing for the application * proto/ Contains our protocol buffer definitions and Go packages Of note: * The log package outputs log messages to a tracing Span, not to a log file * If you do need to output some startup info, log.Logger is the standard log.Logger type, defaults to the default Logger * The errors package is a drop in replacement for "log", except Errorf() and New() take a Context * errors will write the error out to the current Span ================================================ FILE: chapter/11/petstore/client/cli/petstore/petstore.go ================================================ package main import ( "context" "flag" "fmt" "os" "strings" "time" "github.com/PacktPublishing/Go-for-DevOps/chapter/11/petstore/client" "google.golang.org/protobuf/encoding/protojson" pb "github.com/PacktPublishing/Go-for-DevOps/chapter/11/petstore/proto" dpb "google.golang.org/genproto/googleapis/type/date" ) var addr = flag.String("addr", "127.0.0.1:6742", "The host:port to connect to") const helpText = ` Petstore CLI Client Help Command Add: Adds pets to the petstore and returns a list of IDs. Syntax: petstore add [pet description in JSON] Example: petstore add '{"name":"Stevie Nicks", "type":"PTFeline", "birthday": {"month": 6, "day": 1, "year": 2005}}' Command Delete: Deletes pets from the petstore. Syntax: petstore delete [id] [id] [id] ... Example: petstore delete 62809742-2de1-4208-a8cc-df485c48c563 83968fb4-9502-4df1-8680-7691fc1d3abe Command Search: Searches for pets in the petstore. Syntax: petstore search param="value" parame="value" Params: Names - Comma separated list of names Type - Comma separated list of pet types BirthdayStart - JSON version of proto date BirthdayEnd - JSON version of proto date Note: If BirthdayStart is provided by not BirthdayEnd, it will be set to the current date + 1 day. If the reverse, BirthdayStart will be set to the Go's zero time. Example: petstore search names="Stevie Nicks, Frank" types="PTFeline" birthdayStart='{"month":1, "day":1, "year":2004}' ` func main() { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() if len(os.Args) < 2 { fmt.Println("Error: arguments are not valid") fmt.Println(helpText) os.Exit(1) } c, err := client.New(*addr) if err != nil { fmt.Printf("Error: problem connecting to server: %s\n", err) os.Exit(1) } cmd := strings.ToLower(os.Args[1]) switch cmd { case "add": if len(os.Args) < 3 { fmt.Println("Error: not enough arguments to add command") fmt.Println(helpText) os.Exit(1) } p := &pb.Pet{} j := os.Args[2] if err := protojson.Unmarshal([]byte(j), p); err != nil { fmt.Printf("Error: problem with your pet description: %s\n", err) os.Exit(1) } ids, err := c.AddPets(ctx, []*pb.Pet{p}) if err != nil { fmt.Printf("Error: problem adding your pets: %s\n", err) os.Exit(1) } fmt.Printf("%s was added as %s\n", p.Name, ids[0]) return case "delete": if len(os.Args) < 3 { fmt.Println("Error: not enough arguments to delete command") fmt.Println(helpText) os.Exit(1) } if err := c.DeletePets(ctx, os.Args[2:]); err != nil { fmt.Printf("Error: problem deleting: %s\n", err) os.Exit(1) } fmt.Println("Delete succeeded") return case "search": if len(os.Args) < 3 { fmt.Println("Error: not enough arguments to search command") fmt.Println(helpText) os.Exit(1) } r := getSearchReq() ch, err := c.SearchPets(ctx, r) if err != nil { fmt.Println(err) os.Exit(1) } for p := range ch { if p.Error() != nil { fmt.Println(p.Error()) os.Exit(1) } fmt.Println(protojson.Format(p)) } case "help": fmt.Println(helpText) default: fmt.Println("Error: unknown command: ", cmd) fmt.Println(helpText) } } func getSearchReq() *pb.SearchPetsReq { argsSeen := map[string]bool{ "names": false, "types": false, "birthdayStart": false, "birthdayEnd": false, } r := &pb.SearchPetsReq{} for _, arg := range os.Args[2:] { switch { case strings.HasPrefix(arg, "names"): if argsSeen["names"] { fmt.Println("cannot have multiple 'names' parameters") os.Exit(1) } sp := strings.Split(arg, "=") if len(sp) != 2 { fmt.Println("names parameter is malformed") os.Exit(1) } names := strings.Trim(sp[1], `"`) sp = strings.Split(names, ",") for _, name := range sp { r.Names = append(r.Names, strings.TrimSpace(name)) } argsSeen["names"] = true case strings.HasPrefix(arg, "types"): if argsSeen["types"] { fmt.Println("cannot have multiple 'types' parameters") os.Exit(1) } sp := strings.Split(arg, "=") if len(sp) != 2 { fmt.Println("types parameter is malformed") os.Exit(1) } types := strings.Trim(sp[1], `"`) sp = strings.Split(types, ",") for _, t := range sp { t = strings.TrimSpace(t) e, ok := pb.PetType_value[t] if !ok { fmt.Printf("types parameter had value %q that we do not recognize\n", t) } r.Types = append(r.Types, pb.PetType(e)) } argsSeen["types"] = true case strings.HasPrefix(arg, "birthdayStart"): if argsSeen["birthdayStart"] { fmt.Println("cannot have multiple 'birthdayStart' parameters") os.Exit(1) } sp := strings.Split(arg, "=") if len(sp) != 2 { fmt.Println("birthdayStart parameter is malformed") os.Exit(1) } start := strings.Trim(sp[1], `"`) d := &dpb.Date{} if err := protojson.Unmarshal([]byte(start), d); err != nil { fmt.Printf("birthdayStart parameter is malformed: %s\n", err) os.Exit(1) } if r.BirthdateRange == nil { r.BirthdateRange = &pb.DateRange{ Start: d, } } else { r.BirthdateRange.Start = d } argsSeen["birthdayStart"] = true case strings.HasPrefix(arg, "birthdayEnd"): if argsSeen["birthdayEnd"] { fmt.Println("cannot have multiple 'birthdayEnd' parameters") os.Exit(1) } sp := strings.Split(arg, "=") if len(sp) != 2 { fmt.Println("birthdayEnd parameter is malformed") os.Exit(1) } end := strings.Trim(sp[1], `"`) d := &dpb.Date{} if err := protojson.Unmarshal([]byte(end), d); err != nil { fmt.Printf("birthdayEnd parameter is malformed: %s\n", err) os.Exit(1) } if r.BirthdateRange == nil { r.BirthdateRange = &pb.DateRange{ End: d, } } else { r.BirthdateRange.End = d } argsSeen["birthdayEnd"] = true default: fmt.Printf("parameter %q is one we don't support\n", arg) os.Exit(1) } // If one of the birthday ranges was put in (start or end) but not the other, add the other. if r.BirthdateRange != nil { rng := r.BirthdateRange if rng.Start != nil && rng.End == nil { t := time.Now().Add(24 * time.Hour) rng.End = &dpb.Date{Month: int32(t.Month()), Day: int32(t.Day()), Year: int32(t.Year())} } if rng.End != nil && rng.Start == nil { t := time.Time{} rng.Start = &dpb.Date{Month: int32(t.Month()), Day: int32(t.Day()), Year: int32(t.Year())} } } } return r } ================================================ FILE: chapter/11/petstore/client/client.go ================================================ // Client provides an API client to the petstore service. package client import ( "context" "fmt" "io" "time" "github.com/PacktPublishing/Go-for-DevOps/chapter/11/petstore/internal/server/storage" "google.golang.org/grpc" "google.golang.org/grpc/metadata" pb "github.com/PacktPublishing/Go-for-DevOps/chapter/11/petstore/proto" ) // Client is a client to the petstore service. type Client struct { client pb.PetStoreClient conn *grpc.ClientConn } // New is the constructor for Client. addr is the server's [host]:[port]. func New(addr string) (*Client, error) { conn, err := grpc.Dial(addr, grpc.WithInsecure()) if err != nil { return nil, err } return &Client{ client: pb.NewPetStoreClient(conn), conn: conn, }, nil } // Pet is a wrapper around a *pb.Pet that can return Go versions of // fields and errors if the returned stream has an error. type Pet struct { *pb.Pet err error } // Proto will give the Pet's proto representation. func (p Pet) Proto() *pb.Pet { return p.Pet } // Birthday returns the Pet's birthday as a time.Time. func (p Pet) Birthday() time.Time { // We are ignoring the error as we will either get a zero time // anyways and the server should be preventing this problem. t, _ := storage.BirthdayToTime(context.Background(), p.Pet.Birthday) return t } // Error indicates if there was an error in the Pet output stream. func (p Pet) Error() error { return p.err } // CallOptions are optional options for an RPC call. type CallOption func(co *callOptions) type callOptions struct { trace *string } // TraceID will cause the RPC call to execute a trace on the service and return "s" to the ID. // If s == nil, this will ignore the option. If "s" is not set after the call finishes, then // no trace was made. func TraceID(s *string) CallOption { return func(co *callOptions) { if s == nil { return } co.trace = s } } // AddPets adds pets to the service and returns their unique identities in the // same order as being added. func (c *Client) AddPets(ctx context.Context, pets []*pb.Pet, options ...CallOption) ([]string, error) { if len(pets) == 0 { return nil, nil } for _, p := range pets { if err := storage.ValidatePet(ctx, p, false); err != nil { return nil, err } } var header metadata.MD ctx, gOpts, f := handleCallOptions(ctx, &header, options) defer f() resp, err := c.client.AddPets(ctx, &pb.AddPetsReq{Pets: pets}, gOpts...) if err != nil { return nil, err } return resp.Ids, nil } // UpdatePets updates pets that already exist in the system. func (c *Client) UpdatePets(ctx context.Context, pets []*pb.Pet, options ...CallOption) error { if len(pets) == 0 { return nil } for _, p := range pets { if err := storage.ValidatePet(ctx, p, true); err != nil { return err } } var header metadata.MD ctx, gOpts, f := handleCallOptions(ctx, &header, options) defer f() _, err := c.client.UpdatePets(ctx, &pb.UpdatePetsReq{Pets: pets}, gOpts...) if err != nil { return err } return nil } // DeletePets deletes pets with the IDs passed. If the ID doesn't exist, the // system ignores it. func (c *Client) DeletePets(ctx context.Context, ids []string, options ...CallOption) error { if len(ids) == 0 { return nil } var header metadata.MD ctx, gOpts, f := handleCallOptions(ctx, &header, options) defer f() _, err := c.client.DeletePets(ctx, &pb.DeletePetsReq{Ids: ids}, gOpts...) if err != nil { return err } return nil } // SearchPets searches the pet store for pets matching the filter. If the filter contains // no entries, then all pets will be returned. func (c *Client) SearchPets(ctx context.Context, filter *pb.SearchPetsReq, options ...CallOption) (chan Pet, error) { if filter == nil { return nil, fmt.Errorf("the filter cannot be nil") } var header metadata.MD ctx, gOpts, f := handleCallOptions(ctx, &header, options) stream, err := c.client.SearchPets(ctx, filter, gOpts...) if err != nil { return nil, err } ch := make(chan Pet, 1) go func() { defer close(ch) defer f() for { p, err := stream.Recv() if err == io.EOF { return } if err != nil { ch <- Pet{err: err} return } ch <- Pet{Pet: p} } }() return ch, nil } // SamplerType is the type of OTEL sampling to do. type SamplerType int32 const ( STUnknown SamplerType = 0 Never SamplerType = 1 Always SamplerType = 2 Float SamplerType = 3 ) var validTypes = map[SamplerType]bool{ Never: true, Always: true, Float: true, } type Sampler struct { // Type is the type of sampling to use. Type SamplerType // Rate is the sampling rate, only used if type is Float. Rate float64 } func (s *Sampler) validate() error { if !validTypes[s.Type] { return fmt.Errorf("type %v is not a supported type", s.Type) } if s.Type == Float { if s.Rate <= 0 || s.Rate > 1 { return fmt.Errorf("Rate must be > 0 && <= 1.0, was %v", s.Rate) } } return nil } func (s *Sampler) proto() *pb.Sampler { return &pb.Sampler{ Type: pb.SamplerType(s.Type), FloatValue: s.Rate, } } func (s *Sampler) fromProto(p *pb.Sampler) { s.Type = SamplerType(p.Type) s.Rate = p.FloatValue } // ChangeSampler changes the sampling type and rate on the server. This is // and admin function that in production should be restricted. func (c *Client) ChangeSampler(ctx context.Context, sc Sampler, options ...CallOption) error { if err := sc.validate(); err != nil { return err } var header metadata.MD ctx, gOpts, f := handleCallOptions(ctx, &header, options) defer f() _, err := c.client.ChangeSampler(ctx, &pb.ChangeSamplerReq{Sampler: sc.proto()}, gOpts...) if err != nil { return err } return nil } func handleCallOptions(ctx context.Context, header *metadata.MD, options []CallOption) (context.Context, []grpc.CallOption, func()) { opts := callOptions{} for _, o := range options { o(&opts) } var gOpts []grpc.CallOption if opts.trace != nil { (*header)["trace"] = nil gOpts = append(gOpts, grpc.Header(header)) } f := func() { if opts.trace != nil { if len((*header)["otel.traceID"]) != 0 { *opts.trace = (*header)["otel.traceID"][0] } } } return ctx, gOpts, f } ================================================ FILE: chapter/11/petstore/client/demo/Dockerfile ================================================ FROM golang:1.17 COPY . /usr/src/demo/ WORKDIR /usr/src/demo/ RUN go env -w GOPROXY=direct GO111MODULE=on WORKDIR /usr/src/demo/client/demo RUN go install CMD ["/go/bin/demo"] ================================================ FILE: chapter/11/petstore/client/demo/demo.go ================================================ /* The demo will take a list of pet names and insert them into the Petstore every 1/2 a second. At the same time, starting 10 seconds after starting to add pets, another goroutine will start and begin random searching for pets in the name file. It will do this len(names) times. Because this is random, sometimes this will be an error (because the pet hasn't been added yet) and sometimes a success. The longer this goes on, the less likely there will be an error. */ package main import ( "context" _ "embed" "log" "math/rand" "strings" "sync" "time" "github.com/PacktPublishing/Go-for-DevOps/chapter/11/petstore/client" pb "github.com/PacktPublishing/Go-for-DevOps/chapter/11/petstore/proto" dpb "google.golang.org/genproto/googleapis/type/date" ) //go:embed names.txt var namesFile string func main() { ctx := context.Background() time.Sleep(1 * time.Second) c, err := client.New("petstore:6742") if err != nil { panic(err) } names := strings.Split(namesFile, "\n") wg := sync.WaitGroup{} wg.Add(1) go func() { defer wg.Done() addNames(ctx, c, names) }() time.Sleep(10 * time.Second) wg.Add(1) go func() { defer wg.Done() searchNames(ctx, c, names) }() wg.Wait() } func addNames(ctx context.Context, c *client.Client, names []string) { start := time.Date(2020, 1, 1, 1, 1, 0, 0, time.UTC) t := 1 for _, name := range names { if strings.TrimSpace(name) == "" { continue } ids, err := c.AddPets( ctx, []*pb.Pet{ { Name: name, Type: pb.PetType(t), Birthday: &dpb.Date{ Month: int32(start.Month()), Day: int32(start.Day()), Year: int32(start.Year()), }, }, }, ) if err != nil { panic("had an unexpected problem: " + err.Error()) } log.Println("Added pet with ID: ", ids[0]) t++ // Only 4 pet types. if t == 5 { t = 1 } start.Add(24 * time.Hour) time.Sleep(500 * time.Millisecond) } } func searchNames(ctx context.Context, c *client.Client, names []string) { r := rand.New(rand.NewSource(time.Now().UnixNano())) l := len(names) for i := 0; i < len(names); i++ { x := r.Intn(l) ch, err := c.SearchPets(ctx, &pb.SearchPetsReq{Names: []string{names[x]}}) if err != nil { log.Fatalf("Search(%s): bad error: %s", names[x], err) } var results []client.Pet for result := range ch { results = append(results, result) } if len(results) > 0 { log.Printf("Search(%s): found", names[x]) } else { log.Printf("Search(%s): pet not found", names[x]) } time.Sleep(1 * time.Second) } } ================================================ FILE: chapter/11/petstore/client/demo/names.txt ================================================ Abigail Ace Adam Addie Admiral Aggie Aires Aj Ajax Aldo Alex Alexus Alf Alfie Allie Ally Amber Amie Amigo Amos Amy Andy Angel Angus Annie Apollo April Archie Argus Aries Armanti Arnie Arrow Ashes Ashley Astro Athena Atlas Audi Augie Aussie Austin Autumn Axel Axle Babbles Babe Baby Baby-doll Babykins Bacchus Bailey Bam-bam Bambi Bandit Banjo Barbie Barclay Barker Barkley Barley Barnaby Barney Baron Bart Basil Baxter Bb Beamer Beanie Beans Bear Beau Beauty Beaux Bebe Beetle Bella Belle Ben Benji Benny Benson Bentley Bernie Bessie Biablo Bibbles Big Boy Big Foot Biggie Billie Billy Bingo Binky Birdie Birdy Biscuit Bishop Gus Guy Gypsy Hailey Haley Hallie Hamlet Hammer Hank Hanna Hannah Hans Happy Hardy Harley Harpo Harrison Harry Harvey Heather Heidi Henry Hercules Hershey Higgins Hobbes Holly Homer Honey Honey-Bear Hooch Hoover Hope Houdini Howie Hudson Huey Hugh Hugo Humphrey Hunter India Indy Iris Isabella Isabelle Itsy Itsy-bitsy Ivory Ivy Izzy Jack Jackie Jackpot Jackson Jade Jagger Jags Jaguar Jake Jamie Jasmine Jasper Jaxson Jazmie Jazz Jelly Jelly-bean Jenna Jenny Jerry Jersey Jess Jesse Jesse James Jessie Jester Jet Jethro Jett Jetta Jewel Jewels Jimmy Jingles JJ Joe Joey Johnny Jojo Joker Jolie Jolly Jordan Josie Joy JR Judy Julius June Misty Mitch Mittens Mitzi Mitzy Mo Mocha Mollie Molly Mona Muffy Nakita Nala Nana Natasha Nellie Nemo Nena Peanut Peanuts Pearl Pebbles Penny Phoebe Phoenix Sara Sarah Sasha Sassie Sassy Savannah Scarlett Shasta Sheba Sheena Shelby Shelly Sienna Sierra Silky Silver Simone Sissy Skeeter Sky Skye Skyler Waldo Wallace Wally Walter Wayne Weaver Webster Wesley Westie ================================================ FILE: chapter/11/petstore/docker-compose.yaml ================================================ version: "2" services: # Jaeger jaeger-all-in-one: image: jaegertracing/all-in-one:latest ports: - "16686:16686" - "14268" - "14250" # Collector otel-collector: image: ${OTELCOL_IMG} command: ["--config=/etc/otel-collector-config.yaml", "${OTELCOL_ARGS}"] volumes: - ./otel-collector-config.yaml:/etc/otel-collector-config.yaml ports: - "1888:1888" # pprof extension - "8888:8888" # Prometheus metrics exposed by the collector - "8889:8889" # Prometheus exporter metrics - "13133:13133" # health_check extension - "4317" # OTLP gRPC receiver - "55670:55679" # zpages extension depends_on: - jaeger-all-in-one demo-client: build: dockerfile: ./client/demo/Dockerfile context: ./ environment: - OTEL_EXPORTER_OTLP_ENDPOINT=otel-collector:4317 depends_on: - demo-server demo-server: build: dockerfile: Dockerfile context: ./ environment: - OTEL_EXPORTER_OTLP_ENDPOINT=otel-collector:4317 ports: - "6742:6742" depends_on: - otel-collector prometheus: container_name: prometheus image: prom/prometheus:latest volumes: - ./prometheus.yaml:/etc/prometheus/prometheus.yml ports: - "9090:9090" ================================================ FILE: chapter/11/petstore/go.mod ================================================ module github.com/PacktPublishing/Go-for-DevOps/chapter/11/petstore go 1.18 require ( github.com/biogo/store v0.0.0-20201120204734-aad293a2328f github.com/google/uuid v1.3.0 github.com/kylelemons/godebug v1.1.0 go.opentelemetry.io/otel v1.4.1 go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.27.0 go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v0.27.0 go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.4.1 go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.4.1 go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.4.1 go.opentelemetry.io/otel/metric v0.27.0 go.opentelemetry.io/otel/sdk v1.4.1 go.opentelemetry.io/otel/sdk/metric v0.27.0 go.opentelemetry.io/otel/trace v1.4.1 google.golang.org/genproto v0.0.0-20220324131243-acbaeb5b85eb google.golang.org/grpc v1.45.0 google.golang.org/protobuf v1.28.0 ) require ( github.com/cenkalti/backoff/v4 v4.1.2 // indirect github.com/go-logr/logr v1.2.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/golang/protobuf v1.5.2 // indirect github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.6.0 // indirect go.opentelemetry.io/otel/internal/metric v0.27.0 // indirect go.opentelemetry.io/proto/otlp v0.12.0 // indirect golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 // indirect golang.org/x/sys v0.0.0-20210510120138-977fb7262007 // indirect golang.org/x/text v0.3.5 // indirect ) ================================================ FILE: chapter/11/petstore/go.sum ================================================ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A= github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/biogo/store v0.0.0-20201120204734-aad293a2328f h1:+6okTAeUsUrdQr/qN7fIODzowrjjCrnJDg/gkYqcSXY= github.com/biogo/store v0.0.0-20201120204734-aad293a2328f/go.mod h1:z52shMwD6SGwRg2iYFjjDwX5Ene4ENTw6HfXraUy/08= github.com/cenkalti/backoff/v4 v4.1.2 h1:6Yo7N8UP2K6LWZnW94DLVSSrbobcWdVzAYOisuDPIFo= github.com/cenkalti/backoff/v4 v4.1.2/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.1.1/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/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/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.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= 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/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 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.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 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.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 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.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o= github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= 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/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= go.opentelemetry.io/otel v1.4.0/go.mod h1:jeAqMFKy2uLIxCtKxoFj0FAL5zAPKQagc3+GtBWakzk= go.opentelemetry.io/otel v1.4.1 h1:QbINgGDDcoQUoMJa2mMaWno49lja9sHwp6aoa2n3a4g= go.opentelemetry.io/otel v1.4.1/go.mod h1:StM6F/0fSwpd8dKWDCdRr7uRvEPYdW0hBSlbdTiUde4= go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.4.0/go.mod h1:VpP4/RMn8bv8gNo9uK7/IMY4mtWLELsS+JIP0inH0h4= go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.4.1/go.mod h1:VpP4/RMn8bv8gNo9uK7/IMY4mtWLELsS+JIP0inH0h4= go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.6.0 h1:XFcfoo+vwXXwopiS7vzwbaFuPplf5GB+WTjaiQXmz3U= go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.6.0/go.mod h1:NEu79Xo32iVb+0gVNV8PMd7GoWqnyDXRlj04yFjqz40= go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.27.0 h1:t1aPfMj5oZzv2EaRmdC2QPQg1a7MaBjraOh4Hjwuia8= go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.27.0/go.mod h1:aZnoYVx7GIuMROciGC3cjZhYxMD/lKroRJUnFY0afu0= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v0.27.0 h1:RJURCSrqUjJiCY3GuFCVP2EPKOQLwNXQ4FI3aH2KoHg= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v0.27.0/go.mod h1:LIc1eCpkU94tPnXxH40ya41Oyxm7sL+oDvxCYPFpnV8= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.4.1 h1:WPpPsAAs8I2rA47v5u0558meKmmwm1Dj99ZbqCV8sZ8= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.4.1/go.mod h1:o5RW5o2pKpJLD5dNTCmjF1DorYwMeFJmb/rKr5sLaa8= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.4.1 h1:AxqDiGk8CorEXStMDZF5Hz9vo9Z7ZZ+I5m8JRl/ko40= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.4.1/go.mod h1:c6E4V3/U+miqjs/8l950wggHGL1qzlp0Ypj9xoGrPqo= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.4.1 h1:yaXaoJjXaJqRnsfW9HrN7pGb7bzcEn31Rk6yo2LFaWo= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.4.1/go.mod h1:BFiGsTMZdqtxufux8ANXuMeRz9dMPVFdJZadUWDFD7o= go.opentelemetry.io/otel/internal/metric v0.27.0 h1:9dAVGAfFiiEq5NVB9FUJ5et+btbDQAUIJehJ+ikyryk= go.opentelemetry.io/otel/internal/metric v0.27.0/go.mod h1:n1CVxRqKqYZtqyTh9U/onvKapPGv7y/rpyOTI+LFNzw= go.opentelemetry.io/otel/metric v0.27.0 h1:HhJPsGhJoKRSegPQILFbODU56NS/L1UE4fS1sC5kIwQ= go.opentelemetry.io/otel/metric v0.27.0/go.mod h1:raXDJ7uP2/Jc0nVZWQjJtzoyssOYWu/+pjZqRzfvZ7g= go.opentelemetry.io/otel/sdk v1.4.0/go.mod h1:71GJPNJh4Qju6zJuYl1CrYtXbrgfau/M9UAggqiy1UE= go.opentelemetry.io/otel/sdk v1.4.1 h1:J7EaW71E0v87qflB4cDolaqq3AcujGrtyIPGQoZOB0Y= go.opentelemetry.io/otel/sdk v1.4.1/go.mod h1:NBwHDgDIBYjwK2WNu1OPgsIc2IJzmBXNnvIJxJc8BpE= go.opentelemetry.io/otel/sdk/metric v0.27.0 h1:CDEu96Js5IP7f4bJ8eimxF09V5hKYmE7CeyKSjmAL1s= go.opentelemetry.io/otel/sdk/metric v0.27.0/go.mod h1:lOgrT5C3ORdbqp2LsDrx+pBj6gbZtQ5Omk27vH3EaW0= go.opentelemetry.io/otel/trace v1.4.0/go.mod h1:uc3eRsqDfWs9R7b92xbQbU42/eTNz4N+gLP8qJCi4aE= go.opentelemetry.io/otel/trace v1.4.1 h1:O+16qcdTrT7zxv2J6GejTPFinSwA++cYerC5iSiF8EQ= go.opentelemetry.io/otel/trace v1.4.1/go.mod h1:iYEVbroFCNut9QkwEczV9vMRPHNKSSwYZjulEtsmhFc= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.opentelemetry.io/proto/otlp v0.12.0 h1:CMJ/3Wp7iOWES+CYLfnBv+DVmPbB+kmy9PJ92XvlR6c= go.opentelemetry.io/proto/otlp v0.12.0/go.mod h1:TsIjwGWIx5VFYv9KGVlOpxoBl5Dy+63SUguV7GGvlSQ= go.uber.org/goleak v1.1.12 h1:gZAh5/EyT/HQwlpkCy6wTpqfH9H8Lz8zbm3dZh+OyzA= go.uber.org/goleak v1.1.12/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 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/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 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-20190108225652-1e06a53dbb7e/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-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 h1:4nGaVu0QrbjT/AK2PRLuQfQuh6DJve+pELhqTdAj3x0= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 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-20181221193216-37e7f081c4d4/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.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 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-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007 h1:gG67DSER+11cZvqIMb8S8bt0vZtiN6xWYARwirrOSfE= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 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.3.5 h1:i6eZZ+zk0SOf0xgBpEpPD18qWcJda6q1sxt3S0kzyUQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 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/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 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-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20220324131243-acbaeb5b85eb h1:0m9wktIpOxGw+SSKmydXWB3Z3GTfcPP6+q75HCQa6HI= google.golang.org/genproto v0.0.0-20220324131243-acbaeb5b85eb/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E= 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.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.43.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= google.golang.org/grpc v1.45.0 h1:NEpgUqV3Z+ZjkqMsxMg11IaDrXY4RY6CQukSGK0uI1M= google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ= 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.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw= google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= ================================================ FILE: chapter/11/petstore/internal/server/errors/errors.go ================================================ // Package errors is a replacement for the golang standard library "errors". This replacement // adds errors to the Open Telemetry spans. The signatures only differs in that // New() now takes a context.Context object and fmt.Errorf() has been moved here and also takes a Context.Context. package errors import ( "context" "errors" "fmt" "go.opentelemetry.io/otel/codes" "go.opentelemetry.io/otel/trace" ) // New creates a new error and writes the error to a span if it exists in the context. func New(ctx context.Context, text string) error { span := trace.SpanFromContext(ctx) err := errors.New(text) span.RecordError(err) span.SetStatus(codes.Error, err.Error()) return err } // Errorf implements fmt.Errorf with the addition of a Context that if it contains a span // will have the error added to the span. func Errorf(ctx context.Context, s string, i ...interface{}) error { span := trace.SpanFromContext(ctx) err := fmt.Errorf(s, i...) span.RecordError(err) span.SetStatus(codes.Error, err.Error()) return err } // As implements errors.As(). func As(err error, target interface{}) bool { return errors.As(err, target) } // Is implements errors.Is(). func Is(err, target error) bool { return errors.Is(err, target) } // Unwrap implemements errors.Unwrap(). func Unwrap(err error) error { return errors.Unwrap(err) } ================================================ FILE: chapter/11/petstore/internal/server/log/log.go ================================================ /* Package log is a replacement for the standard library log package that logs to OTEL spans contained in Context objects. These are seen as events with the attribute "log" set to true. The preferred way to log is to use an event: func someFunc(ctx context.Context) { e := NewEvent("someFunc()") defer e.Done(ctx) start := time.Now() defer func() { e.Add("latency.ns", int(time.Since(start))) }() } This records an event in the current span that has a key of "latency.ns" with the value in nano-seconds the operation took. You can use this to log in a similar manner to the logging package with Println and Printf. This is generally only useful for some generic debugging where you want to log something and filter the trace by messages with key "log". Generally these are messages you don't want to keep. func main() { ctx := context.Background() log.SetFlags(log.LstdFlags | log.Lshortfile) log.Println(ctx, "Starting main") log.Printf(ctx, "Env variables: %v", os.Environ()) } The above won't log anything, as there is no Span on the Context. If there was it would get output to the Open Telementry provider. If you need to use the standard library log, you can use Logger: log.Logger.Println("hello world") This would print whever the stanard logger prints to. This defaults to the standard logger, but you can replace with another Logger if you wish. You should only log messages with a standard logger when it can't be output to a trace. These are critical messages that indicate a definite bug. This keeps logging to only critical events and de-clutters what you need to look at to when doing a debug. */ package log import ( "context" "fmt" "log" "runtime" "sync" "time" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/trace" ) const ( Ldate = 1 << iota // the date in the local time zone: 2009/01/23 Ltime // the time in the local time zone: 01:23:23 Lmicroseconds // microsecond resolution: 01:23:23.123123. assumes Ltime. Llongfile // full file name and line number: /a/b/c/d.go:23 Lshortfile // final file name element and line number: d.go:23. overrides Llongfile LUTC // if Ldate or Ltime is set, use UTC rather than the local time zone LstdFlags = Ldate | Ltime // initial values for the standard logger ) // Logger provides access to the standard library's default logger. // This can be replaced in main with a logger of your choice. var Logger *log.Logger = log.Default() // setup the standard logger with flags. var std = &logger{flag: LstdFlags} // pool provides a pool of Event objects to keep our allocations to a minimum. var pool = &eventPool{ buf: make(chan *Event, 100), pool: sync.Pool{ New: func() interface{} { return &Event{} }, }, } // eventPool uses a set amount of Event objects and a sync.Pool for overflow. // Note: this would actually be a great place for metrics to key in on what would be // an optimal size for buf to prevent pool use. type eventPool struct { buf chan *Event pool sync.Pool } func (e *eventPool) get() *Event { select { case ev := <-e.buf: return ev default: } return e.pool.Get().(*Event) } func (e *eventPool) put(ev *Event) { ev.reset() select { case e.buf <- ev: default: } e.pool.Put(ev) } // Event represents a named event that occurs. This is the prefered way to log data. // Events have attributes and those attributes are key/value pairs. You create // an event and stuff attributes using Add() until the event is over and call Done(). // This will render the event to the current span. if no attrs exist, the event is ignored. // To avoid extra allocations type Event struct { name string attrs []attribute.KeyValue } // NewEvent returns a new Event. func NewEvent(name string) *Event { ev := pool.get() ev.name = name return ev } func (e *Event) reset() { e.name = "" e.attrs = e.attrs[0:0] } // Add adds an attribute named k with value i. i can be: bool, []bool, float64, []float64, int, []int, int64, []int64, string and []string. // If the value isn't one of those values, a standard log message is printed indicating a bug. func (e *Event) Add(k string, i interface{}) { if e.name == "" { return } switch v := i.(type) { case bool: e.attrs = append(e.attrs, attribute.Bool(k, v)) case []bool: e.attrs = append(e.attrs, attribute.BoolSlice(k, v)) case float64: e.attrs = append(e.attrs, attribute.Float64(k, v)) case []float64: e.attrs = append(e.attrs, attribute.Float64Slice(k, v)) case int: e.attrs = append(e.attrs, attribute.Int(k, v)) case []int: e.attrs = append(e.attrs, attribute.IntSlice(k, v)) case int64: e.attrs = append(e.attrs, attribute.Int64(k, v)) case []int64: e.attrs = append(e.attrs, attribute.Int64Slice(k, v)) case string: e.attrs = append(e.attrs, attribute.String(k, v)) case []string: e.attrs = append(e.attrs, attribute.StringSlice(k, v)) case time.Duration: e.attrs = append(e.attrs, attribute.String(k, v.String())) default: log.Printf("bug: event.Add(): receiveing %T which is not supported", v) } } // Done renders the Event to the span in the Context. If there are no attributes on the Event, this is a no-oop. // Once Done is called, the Event object MUST not be used again. func (e *Event) Done(ctx context.Context) { defer pool.put(e) if e.name == "" { return } span := trace.SpanFromContext(ctx) if e.attrs == nil { return } span.AddEvent(e.name, trace.WithAttributes(e.attrs...)) } // Println acts like log.Println() except we log to the OTEL span in the Context. func Println(ctx context.Context, v ...interface{}) { span := trace.SpanFromContext(ctx) if !span.IsRecording() { return } std.output(span, 2, fmt.Sprintln(v...)) } // Printf acts like log.Printf() except we log to the OTEL span in the Context. func Printf(ctx context.Context, format string, v ...interface{}) { span := trace.SpanFromContext(ctx) if !span.IsRecording() { return } std.output(span, 2, fmt.Sprintf(format, v...)) } // SetFlags sets the output flags for the standard logger. func SetFlags(flag int) { std.flag = flag } // logger is an implementation of log.Logger that writes to a Span. type logger struct { mu sync.Mutex flag int // properties buf []byte // for accumulating text to write } // Cheap integer to fixed-width decimal ASCII. Give a negative width to avoid zero-padding. func itoa(buf *[]byte, i int, wid int) { // Assemble decimal in reverse order. var b [20]byte bp := len(b) - 1 for i >= 10 || wid > 1 { wid-- q := i / 10 b[bp] = byte('0' + i - q*10) bp-- i = q } // i < 10 b[bp] = byte('0' + i) *buf = append(*buf, b[bp:]...) } func (l *logger) output(span trace.Span, calldepth int, s string) error { now := time.Now() // get this early var file string var line int l.mu.Lock() defer l.mu.Unlock() if l.flag&(Lshortfile|Llongfile) != 0 { // Release lock while getting caller info - it's expensive. l.mu.Unlock() var ok bool _, file, line, ok = runtime.Caller(calldepth) if !ok { file = "???" line = 0 } l.mu.Lock() } l.buf = l.buf[:0] l.formatHeader(&l.buf, now, file, line) l.buf = append(l.buf, s...) if len(s) == 0 || s[len(s)-1] != '\n' { l.buf = append(l.buf, '\n') } span.AddEvent(string(l.buf), trace.WithAttributes(attribute.Bool("log", true))) return nil } // formatHeader writes log header to buf in following order: // * date and/or time (if corresponding flags are provided), // * file and line number (if corresponding flags are provided), func (l *logger) formatHeader(buf *[]byte, t time.Time, file string, line int) { if l.flag&(Ldate|Ltime|Lmicroseconds) != 0 { if l.flag&LUTC != 0 { t = t.UTC() } if l.flag&Ldate != 0 { year, month, day := t.Date() itoa(buf, year, 4) *buf = append(*buf, '/') itoa(buf, int(month), 2) *buf = append(*buf, '/') itoa(buf, day, 2) *buf = append(*buf, ' ') } if l.flag&(Ltime|Lmicroseconds) != 0 { hour, min, sec := t.Clock() itoa(buf, hour, 2) *buf = append(*buf, ':') itoa(buf, min, 2) *buf = append(*buf, ':') itoa(buf, sec, 2) if l.flag&Lmicroseconds != 0 { *buf = append(*buf, '.') itoa(buf, t.Nanosecond()/1e3, 6) } *buf = append(*buf, ' ') } } if l.flag&(Lshortfile|Llongfile) != 0 { if l.flag&Lshortfile != 0 { short := file for i := len(file) - 1; i > 0; i-- { if file[i] == '/' { short = file[i+1:] break } } file = short } *buf = append(*buf, file...) *buf = append(*buf, ':') itoa(buf, line, -1) *buf = append(*buf, ": "...) } } ================================================ FILE: chapter/11/petstore/internal/server/server.go ================================================ // Package server contains our gRPC server implementation for the pet store. package server import ( "context" "fmt" "net" "strconv" "sync" "time" "github.com/PacktPublishing/Go-for-DevOps/chapter/11/petstore/internal/server/errors" "github.com/PacktPublishing/Go-for-DevOps/chapter/11/petstore/internal/server/storage" "github.com/PacktPublishing/Go-for-DevOps/chapter/11/petstore/internal/server/telemetry/metrics" "github.com/PacktPublishing/Go-for-DevOps/chapter/11/petstore/internal/server/telemetry/tracing" "github.com/google/uuid" "go.opentelemetry.io/otel/attribute" otelCodes "go.opentelemetry.io/otel/codes" "go.opentelemetry.io/otel/metric" sdkTrace "go.opentelemetry.io/otel/sdk/trace" "go.opentelemetry.io/otel/trace" "google.golang.org/grpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/metadata" "google.golang.org/grpc/peer" "google.golang.org/grpc/reflection" "google.golang.org/grpc/status" "google.golang.org/protobuf/encoding/protojson" "google.golang.org/protobuf/proto" pb "github.com/PacktPublishing/Go-for-DevOps/chapter/11/petstore/proto" ) // These represent all of our OTEL metric counters. var ( totalCount, addCount, deleteCount, updateCount, searchCount metric.Int64Counter addCurrent, deleteCurrent, updateCurrent, searchCurrent metric.Int64UpDownCounter addLat, deleteLat, updateLat, searchLat metric.Int64Histogram addErrors, deleteErrors, updateErrors, searchErrors metric.Int64Counter ) // This fetches all of our counters. You can only do this in init(). func init() { totalCount = metrics.Get.Int64("petstore/server/totals/requests") addCount = metrics.Get.Int64("petstore/server/AddPets/requests") deleteCount = metrics.Get.Int64("petstore/server/DeletePets/requests") updateCount = metrics.Get.Int64("petstore/server/UpdatePets/requests") searchCount = metrics.Get.Int64("petstore/server/SearchPets/requests") addCurrent = metrics.Get.Int64UD("petstore/server/AddPets/current") deleteCurrent = metrics.Get.Int64UD("petstore/server/DeletePets/current") updateCurrent = metrics.Get.Int64UD("petstore/server/UpdatePets/current") searchCurrent = metrics.Get.Int64UD("petstore/server/SearchPets/current") addErrors = metrics.Get.Int64("petstore/server/AddPets/errors") deleteErrors = metrics.Get.Int64("petstore/server/DeletePets/errors") updateErrors = metrics.Get.Int64("petstore/server/UpdatePets/errors") searchErrors = metrics.Get.Int64("petstore/server/SearchPets/errors") addLat = metrics.Get.Int64Hist("petstore/server/AddPets/latency") deleteLat = metrics.Get.Int64Hist("petstore/server/DeletePets/latency") updateLat = metrics.Get.Int64Hist("petstore/server/UpdatePets/latency") searchLat = metrics.Get.Int64Hist("petstore/server/SearchPets/latency") } // API implements our gRPC server's API. type API struct { pb.UnimplementedPetStoreServer addr string store storage.Data grpcServer *grpc.Server gOpts []grpc.ServerOption mu sync.Mutex } // Option is an optional arguments to New(). type Option func(a *API) // WithGRPCOpts creates the gRPC server with the options passed. func WithGRPCOpts(opts ...grpc.ServerOption) Option { return func(a *API) { a.gOpts = append(a.gOpts, opts...) } } // New is the constructore for API. func New(addr string, store storage.Data, options ...Option) (*API, error) { a := &API{addr: addr, store: store} for _, o := range options { o(a) } a.grpcServer = grpc.NewServer(a.gOpts...) a.grpcServer.RegisterService(&pb.PetStore_ServiceDesc, a) reflection.Register(a.grpcServer) return a, nil } // Start starts the server. This blocks until Stop() is called. func (a *API) Start() error { a.mu.Lock() defer a.mu.Unlock() lis, err := net.Listen("tcp", a.addr) if err != nil { return err } return a.grpcServer.Serve(lis) } // Stop stops the server. func (a *API) Stop() { a.mu.Lock() defer a.mu.Unlock() a.grpcServer.Stop() } // AddPets adds pets to the pet store. func (a *API) AddPets(ctx context.Context, req *pb.AddPetsReq) (resp *pb.AddPetsResp, err error) { // Handle tracing. ctx, _, end := doTrace(ctx, "server.AddPets()", req) defer func() { end(err) }() // Handle metrics. metrics.Meter.RecordBatch( ctx, nil, totalCount.Measurement(1), addCount.Measurement(1), addCurrent.Measurement(1), ) t := time.Now() defer func() { metrics.Meter.RecordBatch( ctx, nil, addCurrent.Measurement(-1), addLat.Measurement(int64(time.Since(t))), ) if err != nil { code := status.Code(err) addErrors.Add(ctx, 1, attribute.String("code", code.String())) } }() // Actual work. ids := make([]string, 0, len(req.Pets)) for _, p := range req.Pets { if err := storage.ValidatePet(ctx, p, false); err != nil { return nil, status.Error(codes.InvalidArgument, err.Error()) } p.Id = uuid.New().String() ids = append(ids, p.Id) } if err = a.store.AddPets(ctx, req.Pets); err != nil { return nil, status.Error(codes.Internal, err.Error()) } return &pb.AddPetsResp{Ids: ids}, nil } // UpdatePets updates pets in the pet store. func (a *API) UpdatePets(ctx context.Context, req *pb.UpdatePetsReq) (resp *pb.UpdatePetsResp, err error) { ctx, _, end := doTrace(ctx, "server.UpdatePets()", req) defer func() { end(err) }() // Handle metrics. metrics.Meter.RecordBatch( ctx, nil, totalCount.Measurement(1), updateCount.Measurement(1), updateCurrent.Measurement(1), ) t := time.Now() defer func() { metrics.Meter.RecordBatch( ctx, nil, updateCurrent.Measurement(-1), updateLat.Measurement(int64(time.Since(t))), ) if err != nil { code := status.Code(err) updateErrors.Add(ctx, 1, attribute.String("code", code.String())) } }() for _, p := range req.Pets { if err = storage.ValidatePet(ctx, p, true); err != nil { return nil, status.Error(codes.InvalidArgument, err.Error()) } } if err = a.store.UpdatePets(ctx, req.Pets); err != nil { return nil, status.Error(codes.Internal, err.Error()) } return &pb.UpdatePetsResp{}, nil } // DeletePets deletes pets from the pet store. func (a *API) DeletePets(ctx context.Context, req *pb.DeletePetsReq) (resp *pb.DeletePetsResp, err error) { ctx, _, end := doTrace(ctx, "server.DeletePets()", req) defer func() { end(err) }() // Handle metrics. metrics.Meter.RecordBatch( ctx, nil, totalCount.Measurement(1), deleteCount.Measurement(1), deleteCurrent.Measurement(1), ) t := time.Now() defer func() { metrics.Meter.RecordBatch( ctx, nil, deleteCurrent.Measurement(-1), deleteLat.Measurement(int64(time.Since(t))), ) if err != nil { code := status.Code(err) deleteErrors.Add(ctx, 1, attribute.String("code", code.String())) } }() if err = a.store.DeletePets(ctx, req.Ids); err != nil { return nil, status.Error(codes.Internal, err.Error()) } return &pb.DeletePetsResp{}, nil } // SearchPets finds pets in the pet store. func (a *API) SearchPets(req *pb.SearchPetsReq, stream pb.PetStore_SearchPetsServer) (err error) { count := 0 ctx, span, end := doTrace(stream.Context(), "server.SearchPets()", req) defer func() { end(err) }() defer func() { span.SetAttributes(attribute.Int("search.results.returned", count)) }() // Handle metrics. metrics.Meter.RecordBatch( ctx, nil, totalCount.Measurement(1), searchCount.Measurement(1), searchCurrent.Measurement(1), ) t := time.Now() defer func() { metrics.Meter.RecordBatch( ctx, nil, searchCurrent.Measurement(-1), searchLat.Measurement(int64(time.Since(t))), ) if err != nil { code := status.Code(err) searchErrors.Add(ctx, 1, attribute.String("code", code.String())) } }() if err = validateSearch(ctx, req); err != nil { return status.Error(codes.InvalidArgument, err.Error()) } ch := a.store.SearchPets(ctx, req) for item := range ch { count++ if item.Error != nil { return status.Error(codes.Internal, item.Error.Error()) } if err := stream.Send(item.Pet); err != nil { return err } } if ctx.Err() != nil { return status.Error(codes.DeadlineExceeded, stream.Context().Err().Error()) } return nil } // ChangeSampler changes the OTEL sampling type. func (a *API) ChangeSampler(ctx context.Context, req *pb.ChangeSamplerReq) (resp *pb.ChangeSamplerResp, err error) { switch req.Sampler.Type { case pb.SamplerType_STUnknown: // Skip, will return an error case pb.SamplerType_STNever: tracing.Sampler.Switch(sdkTrace.NeverSample()) return &pb.ChangeSamplerResp{}, nil case pb.SamplerType_STAlways: tracing.Sampler.Switch(sdkTrace.AlwaysSample()) return &pb.ChangeSamplerResp{}, nil case pb.SamplerType_STFloat: if req.Sampler.FloatValue <= 0 || req.Sampler.FloatValue > 1 { return nil, status.Error(codes.InvalidArgument, fmt.Sprintf("float_value=%v is invalid", req.Sampler.FloatValue)) } tracing.Sampler.Switch(sdkTrace.TraceIDRatioBased(req.Sampler.FloatValue)) return &pb.ChangeSamplerResp{}, nil } return nil, status.Error(codes.InvalidArgument, fmt.Sprintf("type==%v is invalid", req.Sampler.Type)) } func validateSearch(ctx context.Context, r *pb.SearchPetsReq) error { for _, t := range r.Types { if t == pb.PetType_PTUnknown { return errors.New(ctx, "cannot search for PetType_Unkonwn") } } if r.BirthdateRange != nil { if r.BirthdateRange.Start == nil { return errors.New(ctx, "cannot have a BirthdateRange.Start that is nil") } if r.BirthdateRange.End == nil { return errors.New(ctx, "cannot have a BirthdateRange.End that is nil") } if _, err := storage.BirthdayToTime(ctx, r.BirthdateRange.Start); err != nil { return errors.Errorf(ctx, "r.BirthdateRange.Start had error: %s", err) } if _, err := storage.BirthdayToTime(ctx, r.BirthdateRange.End); err != nil { return errors.Errorf(ctx, "r.BirthdateRange.End had error: %s", err) } } return nil } func doTrace(ctx context.Context, name string, req proto.Message) (newCtx context.Context, span trace.Span, end func(err error)) { ctx, span = tracing.Tracer.Start( ctx, name, trace.WithAttributes( attribute.String("args", protojson.Format(req)), attribute.Bool("grpcCall", true), ), ) p, ok := peer.FromContext(ctx) if ok { host, port, err := net.SplitHostPort(p.Addr.String()) if err == nil { portNum, _ := strconv.Atoi(port) span.SetAttributes( attribute.String("net.peer.ip", host), attribute.Int("net.peer.port", portNum), ) } } // If they asked for a trace, send back the trace ID. if ctx.Value("trace") != nil { id := span.SpanContext().TraceID().String() if id != "" { header := metadata.Pairs("traceID", convertTraceID(id)) grpc.SendHeader(ctx, header) } } return ctx, span, func(err error) { if err != nil { span.SetStatus(otelCodes.Error, err.Error()) span.SetAttributes( attribute.Bool("error", true), attribute.String("errorMsg", err.Error()), ) span.End() return } span.SetStatus(otelCodes.Ok, "") span.End() } } func convertTraceID(id string) string { if len(id) < 16 { return "" } if len(id) > 16 { id = id[16:] } intValue, err := strconv.ParseUint(id, 16, 64) if err != nil { return "" } return strconv.FormatUint(intValue, 10) } ================================================ FILE: chapter/11/petstore/internal/server/storage/mem/mem.go ================================================ // Package mem contains an in-memory storage implementation of storage.Data. // This is great for unit tests and demos. Our implementation uses a // left-leaning red black tree for storage of entries by birthdays and maps // for all other indexes. Filtering is done by searching all indexes for matches // by each filter and if all matches succeed we stream the entry found. package mem import ( "context" "sync" "time" "github.com/PacktPublishing/Go-for-DevOps/chapter/11/petstore/internal/server/errors" "github.com/PacktPublishing/Go-for-DevOps/chapter/11/petstore/internal/server/log" "github.com/PacktPublishing/Go-for-DevOps/chapter/11/petstore/internal/server/storage" "github.com/biogo/store/llrb" pb "github.com/PacktPublishing/Go-for-DevOps/chapter/11/petstore/proto" ) // birthdays represents a set of pets that share the same birthday with // keys that are pet IDs. This is what we insert into our birthday tree. type birthdays map[string]*pb.Pet // Compare implements the llrb.Comparable.Compare(). func (bi birthdays) Compare(b llrb.Comparable) int { var ap, bp *pb.Pet // Get any entry in the map, all have the same birthday. for _, ap = range bi { break } for _, bp = range b.(birthdays) { break } // Ignore errors because we have to conform to a function def // and we should not be storing records with errors in the Birthday. at, _ := storage.BirthdayToTime(nil, ap.Birthday) bt, _ := storage.BirthdayToTime(nil, bp.Birthday) switch { case at.Before(bt): return -1 case at.Equal(bt): return 0 } return 1 } // birthdayGet is what we use to search for a pets with a particular birthday. type birthdayGet struct { *pb.Pet } // Compare implements the llrb.Comparable.Compare(). func (bi birthdayGet) Compare(b llrb.Comparable) int { // Ignore errors because we have to conform to a function def // and we should not be storing records with errors in the Birthday. at, _ := storage.BirthdayToTime(nil, bi.Pet.Birthday) var bt time.Time switch v := b.(type) { case birthdayGet: bt, _ = storage.BirthdayToTime(nil, v.Pet.Birthday) case birthdays: var p *pb.Pet for _, p = range v { break } bt, _ = storage.BirthdayToTime(nil, p.Birthday) } switch { case at.Before(bt): return -1 case at.Equal(bt): return 0 } return 1 } // Data implements storage.Data. type Data struct { mu sync.RWMutex // protects the items in this block birthday *llrb.Tree names map[string]map[string]*pb.Pet ids map[string]*pb.Pet types map[pb.PetType]map[string]*pb.Pet // searches contains all the search calls that must be done // when we do a search. This is populated in New(). searches []func(context.Context, *pb.SearchPetsReq) []string } // New is the constructor for Data. func New() *Data { d := Data{ names: map[string]map[string]*pb.Pet{}, ids: map[string]*pb.Pet{}, birthday: &llrb.Tree{}, types: map[pb.PetType]map[string]*pb.Pet{}, } d.searches = []func(context.Context, *pb.SearchPetsReq) []string{ d.byNames, d.byTypes, d.byBirthdays, } return &d } // AddPets implements storage.Data.AddPets(). func (d *Data) AddPets(ctx context.Context, pets []*pb.Pet) error { e := log.NewEvent("mem.data.AddPets()") defer e.Done(ctx) start := time.Now() defer func() { e.Add("latency.ns", time.Since(start)) }() d.mu.RLock() // Make sure that none of these IDs somehow exist already. for _, p := range pets { if _, ok := d.ids[p.Id]; ok { return errors.Errorf(ctx, "pet with ID(%s) is already present", p.Id) } } d.mu.RUnlock() d.mu.Lock() defer d.mu.Unlock() d.populate(ctx, pets) return nil } // UpdatePets implements storage.Data.AddPets(). func (d *Data) UpdatePets(ctx context.Context, pets []*pb.Pet) error { d.mu.RLock() // Make sure that ALL of these IDs somehow exist already. for _, p := range pets { if _, ok := d.ids[p.Id]; !ok { return errors.Errorf(ctx, "pet with ID(%s) doesn't exist", p.Id) } } d.mu.RUnlock() d.mu.Lock() defer d.mu.Unlock() d.populate(ctx, pets) return nil } func (d *Data) populate(ctx context.Context, pets []*pb.Pet) { e := log.NewEvent("mem.data.populate()") defer e.Done(ctx) for _, p := range pets { d.ids[p.Id] = p if v, ok := d.names[p.Name]; ok { v[p.Id] = p } else { d.names[p.Name] = map[string]*pb.Pet{ p.Id: p, } } if v, ok := d.types[p.Type]; ok { v[p.Id] = p } else { d.types[p.Type] = map[string]*pb.Pet{ p.Id: p, } } v := d.birthday.Get(birthdayGet{p}) if v == nil { d.birthday.Insert(birthdays{p.Id: p}) continue } v.(birthdays)[p.Id] = p } } // DeletePets implements stroage.Data.DeletePets(). func (d *Data) DeletePets(ctx context.Context, ids []string) error { e := log.NewEvent("mem.data.DeletePets()") defer e.Done(ctx) start := time.Now() defer func() { e.Add("latency.ns", time.Since(start)) }() d.mu.Lock() defer d.mu.Unlock() for _, id := range ids { p, ok := d.ids[id] if !ok { continue } delete(d.ids, id) if v, ok := d.names[p.Name]; ok { if len(v) == 1 { delete(d.names, p.Name) } else { delete(v, id) } } if v, ok := d.types[p.Type]; ok { if len(v) == 1 { delete(d.types, p.Type) } else { delete(v, id) } } v := d.birthday.Get(birthdayGet{p}) if v == nil { continue } if len(v.(birthdays)) == 1 { d.birthday.Delete(birthdayGet{p}) } delete(v.(birthdays), p.Id) } return nil } // SearchPets implements storage.Data.SearchPets(). func (d *Data) SearchPets(ctx context.Context, filter *pb.SearchPetsReq) chan storage.SearchItem { petsCh := make(chan storage.SearchItem, 1) go func() { defer close(petsCh) d.searchPets(ctx, filter, petsCh) }() return petsCh } func (d *Data) searchPets(ctx context.Context, filter *pb.SearchPetsReq, out chan storage.SearchItem) { e := log.NewEvent("mem.data.searchPets()") defer e.Done(ctx) d.mu.RLock() defer d.mu.RUnlock() filters := 0 if len(filter.Names) > 0 { e.Add("filterNames", true) filters++ } if len(filter.Types) > 0 { e.Add("filterTypes", true) filters++ } if filter.BirthdateRange != nil { e.Add("filterBirthday", true) filters++ } // They didn't provide filters, so just return everything. if filters == 0 { e.Add("returnAll", true) d.returnAll(ctx, out) return } searchCh := make(chan []string, len(d.searches)) wg := sync.WaitGroup{} wg.Add(len(d.searches)) goCount := 0 // Spin off our searches. for _, search := range d.searches { goCount++ search := search go func() { defer wg.Done() r := search(ctx, filter) select { case <-ctx.Done(): case searchCh <- r: } }() } e.Add("search.goroutines", goCount) // Wait for our searches to complete then close our searchCh. go func() { wg.Wait(); close(searchCh) }() // Collect all IDs from searches and count them. When one hits // the total number of filters send the matching pet to the caller. m := map[string]int{} matchCh := make(chan string, 1) go func() { defer close(matchCh) for ids := range searchCh { for _, id := range ids { count := m[id] count++ m[id] = count if count == filters { matchCh <- id } } } }() // This handles all our matches getting returned. valCount := 0 latency := 0 defer func() { if valCount > 0 { e.Add("upstream.recv.latency.avg.ns", latency/valCount) } }() for { select { case <-ctx.Done(): return case id, ok := <-matchCh: if !ok { return } start := time.Now() out <- storage.SearchItem{Pet: d.ids[id]} valCount++ latency += int(time.Since(start)) } } } // returnAll streams all the pets that we have. func (d *Data) returnAll(ctx context.Context, out chan storage.SearchItem) { e := log.NewEvent("mem.data.returnAll()") defer e.Done(ctx) start := time.Now() count := 0 defer func() { e.Add("latency.ns", int(time.Since(start))) e.Add("count", count) }() for _, p := range d.ids { count++ select { case <-ctx.Done(): return case out <- storage.SearchItem{Pet: p}: } } } // byNames returns IDs of pets that have the names matched in the filter. func (d *Data) byNames(ctx context.Context, filter *pb.SearchPetsReq) []string { if len(filter.Names) == 0 { return nil } e := log.NewEvent("mem.data.byNames()") defer e.Done(ctx) start := time.Now() count := 0 defer func() { e.Add("latency.ns", int(time.Since(start))) e.Add("count", count) }() var ids []string for _, n := range filter.Names { count++ if ctx.Err() != nil { return nil } p, ok := d.names[n] if !ok { continue } for id := range p { ids = append(ids, id) } } return ids } // byTypes returns IDs of pets that have the types matched in the filter. func (d *Data) byTypes(ctx context.Context, filter *pb.SearchPetsReq) []string { if len(filter.Types) == 0 { return nil } e := log.NewEvent("mem.data.byTypes()") defer e.Done(ctx) start := time.Now() count := 0 defer func() { e.Add("latency.ns", int(time.Since(start))) e.Add("count", count) }() var ids []string for _, t := range filter.Types { count++ if ctx.Err() != nil { return nil } p, ok := d.types[t] if !ok { continue } for id := range p { ids = append(ids, id) } } return ids } // byBirthdays returns IDs of pets that have the birthdays matched in the filter. func (d *Data) byBirthdays(ctx context.Context, filter *pb.SearchPetsReq) []string { if filter.BirthdateRange == nil { return nil } e := log.NewEvent("mem.data.byBirthdays()") defer e.Done(ctx) start := time.Now() count := 0 defer func() { e.Add("latency.ns", int(time.Since(start))) e.Add("count", count) }() var ids []string d.birthday.DoRange( func(c llrb.Comparable) (done bool) { for _, p := range c.(birthdays) { if ctx.Err() != nil { return true } ids = append(ids, p.Id) } return }, birthdayGet{&pb.Pet{Birthday: filter.BirthdateRange.Start}}, birthdayGet{&pb.Pet{Birthday: filter.BirthdateRange.End}}, ) if ctx.Err() != nil { return nil } count = len(ids) return ids } ================================================ FILE: chapter/11/petstore/internal/server/storage/mem/mem_test.go ================================================ package mem import ( "context" "sort" "strconv" "testing" "github.com/PacktPublishing/Go-for-DevOps/chapter/11/petstore/internal/server/storage" "github.com/kylelemons/godebug/pretty" "google.golang.org/protobuf/proto" pb "github.com/PacktPublishing/Go-for-DevOps/chapter/11/petstore/proto" dpb "google.golang.org/genproto/googleapis/type/date" ) // This tests we implement the interface. var _ storage.Data = &Data{} var pets = []*pb.Pet{ { Id: "0", Name: "Adam", Type: pb.PetType_PTCanine, Birthday: &dpb.Date{Month: 1, Day: 1, Year: 2020}, }, { Id: "1", Name: "Becky", Type: pb.PetType_PTFeline, Birthday: &dpb.Date{Month: 2, Day: 1, Year: 2020}, }, { Id: "2", Name: "Calvin", Type: pb.PetType_PTFeline, Birthday: &dpb.Date{Month: 2, Day: 2, Year: 2020}, }, { Id: "3", Name: "David", Type: pb.PetType_PTBird, Birthday: &dpb.Date{Month: 2, Day: 2, Year: 2021}, }, { Id: "4", Name: "Elaine", Type: pb.PetType_PTReptile, Birthday: &dpb.Date{Month: 2, Day: 2, Year: 2021}, }, { Id: "5", Name: "Elaine", Type: pb.PetType_PTReptile, Birthday: &dpb.Date{Month: 2, Day: 3, Year: 2021}, }, } // makePets takes the global "pets" var and clones everything in it and puts it into // a *Data so we have test data. func makePets() *Data { d := New() n := []*pb.Pet{} for _, p := range pets { n = append(n, proto.Clone(p).(*pb.Pet)) } d.AddPets(context.Background(), n) return d } func TestByNames(t *testing.T) { d := makePets() got := d.byNames(context.Background(), &pb.SearchPetsReq{Names: []string{"David", "Elaine"}}) sort.Strings(got) want := []string{"3", "4", "5"} if diff := pretty.Compare(want, got); diff != "" { t.Errorf("TestByNames: -want/+got:\n%s", diff) } } func TestByTypes(t *testing.T) { d := makePets() got := d.byTypes(context.Background(), &pb.SearchPetsReq{Types: []pb.PetType{pb.PetType_PTCanine, pb.PetType_PTReptile}}) sort.Strings(got) want := []string{"0", "4", "5"} if diff := pretty.Compare(want, got); diff != "" { t.Errorf("TestByTypes: -want/+got:\n%s", diff) } } func TestByBirthdays(t *testing.T) { d := makePets() got := d.byBirthdays( context.Background(), &pb.SearchPetsReq{ BirthdateRange: &pb.DateRange{ Start: &dpb.Date{Month: 2, Day: 1, Year: 2020}, End: &dpb.Date{Month: 2, Day: 3, Year: 2021}, }, }, ) sort.Strings(got) want := []string{"1", "2", "3", "4"} if diff := pretty.Compare(want, got); diff != "" { t.Errorf("TestByBirthdays: -want/+got:\n%s", diff) } } func TestDeletePets(t *testing.T) { d := makePets() deletions := []string{"3", "5", "20"} if err := d.DeletePets(context.Background(), deletions); err != nil { t.Fatalf("TestDeletePets: got err == %v, want err == nil", err) } // Don't check the last deletion, it is only there to make sure // a non-existent value doesn't do anything. for _, id := range deletions[:len(deletions)-1] { if _, ok := d.ids[id]; ok { t.Errorf("TestDeletePets: found ids[%s]", id) } i, _ := strconv.Atoi(id) if m, ok := d.names[pets[i].Name]; ok { if _, ok := m[id]; ok { t.Errorf("TestDeletePets: found(%s) in names", id) } } if m, ok := d.types[pets[i].Type]; ok { if _, ok := m[id]; ok { t.Errorf("TestDeletePets: found(%s) in types", id) } } v := d.birthday.Get(birthdayGet{pets[i]}) if v != nil { if _, ok := v.(birthdays)[id]; ok { t.Errorf("TestDeletePets: found(%s) in birthday tree", id) } } } } func TestSearchPets(t *testing.T) { d := makePets() ch := d.SearchPets( context.Background(), &pb.SearchPetsReq{ Names: []string{ "Becky", "Calvin", "David", "Elaine", }, Types: []pb.PetType{ pb.PetType_PTReptile, pb.PetType_PTFeline, }, BirthdateRange: &pb.DateRange{ Start: &dpb.Date{Month: 2, Day: 2, Year: 2021}, End: &dpb.Date{Month: 2, Day: 3, Year: 2021}, }, }, ) got := []storage.SearchItem{} for item := range ch { got = append(got, item) } want := []storage.SearchItem{{Pet: pets[4]}} config := pretty.Config{TrackCycles: true} if diff := config.Compare(want, got); diff != "" { t.Errorf("TestSearchPets: -want/+got:\n%s", diff) } } ================================================ FILE: chapter/11/petstore/internal/server/storage/storage.go ================================================ package storage import ( "context" "strings" "time" "github.com/PacktPublishing/Go-for-DevOps/chapter/11/petstore/internal/server/errors" dpb "google.golang.org/genproto/googleapis/type/date" pb "github.com/PacktPublishing/Go-for-DevOps/chapter/11/petstore/proto" ) // Data represents our data storage. type Data interface { // AddPets adds pet entries into storage. AddPets(ctx context.Context, pets []*pb.Pet) error // UpdatePets updates pet entries in storage. UpdatePets(ctx context.Context, pets []*pb.Pet) error // DeletePets deletes pets in storage by their ID. Will not error // on IDs not found. DeletePets(ctx context.Context, ids []string) error // SearchPets searches storage for pet entries that match the // filter. SearchPets(ctx context.Context, filter *pb.SearchPetsReq) chan SearchItem } // SearchItem is an item returned by a search. type SearchItem struct { // Pet is the pet that matched the search filters. Pet *pb.Pet // Error indicates that there was an error. If set the channel // will close after this entry. Error error } // ValidatePet validates that *pb.Pet has valid fields. func ValidatePet(ctx context.Context, p *pb.Pet, forUpdate bool) error { if forUpdate && p.Id == "" { return errors.New(ctx, "updates must have the Id field set") } else { if !forUpdate && p.Id != "" { return errors.New(ctx, "cannot set the Id field") } } p.Name = strings.TrimSpace(p.Name) if p.Name == "" { return errors.New(ctx, "cannot have a pet without a name") } if p.Type == pb.PetType_PTUnknown { return errors.New(ctx, "cannot have an unknown pet type") } _, err := BirthdayToTime(ctx, p.Birthday) if err != nil { return errors.Errorf(ctx, "pet(%s) had an error in its birthday: %w", p.Name, err) } return nil } // BirthdayToTime converts the *pb.Pet.Birthday field to a time.Time object. func BirthdayToTime(ctx context.Context, d *dpb.Date) (time.Time, error) { if d.Month < 1 || d.Month > 12 { return time.Time{}, errors.Errorf(ctx, "month must be 1-12, was %d", d.Month) } if d.Day < 1 || d.Day > 31 { return time.Time{}, errors.Errorf(ctx, "day(%d) was invalid", d.Day) } t := time.Date(int(d.Year), time.Month(d.Month), int(d.Day), 0, 0, 0, 0, time.UTC) if t.Month() != time.Month(d.Month) { return time.Time{}, errors.Errorf(ctx, "month %v does not have %d days", time.Month(d.Month), d.Day) } return t, nil } ================================================ FILE: chapter/11/petstore/internal/server/telemetry/metrics/metrics.go ================================================ /* Package metrics provides setup of metrics that can be used internally to measure various application states. All metrics for the application are defined here and other applications use this package to grab the metrics and use them. This package will also report any metric that is not used in the first 10 seconds after the app has started to prevent useless metrics from existing, as all metrics should be grabbed by that time. In a package you want to set metrics, you can do it as follows: var addCount metrics.Int64Counter func init() { addCounter = metrics.Get.Int64("petstore/server/AddPets/requests") } ... func (s *Server) AddPets(ctx context.Context, req *pb.AddPetsReq) (*pb.AddpetsResp, error) { ... // Do this if you have multiple changes that don't require special labels per update. metrics.Meter.RecordBatch(ctx, nil, addCounter.Measure(ctx, 1)) // Do this if you only need to make one change or need special labels. addCounter.Add(ctx, 1, attribute.String("label", "value") ... } To cause metrics to be exported package main(): func main() { ... stop, err := metrics.Start(ctx, metrics.OTELGRPC{Addr: "ip:port"}) if err != nil { log.Fatal(err) } defer stop() ... } */ package metrics import ( "html/template" "sort" "strings" "sync" "time" "github.com/PacktPublishing/Go-for-DevOps/chapter/11/petstore/internal/server/log" "go.opentelemetry.io/otel/metric" "go.opentelemetry.io/otel/metric/global" ) type metricType int const ( unknown = 0 mtInt64 = 1 mtInt64Hist = 2 mtInt64UD = 3 ) type metricDef struct { mtype metricType name string desc string } var metrics = []metricDef{ // Histograms {mtInt64Hist, "petstore/server/AddPets/latency", "The latency of an AddPets() request in nanoseconds"}, {mtInt64Hist, "petstore/server/DeletePets/latency", "The latency of an DeletePets() request in nanoseconds"}, {mtInt64Hist, "petstore/server/UpdatePets/latency", "The latency of an UpdatePets() request in nanoseconds"}, {mtInt64Hist, "petstore/server/SearchPets/latency", "The latency of a SearchPets() request in nanoseconds"}, // Counters {mtInt64, "petstore/server/AddPets/requests", "The total requests made to AddPets()"}, {mtInt64, "petstore/server/DeletePets/requests", "The total requests made to DeletePets()"}, {mtInt64, "petstore/server/UpdatePets/requests", "The total requests made to UpdatePets()"}, {mtInt64, "petstore/server/SearchPets/requests", "The total requests made to SearchPets()"}, {mtInt64, "petstore/server/totals/requests", "The total requests made to the server"}, {mtInt64, "petstore/server/AddPets/errors", "The total error count"}, {mtInt64, "petstore/server/DeletePets/errors", "The total error couunt"}, {mtInt64, "petstore/server/UpdatePets/errors", "The total error count"}, {mtInt64, "petstore/server/SearchPets/errors", "The total error count"}, {mtInt64, "petstore/server/totals/errors", "The total error count for all RPCs"}, // UpDown Counters {mtInt64UD, "petstore/server/AddPets/current", "The amount of requests currently being proccessed"}, {mtInt64UD, "petstore/server/DeletePets/current", "The amount of requests currently being proccessed"}, {mtInt64UD, "petstore/server/UpdatePets/current", "The amount of requests currently being proccessed"}, {mtInt64UD, "petstore/server/SearchPets/current", "The amount of requests currently being proccessed"}, } // Meter is the meter for the petstore. var Meter = global.Meter("petstore") // Get is used to lookup metrics by name. var Get = newLookups() var unusedMetricsTmpl = template.Must( template.New("").Parse( ` The following metrics appeart to be unused: {{- range .}} {{.}} {{- end }} `, ), ) // Lookups provides lookups for metrics based on their names. type Lookups struct { mtInt64Hist map[string]metric.Int64Histogram mtInt64UD map[string]metric.Int64UpDownCounter mtInt64 map[string]metric.Int64Counter mu sync.Mutex used map[string]bool } func newLookups() *Lookups { l := &Lookups{ mtInt64Hist: map[string]metric.Int64Histogram{}, mtInt64: map[string]metric.Int64Counter{}, mtInt64UD: map[string]metric.Int64UpDownCounter{}, used: map[string]bool{}, } exists := map[string]bool{} for _, m := range metrics { if m.mtype == unknown { log.Logger.Fatalf("metric with type(%v) cannot be added", m.mtype) } if m.name == "" { log.Logger.Fatalf("metric cannot be missing a name") } if m.desc == "" { log.Logger.Fatalf("metric cannot be missing a desc") } if exists[m.name] { log.Logger.Fatalf("cannot have two metrics with same name(%s)", m.name) } exists[m.name] = true switch m.mtype { case mtInt64Hist: l.mtInt64Hist[m.name] = metric.Must(Meter).NewInt64Histogram(m.name, metric.WithDescription(m.desc)) case mtInt64UD: l.mtInt64UD[m.name] = metric.Must(Meter).NewInt64UpDownCounter(m.name, metric.WithDescription(m.desc)) case mtInt64: l.mtInt64[m.name] = metric.Must(Meter).NewInt64Counter(m.name, metric.WithDescription(m.desc)) default: log.Logger.Fatalf("bug: we defined a metric type(%v) without adding support", m.mtype) } } go func() { time.Sleep(10 * time.Second) unused := l.unused() s := strings.Builder{} if err := unusedMetricsTmpl.Execute(&s, unused); err != nil { log.Logger.Fatalf("unusedMetricTmpl execute error: %s", err) } log.Logger.Println(s.String()) }() return l } // Int64 grabs the Int64Counter metric named "s". If not found, panics. func (l *Lookups) Int64(s string) metric.Int64Counter { l.mu.Lock() defer l.mu.Unlock() m, ok := l.mtInt64[s] if !ok { log.Logger.Fatalf("int64 metric(%s) is not defined", s) } l.used[s] = true return m } // Int64s grabs a list of Int64Counters. func (l *Lookups) Int64s(s ...string) []metric.Int64Counter { v := make([]metric.Int64Counter, 0, len(s)) for _, name := range s { v = append(v, l.Int64(name)) } return v } // Int64UD grabs the Int64UpDownCounter metric named "s". If not found, panics. func (l *Lookups) Int64UD(s string) metric.Int64UpDownCounter { l.mu.Lock() defer l.mu.Unlock() m, ok := l.mtInt64UD[s] if !ok { log.Logger.Fatalf("int64ud metric(%s) is not defined", s) } l.used[s] = true return m } // Int64UDs grabs a list of Int64UpDownCounters. func (l *Lookups) Int64UDs(s ...string) []metric.Int64UpDownCounter { v := make([]metric.Int64UpDownCounter, 0, len(s)) for _, name := range s { v = append(v, l.Int64UD(name)) } return v } // Int64Hist grabs the Int64Histogram metric named "s". If not found, panics. func (l *Lookups) Int64Hist(s string) metric.Int64Histogram { l.mu.Lock() defer l.mu.Unlock() m, ok := l.mtInt64Hist[s] if !ok { log.Logger.Fatalf("int64 histogram metric(%s) is not defined", s) } l.used[s] = true return m } func (l *Lookups) Int64Hists(s ...string) []metric.Int64Histogram { v := make([]metric.Int64Histogram, 0, len(s)) for _, name := range s { v = append(v, l.Int64Hist(name)) } return v } func (l *Lookups) unused() []string { l.mu.Lock() defer l.mu.Unlock() unused := []string{} for k := range l.mtInt64Hist { if !l.used[k] { unused = append(unused, k) } } for k := range l.mtInt64 { if !l.used[k] { unused = append(unused, k) } } sort.Strings(unused) return unused } ================================================ FILE: chapter/11/petstore/internal/server/telemetry/metrics/start.go ================================================ package metrics import ( "context" "fmt" "time" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/exporters/otlp/otlpmetric" "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc" "go.opentelemetry.io/otel/metric/global" "go.opentelemetry.io/otel/sdk/metric/controller/basic" processor "go.opentelemetry.io/otel/sdk/metric/processor/basic" "go.opentelemetry.io/otel/sdk/metric/selector/simple" ) // Controller represents the controller to send metrics to. type Controller interface { isController() } // OTELGRPC represents exporting to the "go.opentelemetry.io/otel/sdk/metric/controller/basic" controller. type OTELGRPC struct { // Addr is the local address to export on. Addr string } func (o OTELGRPC) isController() {} // Stop is used to stop OTEL metric handling. type Stop func() // Start is used to start OTEL metric handling. func Start(ctx context.Context, c Controller) (Stop, error) { control, err := newController(ctx, c) if err != nil { return nil, err } err = control.Start(ctx) if err != nil { return nil, err } return func() { ctx, cancel := context.WithTimeout(ctx, 1*time.Second) defer cancel() if err := control.Stop(ctx); err != nil { otel.Handle(err) } }, nil } func newController(ctx context.Context, c Controller) (*basic.Controller, error) { switch v := c.(type) { case OTELGRPC: return otelGRPC(ctx, v) } return nil, fmt.Errorf("%T is not a valid Controller", c) } func otelGRPC(ctx context.Context, args OTELGRPC) (*basic.Controller, error) { metricClient := otlpmetricgrpc.NewClient( otlpmetricgrpc.WithInsecure(), otlpmetricgrpc.WithEndpoint(args.Addr), ) metricExp, err := otlpmetric.New(ctx, metricClient) if err != nil { return nil, fmt.Errorf("Failed to create the collector metric exporter") } pusher := basic.New( processor.NewFactory( simple.NewWithHistogramDistribution(), metricExp, ), basic.WithExporter(metricExp), basic.WithCollectPeriod(10*time.Second), ) global.SetMeterProvider(pusher) return pusher, nil } ================================================ FILE: chapter/11/petstore/internal/server/telemetry/tracing/sampler/sampler.go ================================================ /* Package sampler offers a Sampler that looks for a TraceID.Valid() == true or a gRPC metadata key called "trace" and if they exist will sample. Otherwise it looks to a child Sampler to determine based upon whatever sampler algorithm is used. In addition we offer the ability to switch out the underlying sampler at anytime in a thread-safe way. You can construct a new Sampler like so: s, err := New(trace.NeverSample) if err != nil { // Do something } The above Sampler would only trace if a TraceID.Valid() == true or gRCP metadate key called "trace" existed. If we want to trace 1% of the time as well, we can do the following: s, err := New(trace.TraceIDRatioBased(.01)) if err != nil { // Do something } */ package sampler import ( "fmt" "sync/atomic" "go.opentelemetry.io/otel/sdk/trace" otelTrace "go.opentelemetry.io/otel/trace" "google.golang.org/grpc/metadata" ) const desc = `This sampler samples if TracdID.Valid(), gRPC metadata contains key "trace" or the child sampler decides to sample` // Sampler decides whether a trace should be sampled and exported. This sampler will sample if // paramters TraceID.Valid() == true or the Context contains gRPC metadata that has key "trace" (it doesn't care about values). type Sampler struct { // child stores a *trace.Sampler. trace.Sampler is an interface. Because atomic.Value cares about // the underlying type, you can't just store trace.Sampler. So we do a pointer, which is the only valid // use of *interface I've ever seen. child atomic.Value // *trace.Sampler } // New creates a new Sampler with the child Sampler used if TraceID.Valid() == false and gRPC metadata does not contain // key "trace". func New(child trace.Sampler) (*Sampler, error) { if child == nil { return nil, fmt.Errorf("child cannot == nil") } s := &Sampler{} s.child.Store(&child) return s, nil } // ShouldSample implements trace.Sampler.ShouldSample. func (s *Sampler) ShouldSample(p trace.SamplingParameters) trace.SamplingResult { psc := otelTrace.SpanContextFromContext(p.ParentContext) if psc.IsValid() { if psc.IsRemote() { if psc.IsSampled() { return trace.SamplingResult{ Decision: trace.RecordAndSample, Tracestate: psc.TraceState(), } } } if psc.IsSampled() { return trace.SamplingResult{ Decision: trace.RecordAndSample, Tracestate: psc.TraceState(), } } } md, ok := metadata.FromIncomingContext(p.ParentContext) if !ok { return (*s.child.Load().(*trace.Sampler)).ShouldSample(p) } if _, ok := md["trace"]; ok { psc := otelTrace.SpanContextFromContext(p.ParentContext) return trace.SamplingResult{ Decision: trace.RecordAndSample, Tracestate: psc.TraceState(), } } return (*s.child.Load().(*trace.Sampler)).ShouldSample(p) } // Description implements trace.Sampler.Description(). func (s *Sampler) Description() string { return desc } // Switch switches the underlying trace.Sampler. func (s *Sampler) Switch(sampler trace.Sampler) { if sampler == nil { panic("cannot call Switch() with a nil Sampler") } s.child.Store(&sampler) } ================================================ FILE: chapter/11/petstore/internal/server/telemetry/tracing/tracing.go ================================================ /* Package tracing provides functions for starting and stopping our Open Telemetry tracing. This package is intended to be used from main and is simple to use. We offer a few choices on where traces export to. Here is an example to trace to stderr for all requests: func main() { ctx := context.Background() // Set us up to always sample. The "trace" package is: "petstore/server/SearchPets/latency" tracing.Sampler.Switch(trace.AlwaysSample()) // Start our tracing and pass the empty Stderr tracing arguments. // Stderr{} has no required fields. stop, err := tracing.Start(ctx, tracing.Stderr{}) if err != nil { log.Fatalf("problem starting telemetry: %s", err) } // Stop kills our exporter when main() ends. defer stop() } */ package tracing import ( "context" "fmt" "io" "log" "os" "time" "github.com/PacktPublishing/Go-for-DevOps/chapter/11/petstore/internal/server/telemetry/tracing/sampler" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/exporters/otlp/otlptrace" "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc" "go.opentelemetry.io/otel/exporters/stdout/stdouttrace" "go.opentelemetry.io/otel/propagation" "go.opentelemetry.io/otel/sdk/resource" sdktrace "go.opentelemetry.io/otel/sdk/trace" semconv "go.opentelemetry.io/otel/semconv/v1.4.0" "go.opentelemetry.io/otel/trace" "google.golang.org/grpc" ) // Tracer is the tracer initialized by Start(). var ( // Tracer is the tracer initialized by Start(). Tracer trace.Tracer // *sdktrace.TracerProvider //otlptrace.Exporter // Sampler is our *sampler.Sampler used by the Tracer. Sampler *sampler.Sampler ) func init() { s, err := sampler.New(sdktrace.TraceIDRatioBased(1)) if err != nil { panic(err) } Sampler = s } // Exporter represents the exporter to send telemetry to. type Exporter interface { isExporter() } // OTELGRPC represents exporting to the go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc exporter. type OTELGRPC struct { // Addr is the local address to export on. Addr string } func (o OTELGRPC) isExporter() {} // Stderr exports trace data to os.Stderr. type Stderr struct{} func (s Stderr) isExporter() {} // File exports trace data to a file. If the file exists, it is overwritten. type File struct { // Path is the path to the file. Path string } func (f File) isExporter() {} // Stop stops our Open Telemetry exporter. type Stop func() // Start creates the OTEL exporter and configures the trace providers. // It returns a Stop() which will stop the exporter. func Start(ctx context.Context, e Exporter) (Stop, error) { log.Println("Sampler: ", Sampler) tp, err := newTraceExporter(ctx, e) if err != nil { return nil, err } Tracer = tp.Tracer("petstore") return func() { var cancel context.CancelFunc ctx, cancel = context.WithTimeout(ctx, 1*time.Second) defer cancel() if err := tp.Shutdown(ctx); err != nil { otel.Handle(err) } }, nil } // newTracerExporter creates an OTLP exporter with our tracer information. func newTraceExporter(ctx context.Context, e Exporter) (*sdktrace.TracerProvider, error) { var exp sdktrace.SpanExporter var err error switch v := e.(type) { case OTELGRPC: exp, err = otelGRPC(ctx, v) case Stderr: exp, err = newFileExporter(os.Stderr) case File: f, err := os.Create(v.Path) if err != nil { return nil, err } exp, err = newFileExporter(f) default: return nil, fmt.Errorf("%T is not a valid Exporter", e) } res, err := resource.New( ctx, resource.WithFromEnv(), resource.WithProcess(), resource.WithTelemetrySDK(), resource.WithHost(), resource.WithAttributes( // the service name used to display traces in backends semconv.ServiceNameKey.String("petstore"), ), ) if err != nil { return nil, err } // set global propagator to tracecontext (the default is no-op). otel.SetTextMapPropagator(propagation.TraceContext{}) prov := sdktrace.NewTracerProvider( sdktrace.WithSampler(Sampler), sdktrace.WithBatcher(exp), sdktrace.WithResource(res), ) otel.SetTracerProvider(prov) return prov, nil } // newFileExporter creates an exporter that writes to a file. func newFileExporter(w io.Writer) (sdktrace.SpanExporter, error) { return stdouttrace.New( stdouttrace.WithWriter(w), stdouttrace.WithPrettyPrint(), ) } func otelGRPC(ctx context.Context, e OTELGRPC) (sdktrace.SpanExporter, error) { //(*otlptrace.Exporter, error) { exp, err := otlptrace.New( ctx, otlptracegrpc.NewClient( otlptracegrpc.WithInsecure(), otlptracegrpc.WithEndpoint(e.Addr), otlptracegrpc.WithDialOption(grpc.WithBlock()), ), ) if err != nil { return nil, err } return exp, nil } ================================================ FILE: chapter/11/petstore/otel-collector-config.yaml ================================================ receivers: otlp: protocols: grpc: exporters: prometheus: endpoint: "0.0.0.0:8889" const_labels: label1: value1 logging: jaeger: endpoint: jaeger-all-in-one:14250 tls: insecure: true processors: batch: service: pipelines: traces: receivers: [otlp] processors: [batch] exporters: [jaeger] metrics: receivers: [otlp] processors: [batch] exporters: [prometheus] ================================================ FILE: chapter/11/petstore/petstore.go ================================================ package main import ( "context" "flag" stdlog "log" "os" "strconv" "github.com/PacktPublishing/Go-for-DevOps/chapter/11/petstore/internal/server" "github.com/PacktPublishing/Go-for-DevOps/chapter/11/petstore/internal/server/log" "github.com/PacktPublishing/Go-for-DevOps/chapter/11/petstore/internal/server/storage/mem" "github.com/PacktPublishing/Go-for-DevOps/chapter/11/petstore/internal/server/telemetry/metrics" "github.com/PacktPublishing/Go-for-DevOps/chapter/11/petstore/internal/server/telemetry/tracing" //grpcotel "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc" "go.opentelemetry.io/otel/sdk/trace" ) // General service flags. var ( addr = flag.String("addr", "0.0.0.0:6742", "The address to run the service on.") ) // Flags are related to OTEL tracing. var ( localDebug = flag.Bool("localDebug", false, "If true, OTEL traces are sent to the console") fileDebug = flag.String("fileDebug", "", "If set, OTEL traces are written to the file path provided") grpcTraces = flag.Bool("grpcTraces", false, "Our traces are exported via gRPC. Must set otelAddr.") traceSampling = flag.String("traceSampling", "never", "Sets the sampling type. By default we never sample unless it is requested by the client."+ "Valid values are: 'never', 'always' and '[float]', where float is a floating point value where any value over 1 is all.", ) ) // These flags relate to exporting our Open Telemetry traces via gRPC. var ( otelAddr = flag.String("otelAddr", "", "The address for our OpenTelemetry agent. If not set, looks for Env variable 'OTEL_EXPORTER_OTLP_ENDPOINT'. If not set defaults to 0.0.0:4317") ) func init() { if *otelAddr == "" { addr, ok := os.LookupEnv("OTEL_EXPORTER_OTLP_ENDPOINT") if !ok { addr = "0.0.0.0:4317" } *otelAddr = addr } } // otelExporter determines if the flags are set to export tracing information // to a destination. If so, we return the arguments needed for that exporter. func otelExporter() tracing.Exporter { if tooManyTrue(*localDebug, *fileDebug, *grpcTraces) { log.Logger.Fatalf("cannot set more than one from this list: localDebug, fileDebug, grpcTraces") } switch { case *localDebug: return tracing.Stderr{} case *fileDebug != "": return tracing.File{Path: *fileDebug} case *grpcTraces: return tracing.OTELGRPC{Addr: *otelAddr} } return tracing.Stderr{} } // otelController is similar to otelExporter except it sets up arguments for metric // exporting. func otelController() metrics.Controller { if *otelAddr != "" { return metrics.OTELGRPC{Addr: *otelAddr} } return nil } // setSampling checks flags and then sets our tracing sampling rate. func setSampling() { switch *traceSampling { case "never": tracing.Sampler.Switch(trace.NeverSample()) return case "always": tracing.Sampler.Switch(trace.AlwaysSample()) return default: if f, err := strconv.ParseFloat(*traceSampling, 64); err == nil { tracing.Sampler.Switch(trace.TraceIDRatioBased(f)) return } } log.Logger.Fatalf("traceSampling=%s is not a valid value", *traceSampling) } // tooManyTrue is given a list of bool or string types. A string type that // is non-empty string is considered true. If more than one value is true, // this returns true. Otherwise it returns false. func tooManyTrue(truths ...interface{}) bool { set := false for _, t := range truths { switch v := t.(type) { case bool: if v && set { return true } if v { set = true } case string: if v != "" && set { return true } if v != "" { set = true } default: panic("not a bool or string") } } return false } func main() { flag.Parse() log.Logger.Println("Flags values") log.Logger.Println("-----------------------------------") flag.VisitAll(func(f *flag.Flag) { log.Logger.Printf("%s: %s\n", f.Name, f.Value) }) log.Logger.Println("-----------------------------------") stdlog.SetFlags(stdlog.LstdFlags | stdlog.Lshortfile) log.SetFlags(log.LstdFlags | log.Lshortfile) log.Logger.SetFlags(stdlog.LstdFlags | stdlog.Lshortfile) ctx := context.Background() // Setup for OTEL tracing. setSampling() e := otelExporter() if e != nil { stop, err := tracing.Start(ctx, e) if err != nil { log.Logger.Fatalf("problem starting telemetry: %s", err) } defer stop() } // Setup for OTEL metrics. c := otelController() if c != nil { stop, err := metrics.Start(ctx, c) if err != nil { log.Logger.Fatal(err) } defer stop() } // Setup for the service. store := mem.New() s, err := server.New( *addr, store, server.WithGRPCOpts( //grpc.UnaryInterceptor(grpcotel.UnaryServerInterceptor(tracing.Tracer)), //grpc.StreamInterceptor(grpcotel.StreamServerInterceptor(tracing.Tracer)), ), ) if err != nil { panic(err) } done := make(chan error, 1) log.Logger.Println("Starting server at: ", *addr) go func() { defer close(done) done <- s.Start() }() log.Logger.Println("Server exited with error: ", <-done) } ================================================ FILE: chapter/11/petstore/prometheus.yaml ================================================ scrape_configs: - job_name: 'otel-collector' scrape_interval: 10s static_configs: - targets: ['otel-collector:8889'] - targets: ['otel-collector:8888'] ================================================ FILE: chapter/11/petstore/proto/buf.gen.yaml ================================================ version: v1 plugins: - name: go out: ./ opt: - paths=source_relative - name: go-grpc out: ./ opt: - paths=source_relative ================================================ FILE: chapter/11/petstore/proto/buf.yaml ================================================ version: v1 deps: - buf.build/googleapis/googleapis lint: use: - DEFAULT breaking: use: - FILE ================================================ FILE: chapter/11/petstore/proto/petstore.pb.go ================================================ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.25.0-devel // protoc v3.18.0 // source: petstore.proto package proto import ( date "google.golang.org/genproto/googleapis/type/date" 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) ) // Desribes the type of pets. type PetType int32 const ( // The type was not set. PetType_PTUnknown PetType = 0 // The pet is a canine. PetType_PTCanine PetType = 1 // The pet is a feline. PetType_PTFeline PetType = 2 // The pet is a bird. PetType_PTBird PetType = 3 // The pet is a reptile. PetType_PTReptile PetType = 4 ) // Enum value maps for PetType. var ( PetType_name = map[int32]string{ 0: "PTUnknown", 1: "PTCanine", 2: "PTFeline", 3: "PTBird", 4: "PTReptile", } PetType_value = map[string]int32{ "PTUnknown": 0, "PTCanine": 1, "PTFeline": 2, "PTBird": 3, "PTReptile": 4, } ) func (x PetType) Enum() *PetType { p := new(PetType) *p = x return p } func (x PetType) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } func (PetType) Descriptor() protoreflect.EnumDescriptor { return file_petstore_proto_enumTypes[0].Descriptor() } func (PetType) Type() protoreflect.EnumType { return &file_petstore_proto_enumTypes[0] } func (x PetType) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } // Deprecated: Use PetType.Descriptor instead. func (PetType) EnumDescriptor() ([]byte, []int) { return file_petstore_proto_rawDescGZIP(), []int{0} } // Types of OTEL sampling we support. type SamplerType int32 const ( SamplerType_STUnknown SamplerType = 0 SamplerType_STNever SamplerType = 1 SamplerType_STAlways SamplerType = 2 SamplerType_STFloat SamplerType = 3 ) // Enum value maps for SamplerType. var ( SamplerType_name = map[int32]string{ 0: "STUnknown", 1: "STNever", 2: "STAlways", 3: "STFloat", } SamplerType_value = map[string]int32{ "STUnknown": 0, "STNever": 1, "STAlways": 2, "STFloat": 3, } ) func (x SamplerType) Enum() *SamplerType { p := new(SamplerType) *p = x return p } func (x SamplerType) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } func (SamplerType) Descriptor() protoreflect.EnumDescriptor { return file_petstore_proto_enumTypes[1].Descriptor() } func (SamplerType) Type() protoreflect.EnumType { return &file_petstore_proto_enumTypes[1] } func (x SamplerType) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } // Deprecated: Use SamplerType.Descriptor instead. func (SamplerType) EnumDescriptor() ([]byte, []int) { return file_petstore_proto_rawDescGZIP(), []int{1} } // Represents a range of dates. type DateRange struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // When to start the range, this is inclusive. Start *date.Date `protobuf:"bytes,1,opt,name=start,proto3" json:"start,omitempty"` // When to end the range, this is exclusive. End *date.Date `protobuf:"bytes,2,opt,name=end,proto3" json:"end,omitempty"` } func (x *DateRange) Reset() { *x = DateRange{} if protoimpl.UnsafeEnabled { mi := &file_petstore_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *DateRange) String() string { return protoimpl.X.MessageStringOf(x) } func (*DateRange) ProtoMessage() {} func (x *DateRange) ProtoReflect() protoreflect.Message { mi := &file_petstore_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 DateRange.ProtoReflect.Descriptor instead. func (*DateRange) Descriptor() ([]byte, []int) { return file_petstore_proto_rawDescGZIP(), []int{0} } func (x *DateRange) GetStart() *date.Date { if x != nil { return x.Start } return nil } func (x *DateRange) GetEnd() *date.Date { if x != nil { return x.End } return nil } // Represents a unique pet. type Pet struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // A UUIDv4 for this pet. This can never be set on an AddPet(). Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` // The name of the pet. Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` // The type of pet. Type PetType `protobuf:"varint,3,opt,name=type,proto3,enum=petstore.PetType" json:"type,omitempty"` // The pet's birthday. Birthday *date.Date `protobuf:"bytes,4,opt,name=birthday,proto3" json:"birthday,omitempty"` } func (x *Pet) Reset() { *x = Pet{} if protoimpl.UnsafeEnabled { mi := &file_petstore_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Pet) String() string { return protoimpl.X.MessageStringOf(x) } func (*Pet) ProtoMessage() {} func (x *Pet) ProtoReflect() protoreflect.Message { mi := &file_petstore_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 Pet.ProtoReflect.Descriptor instead. func (*Pet) Descriptor() ([]byte, []int) { return file_petstore_proto_rawDescGZIP(), []int{1} } func (x *Pet) GetId() string { if x != nil { return x.Id } return "" } func (x *Pet) GetName() string { if x != nil { return x.Name } return "" } func (x *Pet) GetType() PetType { if x != nil { return x.Type } return PetType_PTUnknown } func (x *Pet) GetBirthday() *date.Date { if x != nil { return x.Birthday } return nil } // The request used to add a pets to the system. type AddPetsReq struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // The pet information to add. Pet.id must not be set. Pets []*Pet `protobuf:"bytes,1,rep,name=pets,proto3" json:"pets,omitempty"` } func (x *AddPetsReq) Reset() { *x = AddPetsReq{} if protoimpl.UnsafeEnabled { mi := &file_petstore_proto_msgTypes[2] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *AddPetsReq) String() string { return protoimpl.X.MessageStringOf(x) } func (*AddPetsReq) ProtoMessage() {} func (x *AddPetsReq) ProtoReflect() protoreflect.Message { mi := &file_petstore_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 AddPetsReq.ProtoReflect.Descriptor instead. func (*AddPetsReq) Descriptor() ([]byte, []int) { return file_petstore_proto_rawDescGZIP(), []int{2} } func (x *AddPetsReq) GetPets() []*Pet { if x != nil { return x.Pets } return nil } // The response do AddPets(). type AddPetsResp struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // The IDs of the pets that were added. Ids []string `protobuf:"bytes,1,rep,name=ids,proto3" json:"ids,omitempty"` } func (x *AddPetsResp) Reset() { *x = AddPetsResp{} if protoimpl.UnsafeEnabled { mi := &file_petstore_proto_msgTypes[3] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *AddPetsResp) String() string { return protoimpl.X.MessageStringOf(x) } func (*AddPetsResp) ProtoMessage() {} func (x *AddPetsResp) ProtoReflect() protoreflect.Message { mi := &file_petstore_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 AddPetsResp.ProtoReflect.Descriptor instead. func (*AddPetsResp) Descriptor() ([]byte, []int) { return file_petstore_proto_rawDescGZIP(), []int{3} } func (x *AddPetsResp) GetIds() []string { if x != nil { return x.Ids } return nil } // The request used to update pets in the system. type UpdatePetsReq struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // The pet information to update. Pet.id must be set. Pets []*Pet `protobuf:"bytes,1,rep,name=pets,proto3" json:"pets,omitempty"` } func (x *UpdatePetsReq) Reset() { *x = UpdatePetsReq{} if protoimpl.UnsafeEnabled { mi := &file_petstore_proto_msgTypes[4] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *UpdatePetsReq) String() string { return protoimpl.X.MessageStringOf(x) } func (*UpdatePetsReq) ProtoMessage() {} func (x *UpdatePetsReq) ProtoReflect() protoreflect.Message { mi := &file_petstore_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 UpdatePetsReq.ProtoReflect.Descriptor instead. func (*UpdatePetsReq) Descriptor() ([]byte, []int) { return file_petstore_proto_rawDescGZIP(), []int{4} } func (x *UpdatePetsReq) GetPets() []*Pet { if x != nil { return x.Pets } return nil } // The response do UpdatePets(). type UpdatePetsResp struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields } func (x *UpdatePetsResp) Reset() { *x = UpdatePetsResp{} if protoimpl.UnsafeEnabled { mi := &file_petstore_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *UpdatePetsResp) String() string { return protoimpl.X.MessageStringOf(x) } func (*UpdatePetsResp) ProtoMessage() {} func (x *UpdatePetsResp) ProtoReflect() protoreflect.Message { mi := &file_petstore_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 UpdatePetsResp.ProtoReflect.Descriptor instead. func (*UpdatePetsResp) Descriptor() ([]byte, []int) { return file_petstore_proto_rawDescGZIP(), []int{5} } // Used to indicate which pets to delete. This is an all or nothing request. type DeletePetsReq struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // The IDs of the pets to delete. Ids []string `protobuf:"bytes,1,rep,name=ids,proto3" json:"ids,omitempty"` } func (x *DeletePetsReq) Reset() { *x = DeletePetsReq{} if protoimpl.UnsafeEnabled { mi := &file_petstore_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *DeletePetsReq) String() string { return protoimpl.X.MessageStringOf(x) } func (*DeletePetsReq) ProtoMessage() {} func (x *DeletePetsReq) ProtoReflect() protoreflect.Message { mi := &file_petstore_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 DeletePetsReq.ProtoReflect.Descriptor instead. func (*DeletePetsReq) Descriptor() ([]byte, []int) { return file_petstore_proto_rawDescGZIP(), []int{6} } func (x *DeletePetsReq) GetIds() []string { if x != nil { return x.Ids } return nil } // The response to a DeletePet(). type DeletePetsResp struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields } func (x *DeletePetsResp) Reset() { *x = DeletePetsResp{} if protoimpl.UnsafeEnabled { mi := &file_petstore_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *DeletePetsResp) String() string { return protoimpl.X.MessageStringOf(x) } func (*DeletePetsResp) ProtoMessage() {} func (x *DeletePetsResp) ProtoReflect() protoreflect.Message { mi := &file_petstore_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 DeletePetsResp.ProtoReflect.Descriptor instead. func (*DeletePetsResp) Descriptor() ([]byte, []int) { return file_petstore_proto_rawDescGZIP(), []int{7} } // The request to search for pets. type SearchPetsReq struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // Pet names to filter by. Names []string `protobuf:"bytes,1,rep,name=names,proto3" json:"names,omitempty"` // Pet types to filter by. Types []PetType `protobuf:"varint,2,rep,packed,name=types,proto3,enum=petstore.PetType" json:"types,omitempty"` // Birthdays to filter by. BirthdateRange *DateRange `protobuf:"bytes,3,opt,name=birthdate_range,json=birthdateRange,proto3" json:"birthdate_range,omitempty"` } func (x *SearchPetsReq) Reset() { *x = SearchPetsReq{} if protoimpl.UnsafeEnabled { mi := &file_petstore_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *SearchPetsReq) String() string { return protoimpl.X.MessageStringOf(x) } func (*SearchPetsReq) ProtoMessage() {} func (x *SearchPetsReq) ProtoReflect() protoreflect.Message { mi := &file_petstore_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 SearchPetsReq.ProtoReflect.Descriptor instead. func (*SearchPetsReq) Descriptor() ([]byte, []int) { return file_petstore_proto_rawDescGZIP(), []int{8} } func (x *SearchPetsReq) GetNames() []string { if x != nil { return x.Names } return nil } func (x *SearchPetsReq) GetTypes() []PetType { if x != nil { return x.Types } return nil } func (x *SearchPetsReq) GetBirthdateRange() *DateRange { if x != nil { return x.BirthdateRange } return nil } type Sampler struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // The type of sampling to change to. Type SamplerType `protobuf:"varint,1,opt,name=type,proto3,enum=petstore.SamplerType" json:"type,omitempty"` // This is the sampling rate if type == STFloat. Values must be // > 0 and <= 1.0 . FloatValue float64 `protobuf:"fixed64,2,opt,name=float_value,json=floatValue,proto3" json:"float_value,omitempty"` } func (x *Sampler) Reset() { *x = Sampler{} if protoimpl.UnsafeEnabled { mi := &file_petstore_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Sampler) String() string { return protoimpl.X.MessageStringOf(x) } func (*Sampler) ProtoMessage() {} func (x *Sampler) ProtoReflect() protoreflect.Message { mi := &file_petstore_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 Sampler.ProtoReflect.Descriptor instead. func (*Sampler) Descriptor() ([]byte, []int) { return file_petstore_proto_rawDescGZIP(), []int{9} } func (x *Sampler) GetType() SamplerType { if x != nil { return x.Type } return SamplerType_STUnknown } func (x *Sampler) GetFloatValue() float64 { if x != nil { return x.FloatValue } return 0 } // Used to request we change the OTEL sampling. type ChangeSamplerReq struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Sampler *Sampler `protobuf:"bytes,1,opt,name=sampler,proto3" json:"sampler,omitempty"` } func (x *ChangeSamplerReq) Reset() { *x = ChangeSamplerReq{} if protoimpl.UnsafeEnabled { mi := &file_petstore_proto_msgTypes[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *ChangeSamplerReq) String() string { return protoimpl.X.MessageStringOf(x) } func (*ChangeSamplerReq) ProtoMessage() {} func (x *ChangeSamplerReq) ProtoReflect() protoreflect.Message { mi := &file_petstore_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 ChangeSamplerReq.ProtoReflect.Descriptor instead. func (*ChangeSamplerReq) Descriptor() ([]byte, []int) { return file_petstore_proto_rawDescGZIP(), []int{10} } func (x *ChangeSamplerReq) GetSampler() *Sampler { if x != nil { return x.Sampler } return nil } // The response to a sampling change. type ChangeSamplerResp struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields } func (x *ChangeSamplerResp) Reset() { *x = ChangeSamplerResp{} if protoimpl.UnsafeEnabled { mi := &file_petstore_proto_msgTypes[11] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *ChangeSamplerResp) String() string { return protoimpl.X.MessageStringOf(x) } func (*ChangeSamplerResp) ProtoMessage() {} func (x *ChangeSamplerResp) ProtoReflect() protoreflect.Message { mi := &file_petstore_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 ChangeSamplerResp.ProtoReflect.Descriptor instead. func (*ChangeSamplerResp) Descriptor() ([]byte, []int) { return file_petstore_proto_rawDescGZIP(), []int{11} } var File_petstore_proto protoreflect.FileDescriptor var file_petstore_proto_rawDesc = []byte{ 0x0a, 0x0e, 0x70, 0x65, 0x74, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x08, 0x70, 0x65, 0x74, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x1a, 0x16, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x74, 0x79, 0x70, 0x65, 0x2f, 0x64, 0x61, 0x74, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x59, 0x0a, 0x09, 0x44, 0x61, 0x74, 0x65, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x27, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x2e, 0x44, 0x61, 0x74, 0x65, 0x52, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x12, 0x23, 0x0a, 0x03, 0x65, 0x6e, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x2e, 0x44, 0x61, 0x74, 0x65, 0x52, 0x03, 0x65, 0x6e, 0x64, 0x22, 0x7f, 0x0a, 0x03, 0x50, 0x65, 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, 0x25, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x11, 0x2e, 0x70, 0x65, 0x74, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x50, 0x65, 0x74, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x2d, 0x0a, 0x08, 0x62, 0x69, 0x72, 0x74, 0x68, 0x64, 0x61, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x2e, 0x44, 0x61, 0x74, 0x65, 0x52, 0x08, 0x62, 0x69, 0x72, 0x74, 0x68, 0x64, 0x61, 0x79, 0x22, 0x2f, 0x0a, 0x0a, 0x41, 0x64, 0x64, 0x50, 0x65, 0x74, 0x73, 0x52, 0x65, 0x71, 0x12, 0x21, 0x0a, 0x04, 0x70, 0x65, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x70, 0x65, 0x74, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x50, 0x65, 0x74, 0x52, 0x04, 0x70, 0x65, 0x74, 0x73, 0x22, 0x1f, 0x0a, 0x0b, 0x41, 0x64, 0x64, 0x50, 0x65, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x12, 0x10, 0x0a, 0x03, 0x69, 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x03, 0x69, 0x64, 0x73, 0x22, 0x32, 0x0a, 0x0d, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x50, 0x65, 0x74, 0x73, 0x52, 0x65, 0x71, 0x12, 0x21, 0x0a, 0x04, 0x70, 0x65, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x70, 0x65, 0x74, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x50, 0x65, 0x74, 0x52, 0x04, 0x70, 0x65, 0x74, 0x73, 0x22, 0x10, 0x0a, 0x0e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x50, 0x65, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x22, 0x21, 0x0a, 0x0d, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x65, 0x74, 0x73, 0x52, 0x65, 0x71, 0x12, 0x10, 0x0a, 0x03, 0x69, 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x03, 0x69, 0x64, 0x73, 0x22, 0x10, 0x0a, 0x0e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x65, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x22, 0x8c, 0x01, 0x0a, 0x0d, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x50, 0x65, 0x74, 0x73, 0x52, 0x65, 0x71, 0x12, 0x14, 0x0a, 0x05, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x05, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x12, 0x27, 0x0a, 0x05, 0x74, 0x79, 0x70, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0e, 0x32, 0x11, 0x2e, 0x70, 0x65, 0x74, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x50, 0x65, 0x74, 0x54, 0x79, 0x70, 0x65, 0x52, 0x05, 0x74, 0x79, 0x70, 0x65, 0x73, 0x12, 0x3c, 0x0a, 0x0f, 0x62, 0x69, 0x72, 0x74, 0x68, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x65, 0x74, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x44, 0x61, 0x74, 0x65, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x0e, 0x62, 0x69, 0x72, 0x74, 0x68, 0x64, 0x61, 0x74, 0x65, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x22, 0x55, 0x0a, 0x07, 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x72, 0x12, 0x29, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x15, 0x2e, 0x70, 0x65, 0x74, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x72, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x01, 0x52, 0x0a, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x3f, 0x0a, 0x10, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x72, 0x52, 0x65, 0x71, 0x12, 0x2b, 0x0a, 0x07, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x70, 0x65, 0x74, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x72, 0x52, 0x07, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x72, 0x22, 0x13, 0x0a, 0x11, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x2a, 0x4f, 0x0a, 0x07, 0x50, 0x65, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0d, 0x0a, 0x09, 0x50, 0x54, 0x55, 0x6e, 0x6b, 0x6e, 0x6f, 0x77, 0x6e, 0x10, 0x00, 0x12, 0x0c, 0x0a, 0x08, 0x50, 0x54, 0x43, 0x61, 0x6e, 0x69, 0x6e, 0x65, 0x10, 0x01, 0x12, 0x0c, 0x0a, 0x08, 0x50, 0x54, 0x46, 0x65, 0x6c, 0x69, 0x6e, 0x65, 0x10, 0x02, 0x12, 0x0a, 0x0a, 0x06, 0x50, 0x54, 0x42, 0x69, 0x72, 0x64, 0x10, 0x03, 0x12, 0x0d, 0x0a, 0x09, 0x50, 0x54, 0x52, 0x65, 0x70, 0x74, 0x69, 0x6c, 0x65, 0x10, 0x04, 0x2a, 0x44, 0x0a, 0x0b, 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x72, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0d, 0x0a, 0x09, 0x53, 0x54, 0x55, 0x6e, 0x6b, 0x6e, 0x6f, 0x77, 0x6e, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x53, 0x54, 0x4e, 0x65, 0x76, 0x65, 0x72, 0x10, 0x01, 0x12, 0x0c, 0x0a, 0x08, 0x53, 0x54, 0x41, 0x6c, 0x77, 0x61, 0x79, 0x73, 0x10, 0x02, 0x12, 0x0b, 0x0a, 0x07, 0x53, 0x54, 0x46, 0x6c, 0x6f, 0x61, 0x74, 0x10, 0x03, 0x32, 0xd0, 0x02, 0x0a, 0x08, 0x50, 0x65, 0x74, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x12, 0x38, 0x0a, 0x07, 0x41, 0x64, 0x64, 0x50, 0x65, 0x74, 0x73, 0x12, 0x14, 0x2e, 0x70, 0x65, 0x74, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x41, 0x64, 0x64, 0x50, 0x65, 0x74, 0x73, 0x52, 0x65, 0x71, 0x1a, 0x15, 0x2e, 0x70, 0x65, 0x74, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x41, 0x64, 0x64, 0x50, 0x65, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x12, 0x41, 0x0a, 0x0a, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x50, 0x65, 0x74, 0x73, 0x12, 0x17, 0x2e, 0x70, 0x65, 0x74, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x50, 0x65, 0x74, 0x73, 0x52, 0x65, 0x71, 0x1a, 0x18, 0x2e, 0x70, 0x65, 0x74, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x50, 0x65, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x12, 0x41, 0x0a, 0x0a, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x65, 0x74, 0x73, 0x12, 0x17, 0x2e, 0x70, 0x65, 0x74, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x65, 0x74, 0x73, 0x52, 0x65, 0x71, 0x1a, 0x18, 0x2e, 0x70, 0x65, 0x74, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x65, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x12, 0x38, 0x0a, 0x0a, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x50, 0x65, 0x74, 0x73, 0x12, 0x17, 0x2e, 0x70, 0x65, 0x74, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x50, 0x65, 0x74, 0x73, 0x52, 0x65, 0x71, 0x1a, 0x0d, 0x2e, 0x70, 0x65, 0x74, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x50, 0x65, 0x74, 0x22, 0x00, 0x30, 0x01, 0x12, 0x4a, 0x0a, 0x0d, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x72, 0x12, 0x1a, 0x2e, 0x70, 0x65, 0x74, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x72, 0x52, 0x65, 0x71, 0x1a, 0x1b, 0x2e, 0x70, 0x65, 0x74, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x42, 0x30, 0x5a, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x50, 0x61, 0x63, 0x6b, 0x74, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x69, 0x6e, 0x67, 0x2f, 0x47, 0x6f, 0x2d, 0x66, 0x6f, 0x72, 0x2d, 0x44, 0x65, 0x76, 0x4f, 0x70, 0x73, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( file_petstore_proto_rawDescOnce sync.Once file_petstore_proto_rawDescData = file_petstore_proto_rawDesc ) func file_petstore_proto_rawDescGZIP() []byte { file_petstore_proto_rawDescOnce.Do(func() { file_petstore_proto_rawDescData = protoimpl.X.CompressGZIP(file_petstore_proto_rawDescData) }) return file_petstore_proto_rawDescData } var file_petstore_proto_enumTypes = make([]protoimpl.EnumInfo, 2) var file_petstore_proto_msgTypes = make([]protoimpl.MessageInfo, 12) var file_petstore_proto_goTypes = []interface{}{ (PetType)(0), // 0: petstore.PetType (SamplerType)(0), // 1: petstore.SamplerType (*DateRange)(nil), // 2: petstore.DateRange (*Pet)(nil), // 3: petstore.Pet (*AddPetsReq)(nil), // 4: petstore.AddPetsReq (*AddPetsResp)(nil), // 5: petstore.AddPetsResp (*UpdatePetsReq)(nil), // 6: petstore.UpdatePetsReq (*UpdatePetsResp)(nil), // 7: petstore.UpdatePetsResp (*DeletePetsReq)(nil), // 8: petstore.DeletePetsReq (*DeletePetsResp)(nil), // 9: petstore.DeletePetsResp (*SearchPetsReq)(nil), // 10: petstore.SearchPetsReq (*Sampler)(nil), // 11: petstore.Sampler (*ChangeSamplerReq)(nil), // 12: petstore.ChangeSamplerReq (*ChangeSamplerResp)(nil), // 13: petstore.ChangeSamplerResp (*date.Date)(nil), // 14: google.type.Date } var file_petstore_proto_depIdxs = []int32{ 14, // 0: petstore.DateRange.start:type_name -> google.type.Date 14, // 1: petstore.DateRange.end:type_name -> google.type.Date 0, // 2: petstore.Pet.type:type_name -> petstore.PetType 14, // 3: petstore.Pet.birthday:type_name -> google.type.Date 3, // 4: petstore.AddPetsReq.pets:type_name -> petstore.Pet 3, // 5: petstore.UpdatePetsReq.pets:type_name -> petstore.Pet 0, // 6: petstore.SearchPetsReq.types:type_name -> petstore.PetType 2, // 7: petstore.SearchPetsReq.birthdate_range:type_name -> petstore.DateRange 1, // 8: petstore.Sampler.type:type_name -> petstore.SamplerType 11, // 9: petstore.ChangeSamplerReq.sampler:type_name -> petstore.Sampler 4, // 10: petstore.PetStore.AddPets:input_type -> petstore.AddPetsReq 6, // 11: petstore.PetStore.UpdatePets:input_type -> petstore.UpdatePetsReq 8, // 12: petstore.PetStore.DeletePets:input_type -> petstore.DeletePetsReq 10, // 13: petstore.PetStore.SearchPets:input_type -> petstore.SearchPetsReq 12, // 14: petstore.PetStore.ChangeSampler:input_type -> petstore.ChangeSamplerReq 5, // 15: petstore.PetStore.AddPets:output_type -> petstore.AddPetsResp 7, // 16: petstore.PetStore.UpdatePets:output_type -> petstore.UpdatePetsResp 9, // 17: petstore.PetStore.DeletePets:output_type -> petstore.DeletePetsResp 3, // 18: petstore.PetStore.SearchPets:output_type -> petstore.Pet 13, // 19: petstore.PetStore.ChangeSampler:output_type -> petstore.ChangeSamplerResp 15, // [15:20] is the sub-list for method output_type 10, // [10:15] is the sub-list for method input_type 10, // [10:10] is the sub-list for extension type_name 10, // [10:10] is the sub-list for extension extendee 0, // [0:10] is the sub-list for field type_name } func init() { file_petstore_proto_init() } func file_petstore_proto_init() { if File_petstore_proto != nil { return } if !protoimpl.UnsafeEnabled { file_petstore_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*DateRange); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_petstore_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Pet); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_petstore_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*AddPetsReq); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_petstore_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*AddPetsResp); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_petstore_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*UpdatePetsReq); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_petstore_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*UpdatePetsResp); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_petstore_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*DeletePetsReq); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_petstore_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*DeletePetsResp); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_petstore_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*SearchPetsReq); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_petstore_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Sampler); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_petstore_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*ChangeSamplerReq); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_petstore_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*ChangeSamplerResp); 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_petstore_proto_rawDesc, NumEnums: 2, NumMessages: 12, NumExtensions: 0, NumServices: 1, }, GoTypes: file_petstore_proto_goTypes, DependencyIndexes: file_petstore_proto_depIdxs, EnumInfos: file_petstore_proto_enumTypes, MessageInfos: file_petstore_proto_msgTypes, }.Build() File_petstore_proto = out.File file_petstore_proto_rawDesc = nil file_petstore_proto_goTypes = nil file_petstore_proto_depIdxs = nil } ================================================ FILE: chapter/11/petstore/proto/petstore.proto ================================================ syntax = "proto3"; package petstore; option go_package = "github.com/PacktPublishing/Go-for-DevOps/proto"; import "google/type/date.proto"; // Desribes the type of pets. enum PetType { // The type was not set. PTUnknown = 0; // The pet is a canine. PTCanine = 1; // The pet is a feline. PTFeline = 2; // The pet is a bird. PTBird = 3; // The pet is a reptile. PTReptile = 4; } // Represents a range of dates. message DateRange { // When to start the range, this is inclusive. google.type.Date start = 1; // When to end the range, this is exclusive. google.type.Date end = 2; } // Represents a unique pet. message Pet { // A UUIDv4 for this pet. This can never be set on an AddPet(). string id = 1; // The name of the pet. string name = 2; // The type of pet. PetType type = 3; // The pet's birthday. google.type.Date birthday = 4; } // The request used to add a pets to the system. message AddPetsReq { // The pet information to add. Pet.id must not be set. repeated Pet pets = 1; } // The response do AddPets(). message AddPetsResp { // The IDs of the pets that were added. repeated string ids = 1; } // The request used to update pets in the system. message UpdatePetsReq { // The pet information to update. Pet.id must be set. repeated Pet pets = 1; } // The response do UpdatePets(). message UpdatePetsResp {} // Used to indicate which pets to delete. This is an all or nothing request. message DeletePetsReq { // The IDs of the pets to delete. repeated string ids = 1; } // The response to a DeletePet(). message DeletePetsResp{} // The request to search for pets. message SearchPetsReq { // Pet names to filter by. repeated string names = 1; // Pet types to filter by. repeated PetType types = 2; // Birthdays to filter by. DateRange birthdate_range = 3; } // Types of OTEL sampling we support. enum SamplerType { STUnknown = 0; STNever = 1; STAlways = 2; STFloat = 3; } message Sampler { // The type of sampling to change to. SamplerType type = 1; // This is the sampling rate if type == STFloat. Values must be // > 0 and <= 1.0 . double float_value = 2; } // Used to request we change the OTEL sampling. message ChangeSamplerReq { Sampler sampler = 1; } // The response to a sampling change. message ChangeSamplerResp{} service PetStore { // Adds pets to the pet store. rpc AddPets(AddPetsReq) returns (AddPetsResp) {}; // Updates pets entries in the store. rpc UpdatePets(UpdatePetsReq) returns (UpdatePetsResp) {}; // Deletes pets from the pet store. rpc DeletePets(DeletePetsReq) returns (DeletePetsResp) {}; // Finds pets in the pet store. rpc SearchPets(SearchPetsReq) returns (stream Pet) {}; // These are for management. In real life I might break this into a new server that is // serving on SSH so that it has to be auth'd and reachable only from in my network // or only if auth'd by something. // Changes the OTEL sampling type. rpc ChangeSampler(ChangeSamplerReq) returns (ChangeSamplerResp) {}; } ================================================ FILE: chapter/11/petstore/proto/petstore_grpc.pb.go ================================================ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. package proto 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.32.0 or later. const _ = grpc.SupportPackageIsVersion7 // PetStoreClient is the client API for PetStore 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 PetStoreClient interface { // Adds pets to the pet store. AddPets(ctx context.Context, in *AddPetsReq, opts ...grpc.CallOption) (*AddPetsResp, error) // Updates pets entries in the store. UpdatePets(ctx context.Context, in *UpdatePetsReq, opts ...grpc.CallOption) (*UpdatePetsResp, error) // Deletes pets from the pet store. DeletePets(ctx context.Context, in *DeletePetsReq, opts ...grpc.CallOption) (*DeletePetsResp, error) // Finds pets in the pet store. SearchPets(ctx context.Context, in *SearchPetsReq, opts ...grpc.CallOption) (PetStore_SearchPetsClient, error) // Changes the OTEL sampling type. ChangeSampler(ctx context.Context, in *ChangeSamplerReq, opts ...grpc.CallOption) (*ChangeSamplerResp, error) } type petStoreClient struct { cc grpc.ClientConnInterface } func NewPetStoreClient(cc grpc.ClientConnInterface) PetStoreClient { return &petStoreClient{cc} } func (c *petStoreClient) AddPets(ctx context.Context, in *AddPetsReq, opts ...grpc.CallOption) (*AddPetsResp, error) { out := new(AddPetsResp) err := c.cc.Invoke(ctx, "/petstore.PetStore/AddPets", in, out, opts...) if err != nil { return nil, err } return out, nil } func (c *petStoreClient) UpdatePets(ctx context.Context, in *UpdatePetsReq, opts ...grpc.CallOption) (*UpdatePetsResp, error) { out := new(UpdatePetsResp) err := c.cc.Invoke(ctx, "/petstore.PetStore/UpdatePets", in, out, opts...) if err != nil { return nil, err } return out, nil } func (c *petStoreClient) DeletePets(ctx context.Context, in *DeletePetsReq, opts ...grpc.CallOption) (*DeletePetsResp, error) { out := new(DeletePetsResp) err := c.cc.Invoke(ctx, "/petstore.PetStore/DeletePets", in, out, opts...) if err != nil { return nil, err } return out, nil } func (c *petStoreClient) SearchPets(ctx context.Context, in *SearchPetsReq, opts ...grpc.CallOption) (PetStore_SearchPetsClient, error) { stream, err := c.cc.NewStream(ctx, &PetStore_ServiceDesc.Streams[0], "/petstore.PetStore/SearchPets", opts...) if err != nil { return nil, err } x := &petStoreSearchPetsClient{stream} if err := x.ClientStream.SendMsg(in); err != nil { return nil, err } if err := x.ClientStream.CloseSend(); err != nil { return nil, err } return x, nil } type PetStore_SearchPetsClient interface { Recv() (*Pet, error) grpc.ClientStream } type petStoreSearchPetsClient struct { grpc.ClientStream } func (x *petStoreSearchPetsClient) Recv() (*Pet, error) { m := new(Pet) if err := x.ClientStream.RecvMsg(m); err != nil { return nil, err } return m, nil } func (c *petStoreClient) ChangeSampler(ctx context.Context, in *ChangeSamplerReq, opts ...grpc.CallOption) (*ChangeSamplerResp, error) { out := new(ChangeSamplerResp) err := c.cc.Invoke(ctx, "/petstore.PetStore/ChangeSampler", in, out, opts...) if err != nil { return nil, err } return out, nil } // PetStoreServer is the server API for PetStore service. // All implementations must embed UnimplementedPetStoreServer // for forward compatibility type PetStoreServer interface { // Adds pets to the pet store. AddPets(context.Context, *AddPetsReq) (*AddPetsResp, error) // Updates pets entries in the store. UpdatePets(context.Context, *UpdatePetsReq) (*UpdatePetsResp, error) // Deletes pets from the pet store. DeletePets(context.Context, *DeletePetsReq) (*DeletePetsResp, error) // Finds pets in the pet store. SearchPets(*SearchPetsReq, PetStore_SearchPetsServer) error // Changes the OTEL sampling type. ChangeSampler(context.Context, *ChangeSamplerReq) (*ChangeSamplerResp, error) mustEmbedUnimplementedPetStoreServer() } // UnimplementedPetStoreServer must be embedded to have forward compatible implementations. type UnimplementedPetStoreServer struct { } func (UnimplementedPetStoreServer) AddPets(context.Context, *AddPetsReq) (*AddPetsResp, error) { return nil, status.Errorf(codes.Unimplemented, "method AddPets not implemented") } func (UnimplementedPetStoreServer) UpdatePets(context.Context, *UpdatePetsReq) (*UpdatePetsResp, error) { return nil, status.Errorf(codes.Unimplemented, "method UpdatePets not implemented") } func (UnimplementedPetStoreServer) DeletePets(context.Context, *DeletePetsReq) (*DeletePetsResp, error) { return nil, status.Errorf(codes.Unimplemented, "method DeletePets not implemented") } func (UnimplementedPetStoreServer) SearchPets(*SearchPetsReq, PetStore_SearchPetsServer) error { return status.Errorf(codes.Unimplemented, "method SearchPets not implemented") } func (UnimplementedPetStoreServer) ChangeSampler(context.Context, *ChangeSamplerReq) (*ChangeSamplerResp, error) { return nil, status.Errorf(codes.Unimplemented, "method ChangeSampler not implemented") } func (UnimplementedPetStoreServer) mustEmbedUnimplementedPetStoreServer() {} // UnsafePetStoreServer may be embedded to opt out of forward compatibility for this service. // Use of this interface is not recommended, as added methods to PetStoreServer will // result in compilation errors. type UnsafePetStoreServer interface { mustEmbedUnimplementedPetStoreServer() } func RegisterPetStoreServer(s grpc.ServiceRegistrar, srv PetStoreServer) { s.RegisterService(&PetStore_ServiceDesc, srv) } func _PetStore_AddPets_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(AddPetsReq) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(PetStoreServer).AddPets(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: "/petstore.PetStore/AddPets", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(PetStoreServer).AddPets(ctx, req.(*AddPetsReq)) } return interceptor(ctx, in, info, handler) } func _PetStore_UpdatePets_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(UpdatePetsReq) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(PetStoreServer).UpdatePets(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: "/petstore.PetStore/UpdatePets", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(PetStoreServer).UpdatePets(ctx, req.(*UpdatePetsReq)) } return interceptor(ctx, in, info, handler) } func _PetStore_DeletePets_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(DeletePetsReq) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(PetStoreServer).DeletePets(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: "/petstore.PetStore/DeletePets", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(PetStoreServer).DeletePets(ctx, req.(*DeletePetsReq)) } return interceptor(ctx, in, info, handler) } func _PetStore_SearchPets_Handler(srv interface{}, stream grpc.ServerStream) error { m := new(SearchPetsReq) if err := stream.RecvMsg(m); err != nil { return err } return srv.(PetStoreServer).SearchPets(m, &petStoreSearchPetsServer{stream}) } type PetStore_SearchPetsServer interface { Send(*Pet) error grpc.ServerStream } type petStoreSearchPetsServer struct { grpc.ServerStream } func (x *petStoreSearchPetsServer) Send(m *Pet) error { return x.ServerStream.SendMsg(m) } func _PetStore_ChangeSampler_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(ChangeSamplerReq) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(PetStoreServer).ChangeSampler(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: "/petstore.PetStore/ChangeSampler", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(PetStoreServer).ChangeSampler(ctx, req.(*ChangeSamplerReq)) } return interceptor(ctx, in, info, handler) } // PetStore_ServiceDesc is the grpc.ServiceDesc for PetStore service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) var PetStore_ServiceDesc = grpc.ServiceDesc{ ServiceName: "petstore.PetStore", HandlerType: (*PetStoreServer)(nil), Methods: []grpc.MethodDesc{ { MethodName: "AddPets", Handler: _PetStore_AddPets_Handler, }, { MethodName: "UpdatePets", Handler: _PetStore_UpdatePets_Handler, }, { MethodName: "DeletePets", Handler: _PetStore_DeletePets_Handler, }, { MethodName: "ChangeSampler", Handler: _PetStore_ChangeSampler_Handler, }, }, Streams: []grpc.StreamDesc{ { StreamName: "SearchPets", Handler: _PetStore_SearchPets_Handler, ServerStreams: true, }, }, Metadata: "petstore.proto", } ================================================ FILE: chapter/11/prometheus.yaml ================================================ scrape_configs: - job_name: 'otel-collector' scrape_interval: 10s static_configs: - targets: ['otel-collector:8889'] - targets: ['otel-collector:8888'] ================================================ FILE: chapter/12/agent.service ================================================ [Unit] Description=System agent for running software After=network.target [Service] Type=simple User=agent Group=agent Restart=on-failure RestartSec=5s ExecStart=/home/agent/bin/agent [Install] WantedBy=multi-user.target ================================================ FILE: chapter/12/goss/allfiles/allfiles.go ================================================ //go:build linux || darwin /* ^ | If you haven't see the above line, this is called a build constraint. This will only build for linux or darwin (osx). You can add other build contstraints for architectures or make it so that there are multiple build constraints. You may also use this to have only certain files that are compiled for certain platforms. This allows you to compile in OS/Arch specific instructions for each platform you support. This is how the standard library "sys" package works, there is a different "sys" for each platform. We are restricting this due to the need to read file permissions that we are only sure of on linux and darwin. */ package main import ( "crypto/sha256" "fmt" "io" "io/fs" "log" "os" "os/user" "path/filepath" "strconv" "strings" "sync" "syscall" "github.com/aelsabbahy/goss" "github.com/aelsabbahy/goss/resource" "gopkg.in/yaml.v2" ) func main() { if len(os.Args) != 2 { fmt.Println("must have single argument, the root path to walk") os.Exit(1) } if !strings.HasPrefix(os.Args[1], "/") { fmt.Println("path argument must be fully qualified") os.Exit(1) } conf := goss.GossConfig{ Files: resource.FileMap{}, } input := make(chan fileEntry, 1) go func() { defer close(input) err := filepath.WalkDir( os.Args[1], func(path string, d fs.DirEntry, err error) error { if err != nil { return err } if d.IsDir() { return nil } info, err := d.Info() if err != nil { return err } input <- fileEntry{p: path, info: info} return nil }, ) if err != nil { fmt.Println(err) os.Exit(1) } }() limit := make(chan struct{}, 1000) wg := sync.WaitGroup{} mu := sync.Mutex{} for fe := range input { fe := fe limit <- struct{}{} wg.Add(1) go func() { defer wg.Done() defer func() { <-limit }() rsc, err := addFileEntries(fe) if err != nil { log.Println("file could not be added: ", err) return } mu.Lock() conf.Files[fe.p] = rsc mu.Unlock() }() } wg.Wait() d, err := yaml.Marshal(conf) if err != nil { panic(err) } fmt.Println(string(d)) } type fileEntry struct { p string info os.FileInfo } func addFileEntries(fe fileEntry) (*resource.File, error) { u, g := mustUserGroup(fe) h, err := doSHA256(fe.p) if err != nil { return nil, err } return &resource.File{ Path: fe.p, Exists: true, Mode: fmt.Sprintf("%04o", fe.info.Mode()), Owner: u, Group: g, Filetype: "file", Sha256: h, }, nil } func mustUserGroup(fe fileEntry) (string, string) { stat := fe.info.Sys().(*syscall.Stat_t) uid := stat.Uid gid := stat.Gid u := strconv.FormatUint(uint64(uid), 10) g := strconv.FormatUint(uint64(gid), 10) usr, err := user.LookupId(u) if err != nil { panic(fmt.Sprintf("file(%s) had UID(%d) we couldn't fine: %s", fe.p, uid, err)) } group, err := user.LookupGroupId(g) if err != nil { panic(fmt.Sprintf("file(%s) had GID(%d) we couldn't fine: %s", fe.p, gid, err)) } return usr.Username, group.Name } func doSHA256(p string) (string, error) { dst := sha256.New() src, err := os.Open(p) if err != nil { return "", fmt.Errorf("could not open file: %s", p) } defer src.Close() if _, err := io.Copy(dst, src); err != nil { panic(fmt.Sprintf("problem doing an SHA256(%s): %s", p, err)) } return fmt.Sprintf("%x", dst.Sum(nil)), nil } ================================================ FILE: chapter/12/packer/README ================================================ These hcl files work, however you will need to put in your own user's access key and secret. ================================================ FILE: chapter/12/packer/amazon.final.pkr.hcl ================================================ packer { required_plugins { amazon = { version = ">= 0.0.1" source = "github.com/hashicorp/amazon" } } } source "amazon-ebs" "ubuntu" { access_key = "your user's access key" secret_key = "[your secret]" ami_name = "ubuntu-amd64-final" instance_type = "t2.micro" region = "us-east-2" source_ami_filter { filters = { name = "ubuntu/images/*ubuntu-xenial-16.04-amd64-server-*" root-device-type = "ebs" virtualization-type = "hvm" } most_recent = true owners = ["099720109477"] } ssh_username = "ubuntu" } build { name = "goBook" sources = [ "source.amazon-ebs.ubuntu" ] // Install Go 1.17.5 provisioner "shell" { environment_vars = [ "DEBIAN_FRONTEND=noninteractive", ] inline = [ "cd ~", "mkdir tmp", "cd tmp", "wget https://golang.org/dl/go1.17.5.linux-amd64.tar.gz", "sudo tar -C /usr/local -xzf go1.17.5.linux-amd64.tar.gz", "echo 'export PATH=$PATH:/usr/local/go/bin' >> ~/.profile", ". ~/.profile", "go version", "cd ~/", "rm -rf tmp/*", "rmdir tmp", ] } // Setup user "agent" with SSH key file provisioner "shell" { inline = [ "sudo adduser --disabled-password --gecos '' agent", ] } provisioner "file" { source = "./files/agent.pub" destination = "/tmp/agent.pub" } provisioner "shell" { inline = [ "sudo mkdir /home/agent/.ssh", "sudo mv /tmp/agent.pub /home/agent/.ssh/authorized_keys", "sudo chown agent:agent /home/agent/.ssh", "sudo chown agent:agent /home/agent/.ssh/authorized_keys", "sudo chmod 400 .ssh/authorized_keys", ] } // Setup agent binary running with systemd file. provisioner "shell" { // This installs dbus-launch environment_vars = [ "DEBIAN_FRONTEND=noninteractive", ] inline = [ "sudo apt-get install -y dbus", "sudo apt-get install -y dbus-x11", ] } provisioner "file" { source = "./files/agent" destination = "/tmp/agent" } provisioner "shell" { inline = [ "sudo mkdir /home/agent/bin", "sudo chown agent:agent /home/agent/bin", “sudo chmod ug+rwx /home/agent/bin”, "sudo mv /tmp/agent /home/agent/bin/agent", "sudo chown agent:agent /home/agent/bin/agent", "sudo chmod 0770 /home/agent/bin/agent", ] } provisioner "file" { source = "./files/agent.service" destination = "/tmp/agent.service" } provisioner "shell" { inline = [ "sudo mv /tmp/agent.service /etc/systemd/system/agent.service", "sudo systemctl enable agent.service", "sudo systemctl daemon-reload", "sudo systemctl start agent.service", "sleep 10", "sudo systemctl is-enabled agent.service", ] } // Setup Goss tool on the image. provisioner "shell" { inline = [ "cd ~", "mkdir tmp", "cd tmp", "sudo curl -L https://github.com/aelsabbahy/goss/releases/latest/download/goss-linux-amd64 -o /usr/local/bin/goss", "sudo chmod +rx /usr/local/bin/goss", "goss -v", "cd ~/", "rm -rf tmp/*", "rmdir tmp", ] } // Copy goss for validating our image onto the image. provisioner "file" { source = "./files/goss" destination = "/home/ubuntu/goss" } // Run the Goss tool using our validation files. provisioner "goss" { retry_timeout = "30s" tests = [ "files/goss/goss.yaml", "files/goss/files.yaml", "files/goss/dbus.yaml", "files/goss/process.yaml", ] } } ================================================ FILE: chapter/12/packer/amazon.goenv.pkr.hcl ================================================ packer { required_plugins { amazon = { version = ">= 0.0.1" source = "github.com/hashicorp/amazon" } goenv = { version = ">= 0.0.14" source = "github.com/johnsiilver/goenv" } } } source "amazon-ebs" "ubuntu" { access_key = "your user's access key" secret_key = "your secret" ami_name = "ubuntu-amd64-goenv4" instance_type = "t2.micro" region = "us-east-2" source_ami_filter { filters = { name = "ubuntu/images/*ubuntu-xenial-16.04-amd64-server-*" root-device-type = "ebs" virtualization-type = "hvm" } most_recent = true owners = ["099720109477"] } ssh_username = "ubuntu" } build { name = "goBook" sources = [ "source.amazon-ebs.ubuntu" ] // Install Go 1.17.5 provisioner "goenv" { version = "1.17.5" } // Setup user "agent" with SSH key file provisioner "shell" { inline = [ "sudo adduser --disabled-password --gecos '' agent", ] } provisioner "file" { source = "./files/agent.pub" destination = "/tmp/agent.pub" } provisioner "shell" { inline = [ "sudo mkdir /home/agent/.ssh", "sudo mv /tmp/agent.pub /home/agent/.ssh/authorized_keys", "sudo chown agent:agent /home/agent/.ssh", "sudo chown agent:agent /home/agent/.ssh/authorized_keys", "sudo chmod 400 .ssh/authorized_keys", ] } // Setup agent binary running with systemd file. provisioner "shell" { // This installs dbus-launch environment_vars = [ "DEBIAN_FRONTEND=noninteractive", ] inline = [ "sudo apt-get install -y dbus", "sudo apt-get install -y dbus-x11", ] } provisioner "file" { source = "./files/agent" destination = "/tmp/agent" } provisioner "shell" { inline = [ "sudo mkdir /home/agent/bin", "sudo chown agent:agent /home/agent/bin", “sudo chmod ug+rwx /home/agent/bin”, "sudo mv /tmp/agent /home/agent/bin/agent", "sudo chown agent:agent /home/agent/bin/agent", "sudo chmod 0770 /home/agent/bin/agent", ] } provisioner "file" { source = "./files/agent.service" destination = "/tmp/agent.service" } provisioner "shell" { inline = [ "sudo mv /tmp/agent.service /etc/systemd/system/agent.service", "sudo systemctl enable agent.service", "sudo systemctl daemon-reload", "sudo systemctl start agent.service", "sleep 10", "sudo systemctl is-enabled agent.service", ] } // Setup Goss tool. provisioner "shell" { inline = [ "cd ~", "mkdir tmp", "cd tmp", "sudo curl -L https://github.com/aelsabbahy/goss/releases/latest/download/goss-linux-amd64 -o /usr/local/bin/goss", "sudo chmod +rx /usr/local/bin/goss", "goss -v", "cd ~/", "rm -rf tmp/*", "rmdir tmp", ] } // Setup goss for validating an image. provisioner "file" { source = "./files/goss" destination = "/home/ubuntu/goss" } provisioner "goss" { retry_timeout = "30s" tests = [ "files/goss/goss.yaml", "files/goss/files.yaml", "files/goss/dbus.yaml", "files/goss/process.yaml", ] } } ================================================ FILE: chapter/12/packer/plugins/goenv/GNUmakefile ================================================ NAME=scaffolding BINARY=packer-plugin-${NAME} COUNT?=1 TEST?=$(shell go list ./...) HASHICORP_PACKER_PLUGIN_SDK_VERSION?=$(shell go list -m github.com/hashicorp/packer-plugin-sdk | cut -d " " -f2) .PHONY: dev build: @go build -o ${BINARY} dev: build @mkdir -p ~/.packer.d/plugins/ @mv ${BINARY} ~/.packer.d/plugins/${BINARY} test: @go test -race -count $(COUNT) $(TEST) -timeout=3m install-packer-sdc: ## Install packer sofware development command @go install github.com/hashicorp/packer-plugin-sdk/cmd/packer-sdc@${HASHICORP_PACKER_PLUGIN_SDK_VERSION} ci-release-docs: install-packer-sdc @packer-sdc renderdocs -src docs -partials docs-partials/ -dst docs/ @/bin/sh -c "[ -d docs ] && zip -r docs.zip docs/" plugin-check: install-packer-sdc build @packer-sdc plugin-check ${BINARY} testacc: dev @PACKER_ACC=1 go test -count $(COUNT) -v $(TEST) -timeout=120m generate: install-packer-sdc @go generate ./... packer-sdc renderdocs -src ./docs -dst ./.docs -partials ./docs-partials # checkout the .docs folder for a preview of the docs ================================================ FILE: chapter/12/packer/plugins/goenv/LICENSE ================================================ MIT License Copyright (c) 2022 John Doak Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: chapter/12/packer/plugins/goenv/goenv.go ================================================ package main import ( "bytes" "context" "fmt" "io" "net/http" "os" "runtime" "strings" "github.com/PacktPublishing/Go-for-DevOps/chapter/12/packer/plugins/goenv/internal/config" "github.com/gopherfs/fs/io/mem/simple" "github.com/hashicorp/hcl/v2/hcldec" "github.com/hashicorp/packer-plugin-sdk/packer" "github.com/hashicorp/packer-plugin-sdk/plugin" "github.com/hashicorp/packer-plugin-sdk/version" packerConfig "github.com/hashicorp/packer-plugin-sdk/template/config" ) const ( ver = "0.0.15" release = "dev" ) var pv *version.PluginVersion func init() { pv = version.InitializePluginVersion(ver, release) } func main() { set := plugin.NewSet() set.SetVersion(pv) set.RegisterProvisioner("goenv", &Provisioner{}) err := set.Run() if err != nil { fmt.Fprintln(os.Stderr, err.Error()) os.Exit(1) } } // Provisioner implements packer.Provisioner. type Provisioner struct { packer.Provisioner // Embed the interface. conf *config.Provisioner content []byte fileName string } func (p *Provisioner) ConfigSpec() hcldec.ObjectSpec { return new(config.FlatProvisioner).HCL2Spec() } func (p *Provisioner) Prepare(raws ...interface{}) error { c := config.Provisioner{} if err := packerConfig.Decode(&c, nil, raws...); err != nil { return err } c.Defaults() p.conf = &c return nil } func (p *Provisioner) Provision(ctx context.Context, u packer.Ui, c packer.Communicator, m map[string]interface{}) error { u.Message("Begin Go environment install") if err := p.fetch(ctx, u, c); err != nil { u.Error(fmt.Sprintf("Error: %s", err)) return err } if err := p.push(ctx, u, c); err != nil { u.Error(fmt.Sprintf("Error: %s", err)) return err } if err := p.unpack(ctx, u, c); err != nil { u.Error(fmt.Sprintf("Error: %s", err)) return err } if err := p.test(ctx, u, c); err != nil { u.Error(fmt.Sprintf("Error: %s", err)) return err } u.Message("Go environment install finished") return nil } func (p *Provisioner) fetch(ctx context.Context, u packer.Ui, c packer.Communicator) error { const ( goURL = `https://golang.org/dl/go%s.linux-%s.tar.gz` name = `go%s.linux-%s.tar.gz` ) platform := runtime.GOARCH if p.conf.Version == "latest" { u.Message("Determining latest Go version") resp, err := http.Get("https://golang.org/VERSION?m=text") if err != nil { u.Error("http get problem: " + err.Error()) return fmt.Errorf("problem asking Google for latest Go version: %s", err) } ver, err := io.ReadAll(resp.Body) if err != nil { u.Error("io read problem: " + err.Error()) return fmt.Errorf("problem reading latest Go version: %s", err) } p.conf.Version = strings.TrimPrefix(string(ver), "go") u.Message("Latest Go version: " + p.conf.Version) } else { u.Message("Go version to use is: " + p.conf.Version) } url := fmt.Sprintf(goURL, p.conf.Version, platform) u.Message("Downloading Go version: " + url) resp, err := http.Get(url) if err != nil { return fmt.Errorf("problem reaching golang.org for version(%s): %s)", p.conf.Version, err) } defer resp.Body.Close() p.content, err = io.ReadAll(resp.Body) if err != nil { return fmt.Errorf("problem downloading file: %s", err) } p.fileName = fmt.Sprintf(name, p.conf.Version, platform) u.Message("Downloading complete") return nil } func (p *Provisioner) push(ctx context.Context, u packer.Ui, c packer.Communicator) error { u.Message("Pushing Go tarball") fs := simple.New() fs.WriteFile("/tarball", p.content, 0700) fi, _ := fs.Stat("/tarball") err := c.Upload( "/tmp/"+p.fileName, bytes.NewReader(p.content), &fi, ) if err != nil { return err } u.Message("Go tarball delivered to: /tmp/" + p.fileName) return nil } func (p *Provisioner) unpack(ctx context.Context, u packer.Ui, c packer.Communicator) error { const cmd = `sudo tar -C /usr/local -xzf /tmp/%s` u.Message("Unpacking Go tarball to /usr/local") b := bytes.Buffer{} rc := &packer.RemoteCmd{ Command: fmt.Sprintf(cmd, p.fileName), Stdout: &b, Stderr: &b, } if err := c.Start(ctx, rc); err != nil { return fmt.Errorf("problem unpacking tarball(%s):\n%s", err, b.String()) } u.Message("Unpacked Go tarball") return nil } func (p *Provisioner) test(ctx context.Context, u packer.Ui, c packer.Communicator) error { u.Message("Testing Go install") b := bytes.Buffer{} rc := &packer.RemoteCmd{ Command: `/usr/local/go/bin/go version`, Stdout: &b, Stderr: &b, } if err := c.Start(ctx, rc); err != nil { return fmt.Errorf("problem testing Go install(%s):\n%s", err, b.String()) } u.Message("Go installed successfully") return nil } ================================================ FILE: chapter/12/packer/plugins/goenv/internal/config/config.go ================================================ package config //go:generate packer-sdc mapstructure-to-hcl2 -type Provisioner // Provisioner is our provisioner configuration. type Provisioner struct { Version string } // Default inputs default values. func (p *Provisioner) Defaults() { if p.Version == "" { p.Version = "latest" } } ================================================ FILE: chapter/12/packer/plugins/goenv/internal/config/config.hcl2spec.go ================================================ // Code generated by "packer-sdc mapstructure-to-hcl2"; DO NOT EDIT. package config import ( "github.com/hashicorp/hcl/v2/hcldec" "github.com/zclconf/go-cty/cty" ) // FlatProvisioner is an auto-generated flat version of Provisioner. // Where the contents of a field with a `mapstructure:,squash` tag are bubbled up. type FlatProvisioner struct { Version *string `cty:"version" hcl:"version"` } // FlatMapstructure returns a new FlatProvisioner. // FlatProvisioner is an auto-generated flat version of Provisioner. // Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. func (*Provisioner) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { return new(FlatProvisioner) } // HCL2Spec returns the hcl spec of a Provisioner. // This spec is used by HCL to read the fields of Provisioner. // The decoded values from this spec will then be applied to a FlatProvisioner. func (*FlatProvisioner) HCL2Spec() map[string]hcldec.Spec { s := map[string]hcldec.Spec{ "version": &hcldec.AttrSpec{Name: "version", Type: cty.String, Required: false}, } return s } ================================================ FILE: chapter/13/petstore-provider/.gitignore ================================================ bin .terraform* *.tfstate* ================================================ FILE: chapter/13/petstore-provider/Makefile ================================================ HOSTNAME=example.com NAMESPACE=gofordevops NAME=petstore BINARY=terraform-provider-${NAME} VERSION=0.1.0 GOARCH := $(shell go env GOARCH) GOOS := $(shell go env GOOS) default: install build: go build -o ${BINARY} install: build mkdir -p ~/.terraform.d/plugins/${HOSTNAME}/${NAMESPACE}/${NAME}/${VERSION}/${GOOS}_${GOARCH} mv ${BINARY} ~/.terraform.d/plugins/${HOSTNAME}/${NAMESPACE}/${NAME}/${VERSION}/${GOOS}_${GOARCH} test: go test ./... -v testacc: TF_ACC=1 go test ./... -v $(TESTARGS) -timeout 120m ================================================ FILE: chapter/13/petstore-provider/docker-compose.yml ================================================ version: '3.7' services: petstore: build: context: ../../11/petstore/. command: - /go/bin/petstore - --localDebug ports: - "6742:6742" ================================================ FILE: chapter/13/petstore-provider/examples/main.tf ================================================ terraform { required_providers { petstore = { version = "0.1.0" source = "example.com/gofordevops/petstore" } } } provider "petstore" { host = "127.0.0.1:6742" } resource "petstore_pet" "thor" { name = "Thor" type = "dog" birthday = "2021-04-01T00:00:00Z" } resource "petstore_pet" "tron" { name = "Tron" type = "cat" birthday = "2020-06-25T00:00:00Z" } variable "pet_name" { type = string default = "Thor" } data "petstore_pet" "all" { depends_on = [petstore_pet.thor, petstore_pet.tron] } # Returns all pets output "all_pets" { value = data.petstore_pet.all } # Only returns Thor by name output "thor" { value = { for pet in data.petstore_pet.all.pets : pet.id => pet if pet.name == var.pet_name } } ================================================ FILE: chapter/13/petstore-provider/go.mod ================================================ module github.com/PacktPublishing/Go-for-DevOps/chapter/13/petstore-provider go 1.17 require ( github.com/biogo/store v0.0.0-20201120204734-aad293a2328f github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320 github.com/hashicorp/terraform-plugin-sdk/v2 v2.11.0 github.com/kylelemons/godebug v1.1.0 github.com/pkg/errors v0.9.1 go.opentelemetry.io/otel v1.4.1 go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.27.0 go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v0.27.0 go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.4.1 go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.4.1 go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.4.1 go.opentelemetry.io/otel/metric v0.27.0 go.opentelemetry.io/otel/sdk v1.4.1 go.opentelemetry.io/otel/sdk/metric v0.27.0 go.opentelemetry.io/otel/trace v1.4.1 google.golang.org/genproto v0.0.0-20200711021454-869866162049 google.golang.org/grpc v1.45.0 google.golang.org/protobuf v1.27.1 ) require ( github.com/agext/levenshtein v1.2.2 // indirect github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect github.com/cenkalti/backoff/v4 v4.1.2 // indirect github.com/fatih/color v1.7.0 // indirect github.com/go-logr/logr v1.2.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/golang/protobuf v1.5.2 // indirect github.com/google/go-cmp v0.5.7 // indirect github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect github.com/hashicorp/errwrap v1.0.0 // indirect github.com/hashicorp/go-hclog v1.2.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/go-plugin v1.4.3 // indirect github.com/hashicorp/go-uuid v1.0.2 // indirect github.com/hashicorp/go-version v1.4.0 // indirect github.com/hashicorp/hcl/v2 v2.11.1 // indirect github.com/hashicorp/logutils v1.0.0 // indirect github.com/hashicorp/terraform-plugin-go v0.8.0 // indirect github.com/hashicorp/terraform-plugin-log v0.3.0 // indirect github.com/hashicorp/terraform-registry-address v0.0.0-20210412075316-9b2996cce896 // indirect github.com/hashicorp/terraform-svchost v0.0.0-20200729002733-f050f53b9734 // indirect github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d // indirect github.com/mattn/go-colorable v0.1.4 // indirect github.com/mattn/go-isatty v0.0.10 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/go-testing-interface v1.14.1 // indirect github.com/mitchellh/go-wordwrap v1.0.0 // indirect github.com/mitchellh/mapstructure v1.4.3 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/oklog/run v1.0.0 // indirect github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect github.com/vmihailenco/msgpack/v4 v4.3.12 // indirect github.com/vmihailenco/tagparser v0.1.1 // indirect github.com/zclconf/go-cty v1.10.0 // indirect go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.6.0 // indirect go.opentelemetry.io/otel/internal/metric v0.27.0 // indirect go.opentelemetry.io/proto/otlp v0.12.0 // indirect golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 // indirect golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 // indirect golang.org/x/text v0.3.5 // indirect google.golang.org/appengine v1.6.6 // indirect ) ================================================ FILE: chapter/13/petstore-provider/go.sum ================================================ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0= github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo= github.com/acomagu/bufpipe v1.0.3/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4= github.com/agext/levenshtein v1.2.1/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= github.com/agext/levenshtein v1.2.2 h1:0S/Yg6LYmFJ5stwQeRp6EeOcCbj7xiqQSdNelsXvaqE= github.com/agext/levenshtein v1.2.2/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= github.com/andybalholm/crlf v0.0.0-20171020200849-670099aa064f/go.mod h1:k8feO4+kXDxro6ErPXBRTJ/ro2mf0SsFG8s7doP9kJE= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/apparentlymart/go-cidr v1.1.0/go.mod h1:EBcsNrHc3zQeuaeCeCtQruQm+n9/YjEn/vI25Lg7Gwc= github.com/apparentlymart/go-dump v0.0.0-20180507223929-23540a00eaa3/go.mod h1:oL81AME2rN47vu18xqj1S1jPIPuN7afo62yKTNn3XMM= github.com/apparentlymart/go-dump v0.0.0-20190214190832-042adf3cf4a0 h1:MzVXffFUye+ZcSR6opIgz9Co7WcDx6ZcY+RjfFHoA0I= github.com/apparentlymart/go-dump v0.0.0-20190214190832-042adf3cf4a0/go.mod h1:oL81AME2rN47vu18xqj1S1jPIPuN7afo62yKTNn3XMM= github.com/apparentlymart/go-textseg v1.0.0 h1:rRmlIsPEEhUTIKQb7T++Nz/A5Q6C9IuX2wFoYVvnCs0= github.com/apparentlymart/go-textseg v1.0.0/go.mod h1:z96Txxhf3xSFMPmb5X/1W05FF/Nj9VFpLOpjS5yuumk= github.com/apparentlymart/go-textseg/v12 v12.0.0/go.mod h1:S/4uRK2UtaQttw1GenVJEynmyUenKwP++x/+DdGV/Ec= github.com/apparentlymart/go-textseg/v13 v13.0.0 h1:Y+KvPE1NYz0xl601PVImeQfFyEy6iT90AvPUL1NNfNw= github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A= github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/biogo/store v0.0.0-20201120204734-aad293a2328f h1:+6okTAeUsUrdQr/qN7fIODzowrjjCrnJDg/gkYqcSXY= github.com/biogo/store v0.0.0-20201120204734-aad293a2328f/go.mod h1:z52shMwD6SGwRg2iYFjjDwX5Ene4ENTw6HfXraUy/08= github.com/cenkalti/backoff/v4 v4.1.2 h1:6Yo7N8UP2K6LWZnW94DLVSSrbobcWdVzAYOisuDPIFo= github.com/cenkalti/backoff/v4 v4.1.2/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.1.1/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/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 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/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o= 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.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4ua+E= github.com/go-git/go-billy/v5 v5.2.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= github.com/go-git/go-billy/v5 v5.3.1/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= github.com/go-git/go-git-fixtures/v4 v4.2.1/go.mod h1:K8zd3kDUAykwTdDCr+I0per6Y6vMiRR/nnVTBtavnB0= github.com/go-git/go-git/v5 v5.4.2/go.mod h1:gQ1kArt6d+n+BGd+/B/I74HwRTLhth2+zti4ihgckDc= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= 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-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68= github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 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.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 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.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o= github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-checkpoint v0.5.0/go.mod h1:7nfLNL10NsxqO4iWuW6tWW0HjZuDrwkBuEQsVcpCOgg= github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320 h1:1/D3zfFHttUKaCaGKZ/dR2roBXv0vKbSCnssIldfQdI= github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320/go.mod h1:EiZBMaudVLy8fmjf9Npq1dq9RalhveqZG5w/yz3mHWs= github.com/hashicorp/go-hclog v0.14.1/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= github.com/hashicorp/go-hclog v1.2.0 h1:La19f8d7WIlm4ogzNHB0JGqs5AUDAZ2UfCY4sJXcJdM= github.com/hashicorp/go-hclog v1.2.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/hashicorp/go-plugin v1.4.3 h1:DXmvivbWD5qdiBts9TpBC7BYL1Aia5sxbRgQB+v6UZM= github.com/hashicorp/go-plugin v1.4.3/go.mod h1:5fGEH17QVwTTcR0zV7yhDPLLmFX9YSZ38b18Udy6vYQ= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2IGE= github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go-version v1.3.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go-version v1.4.0 h1:aAQzgqIrRKRa7w75CKpbBxYsmUoPjzVm1W59ca1L0J4= github.com/hashicorp/go-version v1.4.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/hc-install v0.3.1/go.mod h1:3LCdWcCDS1gaHC9mhHCGbkYfoY6vdsKohGjugbZdZak= github.com/hashicorp/hcl/v2 v2.11.1 h1:yTyWcXcm9XB0TEkyU/JCRU6rYy4K+mgLtzn2wlrJbcc= github.com/hashicorp/hcl/v2 v2.11.1/go.mod h1:FwWsfWEjyV/CMj8s/gqAuiviY72rJ1/oayI9WftqcKg= github.com/hashicorp/logutils v1.0.0 h1:dLEQVugN8vlakKOUE3ihGLTZJRB4j+M2cdTm/ORI65Y= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= github.com/hashicorp/terraform-exec v0.16.0/go.mod h1:wB5JHmjxZ/YVNZuv9npAXKmz5pGyxy8PSi0GRR0+YjA= github.com/hashicorp/terraform-json v0.13.0/go.mod h1:y5OdLBCT+rxbwnpxZs9kGL7R9ExU76+cpdY8zHwoazk= github.com/hashicorp/terraform-plugin-go v0.8.0 h1:MvY43PcDj9VlBjYifBWCO/6j1wf106xU8d5Tob/WRs0= github.com/hashicorp/terraform-plugin-go v0.8.0/go.mod h1:E3GuvfX0Pz2Azcl6BegD6t51StXsVZMOYQoGO8mkHM0= github.com/hashicorp/terraform-plugin-log v0.3.0 h1:NPENNOjaJSVX0f7JJTl4f/2JKRPQ7S2ZN9B4NSqq5kA= github.com/hashicorp/terraform-plugin-log v0.3.0/go.mod h1:EjueSP/HjlyFAsDqt+okpCPjkT4NDynAe32AeDC4vps= github.com/hashicorp/terraform-plugin-sdk/v2 v2.11.0 h1:ZcHivbkgrQMHOLm6LBvPev7DTUMKmsC8SNcskgLpOYc= github.com/hashicorp/terraform-plugin-sdk/v2 v2.11.0/go.mod h1:TPjMXvpPNWagHzYOmVPzzRRIBTuaLVukR+esL08tgzg= github.com/hashicorp/terraform-registry-address v0.0.0-20210412075316-9b2996cce896 h1:1FGtlkJw87UsTMg5s8jrekrHmUPUJaMcu6ELiVhQrNw= github.com/hashicorp/terraform-registry-address v0.0.0-20210412075316-9b2996cce896/go.mod h1:bzBPnUIkI0RxauU8Dqo+2KrZZ28Cf48s8V6IHt3p4co= github.com/hashicorp/terraform-svchost v0.0.0-20200729002733-f050f53b9734 h1:HKLsbzeOsfXmKNpr3GiT18XAblV0BjCbzL8KQAMZGa0= github.com/hashicorp/terraform-svchost v0.0.0-20200729002733-f050f53b9734/go.mod h1:kNDNcF7sN4DocDLBkQYz73HGKwN1ANB1blq4lIYLYvg= github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d h1:kJCB4vdITiW1eC1vq2e6IsrXKrZit1bv/TDYFGMp4BQ= github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4= github.com/jhump/protoreflect v1.6.0 h1:h5jfMVslIg6l29nsMs0D8Wj17RDVdNYti0vDN/PZZoE= github.com/jhump/protoreflect v1.6.0/go.mod h1:eaTn3RZAmMBcV0fifFvlm6VHNz3wSkYyXYWUh7ymB74= github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA= github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.10 h1:qxFzApOv4WsAL965uUPIsXzAKCZxN2p9UqdhFS4ZW10= github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= github.com/mitchellh/go-testing-interface v1.14.1 h1:jrgshOhYAUVNMAJiKbEu7EqAwgJJ2JqpQmpLJOu07cU= github.com/mitchellh/go-testing-interface v1.14.1/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8= github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= github.com/mitchellh/go-wordwrap v1.0.0 h1:6GlHJ/LTGMrIJbwgdqdl2eEH8o+Exx/0m8ir9Gns0u4= github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= github.com/mitchellh/mapstructure v1.4.3 h1:OVowDSCllw/YjdLkam3/sm7wEtOy59d8ndGgCcyj8cs= github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nsf/jsondiff v0.0.0-20200515183724-f29ed568f4ce h1:RPclfga2SEJmgMmz2k+Mg7cowZ8yv4Trqw9UsJby758= github.com/nsf/jsondiff v0.0.0-20200515183724-f29ed568f4ce/go.mod h1:uFMI8w+ref4v2r9jz+c9i1IfIttS/OkmLfrk1jne5hs= github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw= github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 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/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/sebdah/goldie v1.0.0/go.mod h1:jXP4hmWywNEwZzhMuv2ccnqTSFpuq8iyQhtQdkkZBH4= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/spf13/pflag v1.0.2/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/vmihailenco/msgpack v3.3.3+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= github.com/vmihailenco/msgpack v4.0.4+incompatible h1:dSLoQfGFAo3F6OoNhwUmLwVgaUXK79GlxNBwueZn0xI= github.com/vmihailenco/msgpack v4.0.4+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= github.com/vmihailenco/msgpack/v4 v4.3.12 h1:07s4sz9IReOgdikxLTKNbBdqDMLsjPKXwvCazn8G65U= github.com/vmihailenco/msgpack/v4 v4.3.12/go.mod h1:gborTTJjAo/GWTqqRjrLCn9pgNN+NXzzngzBKDPIqw4= github.com/vmihailenco/tagparser v0.1.1 h1:quXMXlA39OCbd2wAdTsGDlK9RkOk6Wuw+x37wVyIuWY= github.com/vmihailenco/tagparser v0.1.1/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI= github.com/xanzy/ssh-agent v0.3.0/go.mod h1:3s9xbODqPuuhK9JV1R321M/FlMZSBvE5aY6eAcqrDh0= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/zclconf/go-cty v1.1.0/go.mod h1:xnAOWiHeOqg2nWS62VtQ7pbOu17FtxJNW8RLEih+O3s= github.com/zclconf/go-cty v1.2.0/go.mod h1:hOPWgoHbaTUnI5k4D2ld+GRpFJSCe6bCM7m1q/N4PQ8= github.com/zclconf/go-cty v1.8.0/go.mod h1:vVKLxnk3puL4qRAv72AO+W99LUD4da90g3uUAzyuvAk= github.com/zclconf/go-cty v1.9.1/go.mod h1:vVKLxnk3puL4qRAv72AO+W99LUD4da90g3uUAzyuvAk= github.com/zclconf/go-cty v1.10.0 h1:mp9ZXQeIcN8kAwuqorjH+Q+njbJKjLrvB2yIh4q7U+0= github.com/zclconf/go-cty v1.10.0/go.mod h1:vVKLxnk3puL4qRAv72AO+W99LUD4da90g3uUAzyuvAk= github.com/zclconf/go-cty-debug v0.0.0-20191215020915-b22d67c1ba0b/go.mod h1:ZRKQfBXbGkpdV6QMzT3rU1kSTAnfu1dO8dPKjYprgj8= go.opentelemetry.io/otel v1.4.0/go.mod h1:jeAqMFKy2uLIxCtKxoFj0FAL5zAPKQagc3+GtBWakzk= go.opentelemetry.io/otel v1.4.1 h1:QbINgGDDcoQUoMJa2mMaWno49lja9sHwp6aoa2n3a4g= go.opentelemetry.io/otel v1.4.1/go.mod h1:StM6F/0fSwpd8dKWDCdRr7uRvEPYdW0hBSlbdTiUde4= go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.4.0/go.mod h1:VpP4/RMn8bv8gNo9uK7/IMY4mtWLELsS+JIP0inH0h4= go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.4.1/go.mod h1:VpP4/RMn8bv8gNo9uK7/IMY4mtWLELsS+JIP0inH0h4= go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.6.0 h1:XFcfoo+vwXXwopiS7vzwbaFuPplf5GB+WTjaiQXmz3U= go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.6.0/go.mod h1:NEu79Xo32iVb+0gVNV8PMd7GoWqnyDXRlj04yFjqz40= go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.27.0 h1:t1aPfMj5oZzv2EaRmdC2QPQg1a7MaBjraOh4Hjwuia8= go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.27.0/go.mod h1:aZnoYVx7GIuMROciGC3cjZhYxMD/lKroRJUnFY0afu0= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v0.27.0 h1:RJURCSrqUjJiCY3GuFCVP2EPKOQLwNXQ4FI3aH2KoHg= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v0.27.0/go.mod h1:LIc1eCpkU94tPnXxH40ya41Oyxm7sL+oDvxCYPFpnV8= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.4.1 h1:WPpPsAAs8I2rA47v5u0558meKmmwm1Dj99ZbqCV8sZ8= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.4.1/go.mod h1:o5RW5o2pKpJLD5dNTCmjF1DorYwMeFJmb/rKr5sLaa8= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.4.1 h1:AxqDiGk8CorEXStMDZF5Hz9vo9Z7ZZ+I5m8JRl/ko40= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.4.1/go.mod h1:c6E4V3/U+miqjs/8l950wggHGL1qzlp0Ypj9xoGrPqo= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.4.1 h1:yaXaoJjXaJqRnsfW9HrN7pGb7bzcEn31Rk6yo2LFaWo= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.4.1/go.mod h1:BFiGsTMZdqtxufux8ANXuMeRz9dMPVFdJZadUWDFD7o= go.opentelemetry.io/otel/internal/metric v0.27.0 h1:9dAVGAfFiiEq5NVB9FUJ5et+btbDQAUIJehJ+ikyryk= go.opentelemetry.io/otel/internal/metric v0.27.0/go.mod h1:n1CVxRqKqYZtqyTh9U/onvKapPGv7y/rpyOTI+LFNzw= go.opentelemetry.io/otel/metric v0.27.0 h1:HhJPsGhJoKRSegPQILFbODU56NS/L1UE4fS1sC5kIwQ= go.opentelemetry.io/otel/metric v0.27.0/go.mod h1:raXDJ7uP2/Jc0nVZWQjJtzoyssOYWu/+pjZqRzfvZ7g= go.opentelemetry.io/otel/sdk v1.4.0/go.mod h1:71GJPNJh4Qju6zJuYl1CrYtXbrgfau/M9UAggqiy1UE= go.opentelemetry.io/otel/sdk v1.4.1 h1:J7EaW71E0v87qflB4cDolaqq3AcujGrtyIPGQoZOB0Y= go.opentelemetry.io/otel/sdk v1.4.1/go.mod h1:NBwHDgDIBYjwK2WNu1OPgsIc2IJzmBXNnvIJxJc8BpE= go.opentelemetry.io/otel/sdk/metric v0.27.0 h1:CDEu96Js5IP7f4bJ8eimxF09V5hKYmE7CeyKSjmAL1s= go.opentelemetry.io/otel/sdk/metric v0.27.0/go.mod h1:lOgrT5C3ORdbqp2LsDrx+pBj6gbZtQ5Omk27vH3EaW0= go.opentelemetry.io/otel/trace v1.4.0/go.mod h1:uc3eRsqDfWs9R7b92xbQbU42/eTNz4N+gLP8qJCi4aE= go.opentelemetry.io/otel/trace v1.4.1 h1:O+16qcdTrT7zxv2J6GejTPFinSwA++cYerC5iSiF8EQ= go.opentelemetry.io/otel/trace v1.4.1/go.mod h1:iYEVbroFCNut9QkwEczV9vMRPHNKSSwYZjulEtsmhFc= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.opentelemetry.io/proto/otlp v0.12.0 h1:CMJ/3Wp7iOWES+CYLfnBv+DVmPbB+kmy9PJ92XvlR6c= go.opentelemetry.io/proto/otlp v0.12.0/go.mod h1:TsIjwGWIx5VFYv9KGVlOpxoBl5Dy+63SUguV7GGvlSQ= go.uber.org/goleak v1.1.12 h1:gZAh5/EyT/HQwlpkCy6wTpqfH9H8Lz8zbm3dZh+OyzA= go.uber.org/goleak v1.1.12/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 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/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180530234432-1e491301e022/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180811021610-c39426892332/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-20190108225652-1e06a53dbb7e/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-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191009170851-d66e71096ffb/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210326060303-6b1517762897/go.mod h1:uSPa2vr4CLtc/ILN5odXGNXS6mhrKVzTaCXzk9m6W3k= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 h1:4nGaVu0QrbjT/AK2PRLuQfQuh6DJve+pELhqTdAj3x0= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 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-20181221193216-37e7f081c4d4/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.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/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-20190222072716-a9d3bda3a223/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-20190502175342-a43fa875dd82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210502180810-71e4cd670f79/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 h1:SrN+KX8Art/Sf4HNj6Zcz06G7VEz+7w9tdXTPOZ7+l4= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5 h1:i6eZZ+zk0SOf0xgBpEpPD18qWcJda6q1sxt3S0kzyUQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 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/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200713011307-fd294ab11aed/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 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/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.6 h1:lMO5rYAqUxkmaj76jAkRUvt5JZgFymx/+Q5Mzfivuhc= google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20170818010345-ee236bd376b0/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 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-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20200711021454-869866162049 h1:YFTFpQhgvrLrmxtiIncJxFXeCyq84ixuKWVCaCAi9Oc= google.golang.org/genproto v0.0.0-20200711021454-869866162049/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/grpc v1.8.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= 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.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.43.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= google.golang.org/grpc v1.45.0 h1:NEpgUqV3Z+ZjkqMsxMg11IaDrXY4RY6CQukSGK0uI1M= google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ= 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.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= ================================================ FILE: chapter/13/petstore-provider/internal/client/client.go ================================================ // Client provides an API client to the petstore service. package client import ( "context" "fmt" "io" "time" "google.golang.org/grpc" "google.golang.org/grpc/metadata" "github.com/PacktPublishing/Go-for-DevOps/chapter/13/petstore-provider/internal/client/internal/server/storage" pb "github.com/PacktPublishing/Go-for-DevOps/chapter/13/petstore-provider/internal/client/proto" ) // Client is a client to the petstore service. type Client struct { client pb.PetStoreClient conn *grpc.ClientConn } // New is the constructor for Client. addr is the server's [host]:[port]. func New(addr string) (*Client, error) { conn, err := grpc.Dial(addr, grpc.WithInsecure()) if err != nil { return nil, err } return &Client{ client: pb.NewPetStoreClient(conn), conn: conn, }, nil } // Pet is a wrapper around a *pb.Pet that can return Go versions of // fields and errors if the returned stream has an error. type Pet struct { *pb.Pet err error } // Proto will give the Pet's proto representation. func (p Pet) Proto() *pb.Pet { return p.Pet } // Birthday returns the Pet's birthday as a time.Time. func (p Pet) Birthday() time.Time { // We are ignoring the error as we will either get a zero time // anyways and the server should be preventing this problem. t, _ := storage.BirthdayToTime(context.Background(), p.Pet.Birthday) return t } // Error indicates if there was an error in the Pet output stream. func (p Pet) Error() error { return p.err } // CallOptions are optional options for an RPC call. type CallOption func(co *callOptions) type callOptions struct { trace *string } // TraceID will cause the RPC call to execute a trace on the service and return "s" to the ID. // If s == nil, this will ignore the option. If "s" is not set after the call finishes, then // no trace was made. func TraceID(s *string) CallOption { return func(co *callOptions) { if s == nil { return } co.trace = s } } // AddPets adds pets to the service and returns their unique identities in the // same order as being added. func (c *Client) AddPets(ctx context.Context, pets []*pb.Pet, options ...CallOption) ([]string, error) { if len(pets) == 0 { return nil, nil } for _, p := range pets { if err := storage.ValidatePet(ctx, p, false); err != nil { return nil, err } } var header metadata.MD ctx, gOpts, f := handleCallOptions(ctx, &header, options) defer f() resp, err := c.client.AddPets(ctx, &pb.AddPetsReq{Pets: pets}, gOpts...) if err != nil { return nil, err } return resp.Ids, nil } // UpdatePets updates pets that already exist in the system. func (c *Client) UpdatePets(ctx context.Context, pets []*pb.Pet, options ...CallOption) error { if len(pets) == 0 { return nil } for _, p := range pets { if err := storage.ValidatePet(ctx, p, true); err != nil { return err } } var header metadata.MD ctx, gOpts, f := handleCallOptions(ctx, &header, options) defer f() _, err := c.client.UpdatePets(ctx, &pb.UpdatePetsReq{Pets: pets}, gOpts...) if err != nil { return err } return nil } // DeletePets deletes pets with the IDs passed. If the ID doesn't exist, the // system ignores it. func (c *Client) DeletePets(ctx context.Context, ids []string, options ...CallOption) error { if len(ids) == 0 { return nil } var header metadata.MD ctx, gOpts, f := handleCallOptions(ctx, &header, options) defer f() _, err := c.client.DeletePets(ctx, &pb.DeletePetsReq{Ids: ids}, gOpts...) if err != nil { return err } return nil } // SearchPets searches the pet store for pets matching the filter. If the filter contains // no entries, then all pets will be returned. func (c *Client) SearchPets(ctx context.Context, filter *pb.SearchPetsReq, options ...CallOption) (chan Pet, error) { if filter == nil { return nil, fmt.Errorf("the filter cannot be nil") } var header metadata.MD ctx, gOpts, f := handleCallOptions(ctx, &header, options) stream, err := c.client.SearchPets(ctx, filter, gOpts...) if err != nil { return nil, err } ch := make(chan Pet, 1) go func() { defer close(ch) defer f() for { p, err := stream.Recv() if err == io.EOF { return } if err != nil { ch <- Pet{err: err} return } ch <- Pet{Pet: p} } }() return ch, nil } // SamplerType is the type of OTEL sampling to do. type SamplerType int32 const ( STUnknown SamplerType = 0 Never SamplerType = 1 Always SamplerType = 2 Float SamplerType = 3 ) var validTypes = map[SamplerType]bool{ Never: true, Always: true, Float: true, } type Sampler struct { // Type is the type of sampling to use. Type SamplerType // Rate is the sampling rate, only used if type is Float. Rate float64 } func (s *Sampler) validate() error { if !validTypes[s.Type] { return fmt.Errorf("type %v is not a supported type", s.Type) } if s.Type == Float { if s.Rate <= 0 || s.Rate > 1 { return fmt.Errorf("Rate must be > 0 && <= 1.0, was %v", s.Rate) } } return nil } func (s *Sampler) proto() *pb.Sampler { return &pb.Sampler{ Type: pb.SamplerType(s.Type), FloatValue: s.Rate, } } func (s *Sampler) fromProto(p *pb.Sampler) { s.Type = SamplerType(p.Type) s.Rate = p.FloatValue } // ChangeSampler changes the sampling type and rate on the server. This is // and admin function that in production should be restricted. func (c *Client) ChangeSampler(ctx context.Context, sc Sampler, options ...CallOption) error { if err := sc.validate(); err != nil { return err } var header metadata.MD ctx, gOpts, f := handleCallOptions(ctx, &header, options) defer f() _, err := c.client.ChangeSampler(ctx, &pb.ChangeSamplerReq{Sampler: sc.proto()}, gOpts...) if err != nil { return err } return nil } func handleCallOptions(ctx context.Context, header *metadata.MD, options []CallOption) (context.Context, []grpc.CallOption, func()) { opts := callOptions{} for _, o := range options { o(&opts) } var gOpts []grpc.CallOption if opts.trace != nil { (*header)["trace"] = nil gOpts = append(gOpts, grpc.Header(header)) } f := func() { if opts.trace != nil { if len((*header)["otel.traceID"]) != 0 { *opts.trace = (*header)["otel.traceID"][0] } } } return ctx, gOpts, f } ================================================ FILE: chapter/13/petstore-provider/internal/client/internal/server/errors/errors.go ================================================ // Package errors is a replacement for the golang standard library "errors". This replacement // adds errors to the Open Telemetry spans. The signatures only differs in that // New() now takes a context.Context object and fmt.Errorf() has been moved here and also takes a Context.Context. package errors import ( "context" "errors" "fmt" "go.opentelemetry.io/otel/codes" "go.opentelemetry.io/otel/trace" ) // New creates a new error and writes the error to a span if it exists in the context. func New(ctx context.Context, text string) error { span := trace.SpanFromContext(ctx) err := errors.New(text) span.RecordError(err) span.SetStatus(codes.Error, err.Error()) return err } // Errorf implements fmt.Errorf with the addition of a Context that if it contains a span // will have the error added to the span. func Errorf(ctx context.Context, s string, i ...interface{}) error { span := trace.SpanFromContext(ctx) err := fmt.Errorf(s, i...) span.RecordError(err) span.SetStatus(codes.Error, err.Error()) return err } // As implements errors.As(). func As(err error, target interface{}) bool { return errors.As(err, target) } // Is implements errors.Is(). func Is(err, target error) bool { return errors.Is(err, target) } // Unwrap implemements errors.Unwrap(). func Unwrap(err error) error { return errors.Unwrap(err) } ================================================ FILE: chapter/13/petstore-provider/internal/client/internal/server/log/log.go ================================================ /* Package log is a replacement for the standard library log package that logs to OTEL spans contained in Context objects. These are seen as events with the attribute "log" set to true. The preferred way to log is to use an event: func someFunc(ctx context.Context) { e := NewEvent("someFunc()") defer e.Done(ctx) start := time.Now() defer func() { e.Add("latency.ns", int(time.Since(start))) }() } This records an event in the current span that has a key of "latency.ns" with the value in nano-seconds the operation took. You can use this to log in a similar manner to the logging package with Println and Printf. This is generally only useful for some generic debugging where you want to log something and filter the trace by messages with key "log". Generally these are messages you don't want to keep. func main() { ctx := context.Background() log.SetFlags(log.LstdFlags | log.Lshortfile) log.Println(ctx, "Starting main") log.Printf(ctx, "Env variables: %v", os.Environ()) } The above won't log anything, as there is no Span on the Context. If there was it would get output to the Open Telementry provider. If you need to use the standard library log, you can use Logger: log.Logger.Println("hello world") This would print whever the stanard logger prints to. This defaults to the standard logger, but you can replace with another Logger if you wish. You should only log messages with a standard logger when it can't be output to a trace. These are critical messages that indicate a definite bug. This keeps logging to only critical events and de-clutters what you need to look at to when doing a debug. */ package log import ( "context" "fmt" "log" "runtime" "sync" "time" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/trace" ) const ( Ldate = 1 << iota // the date in the local time zone: 2009/01/23 Ltime // the time in the local time zone: 01:23:23 Lmicroseconds // microsecond resolution: 01:23:23.123123. assumes Ltime. Llongfile // full file name and line number: /a/b/c/d.go:23 Lshortfile // final file name element and line number: d.go:23. overrides Llongfile LUTC // if Ldate or Ltime is set, use UTC rather than the local time zone LstdFlags = Ldate | Ltime // initial values for the standard logger ) // Logger provides access to the standard library's default logger. // This can be replaced in main with a logger of your choice. var Logger *log.Logger = log.Default() // setup the standard logger with flags. var std = &logger{flag: LstdFlags} // pool provides a pool of Event objects to keep our allocations to a minimum. var pool = &eventPool{ buf: make(chan *Event, 100), pool: sync.Pool{ New: func() interface{} { return &Event{} }, }, } // eventPool uses a set amount of Event objects and a sync.Pool for overflow. // Note: this would actually be a great place for metrics to key in on what would be // an optimal size for buf to prevent pool use. type eventPool struct { buf chan *Event pool sync.Pool } func (e *eventPool) get() *Event { select { case ev := <-e.buf: return ev default: } return e.pool.Get().(*Event) } func (e *eventPool) put(ev *Event) { ev.reset() select { case e.buf <- ev: default: } e.pool.Put(ev) } // Event represents a named event that occurs. This is the prefered way to log data. // Events have attributes and those attributes are key/value pairs. You create // an event and stuff attributes using Add() until the event is over and call Done(). // This will render the event to the current span. if no attrs exist, the event is ignored. // To avoid extra allocations type Event struct { name string attrs []attribute.KeyValue } // NewEvent returns a new Event. func NewEvent(name string) *Event { ev := pool.get() ev.name = name return ev } func (e *Event) reset() { e.name = "" e.attrs = e.attrs[0:0] } // Add adds an attribute named k with value i. i can be: bool, []bool, float64, []float64, int, []int, int64, []int64, string and []string. // If the value isn't one of those values, a standard log message is printed indicating a bug. func (e *Event) Add(k string, i interface{}) { if e.name == "" { return } switch v := i.(type) { case bool: e.attrs = append(e.attrs, attribute.Bool(k, v)) case []bool: e.attrs = append(e.attrs, attribute.BoolSlice(k, v)) case float64: e.attrs = append(e.attrs, attribute.Float64(k, v)) case []float64: e.attrs = append(e.attrs, attribute.Float64Slice(k, v)) case int: e.attrs = append(e.attrs, attribute.Int(k, v)) case []int: e.attrs = append(e.attrs, attribute.IntSlice(k, v)) case int64: e.attrs = append(e.attrs, attribute.Int64(k, v)) case []int64: e.attrs = append(e.attrs, attribute.Int64Slice(k, v)) case string: e.attrs = append(e.attrs, attribute.String(k, v)) case []string: e.attrs = append(e.attrs, attribute.StringSlice(k, v)) default: log.Printf("bug: event.Add(): receiveing %T which is not supported", v) } } // Done renders the Event to the span in the Context. If there are no attributes on the Event, this is a no-oop. // Once Done is called, the Event object MUST not be used again. func (e *Event) Done(ctx context.Context) { defer pool.put(e) if e.name == "" { return } span := trace.SpanFromContext(ctx) if e.attrs == nil { return } span.AddEvent(e.name, trace.WithAttributes(e.attrs...)) } // Println acts like log.Println() except we log to the OTEL span in the Context. func Println(ctx context.Context, v ...interface{}) { span := trace.SpanFromContext(ctx) if !span.IsRecording() { return } std.output(span, 2, fmt.Sprintln(v...)) } // Printf acts like log.Printf() except we log to the OTEL span in the Context. func Printf(ctx context.Context, format string, v ...interface{}) { span := trace.SpanFromContext(ctx) if !span.IsRecording() { return } std.output(span, 2, fmt.Sprintf(format, v...)) } // SetFlags sets the output flags for the standard logger. func SetFlags(flag int) { std.flag = flag } // logger is an implementation of log.Logger that writes to a Span. type logger struct { mu sync.Mutex flag int // properties buf []byte // for accumulating text to write } // Cheap integer to fixed-width decimal ASCII. Give a negative width to avoid zero-padding. func itoa(buf *[]byte, i int, wid int) { // Assemble decimal in reverse order. var b [20]byte bp := len(b) - 1 for i >= 10 || wid > 1 { wid-- q := i / 10 b[bp] = byte('0' + i - q*10) bp-- i = q } // i < 10 b[bp] = byte('0' + i) *buf = append(*buf, b[bp:]...) } func (l *logger) output(span trace.Span, calldepth int, s string) error { now := time.Now() // get this early var file string var line int l.mu.Lock() defer l.mu.Unlock() if l.flag&(Lshortfile|Llongfile) != 0 { // Release lock while getting caller info - it's expensive. l.mu.Unlock() var ok bool _, file, line, ok = runtime.Caller(calldepth) if !ok { file = "???" line = 0 } l.mu.Lock() } l.buf = l.buf[:0] l.formatHeader(&l.buf, now, file, line) l.buf = append(l.buf, s...) if len(s) == 0 || s[len(s)-1] != '\n' { l.buf = append(l.buf, '\n') } span.AddEvent(string(l.buf), trace.WithAttributes(attribute.Bool("log", true))) return nil } // formatHeader writes log header to buf in following order: // * date and/or time (if corresponding flags are provided), // * file and line number (if corresponding flags are provided), func (l *logger) formatHeader(buf *[]byte, t time.Time, file string, line int) { if l.flag&(Ldate|Ltime|Lmicroseconds) != 0 { if l.flag&LUTC != 0 { t = t.UTC() } if l.flag&Ldate != 0 { year, month, day := t.Date() itoa(buf, year, 4) *buf = append(*buf, '/') itoa(buf, int(month), 2) *buf = append(*buf, '/') itoa(buf, day, 2) *buf = append(*buf, ' ') } if l.flag&(Ltime|Lmicroseconds) != 0 { hour, min, sec := t.Clock() itoa(buf, hour, 2) *buf = append(*buf, ':') itoa(buf, min, 2) *buf = append(*buf, ':') itoa(buf, sec, 2) if l.flag&Lmicroseconds != 0 { *buf = append(*buf, '.') itoa(buf, t.Nanosecond()/1e3, 6) } *buf = append(*buf, ' ') } } if l.flag&(Lshortfile|Llongfile) != 0 { if l.flag&Lshortfile != 0 { short := file for i := len(file) - 1; i > 0; i-- { if file[i] == '/' { short = file[i+1:] break } } file = short } *buf = append(*buf, file...) *buf = append(*buf, ':') itoa(buf, line, -1) *buf = append(*buf, ": "...) } } ================================================ FILE: chapter/13/petstore-provider/internal/client/internal/server/storage/mem/mem.go ================================================ // Package mem contains an in-memory storage implementation of storage.Data. // This is great for unit tests and demos. Our implementation uses a // left-leaning red black tree for storage of entries by birthdays and maps // for all other indexes. Filtering is done by searching all indexes for matches // by each filter and if all matches succeed we stream the entry found. package mem import ( "context" "sync" "time" "github.com/PacktPublishing/Go-for-DevOps/chapter/13/petstore-provider/internal/client/internal/server/errors" "github.com/PacktPublishing/Go-for-DevOps/chapter/13/petstore-provider/internal/client/internal/server/log" "github.com/PacktPublishing/Go-for-DevOps/chapter/13/petstore-provider/internal/client/internal/server/storage" pb "github.com/PacktPublishing/Go-for-DevOps/chapter/13/petstore-provider/internal/client/proto" "github.com/biogo/store/llrb" ) // birthdays represents a set of pets that share the same birthday with // keys that are pet IDs. This is what we insert into our birthday tree. type birthdays map[string]*pb.Pet // Compare implements the llrb.Comparable.Compare(). func (bi birthdays) Compare(b llrb.Comparable) int { var ap, bp *pb.Pet // Get any entry in the map, all have the same birthday. for _, ap = range bi { break } for _, bp = range b.(birthdays) { break } // Ignore errors because we have to conform to a function def // and we should not be storing records with errors in the Birthday. at, _ := storage.BirthdayToTime(nil, ap.Birthday) bt, _ := storage.BirthdayToTime(nil, bp.Birthday) switch { case at.Before(bt): return -1 case at.Equal(bt): return 0 } return 1 } // birthdayGet is what we use to search for a pets with a particular birthday. type birthdayGet struct { *pb.Pet } // Compare implements the llrb.Comparable.Compare(). func (bi birthdayGet) Compare(b llrb.Comparable) int { // Ignore errors because we have to conform to a function def // and we should not be storing records with errors in the Birthday. at, _ := storage.BirthdayToTime(nil, bi.Pet.Birthday) var bt time.Time switch v := b.(type) { case birthdayGet: bt, _ = storage.BirthdayToTime(nil, v.Pet.Birthday) case birthdays: var p *pb.Pet for _, p = range v { break } bt, _ = storage.BirthdayToTime(nil, p.Birthday) } switch { case at.Before(bt): return -1 case at.Equal(bt): return 0 } return 1 } // Data implements storage.Data. type Data struct { mu sync.RWMutex // protects the items in this block birthday *llrb.Tree names map[string]map[string]*pb.Pet ids map[string]*pb.Pet types map[pb.PetType]map[string]*pb.Pet // searches contains all the search calls that must be done // when we do a search. This is populated in New(). searches []func(context.Context, *pb.SearchPetsReq) []string } // New is the constructor for Data. func New() *Data { d := Data{ names: map[string]map[string]*pb.Pet{}, ids: map[string]*pb.Pet{}, birthday: &llrb.Tree{}, types: map[pb.PetType]map[string]*pb.Pet{}, } d.searches = []func(context.Context, *pb.SearchPetsReq) []string{ d.byNames, d.byTypes, d.byBirthdays, } return &d } // AddPets implements storage.Data.AddPets(). func (d *Data) AddPets(ctx context.Context, pets []*pb.Pet) error { e := log.NewEvent("mem.data.AddPets()") defer e.Done(ctx) start := time.Now() defer func() { e.Add("latency.ns", time.Since(start)) }() d.mu.RLock() // Make sure that none of these IDs somehow exist already. for _, p := range pets { if _, ok := d.ids[p.Id]; ok { return errors.Errorf(ctx, "pet with ID(%s) is already present", p.Id) } } d.mu.RUnlock() d.mu.Lock() defer d.mu.Unlock() d.populate(ctx, pets) return nil } // UpdatePets implements storage.Data.AddPets(). func (d *Data) UpdatePets(ctx context.Context, pets []*pb.Pet) error { d.mu.RLock() // Make sure that ALL of these IDs somehow exist already. for _, p := range pets { if _, ok := d.ids[p.Id]; !ok { return errors.Errorf(ctx, "pet with ID(%s) doesn't exist", p.Id) } } d.mu.RUnlock() d.mu.Lock() defer d.mu.Unlock() d.populate(ctx, pets) return nil } func (d *Data) populate(ctx context.Context, pets []*pb.Pet) { e := log.NewEvent("mem.data.populate()") defer e.Done(ctx) for _, p := range pets { d.ids[p.Id] = p if v, ok := d.names[p.Name]; ok { v[p.Id] = p } else { d.names[p.Name] = map[string]*pb.Pet{ p.Id: p, } } if v, ok := d.types[p.Type]; ok { v[p.Id] = p } else { d.types[p.Type] = map[string]*pb.Pet{ p.Id: p, } } v := d.birthday.Get(birthdayGet{p}) if v == nil { d.birthday.Insert(birthdays{p.Id: p}) continue } v.(birthdays)[p.Id] = p } } // DeletePets implements stroage.Data.DeletePets(). func (d *Data) DeletePets(ctx context.Context, ids []string) error { e := log.NewEvent("mem.data.DeletePets()") defer e.Done(ctx) start := time.Now() defer func() { e.Add("latency.ns", time.Since(start)) }() d.mu.Lock() defer d.mu.Unlock() for _, id := range ids { p, ok := d.ids[id] if !ok { continue } delete(d.ids, id) if v, ok := d.names[p.Name]; ok { if len(v) == 1 { delete(d.names, p.Name) } else { delete(v, id) } } if v, ok := d.types[p.Type]; ok { if len(v) == 1 { delete(d.types, p.Type) } else { delete(v, id) } } v := d.birthday.Get(birthdayGet{p}) if v == nil { continue } if len(v.(birthdays)) == 1 { d.birthday.Delete(birthdayGet{p}) } delete(v.(birthdays), p.Id) } return nil } // SearchPets implements storage.Data.SearchPets(). func (d *Data) SearchPets(ctx context.Context, filter *pb.SearchPetsReq) chan storage.SearchItem { petsCh := make(chan storage.SearchItem, 1) go func() { defer close(petsCh) d.searchPets(ctx, filter, petsCh) }() return petsCh } func (d *Data) searchPets(ctx context.Context, filter *pb.SearchPetsReq, out chan storage.SearchItem) { e := log.NewEvent("mem.data.searchPets()") defer e.Done(ctx) d.mu.RLock() defer d.mu.RUnlock() filters := 0 if len(filter.Names) > 0 { e.Add("filterNames", true) filters++ } if len(filter.Types) > 0 { e.Add("filterTypes", true) filters++ } if filter.BirthdateRange != nil { e.Add("filterBirthday", true) filters++ } // They didn't provide filters, so just return everything. if filters == 0 { e.Add("returnAll", true) d.returnAll(ctx, out) return } searchCh := make(chan []string, len(d.searches)) wg := sync.WaitGroup{} wg.Add(len(d.searches)) goCount := 0 // Spin off our searches. for _, search := range d.searches { goCount++ search := search go func() { defer wg.Done() r := search(ctx, filter) select { case <-ctx.Done(): case searchCh <- r: } }() } e.Add("search.goroutines", goCount) // Wait for our searches to complete then close our searchCh. go func() { wg.Wait(); close(searchCh) }() // Collect all IDs from searches and count them. When one hits // the total number of filters send the matching pet to the caller. m := map[string]int{} matchCh := make(chan string, 1) go func() { defer close(matchCh) for ids := range searchCh { for _, id := range ids { count := m[id] count++ m[id] = count if count == filters { matchCh <- id } } } }() // This handles all our matches getting returned. valCount := 0 latency := 0 defer func() { if valCount > 0 { e.Add("upstream.recv.latency.avg.ns", latency/valCount) } }() for { select { case <-ctx.Done(): return case id, ok := <-matchCh: if !ok { return } start := time.Now() out <- storage.SearchItem{Pet: d.ids[id]} valCount++ latency += int(time.Since(start)) } } } // returnAll streams all the pets that we have. func (d *Data) returnAll(ctx context.Context, out chan storage.SearchItem) { e := log.NewEvent("mem.data.returnAll()") defer e.Done(ctx) start := time.Now() count := 0 defer func() { e.Add("latency.ns", int(time.Since(start))) e.Add("count", count) }() for _, p := range d.ids { count++ select { case <-ctx.Done(): return case out <- storage.SearchItem{Pet: p}: } } } // byNames returns IDs of pets that have the names matched in the filter. func (d *Data) byNames(ctx context.Context, filter *pb.SearchPetsReq) []string { if len(filter.Names) == 0 { return nil } e := log.NewEvent("mem.data.byNames()") defer e.Done(ctx) start := time.Now() count := 0 defer func() { e.Add("latency.ns", int(time.Since(start))) e.Add("count", count) }() var ids []string for _, n := range filter.Names { count++ if ctx.Err() != nil { return nil } p, ok := d.names[n] if !ok { continue } for id := range p { ids = append(ids, id) } } return ids } // byTypes returns IDs of pets that have the types matched in the filter. func (d *Data) byTypes(ctx context.Context, filter *pb.SearchPetsReq) []string { if len(filter.Types) == 0 { return nil } e := log.NewEvent("mem.data.byTypes()") defer e.Done(ctx) start := time.Now() count := 0 defer func() { e.Add("latency.ns", int(time.Since(start))) e.Add("count", count) }() var ids []string for _, t := range filter.Types { count++ if ctx.Err() != nil { return nil } p, ok := d.types[t] if !ok { continue } for id := range p { ids = append(ids, id) } } return ids } // byBirthdays returns IDs of pets that have the birthdays matched in the filter. func (d *Data) byBirthdays(ctx context.Context, filter *pb.SearchPetsReq) []string { if filter.BirthdateRange == nil { return nil } e := log.NewEvent("mem.data.byBirthdays()") defer e.Done(ctx) start := time.Now() count := 0 defer func() { e.Add("latency.ns", int(time.Since(start))) e.Add("count", count) }() var ids []string d.birthday.DoRange( func(c llrb.Comparable) (done bool) { for _, p := range c.(birthdays) { if ctx.Err() != nil { return true } ids = append(ids, p.Id) } return }, birthdayGet{&pb.Pet{Birthday: filter.BirthdateRange.Start}}, birthdayGet{&pb.Pet{Birthday: filter.BirthdateRange.End}}, ) if ctx.Err() != nil { return nil } count = len(ids) return ids } ================================================ FILE: chapter/13/petstore-provider/internal/client/internal/server/storage/mem/mem_test.go ================================================ package mem import ( "context" "sort" "strconv" "testing" "github.com/kylelemons/godebug/pretty" dpb "google.golang.org/genproto/googleapis/type/date" "google.golang.org/protobuf/proto" "github.com/PacktPublishing/Go-for-DevOps/chapter/13/petstore-provider/internal/client/internal/server/storage" pb "github.com/PacktPublishing/Go-for-DevOps/chapter/13/petstore-provider/internal/client/proto" ) // This tests we implement the interface. var _ storage.Data = &Data{} var pets = []*pb.Pet{ { Id: "0", Name: "Adam", Type: pb.PetType_PTCanine, Birthday: &dpb.Date{Month: 1, Day: 1, Year: 2020}, }, { Id: "1", Name: "Becky", Type: pb.PetType_PTFeline, Birthday: &dpb.Date{Month: 2, Day: 1, Year: 2020}, }, { Id: "2", Name: "Calvin", Type: pb.PetType_PTFeline, Birthday: &dpb.Date{Month: 2, Day: 2, Year: 2020}, }, { Id: "3", Name: "David", Type: pb.PetType_PTBird, Birthday: &dpb.Date{Month: 2, Day: 2, Year: 2021}, }, { Id: "4", Name: "Elaine", Type: pb.PetType_PTReptile, Birthday: &dpb.Date{Month: 2, Day: 2, Year: 2021}, }, { Id: "5", Name: "Elaine", Type: pb.PetType_PTReptile, Birthday: &dpb.Date{Month: 2, Day: 3, Year: 2021}, }, } // makePets takes the global "pets" var and clones everything in it and puts it into // a *Data so we have test data. func makePets() *Data { d := New() n := []*pb.Pet{} for _, p := range pets { n = append(n, proto.Clone(p).(*pb.Pet)) } d.AddPets(context.Background(), n) return d } func TestByNames(t *testing.T) { d := makePets() got := d.byNames(context.Background(), &pb.SearchPetsReq{Names: []string{"David", "Elaine"}}) sort.Strings(got) want := []string{"3", "4", "5"} if diff := pretty.Compare(want, got); diff != "" { t.Errorf("TestByNames: -want/+got:\n%s", diff) } } func TestByTypes(t *testing.T) { d := makePets() got := d.byTypes(context.Background(), &pb.SearchPetsReq{Types: []pb.PetType{pb.PetType_PTCanine, pb.PetType_PTReptile}}) sort.Strings(got) want := []string{"0", "4", "5"} if diff := pretty.Compare(want, got); diff != "" { t.Errorf("TestByTypes: -want/+got:\n%s", diff) } } func TestByBirthdays(t *testing.T) { d := makePets() got := d.byBirthdays( context.Background(), &pb.SearchPetsReq{ BirthdateRange: &pb.DateRange{ Start: &dpb.Date{Month: 2, Day: 1, Year: 2020}, End: &dpb.Date{Month: 2, Day: 3, Year: 2021}, }, }, ) sort.Strings(got) want := []string{"1", "2", "3", "4"} if diff := pretty.Compare(want, got); diff != "" { t.Errorf("TestByBirthdays: -want/+got:\n%s", diff) } } func TestDeletePets(t *testing.T) { d := makePets() deletions := []string{"3", "5", "20"} if err := d.DeletePets(context.Background(), deletions); err != nil { t.Fatalf("TestDeletePets: got err == %v, want err == nil", err) } // Don't check the last deletion, it is only there to make sure // a non-existent value doesn't do anything. for _, id := range deletions[:len(deletions)-1] { if _, ok := d.ids[id]; ok { t.Errorf("TestDeletePets: found ids[%s]", id) } i, _ := strconv.Atoi(id) if m, ok := d.names[pets[i].Name]; ok { if _, ok := m[id]; ok { t.Errorf("TestDeletePets: found(%s) in names", id) } } if m, ok := d.types[pets[i].Type]; ok { if _, ok := m[id]; ok { t.Errorf("TestDeletePets: found(%s) in types", id) } } v := d.birthday.Get(birthdayGet{pets[i]}) if v != nil { if _, ok := v.(birthdays)[id]; ok { t.Errorf("TestDeletePets: found(%s) in birthday tree", id) } } } } func TestSearchPets(t *testing.T) { d := makePets() ch := d.SearchPets( context.Background(), &pb.SearchPetsReq{ Names: []string{ "Becky", "Calvin", "David", "Elaine", }, Types: []pb.PetType{ pb.PetType_PTReptile, pb.PetType_PTFeline, }, BirthdateRange: &pb.DateRange{ Start: &dpb.Date{Month: 2, Day: 2, Year: 2021}, End: &dpb.Date{Month: 2, Day: 3, Year: 2021}, }, }, ) got := []storage.SearchItem{} for item := range ch { got = append(got, item) } want := []storage.SearchItem{{Pet: pets[4]}} config := pretty.Config{TrackCycles: true} if diff := config.Compare(want, got); diff != "" { t.Errorf("TestSearchPets: -want/+got:\n%s", diff) } } ================================================ FILE: chapter/13/petstore-provider/internal/client/internal/server/storage/storage.go ================================================ package storage import ( "context" "strings" "time" dpb "google.golang.org/genproto/googleapis/type/date" "github.com/PacktPublishing/Go-for-DevOps/chapter/13/petstore-provider/internal/client/internal/server/errors" pb "github.com/PacktPublishing/Go-for-DevOps/chapter/13/petstore-provider/internal/client/proto" ) // Data represents our data storage. type Data interface { // AddPets adds pet entries into storage. AddPets(ctx context.Context, pets []*pb.Pet) error // UpdatePets updates pet entries in storage. UpdatePets(ctx context.Context, pets []*pb.Pet) error // DeletePets deletes pets in storage by their ID. Will not error // on IDs not found. DeletePets(ctx context.Context, ids []string) error // SearchPets searches storage for pet entries that match the // filter. SearchPets(ctx context.Context, filter *pb.SearchPetsReq) chan SearchItem } // SearchItem is an item returned by a search. type SearchItem struct { // Pet is the pet that matched the search filters. Pet *pb.Pet // Error indicates that there was an error. If set the channel // will close after this entry. Error error } // ValidatePet validates that *pb.Pet has valid fields. func ValidatePet(ctx context.Context, p *pb.Pet, forUpdate bool) error { if forUpdate && p.Id == "" { return errors.New(ctx, "updates must have the Id field set") } else { if !forUpdate && p.Id != "" { return errors.New(ctx, "cannot set the Id field") } } p.Name = strings.TrimSpace(p.Name) if p.Name == "" { return errors.New(ctx, "cannot have a pet without a name") } if p.Type == pb.PetType_PTUnknown { return errors.New(ctx, "cannot have an unknown pet type") } _, err := BirthdayToTime(ctx, p.Birthday) if err != nil { return errors.Errorf(ctx, "pet(%s) had an error in its birthday: %w", p.Name, err) } return nil } // BirthdayToTime converts the *pb.Pet.Birthday field to a time.Time object. func BirthdayToTime(ctx context.Context, d *dpb.Date) (time.Time, error) { if d.Month < 1 || d.Month > 12 { return time.Time{}, errors.Errorf(ctx, "month must be 1-12, was %d", d.Month) } if d.Day < 1 || d.Day > 31 { return time.Time{}, errors.Errorf(ctx, "day(%d) was invalid", d.Day) } t := time.Date(int(d.Year), time.Month(d.Month), int(d.Day), 0, 0, 0, 0, time.UTC) if t.Month() != time.Month(d.Month) { return time.Time{}, errors.Errorf(ctx, "month %v does not have %d days", time.Month(d.Month), d.Day) } return t, nil } ================================================ FILE: chapter/13/petstore-provider/internal/client/internal/server/telemetry/metrics/metrics.go ================================================ /* Package metrics provides setup of metrics that can be used internally to measure various application states. All metrics for the application are defined here and other applications use this package to grab the metrics and use them. This package will also report any metric that is not used in the first 10 seconds after the app has started to prevent useless metrics from existing, as all metrics should be grabbed by that time. In a package you want to set metrics, you can do it as follows: var addCount metrics.Int64Counter func init() { addCounter = metrics.Get.Int64("petstore/server/AddPets/requests") } ... func (s *Server) AddPets(ctx context.Context, req *pb.AddPetsReq) (*pb.AddpetsResp, error) { ... // Do this if you have multiple changes that don't require special labels per update. metrics.Meter.RecordBatch(ctx, nil, addCounter.Measure(ctx, 1)) // Do this if you only need to make one change or need special labels. addCounter.Add(ctx, 1, attribute.String("label", "value") ... } To cause metrics to be exported package main(): func main() { ... stop, err := metrics.Start(ctx, metrics.OTELGRPC{Addr: "ip:port"}) if err != nil { log.Fatal(err) } defer stop() ... } */ package metrics import ( "html/template" "sort" "strings" "sync" "time" "github.com/PacktPublishing/Go-for-DevOps/chapter/13/petstore-provider/internal/client/internal/server/log" "go.opentelemetry.io/otel/metric" "go.opentelemetry.io/otel/metric/global" ) type metricType int const ( unknown = 0 mtInt64 = 1 mtInt64Hist = 2 mtInt64UD = 3 ) type metricDef struct { mtype metricType name string desc string } var metrics = []metricDef{ // Histograms {mtInt64Hist, "petstore/server/AddPets/latency", "The latency of an AddPets() request in nanoseconds"}, {mtInt64Hist, "petstore/server/DeletePets/latency", "The latency of an DeletePets() request in nanoseconds"}, {mtInt64Hist, "petstore/server/UpdatePets/latency", "The latency of an UpdatePets() request in nanoseconds"}, {mtInt64Hist, "petstore/server/SearchPets/latency", "The latency of a SearchPets() request in nanoseconds"}, // Counters {mtInt64, "petstore/server/AddPets/requests", "The total requests made to AddPets()"}, {mtInt64, "petstore/server/DeletePets/requests", "The total requests made to DeletePets()"}, {mtInt64, "petstore/server/UpdatePets/requests", "The total requests made to UpdatePets()"}, {mtInt64, "petstore/server/SearchPets/requests", "The total requests made to SearchPets()"}, {mtInt64, "petstore/server/totals/requests", "The total requests made to the server"}, {mtInt64, "petstore/server/AddPets/errors", "The total error count"}, {mtInt64, "petstore/server/DeletePets/errors", "The total error couunt"}, {mtInt64, "petstore/server/UpdatePets/errors", "The total error count"}, {mtInt64, "petstore/server/SearchPets/errors", "The total error count"}, {mtInt64, "petstore/server/totals/errors", "The total error count for all RPCs"}, // UpDown Counters {mtInt64UD, "petstore/server/AddPets/current", "The amount of requests currently being proccessed"}, {mtInt64UD, "petstore/server/DeletePets/current", "The amount of requests currently being proccessed"}, {mtInt64UD, "petstore/server/UpdatePets/current", "The amount of requests currently being proccessed"}, {mtInt64UD, "petstore/server/SearchPets/current", "The amount of requests currently being proccessed"}, } // Meter is the meter for the petstore. var Meter = global.Meter("petstore") // Get is used to lookup metrics by name. var Get = newLookups() var unusedMetricsTmpl = template.Must( template.New("").Parse( ` The following metrics appeart to be unused: {{- range .}} {{.}} {{- end }} `, ), ) // Lookups provides lookups for metrics based on their names. type Lookups struct { mtInt64Hist map[string]metric.Int64Histogram mtInt64UD map[string]metric.Int64UpDownCounter mtInt64 map[string]metric.Int64Counter mu sync.Mutex used map[string]bool } func newLookups() *Lookups { l := &Lookups{ mtInt64Hist: map[string]metric.Int64Histogram{}, mtInt64: map[string]metric.Int64Counter{}, mtInt64UD: map[string]metric.Int64UpDownCounter{}, used: map[string]bool{}, } exists := map[string]bool{} for _, m := range metrics { if m.mtype == unknown { log.Logger.Fatalf("metric with type(%v) cannot be added", m.mtype) } if m.name == "" { log.Logger.Fatalf("metric cannot be missing a name") } if m.desc == "" { log.Logger.Fatalf("metric cannot be missing a desc") } if exists[m.name] { log.Logger.Fatalf("cannot have two metrics with same name(%s)", m.name) } exists[m.name] = true switch m.mtype { case mtInt64Hist: l.mtInt64Hist[m.name] = metric.Must(Meter).NewInt64Histogram(m.name, metric.WithDescription(m.desc)) case mtInt64UD: l.mtInt64UD[m.name] = metric.Must(Meter).NewInt64UpDownCounter(m.name, metric.WithDescription(m.desc)) case mtInt64: l.mtInt64[m.name] = metric.Must(Meter).NewInt64Counter(m.name, metric.WithDescription(m.desc)) default: log.Logger.Fatalf("bug: we defined a metric type(%v) without adding support", m.mtype) } } go func() { time.Sleep(10 * time.Second) unused := l.unused() s := strings.Builder{} if err := unusedMetricsTmpl.Execute(&s, unused); err != nil { log.Logger.Fatalf("unusedMetricTmpl execute error: %s", err) } log.Logger.Println(s.String()) }() return l } // Int64 grabs the Int64Counter metric named "s". If not found, panics. func (l *Lookups) Int64(s string) metric.Int64Counter { l.mu.Lock() defer l.mu.Unlock() m, ok := l.mtInt64[s] if !ok { log.Logger.Fatalf("int64 metric(%s) is not defined", s) } l.used[s] = true return m } // Int64s grabs a list of Int64Counters. func (l *Lookups) Int64s(s ...string) []metric.Int64Counter { v := make([]metric.Int64Counter, 0, len(s)) for _, name := range s { v = append(v, l.Int64(name)) } return v } // Int64UD grabs the Int64UpDownCounter metric named "s". If not found, panics. func (l *Lookups) Int64UD(s string) metric.Int64UpDownCounter { l.mu.Lock() defer l.mu.Unlock() m, ok := l.mtInt64UD[s] if !ok { log.Logger.Fatalf("int64ud metric(%s) is not defined", s) } l.used[s] = true return m } // Int64UDs grabs a list of Int64UpDownCounters. func (l *Lookups) Int64UDs(s ...string) []metric.Int64UpDownCounter { v := make([]metric.Int64UpDownCounter, 0, len(s)) for _, name := range s { v = append(v, l.Int64UD(name)) } return v } // Int64Hist grabs the Int64Histogram metric named "s". If not found, panics. func (l *Lookups) Int64Hist(s string) metric.Int64Histogram { l.mu.Lock() defer l.mu.Unlock() m, ok := l.mtInt64Hist[s] if !ok { log.Logger.Fatalf("int64 histogram metric(%s) is not defined", s) } l.used[s] = true return m } func (l *Lookups) Int64Hists(s ...string) []metric.Int64Histogram { v := make([]metric.Int64Histogram, 0, len(s)) for _, name := range s { v = append(v, l.Int64Hist(name)) } return v } func (l *Lookups) unused() []string { l.mu.Lock() defer l.mu.Unlock() unused := []string{} for k := range l.mtInt64Hist { if !l.used[k] { unused = append(unused, k) } } for k := range l.mtInt64 { if !l.used[k] { unused = append(unused, k) } } sort.Strings(unused) return unused } ================================================ FILE: chapter/13/petstore-provider/internal/client/internal/server/telemetry/metrics/start.go ================================================ package metrics import ( "context" "fmt" "time" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/exporters/otlp/otlpmetric" "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc" "go.opentelemetry.io/otel/metric/global" "go.opentelemetry.io/otel/sdk/metric/controller/basic" processor "go.opentelemetry.io/otel/sdk/metric/processor/basic" "go.opentelemetry.io/otel/sdk/metric/selector/simple" ) // Controller represents the controller to send metrics to. type Controller interface { isController() } // OTELGRPC represents exporting to the "go.opentelemetry.io/otel/sdk/metric/controller/basic" controller. type OTELGRPC struct { // Addr is the local address to export on. Addr string } func (o OTELGRPC) isController() {} // Stop is used to stop OTEL metric handling. type Stop func() // Start is used to start OTEL metric handling. func Start(ctx context.Context, c Controller) (Stop, error) { control, err := newController(ctx, c) if err != nil { return nil, err } err = control.Start(ctx) if err != nil { return nil, err } return func() { ctx, cancel := context.WithTimeout(ctx, 1*time.Second) defer cancel() if err := control.Stop(ctx); err != nil { otel.Handle(err) } }, nil } func newController(ctx context.Context, c Controller) (*basic.Controller, error) { switch v := c.(type) { case OTELGRPC: return otelGRPC(ctx, v) } return nil, fmt.Errorf("%T is not a valid Controller", c) } func otelGRPC(ctx context.Context, args OTELGRPC) (*basic.Controller, error) { metricClient := otlpmetricgrpc.NewClient( otlpmetricgrpc.WithInsecure(), otlpmetricgrpc.WithEndpoint(args.Addr), ) metricExp, err := otlpmetric.New(ctx, metricClient) if err != nil { return nil, fmt.Errorf("Failed to create the collector metric exporter") } pusher := basic.New( processor.NewFactory( simple.NewWithHistogramDistribution(), metricExp, ), basic.WithExporter(metricExp), basic.WithCollectPeriod(10*time.Second), ) global.SetMeterProvider(pusher) return pusher, nil } ================================================ FILE: chapter/13/petstore-provider/internal/client/internal/server/telemetry/tracing/sampler/sampler.go ================================================ /* Package sampler offers a Sampler that looks for a TraceID.Valid() == true or a gRPC metadata key called "trace" and if they exist will sample. Otherwise it looks to a child Sampler to determine based upon whatever sampler algorithm is used. In addition we offer the ability to switch out the underlying sampler at anytime in a thread-safe way. You can construct a new Sampler like so: s, err := New(trace.NeverSample) if err != nil { // Do something } The above Sampler would only trace if a TraceID.Valid() == true or gRCP metadate key called "trace" existed. If we want to trace 1% of the time as well, we can do the following: s, err := New(trace.TraceIDRatioBased(.01)) if err != nil { // Do something } */ package sampler import ( "fmt" "sync/atomic" "go.opentelemetry.io/otel/sdk/trace" otelTrace "go.opentelemetry.io/otel/trace" "google.golang.org/grpc/metadata" ) const desc = `This sampler samples if TracdID.Valid(), gRPC metadata contains key "trace" or the child sampler decides to sample` // Sampler decides whether a trace should be sampled and exported. This sampler will sample if // paramters TraceID.Valid() == true or the Context contains gRPC metadata that has key "trace" (it doesn't care about values). type Sampler struct { // child stores a *trace.Sampler. trace.Sampler is an interface. Because atomic.Value cares about // the underlying type, you can't just store trace.Sampler. So we do a pointer, which is the only valid // use of *interface I've ever seen. child atomic.Value // *trace.Sampler } // New creates a new Sampler with the child Sampler used if TraceID.Valid() == false and gRPC metadata does not contain // key "trace". func New(child trace.Sampler) (*Sampler, error) { if child == nil { return nil, fmt.Errorf("child cannot == nil") } s := &Sampler{} s.child.Store(&child) return s, nil } // ShouldSample implements trace.Sampler.ShouldSample. func (s *Sampler) ShouldSample(p trace.SamplingParameters) trace.SamplingResult { psc := otelTrace.SpanContextFromContext(p.ParentContext) if psc.IsValid() { if psc.IsRemote() { if psc.IsSampled() { return trace.SamplingResult{ Decision: trace.RecordAndSample, Tracestate: psc.TraceState(), } } } if psc.IsSampled() { return trace.SamplingResult{ Decision: trace.RecordAndSample, Tracestate: psc.TraceState(), } } } md, ok := metadata.FromIncomingContext(p.ParentContext) if !ok { return (*s.child.Load().(*trace.Sampler)).ShouldSample(p) } if _, ok := md["trace"]; ok { psc := otelTrace.SpanContextFromContext(p.ParentContext) return trace.SamplingResult{ Decision: trace.RecordAndSample, Tracestate: psc.TraceState(), } } return (*s.child.Load().(*trace.Sampler)).ShouldSample(p) } // Description implements trace.Sampler.Description(). func (s *Sampler) Description() string { return desc } // Switch switches the underlying trace.Sampler. func (s *Sampler) Switch(sampler trace.Sampler) { if sampler == nil { panic("cannot call Switch() with a nil Sampler") } s.child.Store(&sampler) } ================================================ FILE: chapter/13/petstore-provider/internal/client/internal/server/telemetry/tracing/tracing.go ================================================ /* Package tracing provides functions for starting and stopping our Open Telemetry tracing. This package is intended to be used from main and is simple to use. We offer a few choices on where traces export to. Here is an example to trace to stderr for all requests: func main() { ctx := context.Background() // Set us up to always sample. The "trace" package is: "petstore/server/SearchPets/latency" tracing.Sampler.Switch(trace.AlwaysSample()) // Start our tracing and pass the empty Stderr tracing arguments. // Stderr{} has no required fields. stop, err := tracing.Start(ctx, tracing.Stderr{}) if err != nil { log.Fatalf("problem starting telemetry: %s", err) } // Stop kills our exporter when main() ends. defer stop() } */ package tracing import ( "context" "fmt" "io" "log" "os" "time" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/exporters/otlp/otlptrace" "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc" "go.opentelemetry.io/otel/exporters/stdout/stdouttrace" "go.opentelemetry.io/otel/propagation" "go.opentelemetry.io/otel/sdk/resource" sdktrace "go.opentelemetry.io/otel/sdk/trace" semconv "go.opentelemetry.io/otel/semconv/v1.4.0" "go.opentelemetry.io/otel/trace" "google.golang.org/grpc" "github.com/PacktPublishing/Go-for-DevOps/chapter/13/petstore-provider/internal/client/internal/server/telemetry/tracing/sampler" ) // Tracer is the tracer initialized by Start(). var ( // Tracer is the tracer initialized by Start(). Tracer trace.Tracer // *sdktrace.TracerProvider //otlptrace.Exporter // Sampler is our *sampler.Sampler used by the Tracer. Sampler *sampler.Sampler ) func init() { s, err := sampler.New(sdktrace.TraceIDRatioBased(1)) if err != nil { panic(err) } Sampler = s } // Exporter represents the exporter to send telemetry to. type Exporter interface { isExporter() } // OTELGRPC represents exporting to the go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc exporter. type OTELGRPC struct { // Addr is the local address to export on. Addr string } func (o OTELGRPC) isExporter() {} // Stderr exports trace data to os.Stderr. type Stderr struct{} func (s Stderr) isExporter() {} // File exports trace data to a file. If the file exists, it is overwritten. type File struct { // Path is the path to the file. Path string } func (f File) isExporter() {} // Stop stops our Open Telemetry exporter. type Stop func() // Start creates the OTEL exporter and configures the trace providers. // It returns a Stop() which will stop the exporter. func Start(ctx context.Context, e Exporter) (Stop, error) { log.Println("Sampler: ", Sampler) tp, err := newTraceExporter(ctx, e) if err != nil { return nil, err } Tracer = tp.Tracer("petstore") return func() { var cancel context.CancelFunc ctx, cancel = context.WithTimeout(ctx, 1*time.Second) defer cancel() if err := tp.Shutdown(ctx); err != nil { otel.Handle(err) } }, nil } // newTracerExporter creates an OTLP exporter with our tracer information. func newTraceExporter(ctx context.Context, e Exporter) (*sdktrace.TracerProvider, error) { var exp sdktrace.SpanExporter var err error switch v := e.(type) { case OTELGRPC: exp, err = otelGRPC(ctx, v) case Stderr: exp, err = newFileExporter(os.Stderr) case File: f, err := os.Create(v.Path) if err != nil { return nil, err } exp, err = newFileExporter(f) default: return nil, fmt.Errorf("%T is not a valid Exporter", e) } res, err := resource.New( ctx, resource.WithFromEnv(), resource.WithProcess(), resource.WithTelemetrySDK(), resource.WithHost(), resource.WithAttributes( // the service name used to display traces in backends semconv.ServiceNameKey.String("petstore"), ), ) if err != nil { return nil, err } // set global propagator to tracecontext (the default is no-op). otel.SetTextMapPropagator(propagation.TraceContext{}) prov := sdktrace.NewTracerProvider( sdktrace.WithSampler(Sampler), sdktrace.WithBatcher(exp), sdktrace.WithResource(res), ) otel.SetTracerProvider(prov) return prov, nil } // newFileExporter creates an exporter that writes to a file. func newFileExporter(w io.Writer) (sdktrace.SpanExporter, error) { return stdouttrace.New( stdouttrace.WithWriter(w), stdouttrace.WithPrettyPrint(), ) } func otelGRPC(ctx context.Context, e OTELGRPC) (sdktrace.SpanExporter, error) { //(*otlptrace.Exporter, error) { exp, err := otlptrace.New( ctx, otlptracegrpc.NewClient( otlptracegrpc.WithInsecure(), otlptracegrpc.WithEndpoint(e.Addr), otlptracegrpc.WithDialOption(grpc.WithBlock()), ), ) if err != nil { return nil, err } return exp, nil } ================================================ FILE: chapter/13/petstore-provider/internal/client/proto/buf.gen.yaml ================================================ version: v1 plugins: - name: go out: ./ opt: - paths=source_relative - name: go-grpc out: ./ opt: - paths=source_relative ================================================ FILE: chapter/13/petstore-provider/internal/client/proto/buf.yaml ================================================ version: v1 deps: - buf.build/googleapis/googleapis lint: use: - DEFAULT breaking: use: - FILE ================================================ FILE: chapter/13/petstore-provider/internal/client/proto/petstore.pb.go ================================================ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.25.0-devel // protoc v3.18.0 // source: petstore.proto package proto import ( date "google.golang.org/genproto/googleapis/type/date" 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) ) // Desribes the type of pets. type PetType int32 const ( // The type was not set. PetType_PTUnknown PetType = 0 // The pet is a canine. PetType_PTCanine PetType = 1 // The pet is a feline. PetType_PTFeline PetType = 2 // The pet is a bird. PetType_PTBird PetType = 3 // The pet is a reptile. PetType_PTReptile PetType = 4 ) // Enum value maps for PetType. var ( PetType_name = map[int32]string{ 0: "PTUnknown", 1: "PTCanine", 2: "PTFeline", 3: "PTBird", 4: "PTReptile", } PetType_value = map[string]int32{ "PTUnknown": 0, "PTCanine": 1, "PTFeline": 2, "PTBird": 3, "PTReptile": 4, } ) func (x PetType) Enum() *PetType { p := new(PetType) *p = x return p } func (x PetType) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } func (PetType) Descriptor() protoreflect.EnumDescriptor { return file_petstore_proto_enumTypes[0].Descriptor() } func (PetType) Type() protoreflect.EnumType { return &file_petstore_proto_enumTypes[0] } func (x PetType) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } // Deprecated: Use PetType.Descriptor instead. func (PetType) EnumDescriptor() ([]byte, []int) { return file_petstore_proto_rawDescGZIP(), []int{0} } // Types of OTEL sampling we support. type SamplerType int32 const ( SamplerType_STUnknown SamplerType = 0 SamplerType_STNever SamplerType = 1 SamplerType_STAlways SamplerType = 2 SamplerType_STFloat SamplerType = 3 ) // Enum value maps for SamplerType. var ( SamplerType_name = map[int32]string{ 0: "STUnknown", 1: "STNever", 2: "STAlways", 3: "STFloat", } SamplerType_value = map[string]int32{ "STUnknown": 0, "STNever": 1, "STAlways": 2, "STFloat": 3, } ) func (x SamplerType) Enum() *SamplerType { p := new(SamplerType) *p = x return p } func (x SamplerType) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } func (SamplerType) Descriptor() protoreflect.EnumDescriptor { return file_petstore_proto_enumTypes[1].Descriptor() } func (SamplerType) Type() protoreflect.EnumType { return &file_petstore_proto_enumTypes[1] } func (x SamplerType) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } // Deprecated: Use SamplerType.Descriptor instead. func (SamplerType) EnumDescriptor() ([]byte, []int) { return file_petstore_proto_rawDescGZIP(), []int{1} } // Represents a range of dates. type DateRange struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // When to start the range, this is inclusive. Start *date.Date `protobuf:"bytes,1,opt,name=start,proto3" json:"start,omitempty"` // When to end the range, this is exclusive. End *date.Date `protobuf:"bytes,2,opt,name=end,proto3" json:"end,omitempty"` } func (x *DateRange) Reset() { *x = DateRange{} if protoimpl.UnsafeEnabled { mi := &file_petstore_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *DateRange) String() string { return protoimpl.X.MessageStringOf(x) } func (*DateRange) ProtoMessage() {} func (x *DateRange) ProtoReflect() protoreflect.Message { mi := &file_petstore_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 DateRange.ProtoReflect.Descriptor instead. func (*DateRange) Descriptor() ([]byte, []int) { return file_petstore_proto_rawDescGZIP(), []int{0} } func (x *DateRange) GetStart() *date.Date { if x != nil { return x.Start } return nil } func (x *DateRange) GetEnd() *date.Date { if x != nil { return x.End } return nil } // Represents a unique pet. type Pet struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // A UUIDv4 for this pet. This can never be set on an AddPet(). Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` // The name of the pet. Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` // The type of pet. Type PetType `protobuf:"varint,3,opt,name=type,proto3,enum=petstore.PetType" json:"type,omitempty"` // The pet's birthday. Birthday *date.Date `protobuf:"bytes,4,opt,name=birthday,proto3" json:"birthday,omitempty"` } func (x *Pet) Reset() { *x = Pet{} if protoimpl.UnsafeEnabled { mi := &file_petstore_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Pet) String() string { return protoimpl.X.MessageStringOf(x) } func (*Pet) ProtoMessage() {} func (x *Pet) ProtoReflect() protoreflect.Message { mi := &file_petstore_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 Pet.ProtoReflect.Descriptor instead. func (*Pet) Descriptor() ([]byte, []int) { return file_petstore_proto_rawDescGZIP(), []int{1} } func (x *Pet) GetId() string { if x != nil { return x.Id } return "" } func (x *Pet) GetName() string { if x != nil { return x.Name } return "" } func (x *Pet) GetType() PetType { if x != nil { return x.Type } return PetType_PTUnknown } func (x *Pet) GetBirthday() *date.Date { if x != nil { return x.Birthday } return nil } // The request used to add a pets to the system. type AddPetsReq struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // The pet information to add. Pet.id must not be set. Pets []*Pet `protobuf:"bytes,1,rep,name=pets,proto3" json:"pets,omitempty"` } func (x *AddPetsReq) Reset() { *x = AddPetsReq{} if protoimpl.UnsafeEnabled { mi := &file_petstore_proto_msgTypes[2] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *AddPetsReq) String() string { return protoimpl.X.MessageStringOf(x) } func (*AddPetsReq) ProtoMessage() {} func (x *AddPetsReq) ProtoReflect() protoreflect.Message { mi := &file_petstore_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 AddPetsReq.ProtoReflect.Descriptor instead. func (*AddPetsReq) Descriptor() ([]byte, []int) { return file_petstore_proto_rawDescGZIP(), []int{2} } func (x *AddPetsReq) GetPets() []*Pet { if x != nil { return x.Pets } return nil } // The response do AddPets(). type AddPetsResp struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // The IDs of the pets that were added. Ids []string `protobuf:"bytes,1,rep,name=ids,proto3" json:"ids,omitempty"` } func (x *AddPetsResp) Reset() { *x = AddPetsResp{} if protoimpl.UnsafeEnabled { mi := &file_petstore_proto_msgTypes[3] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *AddPetsResp) String() string { return protoimpl.X.MessageStringOf(x) } func (*AddPetsResp) ProtoMessage() {} func (x *AddPetsResp) ProtoReflect() protoreflect.Message { mi := &file_petstore_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 AddPetsResp.ProtoReflect.Descriptor instead. func (*AddPetsResp) Descriptor() ([]byte, []int) { return file_petstore_proto_rawDescGZIP(), []int{3} } func (x *AddPetsResp) GetIds() []string { if x != nil { return x.Ids } return nil } // The request used to update pets in the system. type UpdatePetsReq struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // The pet information to update. Pet.id must be set. Pets []*Pet `protobuf:"bytes,1,rep,name=pets,proto3" json:"pets,omitempty"` } func (x *UpdatePetsReq) Reset() { *x = UpdatePetsReq{} if protoimpl.UnsafeEnabled { mi := &file_petstore_proto_msgTypes[4] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *UpdatePetsReq) String() string { return protoimpl.X.MessageStringOf(x) } func (*UpdatePetsReq) ProtoMessage() {} func (x *UpdatePetsReq) ProtoReflect() protoreflect.Message { mi := &file_petstore_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 UpdatePetsReq.ProtoReflect.Descriptor instead. func (*UpdatePetsReq) Descriptor() ([]byte, []int) { return file_petstore_proto_rawDescGZIP(), []int{4} } func (x *UpdatePetsReq) GetPets() []*Pet { if x != nil { return x.Pets } return nil } // The response do UpdatePets(). type UpdatePetsResp struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields } func (x *UpdatePetsResp) Reset() { *x = UpdatePetsResp{} if protoimpl.UnsafeEnabled { mi := &file_petstore_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *UpdatePetsResp) String() string { return protoimpl.X.MessageStringOf(x) } func (*UpdatePetsResp) ProtoMessage() {} func (x *UpdatePetsResp) ProtoReflect() protoreflect.Message { mi := &file_petstore_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 UpdatePetsResp.ProtoReflect.Descriptor instead. func (*UpdatePetsResp) Descriptor() ([]byte, []int) { return file_petstore_proto_rawDescGZIP(), []int{5} } // Used to indicate which pets to delete. This is an all or nothing request. type DeletePetsReq struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // The IDs of the pets to delete. Ids []string `protobuf:"bytes,1,rep,name=ids,proto3" json:"ids,omitempty"` } func (x *DeletePetsReq) Reset() { *x = DeletePetsReq{} if protoimpl.UnsafeEnabled { mi := &file_petstore_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *DeletePetsReq) String() string { return protoimpl.X.MessageStringOf(x) } func (*DeletePetsReq) ProtoMessage() {} func (x *DeletePetsReq) ProtoReflect() protoreflect.Message { mi := &file_petstore_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 DeletePetsReq.ProtoReflect.Descriptor instead. func (*DeletePetsReq) Descriptor() ([]byte, []int) { return file_petstore_proto_rawDescGZIP(), []int{6} } func (x *DeletePetsReq) GetIds() []string { if x != nil { return x.Ids } return nil } // The response to a DeletePet(). type DeletePetsResp struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields } func (x *DeletePetsResp) Reset() { *x = DeletePetsResp{} if protoimpl.UnsafeEnabled { mi := &file_petstore_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *DeletePetsResp) String() string { return protoimpl.X.MessageStringOf(x) } func (*DeletePetsResp) ProtoMessage() {} func (x *DeletePetsResp) ProtoReflect() protoreflect.Message { mi := &file_petstore_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 DeletePetsResp.ProtoReflect.Descriptor instead. func (*DeletePetsResp) Descriptor() ([]byte, []int) { return file_petstore_proto_rawDescGZIP(), []int{7} } // The request to search for pets. type SearchPetsReq struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // Pet names to filter by. Names []string `protobuf:"bytes,1,rep,name=names,proto3" json:"names,omitempty"` // Pet types to filter by. Types []PetType `protobuf:"varint,2,rep,packed,name=types,proto3,enum=petstore.PetType" json:"types,omitempty"` // Birthdays to filter by. BirthdateRange *DateRange `protobuf:"bytes,3,opt,name=birthdate_range,json=birthdateRange,proto3" json:"birthdate_range,omitempty"` } func (x *SearchPetsReq) Reset() { *x = SearchPetsReq{} if protoimpl.UnsafeEnabled { mi := &file_petstore_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *SearchPetsReq) String() string { return protoimpl.X.MessageStringOf(x) } func (*SearchPetsReq) ProtoMessage() {} func (x *SearchPetsReq) ProtoReflect() protoreflect.Message { mi := &file_petstore_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 SearchPetsReq.ProtoReflect.Descriptor instead. func (*SearchPetsReq) Descriptor() ([]byte, []int) { return file_petstore_proto_rawDescGZIP(), []int{8} } func (x *SearchPetsReq) GetNames() []string { if x != nil { return x.Names } return nil } func (x *SearchPetsReq) GetTypes() []PetType { if x != nil { return x.Types } return nil } func (x *SearchPetsReq) GetBirthdateRange() *DateRange { if x != nil { return x.BirthdateRange } return nil } type Sampler struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // The type of sampling to change to. Type SamplerType `protobuf:"varint,1,opt,name=type,proto3,enum=petstore.SamplerType" json:"type,omitempty"` // This is the sampling rate if type == STFloat. Values must be // > 0 and <= 1.0 . FloatValue float64 `protobuf:"fixed64,2,opt,name=float_value,json=floatValue,proto3" json:"float_value,omitempty"` } func (x *Sampler) Reset() { *x = Sampler{} if protoimpl.UnsafeEnabled { mi := &file_petstore_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Sampler) String() string { return protoimpl.X.MessageStringOf(x) } func (*Sampler) ProtoMessage() {} func (x *Sampler) ProtoReflect() protoreflect.Message { mi := &file_petstore_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 Sampler.ProtoReflect.Descriptor instead. func (*Sampler) Descriptor() ([]byte, []int) { return file_petstore_proto_rawDescGZIP(), []int{9} } func (x *Sampler) GetType() SamplerType { if x != nil { return x.Type } return SamplerType_STUnknown } func (x *Sampler) GetFloatValue() float64 { if x != nil { return x.FloatValue } return 0 } // Used to request we change the OTEL sampling. type ChangeSamplerReq struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Sampler *Sampler `protobuf:"bytes,1,opt,name=sampler,proto3" json:"sampler,omitempty"` } func (x *ChangeSamplerReq) Reset() { *x = ChangeSamplerReq{} if protoimpl.UnsafeEnabled { mi := &file_petstore_proto_msgTypes[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *ChangeSamplerReq) String() string { return protoimpl.X.MessageStringOf(x) } func (*ChangeSamplerReq) ProtoMessage() {} func (x *ChangeSamplerReq) ProtoReflect() protoreflect.Message { mi := &file_petstore_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 ChangeSamplerReq.ProtoReflect.Descriptor instead. func (*ChangeSamplerReq) Descriptor() ([]byte, []int) { return file_petstore_proto_rawDescGZIP(), []int{10} } func (x *ChangeSamplerReq) GetSampler() *Sampler { if x != nil { return x.Sampler } return nil } // The response to a sampling change. type ChangeSamplerResp struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields } func (x *ChangeSamplerResp) Reset() { *x = ChangeSamplerResp{} if protoimpl.UnsafeEnabled { mi := &file_petstore_proto_msgTypes[11] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *ChangeSamplerResp) String() string { return protoimpl.X.MessageStringOf(x) } func (*ChangeSamplerResp) ProtoMessage() {} func (x *ChangeSamplerResp) ProtoReflect() protoreflect.Message { mi := &file_petstore_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 ChangeSamplerResp.ProtoReflect.Descriptor instead. func (*ChangeSamplerResp) Descriptor() ([]byte, []int) { return file_petstore_proto_rawDescGZIP(), []int{11} } var File_petstore_proto protoreflect.FileDescriptor var file_petstore_proto_rawDesc = []byte{ 0x0a, 0x0e, 0x70, 0x65, 0x74, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x08, 0x70, 0x65, 0x74, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x1a, 0x16, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x74, 0x79, 0x70, 0x65, 0x2f, 0x64, 0x61, 0x74, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x59, 0x0a, 0x09, 0x44, 0x61, 0x74, 0x65, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x27, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x2e, 0x44, 0x61, 0x74, 0x65, 0x52, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x12, 0x23, 0x0a, 0x03, 0x65, 0x6e, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x2e, 0x44, 0x61, 0x74, 0x65, 0x52, 0x03, 0x65, 0x6e, 0x64, 0x22, 0x7f, 0x0a, 0x03, 0x50, 0x65, 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, 0x25, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x11, 0x2e, 0x70, 0x65, 0x74, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x50, 0x65, 0x74, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x2d, 0x0a, 0x08, 0x62, 0x69, 0x72, 0x74, 0x68, 0x64, 0x61, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x2e, 0x44, 0x61, 0x74, 0x65, 0x52, 0x08, 0x62, 0x69, 0x72, 0x74, 0x68, 0x64, 0x61, 0x79, 0x22, 0x2f, 0x0a, 0x0a, 0x41, 0x64, 0x64, 0x50, 0x65, 0x74, 0x73, 0x52, 0x65, 0x71, 0x12, 0x21, 0x0a, 0x04, 0x70, 0x65, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x70, 0x65, 0x74, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x50, 0x65, 0x74, 0x52, 0x04, 0x70, 0x65, 0x74, 0x73, 0x22, 0x1f, 0x0a, 0x0b, 0x41, 0x64, 0x64, 0x50, 0x65, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x12, 0x10, 0x0a, 0x03, 0x69, 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x03, 0x69, 0x64, 0x73, 0x22, 0x32, 0x0a, 0x0d, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x50, 0x65, 0x74, 0x73, 0x52, 0x65, 0x71, 0x12, 0x21, 0x0a, 0x04, 0x70, 0x65, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x70, 0x65, 0x74, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x50, 0x65, 0x74, 0x52, 0x04, 0x70, 0x65, 0x74, 0x73, 0x22, 0x10, 0x0a, 0x0e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x50, 0x65, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x22, 0x21, 0x0a, 0x0d, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x65, 0x74, 0x73, 0x52, 0x65, 0x71, 0x12, 0x10, 0x0a, 0x03, 0x69, 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x03, 0x69, 0x64, 0x73, 0x22, 0x10, 0x0a, 0x0e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x65, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x22, 0x8c, 0x01, 0x0a, 0x0d, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x50, 0x65, 0x74, 0x73, 0x52, 0x65, 0x71, 0x12, 0x14, 0x0a, 0x05, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x05, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x12, 0x27, 0x0a, 0x05, 0x74, 0x79, 0x70, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0e, 0x32, 0x11, 0x2e, 0x70, 0x65, 0x74, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x50, 0x65, 0x74, 0x54, 0x79, 0x70, 0x65, 0x52, 0x05, 0x74, 0x79, 0x70, 0x65, 0x73, 0x12, 0x3c, 0x0a, 0x0f, 0x62, 0x69, 0x72, 0x74, 0x68, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x65, 0x74, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x44, 0x61, 0x74, 0x65, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x0e, 0x62, 0x69, 0x72, 0x74, 0x68, 0x64, 0x61, 0x74, 0x65, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x22, 0x55, 0x0a, 0x07, 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x72, 0x12, 0x29, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x15, 0x2e, 0x70, 0x65, 0x74, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x72, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x01, 0x52, 0x0a, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x3f, 0x0a, 0x10, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x72, 0x52, 0x65, 0x71, 0x12, 0x2b, 0x0a, 0x07, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x70, 0x65, 0x74, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x72, 0x52, 0x07, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x72, 0x22, 0x13, 0x0a, 0x11, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x2a, 0x4f, 0x0a, 0x07, 0x50, 0x65, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0d, 0x0a, 0x09, 0x50, 0x54, 0x55, 0x6e, 0x6b, 0x6e, 0x6f, 0x77, 0x6e, 0x10, 0x00, 0x12, 0x0c, 0x0a, 0x08, 0x50, 0x54, 0x43, 0x61, 0x6e, 0x69, 0x6e, 0x65, 0x10, 0x01, 0x12, 0x0c, 0x0a, 0x08, 0x50, 0x54, 0x46, 0x65, 0x6c, 0x69, 0x6e, 0x65, 0x10, 0x02, 0x12, 0x0a, 0x0a, 0x06, 0x50, 0x54, 0x42, 0x69, 0x72, 0x64, 0x10, 0x03, 0x12, 0x0d, 0x0a, 0x09, 0x50, 0x54, 0x52, 0x65, 0x70, 0x74, 0x69, 0x6c, 0x65, 0x10, 0x04, 0x2a, 0x44, 0x0a, 0x0b, 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x72, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0d, 0x0a, 0x09, 0x53, 0x54, 0x55, 0x6e, 0x6b, 0x6e, 0x6f, 0x77, 0x6e, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x53, 0x54, 0x4e, 0x65, 0x76, 0x65, 0x72, 0x10, 0x01, 0x12, 0x0c, 0x0a, 0x08, 0x53, 0x54, 0x41, 0x6c, 0x77, 0x61, 0x79, 0x73, 0x10, 0x02, 0x12, 0x0b, 0x0a, 0x07, 0x53, 0x54, 0x46, 0x6c, 0x6f, 0x61, 0x74, 0x10, 0x03, 0x32, 0xd0, 0x02, 0x0a, 0x08, 0x50, 0x65, 0x74, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x12, 0x38, 0x0a, 0x07, 0x41, 0x64, 0x64, 0x50, 0x65, 0x74, 0x73, 0x12, 0x14, 0x2e, 0x70, 0x65, 0x74, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x41, 0x64, 0x64, 0x50, 0x65, 0x74, 0x73, 0x52, 0x65, 0x71, 0x1a, 0x15, 0x2e, 0x70, 0x65, 0x74, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x41, 0x64, 0x64, 0x50, 0x65, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x12, 0x41, 0x0a, 0x0a, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x50, 0x65, 0x74, 0x73, 0x12, 0x17, 0x2e, 0x70, 0x65, 0x74, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x50, 0x65, 0x74, 0x73, 0x52, 0x65, 0x71, 0x1a, 0x18, 0x2e, 0x70, 0x65, 0x74, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x50, 0x65, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x12, 0x41, 0x0a, 0x0a, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x65, 0x74, 0x73, 0x12, 0x17, 0x2e, 0x70, 0x65, 0x74, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x65, 0x74, 0x73, 0x52, 0x65, 0x71, 0x1a, 0x18, 0x2e, 0x70, 0x65, 0x74, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x65, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x12, 0x38, 0x0a, 0x0a, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x50, 0x65, 0x74, 0x73, 0x12, 0x17, 0x2e, 0x70, 0x65, 0x74, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x50, 0x65, 0x74, 0x73, 0x52, 0x65, 0x71, 0x1a, 0x0d, 0x2e, 0x70, 0x65, 0x74, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x50, 0x65, 0x74, 0x22, 0x00, 0x30, 0x01, 0x12, 0x4a, 0x0a, 0x0d, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x72, 0x12, 0x1a, 0x2e, 0x70, 0x65, 0x74, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x72, 0x52, 0x65, 0x71, 0x1a, 0x1b, 0x2e, 0x70, 0x65, 0x74, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x42, 0x30, 0x5a, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x50, 0x61, 0x63, 0x6b, 0x74, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x69, 0x6e, 0x67, 0x2f, 0x47, 0x6f, 0x2d, 0x66, 0x6f, 0x72, 0x2d, 0x44, 0x65, 0x76, 0x4f, 0x70, 0x73, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( file_petstore_proto_rawDescOnce sync.Once file_petstore_proto_rawDescData = file_petstore_proto_rawDesc ) func file_petstore_proto_rawDescGZIP() []byte { file_petstore_proto_rawDescOnce.Do(func() { file_petstore_proto_rawDescData = protoimpl.X.CompressGZIP(file_petstore_proto_rawDescData) }) return file_petstore_proto_rawDescData } var file_petstore_proto_enumTypes = make([]protoimpl.EnumInfo, 2) var file_petstore_proto_msgTypes = make([]protoimpl.MessageInfo, 12) var file_petstore_proto_goTypes = []interface{}{ (PetType)(0), // 0: petstore.PetType (SamplerType)(0), // 1: petstore.SamplerType (*DateRange)(nil), // 2: petstore.DateRange (*Pet)(nil), // 3: petstore.Pet (*AddPetsReq)(nil), // 4: petstore.AddPetsReq (*AddPetsResp)(nil), // 5: petstore.AddPetsResp (*UpdatePetsReq)(nil), // 6: petstore.UpdatePetsReq (*UpdatePetsResp)(nil), // 7: petstore.UpdatePetsResp (*DeletePetsReq)(nil), // 8: petstore.DeletePetsReq (*DeletePetsResp)(nil), // 9: petstore.DeletePetsResp (*SearchPetsReq)(nil), // 10: petstore.SearchPetsReq (*Sampler)(nil), // 11: petstore.Sampler (*ChangeSamplerReq)(nil), // 12: petstore.ChangeSamplerReq (*ChangeSamplerResp)(nil), // 13: petstore.ChangeSamplerResp (*date.Date)(nil), // 14: google.type.Date } var file_petstore_proto_depIdxs = []int32{ 14, // 0: petstore.DateRange.start:type_name -> google.type.Date 14, // 1: petstore.DateRange.end:type_name -> google.type.Date 0, // 2: petstore.Pet.type:type_name -> petstore.PetType 14, // 3: petstore.Pet.birthday:type_name -> google.type.Date 3, // 4: petstore.AddPetsReq.pets:type_name -> petstore.Pet 3, // 5: petstore.UpdatePetsReq.pets:type_name -> petstore.Pet 0, // 6: petstore.SearchPetsReq.types:type_name -> petstore.PetType 2, // 7: petstore.SearchPetsReq.birthdate_range:type_name -> petstore.DateRange 1, // 8: petstore.Sampler.type:type_name -> petstore.SamplerType 11, // 9: petstore.ChangeSamplerReq.sampler:type_name -> petstore.Sampler 4, // 10: petstore.PetStore.AddPets:input_type -> petstore.AddPetsReq 6, // 11: petstore.PetStore.UpdatePets:input_type -> petstore.UpdatePetsReq 8, // 12: petstore.PetStore.DeletePets:input_type -> petstore.DeletePetsReq 10, // 13: petstore.PetStore.SearchPets:input_type -> petstore.SearchPetsReq 12, // 14: petstore.PetStore.ChangeSampler:input_type -> petstore.ChangeSamplerReq 5, // 15: petstore.PetStore.AddPets:output_type -> petstore.AddPetsResp 7, // 16: petstore.PetStore.UpdatePets:output_type -> petstore.UpdatePetsResp 9, // 17: petstore.PetStore.DeletePets:output_type -> petstore.DeletePetsResp 3, // 18: petstore.PetStore.SearchPets:output_type -> petstore.Pet 13, // 19: petstore.PetStore.ChangeSampler:output_type -> petstore.ChangeSamplerResp 15, // [15:20] is the sub-list for method output_type 10, // [10:15] is the sub-list for method input_type 10, // [10:10] is the sub-list for extension type_name 10, // [10:10] is the sub-list for extension extendee 0, // [0:10] is the sub-list for field type_name } func init() { file_petstore_proto_init() } func file_petstore_proto_init() { if File_petstore_proto != nil { return } if !protoimpl.UnsafeEnabled { file_petstore_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*DateRange); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_petstore_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Pet); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_petstore_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*AddPetsReq); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_petstore_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*AddPetsResp); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_petstore_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*UpdatePetsReq); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_petstore_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*UpdatePetsResp); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_petstore_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*DeletePetsReq); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_petstore_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*DeletePetsResp); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_petstore_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*SearchPetsReq); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_petstore_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Sampler); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_petstore_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*ChangeSamplerReq); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_petstore_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*ChangeSamplerResp); 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_petstore_proto_rawDesc, NumEnums: 2, NumMessages: 12, NumExtensions: 0, NumServices: 1, }, GoTypes: file_petstore_proto_goTypes, DependencyIndexes: file_petstore_proto_depIdxs, EnumInfos: file_petstore_proto_enumTypes, MessageInfos: file_petstore_proto_msgTypes, }.Build() File_petstore_proto = out.File file_petstore_proto_rawDesc = nil file_petstore_proto_goTypes = nil file_petstore_proto_depIdxs = nil } ================================================ FILE: chapter/13/petstore-provider/internal/client/proto/petstore.proto ================================================ syntax = "proto3"; package petstore; option go_package = "github.com/PacktPublishing/Go-for-DevOps/proto"; import "google/type/date.proto"; // Desribes the type of pets. enum PetType { // The type was not set. PTUnknown = 0; // The pet is a canine. PTCanine = 1; // The pet is a feline. PTFeline = 2; // The pet is a bird. PTBird = 3; // The pet is a reptile. PTReptile = 4; } // Represents a range of dates. message DateRange { // When to start the range, this is inclusive. google.type.Date start = 1; // When to end the range, this is exclusive. google.type.Date end = 2; } // Represents a unique pet. message Pet { // A UUIDv4 for this pet. This can never be set on an AddPet(). string id = 1; // The name of the pet. string name = 2; // The type of pet. PetType type = 3; // The pet's birthday. google.type.Date birthday = 4; } // The request used to add a pets to the system. message AddPetsReq { // The pet information to add. Pet.id must not be set. repeated Pet pets = 1; } // The response do AddPets(). message AddPetsResp { // The IDs of the pets that were added. repeated string ids = 1; } // The request used to update pets in the system. message UpdatePetsReq { // The pet information to update. Pet.id must be set. repeated Pet pets = 1; } // The response do UpdatePets(). message UpdatePetsResp {} // Used to indicate which pets to delete. This is an all or nothing request. message DeletePetsReq { // The IDs of the pets to delete. repeated string ids = 1; } // The response to a DeletePet(). message DeletePetsResp{} // The request to search for pets. message SearchPetsReq { // Pet names to filter by. repeated string names = 1; // Pet types to filter by. repeated PetType types = 2; // Birthdays to filter by. DateRange birthdate_range = 3; } // Types of OTEL sampling we support. enum SamplerType { STUnknown = 0; STNever = 1; STAlways = 2; STFloat = 3; } message Sampler { // The type of sampling to change to. SamplerType type = 1; // This is the sampling rate if type == STFloat. Values must be // > 0 and <= 1.0 . double float_value = 2; } // Used to request we change the OTEL sampling. message ChangeSamplerReq { Sampler sampler = 1; } // The response to a sampling change. message ChangeSamplerResp{} service PetStore { // Adds pets to the pet store. rpc AddPets(AddPetsReq) returns (AddPetsResp) {}; // Updates pets entries in the store. rpc UpdatePets(UpdatePetsReq) returns (UpdatePetsResp) {}; // Deletes pets from the pet store. rpc DeletePets(DeletePetsReq) returns (DeletePetsResp) {}; // Finds pets in the pet store. rpc SearchPets(SearchPetsReq) returns (stream Pet) {}; // These are for management. In real life I might break this into a new server that is // serving on SSH so that it has to be auth'd and reachable only from in my network // or only if auth'd by something. // Changes the OTEL sampling type. rpc ChangeSampler(ChangeSamplerReq) returns (ChangeSamplerResp) {}; } ================================================ FILE: chapter/13/petstore-provider/internal/client/proto/petstore_grpc.pb.go ================================================ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. package proto 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.32.0 or later. const _ = grpc.SupportPackageIsVersion7 // PetStoreClient is the client API for PetStore 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 PetStoreClient interface { // Adds pets to the pet store. AddPets(ctx context.Context, in *AddPetsReq, opts ...grpc.CallOption) (*AddPetsResp, error) // Updates pets entries in the store. UpdatePets(ctx context.Context, in *UpdatePetsReq, opts ...grpc.CallOption) (*UpdatePetsResp, error) // Deletes pets from the pet store. DeletePets(ctx context.Context, in *DeletePetsReq, opts ...grpc.CallOption) (*DeletePetsResp, error) // Finds pets in the pet store. SearchPets(ctx context.Context, in *SearchPetsReq, opts ...grpc.CallOption) (PetStore_SearchPetsClient, error) // Changes the OTEL sampling type. ChangeSampler(ctx context.Context, in *ChangeSamplerReq, opts ...grpc.CallOption) (*ChangeSamplerResp, error) } type petStoreClient struct { cc grpc.ClientConnInterface } func NewPetStoreClient(cc grpc.ClientConnInterface) PetStoreClient { return &petStoreClient{cc} } func (c *petStoreClient) AddPets(ctx context.Context, in *AddPetsReq, opts ...grpc.CallOption) (*AddPetsResp, error) { out := new(AddPetsResp) err := c.cc.Invoke(ctx, "/petstore.PetStore/AddPets", in, out, opts...) if err != nil { return nil, err } return out, nil } func (c *petStoreClient) UpdatePets(ctx context.Context, in *UpdatePetsReq, opts ...grpc.CallOption) (*UpdatePetsResp, error) { out := new(UpdatePetsResp) err := c.cc.Invoke(ctx, "/petstore.PetStore/UpdatePets", in, out, opts...) if err != nil { return nil, err } return out, nil } func (c *petStoreClient) DeletePets(ctx context.Context, in *DeletePetsReq, opts ...grpc.CallOption) (*DeletePetsResp, error) { out := new(DeletePetsResp) err := c.cc.Invoke(ctx, "/petstore.PetStore/DeletePets", in, out, opts...) if err != nil { return nil, err } return out, nil } func (c *petStoreClient) SearchPets(ctx context.Context, in *SearchPetsReq, opts ...grpc.CallOption) (PetStore_SearchPetsClient, error) { stream, err := c.cc.NewStream(ctx, &PetStore_ServiceDesc.Streams[0], "/petstore.PetStore/SearchPets", opts...) if err != nil { return nil, err } x := &petStoreSearchPetsClient{stream} if err := x.ClientStream.SendMsg(in); err != nil { return nil, err } if err := x.ClientStream.CloseSend(); err != nil { return nil, err } return x, nil } type PetStore_SearchPetsClient interface { Recv() (*Pet, error) grpc.ClientStream } type petStoreSearchPetsClient struct { grpc.ClientStream } func (x *petStoreSearchPetsClient) Recv() (*Pet, error) { m := new(Pet) if err := x.ClientStream.RecvMsg(m); err != nil { return nil, err } return m, nil } func (c *petStoreClient) ChangeSampler(ctx context.Context, in *ChangeSamplerReq, opts ...grpc.CallOption) (*ChangeSamplerResp, error) { out := new(ChangeSamplerResp) err := c.cc.Invoke(ctx, "/petstore.PetStore/ChangeSampler", in, out, opts...) if err != nil { return nil, err } return out, nil } // PetStoreServer is the server API for PetStore service. // All implementations must embed UnimplementedPetStoreServer // for forward compatibility type PetStoreServer interface { // Adds pets to the pet store. AddPets(context.Context, *AddPetsReq) (*AddPetsResp, error) // Updates pets entries in the store. UpdatePets(context.Context, *UpdatePetsReq) (*UpdatePetsResp, error) // Deletes pets from the pet store. DeletePets(context.Context, *DeletePetsReq) (*DeletePetsResp, error) // Finds pets in the pet store. SearchPets(*SearchPetsReq, PetStore_SearchPetsServer) error // Changes the OTEL sampling type. ChangeSampler(context.Context, *ChangeSamplerReq) (*ChangeSamplerResp, error) mustEmbedUnimplementedPetStoreServer() } // UnimplementedPetStoreServer must be embedded to have forward compatible implementations. type UnimplementedPetStoreServer struct { } func (UnimplementedPetStoreServer) AddPets(context.Context, *AddPetsReq) (*AddPetsResp, error) { return nil, status.Errorf(codes.Unimplemented, "method AddPets not implemented") } func (UnimplementedPetStoreServer) UpdatePets(context.Context, *UpdatePetsReq) (*UpdatePetsResp, error) { return nil, status.Errorf(codes.Unimplemented, "method UpdatePets not implemented") } func (UnimplementedPetStoreServer) DeletePets(context.Context, *DeletePetsReq) (*DeletePetsResp, error) { return nil, status.Errorf(codes.Unimplemented, "method DeletePets not implemented") } func (UnimplementedPetStoreServer) SearchPets(*SearchPetsReq, PetStore_SearchPetsServer) error { return status.Errorf(codes.Unimplemented, "method SearchPets not implemented") } func (UnimplementedPetStoreServer) ChangeSampler(context.Context, *ChangeSamplerReq) (*ChangeSamplerResp, error) { return nil, status.Errorf(codes.Unimplemented, "method ChangeSampler not implemented") } func (UnimplementedPetStoreServer) mustEmbedUnimplementedPetStoreServer() {} // UnsafePetStoreServer may be embedded to opt out of forward compatibility for this service. // Use of this interface is not recommended, as added methods to PetStoreServer will // result in compilation errors. type UnsafePetStoreServer interface { mustEmbedUnimplementedPetStoreServer() } func RegisterPetStoreServer(s grpc.ServiceRegistrar, srv PetStoreServer) { s.RegisterService(&PetStore_ServiceDesc, srv) } func _PetStore_AddPets_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(AddPetsReq) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(PetStoreServer).AddPets(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: "/petstore.PetStore/AddPets", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(PetStoreServer).AddPets(ctx, req.(*AddPetsReq)) } return interceptor(ctx, in, info, handler) } func _PetStore_UpdatePets_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(UpdatePetsReq) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(PetStoreServer).UpdatePets(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: "/petstore.PetStore/UpdatePets", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(PetStoreServer).UpdatePets(ctx, req.(*UpdatePetsReq)) } return interceptor(ctx, in, info, handler) } func _PetStore_DeletePets_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(DeletePetsReq) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(PetStoreServer).DeletePets(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: "/petstore.PetStore/DeletePets", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(PetStoreServer).DeletePets(ctx, req.(*DeletePetsReq)) } return interceptor(ctx, in, info, handler) } func _PetStore_SearchPets_Handler(srv interface{}, stream grpc.ServerStream) error { m := new(SearchPetsReq) if err := stream.RecvMsg(m); err != nil { return err } return srv.(PetStoreServer).SearchPets(m, &petStoreSearchPetsServer{stream}) } type PetStore_SearchPetsServer interface { Send(*Pet) error grpc.ServerStream } type petStoreSearchPetsServer struct { grpc.ServerStream } func (x *petStoreSearchPetsServer) Send(m *Pet) error { return x.ServerStream.SendMsg(m) } func _PetStore_ChangeSampler_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(ChangeSamplerReq) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(PetStoreServer).ChangeSampler(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: "/petstore.PetStore/ChangeSampler", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(PetStoreServer).ChangeSampler(ctx, req.(*ChangeSamplerReq)) } return interceptor(ctx, in, info, handler) } // PetStore_ServiceDesc is the grpc.ServiceDesc for PetStore service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) var PetStore_ServiceDesc = grpc.ServiceDesc{ ServiceName: "petstore.PetStore", HandlerType: (*PetStoreServer)(nil), Methods: []grpc.MethodDesc{ { MethodName: "AddPets", Handler: _PetStore_AddPets_Handler, }, { MethodName: "UpdatePets", Handler: _PetStore_UpdatePets_Handler, }, { MethodName: "DeletePets", Handler: _PetStore_DeletePets_Handler, }, { MethodName: "ChangeSampler", Handler: _PetStore_ChangeSampler_Handler, }, }, Streams: []grpc.StreamDesc{ { StreamName: "SearchPets", Handler: _PetStore_SearchPets_Handler, ServerStreams: true, }, }, Metadata: "petstore.proto", } ================================================ FILE: chapter/13/petstore-provider/internal/data_source_pet.go ================================================ package petstore import ( "context" "strconv" "time" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/pkg/errors" "google.golang.org/genproto/googleapis/type/date" "github.com/PacktPublishing/Go-for-DevOps/chapter/13/petstore-provider/internal/client" pb "github.com/PacktPublishing/Go-for-DevOps/chapter/13/petstore-provider/internal/client/proto" ) func dataSourcePet() *schema.Resource { return &schema.Resource{ ReadContext: dataSourcePetRead, Schema: getPetDataSchema(), } } // clientFromMeta casts meta into a Pet Store client or returns an error func clientFromMeta(meta interface{}) (*client.Client, error) { psClient, ok := meta.(*client.Client) if !ok { return nil, errors.New("meta does not contain a Pet Store client") } return psClient, nil } // dataSourcePetRead finds pets in the pet store given an ID func dataSourcePetRead(ctx context.Context, data *schema.ResourceData, meta interface{}) diag.Diagnostics { psClient, err := clientFromMeta(meta) if err != nil { return diag.FromErr(err) } pets, err := findPetsInStore(ctx, psClient, findPetsRequest{ Name: data.Get("name").(string), Birthday: data.Get("birthday").(string), Type: PetType(data.Get("type").(string)), ID: data.Get("pet_id").(string), }) if err != nil { return diag.FromErr(err) } // always run data.SetId(strconv.FormatInt(time.Now().Unix(), 10)) if err := data.Set("pets", flattenPets(pets)); err != nil { return diag.FromErr(err) } return nil } func flattenPets(pets []*client.Pet) []interface{} { ifacePets := make([]interface{}, len(pets), len(pets)) for i, pet := range pets { ifacePets[i] = petToMap(pet) } return ifacePets } // setDataFromPet populates the resource data from the pet func setDataFromPet(pet *client.Pet, data *schema.ResourceData) diag.Diagnostics { petMap := petToMap(pet) var diags diag.Diagnostics for k, v := range petMap { if err := data.Set(k, v); err != nil { diags = append(diags, diag.Errorf("failed to set %s: %s", k, err)...) } } return diags } func petToMap(pet *client.Pet) map[string]interface{} { return map[string]interface{}{ "name": pet.Name, "type": string(protoPetTypeToPetType(pet.Type)), "birthday": pet.Birthday().Format("2006-01-02T15:04:05Z07:00"), "id": pet.Id, } } // fillPetFromData populates a pet from resource data func fillPetFromData(pet *client.Pet, data *schema.ResourceData) diag.Diagnostics { var diags diag.Diagnostics pet.Pet.Id = data.Id() pet.Pet.Name = data.Get("name").(string) pet.Pet.Type = petTypeToProtoPetType(PetType(data.Get("type").(string))) if bday, ok := data.Get("birthday").(string); ok { t, err := time.Parse(time.RFC3339, bday) if err != nil { diags = append(diags, diag.FromErr(err)...) } pet.Pet.Birthday = timeToPbDate(t) } return diags } func timeToPbDate(t time.Time) *date.Date { return &date.Date{ Year: int32(t.Year()), Month: int32(t.Month()), Day: int32(t.Day()), } } type findPetsRequest struct { ID string Name string Type PetType Birthday string } // findPetInStore searches the pet store for a pet that matches the custom resource pet. func findPetsInStore(ctx context.Context, psClient *client.Client, req findPetsRequest) ([]*client.Pet, error) { searchReq := &pb.SearchPetsReq{} if string(req.Type) != "" { searchReq.Types = []pb.PetType{petTypeToProtoPetType(req.Type)} } if req.Name != "" { searchReq.Names = []string{req.Name} } petsChan, err := psClient.SearchPets(ctx, searchReq) if err != nil { return nil, errors.Wrap(err, "failed searching for pet") } var pets []*client.Pet for pet := range petsChan { pet := pet if pet.Error() != nil { return nil, pet.Error() } if req.ID == "" || pet.Id == req.ID { pets = append(pets, &pet) } } return pets, nil } func petTypeToProtoPetType(petType PetType) pb.PetType { switch petType { case DogPetType: return pb.PetType_PTCanine case CatPetType: return pb.PetType_PTFeline case BirdPetType: return pb.PetType_PTBird default: return pb.PetType_PTReptile } } func protoPetTypeToPetType(pbPetType pb.PetType) PetType { switch pbPetType { case pb.PetType_PTCanine: return DogPetType case pb.PetType_PTFeline: return CatPetType case pb.PetType_PTBird: return BirdPetType default: return ReptilePetType } } ================================================ FILE: chapter/13/petstore-provider/internal/provider.go ================================================ package petstore import ( "context" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/PacktPublishing/Go-for-DevOps/chapter/13/petstore-provider/internal/client" ) // Provider is the entry point for defining the Terraform provider, and will create a new Pet Store provider. func Provider() *schema.Provider { return &schema.Provider{ Schema: map[string]*schema.Schema{ "host": { Type: schema.TypeString, Optional: true, DefaultFunc: schema.EnvDefaultFunc("PETSTORE_HOST", nil), }, }, ResourcesMap: map[string]*schema.Resource{ "petstore_pet": resourcePet(), }, DataSourcesMap: map[string]*schema.Resource{ "petstore_pet": dataSourcePet(), }, ConfigureContextFunc: configure, } } // configure builds a new Pet Store client the provider will use to interact with the Pet Store service func configure(_ context.Context, data *schema.ResourceData) (interface{}, diag.Diagnostics) { // Warning or errors can be collected in a slice type var diags diag.Diagnostics host, ok := data.Get("host").(string) if !ok { return nil, diag.Errorf("the host (127.0.0.1:443) must be provided explicitly or via env var PETSTORE_HOST") } c, err := client.New(host) if err != nil { return nil, append(diags, diag.Diagnostic{ Severity: diag.Error, Summary: "Unable to create Pet Store client", Detail: "Unable to connect to the Pet Store service", }) } return c, diags } ================================================ FILE: chapter/13/petstore-provider/internal/resource_pets.go ================================================ package petstore import ( "context" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/PacktPublishing/Go-for-DevOps/chapter/13/petstore-provider/internal/client" pb "github.com/PacktPublishing/Go-for-DevOps/chapter/13/petstore-provider/internal/client/proto" ) func resourcePet() *schema.Resource { return &schema.Resource{ CreateContext: resourcePetCreate, ReadContext: resourcePetRead, UpdateContext: resourcePetUpdate, DeleteContext: resourcePetDelete, Schema: getPetResourceSchema(), Importer: &schema.ResourceImporter{ StateContext: schema.ImportStatePassthroughContext, }, } } // resourcePetCreate creates a pet in the pet store func resourcePetCreate(ctx context.Context, data *schema.ResourceData, meta interface{}) diag.Diagnostics { psClient, err := clientFromMeta(meta) if err != nil { return diag.FromErr(err) } pet := &client.Pet{Pet: &pb.Pet{}} diags := fillPetFromData(pet, data) ids, err := psClient.AddPets(ctx, []*pb.Pet{pet.Pet}) if err != nil { return append(diags, diag.FromErr(err)...) } data.SetId(ids[0]) return diags } // resourcePetRead finds a pet in the pet store by ID and populate the resource data func resourcePetRead(ctx context.Context, data *schema.ResourceData, meta interface{}) diag.Diagnostics { psClient, err := clientFromMeta(meta) if err != nil { return diag.FromErr(err) } pets, err := findPetsInStore(ctx, psClient, findPetsRequest{ID: data.Id()}) if err != nil { return diag.FromErr(err) } if len(pets) == 0 { return nil } return setDataFromPet(pets[0], data) } // resourcePetUpdate updates a pet in the pet store by ID func resourcePetUpdate(ctx context.Context, data *schema.ResourceData, meta interface{}) diag.Diagnostics { psClient, err := clientFromMeta(meta) if err != nil { return diag.FromErr(err) } pets, err := findPetsInStore(ctx, psClient, findPetsRequest{ID: data.Id()}) if err != nil { return diag.FromErr(err) } if len(pets) == 0 { return diag.Diagnostics{ { Severity: diag.Error, Summary: "no pet was found", Detail: "no pet was found when trying to update the pet by ID", }, } } pet := pets[0] diags := fillPetFromData(pet, data) if diags.HasError() { return diags } if err := psClient.UpdatePets(ctx, []*pb.Pet{pet.Pet}); err != nil { return append(diags, diag.FromErr(err)...) } return diags } // resourcePetDelete deletes a pet in teh pet store by ID func resourcePetDelete(ctx context.Context, data *schema.ResourceData, meta interface{}) diag.Diagnostics { psClient, err := clientFromMeta(meta) if err != nil { return diag.FromErr(err) } if err := psClient.DeletePets(ctx, []string{data.Id()}); err != nil { return diag.FromErr(err) } return nil } ================================================ FILE: chapter/13/petstore-provider/internal/schema.go ================================================ package petstore import ( "fmt" "github.com/hashicorp/go-cty/cty" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" ) type PetType string const ( DogPetType PetType = "dog" CatPetType PetType = "cat" BirdPetType PetType = "bird" ReptilePetType PetType = "reptile" ) func getPetDataSchema() map[string]*schema.Schema { return map[string]*schema.Schema{ "pet_id": { Type: schema.TypeString, Optional: true, }, "name": { Type: schema.TypeString, Optional: true, ValidateDiagFunc: validateName(), }, "type": { Type: schema.TypeString, Optional: true, ValidateDiagFunc: validateType(), }, "birthday": { Type: schema.TypeString, Optional: true, ValidateDiagFunc: validateBirthday(), }, "pets": { Type: schema.TypeList, Computed: true, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "id": { Type: schema.TypeString, Computed: true, }, "name": { Type: schema.TypeString, Computed: true, }, "type": { Type: schema.TypeString, Computed: true, }, "birthday": { Type: schema.TypeString, Computed: true, }, }, }, }, } } func getPetResourceSchema() map[string]*schema.Schema { return map[string]*schema.Schema{ "id": { Type: schema.TypeString, Optional: true, Computed: true, }, "name": { Type: schema.TypeString, Required: true, ValidateDiagFunc: validateName(), }, "type": { Type: schema.TypeString, Required: true, ValidateDiagFunc: validateType(), }, "birthday": { Type: schema.TypeString, Required: true, ValidateDiagFunc: validateBirthday(), }, } } func validateName() schema.SchemaValidateDiagFunc { return validateDiagFunc(validation.All(validation.StringIsNotEmpty, validation.StringIsNotWhiteSpace)) } func validateType() schema.SchemaValidateDiagFunc { return validateDiagFunc(validation.StringInSlice([]string{ string(DogPetType), string(CatPetType), string(ReptilePetType), string(BirdPetType), }, true)) } func validateBirthday() schema.SchemaValidateDiagFunc { return validateDiagFunc(validation.IsRFC3339Time) } func validateDiagFunc(validateFunc func(interface{}, string) ([]string, []error)) schema.SchemaValidateDiagFunc { return func(i interface{}, path cty.Path) diag.Diagnostics { warnings, errs := validateFunc(i, fmt.Sprintf("%+v", path)) var diags diag.Diagnostics for _, warning := range warnings { diags = append(diags, diag.Diagnostic{ Severity: diag.Warning, Summary: warning, }) } for _, err := range errs { diags = append(diags, diag.Diagnostic{ Severity: diag.Error, Summary: err.Error(), }) } return diags } } ================================================ FILE: chapter/13/petstore-provider/main.go ================================================ package main import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/plugin" petstore "github.com/PacktPublishing/Go-for-DevOps/chapter/13/petstore-provider/internal" ) func main() { plugin.Serve(&plugin.ServeOpts{ ProviderFunc: func() *schema.Provider { return petstore.Provider() }, }) } ================================================ FILE: chapter/14/petstore-operator/.dockerignore ================================================ # More info: https://docs.docker.com/engine/reference/builder/#dockerignore-file # Ignore build and test binaries. bin/ testbin/ ================================================ FILE: chapter/14/petstore-operator/.gitignore ================================================ # Binaries for programs and plugins *.exe *.exe~ *.dll *.so *.dylib bin testbin/* # Test binary, build with `go test -c` *.test # Output of the go coverage tool, specifically when used with LiteIDE *.out # Kubernetes Generated files - skip generated files, except for vendored files !vendor/**/zz_generated.* # editor and IDE paraphernalia .idea *.swp *.swo *~ ================================================ FILE: chapter/14/petstore-operator/Dockerfile ================================================ # Build the manager binary FROM golang:1.17 as builder WORKDIR /workspace # Copy the Go Modules manifests COPY go.mod go.mod COPY go.sum go.sum # cache deps before building and copying source so that we don't need to re-download as much # and so that source changes don't invalidate our downloaded layer RUN go mod download # Copy the go source COPY main.go main.go COPY api/ api/ COPY controllers/ controllers/ COPY client/ client/ # Build RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a -o manager main.go # Use distroless as minimal base image to package the manager binary # Refer to https://github.com/GoogleContainerTools/distroless for more details FROM gcr.io/distroless/static:nonroot WORKDIR / COPY --from=builder /workspace/manager . USER 65532:65532 ENTRYPOINT ["/manager"] ================================================ FILE: chapter/14/petstore-operator/Makefile ================================================ # VERSION defines the project version for the bundle. # Update this value when you upgrade the version of your project. # To re-generate a bundle for another specific version without changing the standard setup, you can: # - use the VERSION as arg of the bundle target (e.g make bundle VERSION=0.0.2) # - use environment variables to overwrite this value (e.g export VERSION=0.0.2) VERSION ?= 0.0.1 # CHANNELS define the bundle channels used in the bundle. # Add a new line here if you would like to change its default config. (E.g CHANNELS = "candidate,fast,stable") # To re-generate a bundle for other specific channels without changing the standard setup, you can: # - use the CHANNELS as arg of the bundle target (e.g make bundle CHANNELS=candidate,fast,stable) # - use environment variables to overwrite this value (e.g export CHANNELS="candidate,fast,stable") ifneq ($(origin CHANNELS), undefined) BUNDLE_CHANNELS := --channels=$(CHANNELS) endif # DEFAULT_CHANNEL defines the default channel used in the bundle. # Add a new line here if you would like to change its default config. (E.g DEFAULT_CHANNEL = "stable") # To re-generate a bundle for any other default channel without changing the default setup, you can: # - use the DEFAULT_CHANNEL as arg of the bundle target (e.g make bundle DEFAULT_CHANNEL=stable) # - use environment variables to overwrite this value (e.g export DEFAULT_CHANNEL="stable") ifneq ($(origin DEFAULT_CHANNEL), undefined) BUNDLE_DEFAULT_CHANNEL := --default-channel=$(DEFAULT_CHANNEL) endif BUNDLE_METADATA_OPTS ?= $(BUNDLE_CHANNELS) $(BUNDLE_DEFAULT_CHANNEL) # IMAGE_TAG_BASE defines the docker.io namespace and part of the image name for remote images. # This variable is used to construct full image tags for bundle and catalog images. # # For example, running 'make bundle-build bundle-push catalog-build catalog-push' will build and push both # example.com/petstore-operator-bundle:$VERSION and example.com/petstore-operator-catalog:$VERSION. IMAGE_TAG_BASE ?= example.com/petstore-operator # BUNDLE_IMG defines the image:tag used for the bundle. # You can use it as an arg. (E.g make bundle-build BUNDLE_IMG=/:) BUNDLE_IMG ?= $(IMAGE_TAG_BASE)-bundle:v$(VERSION) # BUNDLE_GEN_FLAGS are the flags passed to the operator-sdk generate bundle command BUNDLE_GEN_FLAGS ?= -q --overwrite --version $(VERSION) $(BUNDLE_METADATA_OPTS) # USE_IMAGE_DIGESTS defines if images are resolved via tags or digests # You can enable this value if you would like to use SHA Based Digests # To enable set flag to true USE_IMAGE_DIGESTS ?= false ifeq ($(USE_IMAGE_DIGESTS), true) BUNDLE_GEN_FLAGS += --use-image-digests endif # Image URL to use all building/pushing image targets IMG ?= controller:latest # ENVTEST_K8S_VERSION refers to the version of kubebuilder assets to be downloaded by envtest binary. ENVTEST_K8S_VERSION = 1.23 # Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set) ifeq (,$(shell go env GOBIN)) GOBIN=$(shell go env GOPATH)/bin else GOBIN=$(shell go env GOBIN) endif # Setting SHELL to bash allows bash commands to be executed by recipes. # This is a requirement for 'setup-envtest.sh' in the test target. # Options are set to exit when a recipe line exits non-zero or a piped command fails. SHELL = /usr/bin/env bash -o pipefail .SHELLFLAGS = -ec .PHONY: all all: build ##@ General # The help target prints out all targets with their descriptions organized # beneath their categories. The categories are represented by '##@' and the # target descriptions by '##'. The awk commands is responsible for reading the # entire set of makefiles included in this invocation, looking for lines of the # file as xyz: ## something, and then pretty-format the target and help. Then, # if there's a line with ##@ something, that gets pretty-printed as a category. # More info on the usage of ANSI control characters for terminal formatting: # https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_parameters # More info on the awk command: # http://linuxcommand.org/lc3_adv_awk.php .PHONY: help help: ## Display this help. @awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m\033[0m\n"} /^[a-zA-Z_0-9-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST) ##@ Development .PHONY: manifests manifests: controller-gen ## Generate WebhookConfiguration, ClusterRole and CustomResourceDefinition objects. $(CONTROLLER_GEN) rbac:roleName=manager-role crd webhook paths="./..." output:crd:artifacts:config=config/crd/bases .PHONY: generate generate: controller-gen ## Generate code containing DeepCopy, DeepCopyInto, and DeepCopyObject method implementations. $(CONTROLLER_GEN) object:headerFile="hack/boilerplate.go.txt" paths="./..." .PHONY: fmt fmt: ## Run go fmt against code. go fmt ./... .PHONY: vet vet: ## Run go vet against code. go vet ./... .PHONY: test test: manifests generate fmt vet envtest ## Run tests. KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) -p path)" go test ./... -coverprofile cover.out ##@ Build .PHONY: build build: generate fmt vet ## Build manager binary. go build -o bin/manager main.go .PHONY: run run: manifests generate fmt vet ## Run a controller from your host. go run ./main.go .PHONY: docker-build docker-build: test ## Build docker image with the manager. docker build -t ${IMG} . .PHONY: docker-push docker-push: ## Push docker image with the manager. docker push ${IMG} ##@ Deployment ifndef ignore-not-found ignore-not-found = false endif .PHONY: install install: manifests kustomize ## Install CRDs into the K8s cluster specified in ~/.kube/config. $(KUSTOMIZE) build config/crd | kubectl apply -f - .PHONY: uninstall uninstall: manifests kustomize ## Uninstall CRDs from the K8s cluster specified in ~/.kube/config. Call with ignore-not-found=true to ignore resource not found errors during deletion. $(KUSTOMIZE) build config/crd | kubectl delete --ignore-not-found=$(ignore-not-found) -f - .PHONY: deploy deploy: manifests kustomize ## Deploy controller to the K8s cluster specified in ~/.kube/config. cd config/manager && $(KUSTOMIZE) edit set image controller=${IMG} $(KUSTOMIZE) build config/default | kubectl apply -f - .PHONY: undeploy undeploy: ## Undeploy controller from the K8s cluster specified in ~/.kube/config. Call with ignore-not-found=true to ignore resource not found errors during deletion. $(KUSTOMIZE) build config/default | kubectl delete --ignore-not-found=$(ignore-not-found) -f - CONTROLLER_GEN = $(shell pwd)/bin/controller-gen .PHONY: controller-gen controller-gen: ## Download controller-gen locally if necessary. $(call go-get-tool,$(CONTROLLER_GEN),sigs.k8s.io/controller-tools/cmd/controller-gen@v0.8.0) KUSTOMIZE = $(shell pwd)/bin/kustomize .PHONY: kustomize kustomize: ## Download kustomize locally if necessary. $(call go-get-tool,$(KUSTOMIZE),sigs.k8s.io/kustomize/kustomize/v3@v3.8.7) ENVTEST = $(shell pwd)/bin/setup-envtest .PHONY: envtest envtest: ## Download envtest-setup locally if necessary. $(call go-get-tool,$(ENVTEST),sigs.k8s.io/controller-runtime/tools/setup-envtest@latest) # go-get-tool will 'go get' any package $2 and install it to $1. PROJECT_DIR := $(shell dirname $(abspath $(lastword $(MAKEFILE_LIST)))) define go-get-tool @[ -f $(1) ] || { \ set -e ;\ TMP_DIR=$$(mktemp -d) ;\ cd $$TMP_DIR ;\ go mod init tmp ;\ echo "Downloading $(2)" ;\ GOBIN=$(PROJECT_DIR)/bin go get $(2) ;\ rm -rf $$TMP_DIR ;\ } endef .PHONY: bundle bundle: manifests kustomize ## Generate bundle manifests and metadata, then validate generated files. operator-sdk generate kustomize manifests -q cd config/manager && $(KUSTOMIZE) edit set image controller=$(IMG) $(KUSTOMIZE) build config/manifests | operator-sdk generate bundle $(BUNDLE_GEN_FLAGS) operator-sdk bundle validate ./bundle .PHONY: bundle-build bundle-build: ## Build the bundle image. docker build -f bundle.Dockerfile -t $(BUNDLE_IMG) . .PHONY: bundle-push bundle-push: ## Push the bundle image. $(MAKE) docker-push IMG=$(BUNDLE_IMG) .PHONY: opm OPM = ./bin/opm opm: ## Download opm locally if necessary. ifeq (,$(wildcard $(OPM))) ifeq (,$(shell which opm 2>/dev/null)) @{ \ set -e ;\ mkdir -p $(dir $(OPM)) ;\ OS=$(shell go env GOOS) && ARCH=$(shell go env GOARCH) && \ curl -sSLo $(OPM) https://github.com/operator-framework/operator-registry/releases/download/v1.19.1/$${OS}-$${ARCH}-opm ;\ chmod +x $(OPM) ;\ } else OPM = $(shell which opm) endif endif # A comma-separated list of bundle images (e.g. make catalog-build BUNDLE_IMGS=example.com/operator-bundle:v0.1.0,example.com/operator-bundle:v0.2.0). # These images MUST exist in a registry and be pull-able. BUNDLE_IMGS ?= $(BUNDLE_IMG) # The image tag given to the resulting catalog image (e.g. make catalog-build CATALOG_IMG=example.com/operator-catalog:v0.2.0). CATALOG_IMG ?= $(IMAGE_TAG_BASE)-catalog:v$(VERSION) # Set CATALOG_BASE_IMG to an existing catalog image tag to add $BUNDLE_IMGS to that image. ifneq ($(origin CATALOG_BASE_IMG), undefined) FROM_INDEX_OPT := --from-index $(CATALOG_BASE_IMG) endif # Build a catalog image by adding bundle images to an empty catalog using the operator package manager tool, 'opm'. # This recipe invokes 'opm' in 'semver' bundle add mode. For more information on add modes, see: # https://github.com/operator-framework/community-operators/blob/7f1438c/docs/packaging-operator.md#updating-your-existing-operator .PHONY: catalog-build catalog-build: opm ## Build a catalog image. $(OPM) index add --container-tool docker --mode semver --tag $(CATALOG_IMG) --bundles $(BUNDLE_IMGS) $(FROM_INDEX_OPT) # Push the catalog image. .PHONY: catalog-push catalog-push: ## Push a catalog image. $(MAKE) docker-push IMG=$(CATALOG_IMG) ================================================ FILE: chapter/14/petstore-operator/PROJECT ================================================ domain: example.com layout: - go.kubebuilder.io/v3 plugins: manifests.sdk.operatorframework.io/v2: {} scorecard.sdk.operatorframework.io/v2: {} projectName: petstore-operator repo: github.com/Go-for-DevOps/chapter/14/petstore-operator resources: - api: crdVersion: v1 namespaced: true controller: true domain: example.com group: petstore kind: Pet path: github.com/Go-for-DevOps/chapter/14/petstore-operator/api/v1alpha1 version: v1alpha1 version: "3" ================================================ FILE: chapter/14/petstore-operator/Tiltfile ================================================ # build and deploy the petstore service the controller will use to store pets k8s_yaml("./config/petstore-service/service.yaml") # build and push the petstore service image to the local registry docker_build("petstore:latest", "../../11/petstore/.") # generate the controller manifest and deploy to the cluster k8s_yaml(kustomize("./config/default")) # build and push the controller image to the local registry docker_build("controller:latest", ".") ================================================ FILE: chapter/14/petstore-operator/api/v1alpha1/groupversion_info.go ================================================ /* Copyright 2022. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 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 v1alpha1 contains API Schema definitions for the petstore v1alpha1 API group //+kubebuilder:object:generate=true //+groupName=petstore.example.com package v1alpha1 import ( "k8s.io/apimachinery/pkg/runtime/schema" "sigs.k8s.io/controller-runtime/pkg/scheme" ) var ( // GroupVersion is group version used to register these objects GroupVersion = schema.GroupVersion{Group: "petstore.example.com", Version: "v1alpha1"} // SchemeBuilder is used to add go types to the GroupVersionKind scheme SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion} // AddToScheme adds the types in this group-version to the given scheme. AddToScheme = SchemeBuilder.AddToScheme ) ================================================ FILE: chapter/14/petstore-operator/api/v1alpha1/pet_types.go ================================================ /* Copyright 2022. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 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 v1alpha1 import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) // PetType is the type of the pet. For example, a dog. // +kubebuilder:validation:Enum=dog;cat;bird;reptile type PetType string const ( DogPetType PetType = "dog" CatPetType PetType = "cat" BirdPetType PetType = "bird" ReptilePetType PetType = "reptile" ) // PetSpec defines the desired state of Pet type PetSpec struct { // Name is the name of the pet Name string `json:"name"` // Type is the type of pet Type PetType `json:"type"` // Birthday is the date the pet was born Birthday metav1.Time `json:"birthday"` } // PetStatus defines the observed state of Pet type PetStatus struct { // ID is the unique identifier created by the service for the pet ID string `json:"id,omitempty"` } //+kubebuilder:object:root=true //+kubebuilder:subresource:status // Pet is the Schema for the pets API type Pet struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` Spec PetSpec `json:"spec,omitempty"` Status PetStatus `json:"status,omitempty"` } //+kubebuilder:object:root=true // PetList contains a list of Pet type PetList struct { metav1.TypeMeta `json:",inline"` metav1.ListMeta `json:"metadata,omitempty"` Items []Pet `json:"items"` } func init() { SchemeBuilder.Register(&Pet{}, &PetList{}) } ================================================ FILE: chapter/14/petstore-operator/api/v1alpha1/zz_generated.deepcopy.go ================================================ //go:build !ignore_autogenerated // +build !ignore_autogenerated /* Copyright 2022. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 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 controller-gen. DO NOT EDIT. package v1alpha1 import ( runtime "k8s.io/apimachinery/pkg/runtime" ) // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Pet) DeepCopyInto(out *Pet) { *out = *in out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) in.Spec.DeepCopyInto(&out.Spec) out.Status = in.Status } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Pet. func (in *Pet) DeepCopy() *Pet { if in == nil { return nil } out := new(Pet) in.DeepCopyInto(out) return out } // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. func (in *Pet) DeepCopyObject() runtime.Object { if c := in.DeepCopy(); c != nil { return c } return nil } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *PetList) DeepCopyInto(out *PetList) { *out = *in out.TypeMeta = in.TypeMeta in.ListMeta.DeepCopyInto(&out.ListMeta) if in.Items != nil { in, out := &in.Items, &out.Items *out = make([]Pet, len(*in)) for i := range *in { (*in)[i].DeepCopyInto(&(*out)[i]) } } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PetList. func (in *PetList) DeepCopy() *PetList { if in == nil { return nil } out := new(PetList) in.DeepCopyInto(out) return out } // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. func (in *PetList) DeepCopyObject() runtime.Object { if c := in.DeepCopy(); c != nil { return c } return nil } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *PetSpec) DeepCopyInto(out *PetSpec) { *out = *in in.Birthday.DeepCopyInto(&out.Birthday) } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PetSpec. func (in *PetSpec) DeepCopy() *PetSpec { if in == nil { return nil } out := new(PetSpec) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *PetStatus) DeepCopyInto(out *PetStatus) { *out = *in } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PetStatus. func (in *PetStatus) DeepCopy() *PetStatus { if in == nil { return nil } out := new(PetStatus) in.DeepCopyInto(out) return out } ================================================ FILE: chapter/14/petstore-operator/client/client.go ================================================ // Client provides an API client to the petstore service. package client import ( "context" "fmt" "io" "time" "github.com/PacktPublishing/Go-for-DevOps/chapter/14/petstore-operator/client/internal/server/storage" "google.golang.org/grpc" "google.golang.org/grpc/metadata" pb "github.com/PacktPublishing/Go-for-DevOps/chapter/14/petstore-operator/client/proto" ) // Client is a client to the petstore service. type Client struct { client pb.PetStoreClient conn *grpc.ClientConn } // New is the constructor for Client. addr is the server's [host]:[port]. func New(addr string) (*Client, error) { conn, err := grpc.Dial(addr, grpc.WithInsecure()) if err != nil { return nil, err } return &Client{ client: pb.NewPetStoreClient(conn), conn: conn, }, nil } // Pet is a wrapper around a *pb.Pet that can return Go versions of // fields and errors if the returned stream has an error. type Pet struct { *pb.Pet err error } // Proto will give the Pet's proto representation. func (p Pet) Proto() *pb.Pet { return p.Pet } // Birthday returns the Pet's birthday as a time.Time. func (p Pet) Birthday() time.Time { // We are ignoring the error as we will either get a zero time // anyways and the server should be preventing this problem. t, _ := storage.BirthdayToTime(context.Background(), p.Pet.Birthday) return t } // Error indicates if there was an error in the Pet output stream. func (p Pet) Error() error { return p.err } // CallOptions are optional options for an RPC call. type CallOption func(co *callOptions) type callOptions struct { trace *string } // TraceID will cause the RPC call to execute a trace on the service and return "s" to the ID. // If s == nil, this will ignore the option. If "s" is not set after the call finishes, then // no trace was made. func TraceID(s *string) CallOption { return func(co *callOptions) { if s == nil { return } co.trace = s } } // AddPets adds pets to the service and returns their unique identities in the // same order as being added. func (c *Client) AddPets(ctx context.Context, pets []*pb.Pet, options ...CallOption) ([]string, error) { if len(pets) == 0 { return nil, nil } for _, p := range pets { if err := storage.ValidatePet(ctx, p, false); err != nil { return nil, err } } var header metadata.MD ctx, gOpts, f := handleCallOptions(ctx, &header, options) defer f() resp, err := c.client.AddPets(ctx, &pb.AddPetsReq{Pets: pets}, gOpts...) if err != nil { return nil, err } return resp.Ids, nil } // UpdatePets updates pets that already exist in the system. func (c *Client) UpdatePets(ctx context.Context, pets []*pb.Pet, options ...CallOption) error { if len(pets) == 0 { return nil } for _, p := range pets { if err := storage.ValidatePet(ctx, p, true); err != nil { return err } } var header metadata.MD ctx, gOpts, f := handleCallOptions(ctx, &header, options) defer f() _, err := c.client.UpdatePets(ctx, &pb.UpdatePetsReq{Pets: pets}, gOpts...) if err != nil { return err } return nil } // DeletePets deletes pets with the IDs passed. If the ID doesn't exist, the // system ignores it. func (c *Client) DeletePets(ctx context.Context, ids []string, options ...CallOption) error { if len(ids) == 0 { return nil } var header metadata.MD ctx, gOpts, f := handleCallOptions(ctx, &header, options) defer f() _, err := c.client.DeletePets(ctx, &pb.DeletePetsReq{Ids: ids}, gOpts...) if err != nil { return err } return nil } // SearchPets searches the pet store for pets matching the filter. If the filter contains // no entries, then all pets will be returned. func (c *Client) SearchPets(ctx context.Context, filter *pb.SearchPetsReq, options ...CallOption) (chan Pet, error) { if filter == nil { return nil, fmt.Errorf("the filter cannot be nil") } var header metadata.MD ctx, gOpts, f := handleCallOptions(ctx, &header, options) stream, err := c.client.SearchPets(ctx, filter, gOpts...) if err != nil { return nil, err } ch := make(chan Pet, 1) go func() { defer close(ch) defer f() for { p, err := stream.Recv() if err == io.EOF { return } if err != nil { ch <- Pet{err: err} return } ch <- Pet{Pet: p} } }() return ch, nil } // SamplerType is the type of OTEL sampling to do. type SamplerType int32 const ( STUnknown SamplerType = 0 Never SamplerType = 1 Always SamplerType = 2 Float SamplerType = 3 ) var validTypes = map[SamplerType]bool{ Never: true, Always: true, Float: true, } type Sampler struct { // Type is the type of sampling to use. Type SamplerType // Rate is the sampling rate, only used if type is Float. Rate float64 } func (s *Sampler) validate() error { if !validTypes[s.Type] { return fmt.Errorf("type %v is not a supported type", s.Type) } if s.Type == Float { if s.Rate <= 0 || s.Rate > 1 { return fmt.Errorf("Rate must be > 0 && <= 1.0, was %v", s.Rate) } } return nil } func (s *Sampler) proto() *pb.Sampler { return &pb.Sampler{ Type: pb.SamplerType(s.Type), FloatValue: s.Rate, } } func (s *Sampler) fromProto(p *pb.Sampler) { s.Type = SamplerType(p.Type) s.Rate = p.FloatValue } // ChangeSampler changes the sampling type and rate on the server. This is // and admin function that in production should be restricted. func (c *Client) ChangeSampler(ctx context.Context, sc Sampler, options ...CallOption) error { if err := sc.validate(); err != nil { return err } var header metadata.MD ctx, gOpts, f := handleCallOptions(ctx, &header, options) defer f() _, err := c.client.ChangeSampler(ctx, &pb.ChangeSamplerReq{Sampler: sc.proto()}, gOpts...) if err != nil { return err } return nil } func handleCallOptions(ctx context.Context, header *metadata.MD, options []CallOption) (context.Context, []grpc.CallOption, func()) { opts := callOptions{} for _, o := range options { o(&opts) } var gOpts []grpc.CallOption if opts.trace != nil { (*header)["trace"] = nil gOpts = append(gOpts, grpc.Header(header)) } f := func() { if opts.trace != nil { if len((*header)["otel.traceID"]) != 0 { *opts.trace = (*header)["otel.traceID"][0] } } } return ctx, gOpts, f } ================================================ FILE: chapter/14/petstore-operator/client/internal/server/errors/errors.go ================================================ // Package errors is a replacement for the golang standard library "errors". This replacement // adds errors to the Open Telemetry spans. The signatures only differs in that // New() now takes a context.Context object and fmt.Errorf() has been moved here and also takes a Context.Context. package errors import ( "context" "errors" "fmt" "go.opentelemetry.io/otel/codes" "go.opentelemetry.io/otel/trace" ) // New creates a new error and writes the error to a span if it exists in the context. func New(ctx context.Context, text string) error { span := trace.SpanFromContext(ctx) err := errors.New(text) span.RecordError(err) span.SetStatus(codes.Error, err.Error()) return err } // Errorf implements fmt.Errorf with the addition of a Context that if it contains a span // will have the error added to the span. func Errorf(ctx context.Context, s string, i ...interface{}) error { span := trace.SpanFromContext(ctx) err := fmt.Errorf(s, i...) span.RecordError(err) span.SetStatus(codes.Error, err.Error()) return err } // As implements errors.As(). func As(err error, target interface{}) bool { return errors.As(err, target) } // Is implements errors.Is(). func Is(err, target error) bool { return errors.Is(err, target) } // Unwrap implemements errors.Unwrap(). func Unwrap(err error) error { return errors.Unwrap(err) } ================================================ FILE: chapter/14/petstore-operator/client/internal/server/log/log.go ================================================ /* Package log is a replacement for the standard library log package that logs to OTEL spans contained in Context objects. These are seen as events with the attribute "log" set to true. The preferred way to log is to use an event: func someFunc(ctx context.Context) { e := NewEvent("someFunc()") defer e.Done(ctx) start := time.Now() defer func() { e.Add("latency.ns", int(time.Since(start))) }() } This records an event in the current span that has a key of "latency.ns" with the value in nano-seconds the operation took. You can use this to log in a similar manner to the logging package with Println and Printf. This is generally only useful for some generic debugging where you want to log something and filter the trace by messages with key "log". Generally these are messages you don't want to keep. func main() { ctx := context.Background() log.SetFlags(log.LstdFlags | log.Lshortfile) log.Println(ctx, "Starting main") log.Printf(ctx, "Env variables: %v", os.Environ()) } The above won't log anything, as there is no Span on the Context. If there was it would get output to the Open Telementry provider. If you need to use the standard library log, you can use Logger: log.Logger.Println("hello world") This would print whever the stanard logger prints to. This defaults to the standard logger, but you can replace with another Logger if you wish. You should only log messages with a standard logger when it can't be output to a trace. These are critical messages that indicate a definite bug. This keeps logging to only critical events and de-clutters what you need to look at to when doing a debug. */ package log import ( "context" "fmt" "log" "runtime" "sync" "time" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/trace" ) const ( Ldate = 1 << iota // the date in the local time zone: 2009/01/23 Ltime // the time in the local time zone: 01:23:23 Lmicroseconds // microsecond resolution: 01:23:23.123123. assumes Ltime. Llongfile // full file name and line number: /a/b/c/d.go:23 Lshortfile // final file name element and line number: d.go:23. overrides Llongfile LUTC // if Ldate or Ltime is set, use UTC rather than the local time zone LstdFlags = Ldate | Ltime // initial values for the standard logger ) // Logger provides access to the standard library's default logger. // This can be replaced in main with a logger of your choice. var Logger *log.Logger = log.Default() // setup the standard logger with flags. var std = &logger{flag: LstdFlags} // pool provides a pool of Event objects to keep our allocations to a minimum. var pool = &eventPool{ buf: make(chan *Event, 100), pool: sync.Pool{ New: func() interface{} { return &Event{} }, }, } // eventPool uses a set amount of Event objects and a sync.Pool for overflow. // Note: this would actually be a great place for metrics to key in on what would be // an optimal size for buf to prevent pool use. type eventPool struct { buf chan *Event pool sync.Pool } func (e *eventPool) get() *Event { select { case ev := <-e.buf: return ev default: } return e.pool.Get().(*Event) } func (e *eventPool) put(ev *Event) { ev.reset() select { case e.buf <- ev: default: } e.pool.Put(ev) } // Event represents a named event that occurs. This is the prefered way to log data. // Events have attributes and those attributes are key/value pairs. You create // an event and stuff attributes using Add() until the event is over and call Done(). // This will render the event to the current span. if no attrs exist, the event is ignored. // To avoid extra allocations type Event struct { name string attrs []attribute.KeyValue } // NewEvent returns a new Event. func NewEvent(name string) *Event { ev := pool.get() ev.name = name return ev } func (e *Event) reset() { e.name = "" e.attrs = e.attrs[0:0] } // Add adds an attribute named k with value i. i can be: bool, []bool, float64, []float64, int, []int, int64, []int64, string and []string. // If the value isn't one of those values, a standard log message is printed indicating a bug. func (e *Event) Add(k string, i interface{}) { if e.name == "" { return } switch v := i.(type) { case bool: e.attrs = append(e.attrs, attribute.Bool(k, v)) case []bool: e.attrs = append(e.attrs, attribute.BoolSlice(k, v)) case float64: e.attrs = append(e.attrs, attribute.Float64(k, v)) case []float64: e.attrs = append(e.attrs, attribute.Float64Slice(k, v)) case int: e.attrs = append(e.attrs, attribute.Int(k, v)) case []int: e.attrs = append(e.attrs, attribute.IntSlice(k, v)) case int64: e.attrs = append(e.attrs, attribute.Int64(k, v)) case []int64: e.attrs = append(e.attrs, attribute.Int64Slice(k, v)) case string: e.attrs = append(e.attrs, attribute.String(k, v)) case []string: e.attrs = append(e.attrs, attribute.StringSlice(k, v)) default: log.Printf("bug: event.Add(): receiveing %T which is not supported", v) } } // Done renders the Event to the span in the Context. If there are no attributes on the Event, this is a no-oop. // Once Done is called, the Event object MUST not be used again. func (e *Event) Done(ctx context.Context) { defer pool.put(e) if e.name == "" { return } span := trace.SpanFromContext(ctx) if e.attrs == nil { return } span.AddEvent(e.name, trace.WithAttributes(e.attrs...)) } // Println acts like log.Println() except we log to the OTEL span in the Context. func Println(ctx context.Context, v ...interface{}) { span := trace.SpanFromContext(ctx) if !span.IsRecording() { return } std.output(span, 2, fmt.Sprintln(v...)) } // Printf acts like log.Printf() except we log to the OTEL span in the Context. func Printf(ctx context.Context, format string, v ...interface{}) { span := trace.SpanFromContext(ctx) if !span.IsRecording() { return } std.output(span, 2, fmt.Sprintf(format, v...)) } // SetFlags sets the output flags for the standard logger. func SetFlags(flag int) { std.flag = flag } // logger is an implementation of log.Logger that writes to a Span. type logger struct { mu sync.Mutex flag int // properties buf []byte // for accumulating text to write } // Cheap integer to fixed-width decimal ASCII. Give a negative width to avoid zero-padding. func itoa(buf *[]byte, i int, wid int) { // Assemble decimal in reverse order. var b [20]byte bp := len(b) - 1 for i >= 10 || wid > 1 { wid-- q := i / 10 b[bp] = byte('0' + i - q*10) bp-- i = q } // i < 10 b[bp] = byte('0' + i) *buf = append(*buf, b[bp:]...) } func (l *logger) output(span trace.Span, calldepth int, s string) error { now := time.Now() // get this early var file string var line int l.mu.Lock() defer l.mu.Unlock() if l.flag&(Lshortfile|Llongfile) != 0 { // Release lock while getting caller info - it's expensive. l.mu.Unlock() var ok bool _, file, line, ok = runtime.Caller(calldepth) if !ok { file = "???" line = 0 } l.mu.Lock() } l.buf = l.buf[:0] l.formatHeader(&l.buf, now, file, line) l.buf = append(l.buf, s...) if len(s) == 0 || s[len(s)-1] != '\n' { l.buf = append(l.buf, '\n') } span.AddEvent(string(l.buf), trace.WithAttributes(attribute.Bool("log", true))) return nil } // formatHeader writes log header to buf in following order: // * date and/or time (if corresponding flags are provided), // * file and line number (if corresponding flags are provided), func (l *logger) formatHeader(buf *[]byte, t time.Time, file string, line int) { if l.flag&(Ldate|Ltime|Lmicroseconds) != 0 { if l.flag&LUTC != 0 { t = t.UTC() } if l.flag&Ldate != 0 { year, month, day := t.Date() itoa(buf, year, 4) *buf = append(*buf, '/') itoa(buf, int(month), 2) *buf = append(*buf, '/') itoa(buf, day, 2) *buf = append(*buf, ' ') } if l.flag&(Ltime|Lmicroseconds) != 0 { hour, min, sec := t.Clock() itoa(buf, hour, 2) *buf = append(*buf, ':') itoa(buf, min, 2) *buf = append(*buf, ':') itoa(buf, sec, 2) if l.flag&Lmicroseconds != 0 { *buf = append(*buf, '.') itoa(buf, t.Nanosecond()/1e3, 6) } *buf = append(*buf, ' ') } } if l.flag&(Lshortfile|Llongfile) != 0 { if l.flag&Lshortfile != 0 { short := file for i := len(file) - 1; i > 0; i-- { if file[i] == '/' { short = file[i+1:] break } } file = short } *buf = append(*buf, file...) *buf = append(*buf, ':') itoa(buf, line, -1) *buf = append(*buf, ": "...) } } ================================================ FILE: chapter/14/petstore-operator/client/internal/server/storage/mem/mem.go ================================================ // Package mem contains an in-memory storage implementation of storage.Data. // This is great for unit tests and demos. Our implementation uses a // left-leaning red black tree for storage of entries by birthdays and maps // for all other indexes. Filtering is done by searching all indexes for matches // by each filter and if all matches succeed we stream the entry found. package mem import ( "context" "sync" "time" "github.com/PacktPublishing/Go-for-DevOps/chapter/14/petstore-operator/client/internal/server/errors" "github.com/PacktPublishing/Go-for-DevOps/chapter/14/petstore-operator/client/internal/server/log" "github.com/PacktPublishing/Go-for-DevOps/chapter/14/petstore-operator/client/internal/server/storage" pb "github.com/PacktPublishing/Go-for-DevOps/chapter/14/petstore-operator/client/proto" "github.com/biogo/store/llrb" ) // birthdays represents a set of pets that share the same birthday with // keys that are pet IDs. This is what we insert into our birthday tree. type birthdays map[string]*pb.Pet // Compare implements the llrb.Comparable.Compare(). func (bi birthdays) Compare(b llrb.Comparable) int { var ap, bp *pb.Pet // Get any entry in the map, all have the same birthday. for _, ap = range bi { break } for _, bp = range b.(birthdays) { break } // Ignore errors because we have to conform to a function def // and we should not be storing records with errors in the Birthday. at, _ := storage.BirthdayToTime(nil, ap.Birthday) bt, _ := storage.BirthdayToTime(nil, bp.Birthday) switch { case at.Before(bt): return -1 case at.Equal(bt): return 0 } return 1 } // birthdayGet is what we use to search for a pets with a particular birthday. type birthdayGet struct { *pb.Pet } // Compare implements the llrb.Comparable.Compare(). func (bi birthdayGet) Compare(b llrb.Comparable) int { // Ignore errors because we have to conform to a function def // and we should not be storing records with errors in the Birthday. at, _ := storage.BirthdayToTime(nil, bi.Pet.Birthday) var bt time.Time switch v := b.(type) { case birthdayGet: bt, _ = storage.BirthdayToTime(nil, v.Pet.Birthday) case birthdays: var p *pb.Pet for _, p = range v { break } bt, _ = storage.BirthdayToTime(nil, p.Birthday) } switch { case at.Before(bt): return -1 case at.Equal(bt): return 0 } return 1 } // Data implements storage.Data. type Data struct { mu sync.RWMutex // protects the items in this block birthday *llrb.Tree names map[string]map[string]*pb.Pet ids map[string]*pb.Pet types map[pb.PetType]map[string]*pb.Pet // searches contains all the search calls that must be done // when we do a search. This is populated in New(). searches []func(context.Context, *pb.SearchPetsReq) []string } // New is the constructor for Data. func New() *Data { d := Data{ names: map[string]map[string]*pb.Pet{}, ids: map[string]*pb.Pet{}, birthday: &llrb.Tree{}, types: map[pb.PetType]map[string]*pb.Pet{}, } d.searches = []func(context.Context, *pb.SearchPetsReq) []string{ d.byNames, d.byTypes, d.byBirthdays, } return &d } // AddPets implements storage.Data.AddPets(). func (d *Data) AddPets(ctx context.Context, pets []*pb.Pet) error { e := log.NewEvent("mem.data.AddPets()") defer e.Done(ctx) start := time.Now() defer func() { e.Add("latency.ns", time.Since(start)) }() d.mu.RLock() // Make sure that none of these IDs somehow exist already. for _, p := range pets { if _, ok := d.ids[p.Id]; ok { return errors.Errorf(ctx, "pet with ID(%s) is already present", p.Id) } } d.mu.RUnlock() d.mu.Lock() defer d.mu.Unlock() d.populate(ctx, pets) return nil } // UpdatePets implements storage.Data.AddPets(). func (d *Data) UpdatePets(ctx context.Context, pets []*pb.Pet) error { d.mu.RLock() // Make sure that ALL of these IDs somehow exist already. for _, p := range pets { if _, ok := d.ids[p.Id]; !ok { return errors.Errorf(ctx, "pet with ID(%s) doesn't exist", p.Id) } } d.mu.RUnlock() d.mu.Lock() defer d.mu.Unlock() d.populate(ctx, pets) return nil } func (d *Data) populate(ctx context.Context, pets []*pb.Pet) { e := log.NewEvent("mem.data.populate()") defer e.Done(ctx) for _, p := range pets { d.ids[p.Id] = p if v, ok := d.names[p.Name]; ok { v[p.Id] = p } else { d.names[p.Name] = map[string]*pb.Pet{ p.Id: p, } } if v, ok := d.types[p.Type]; ok { v[p.Id] = p } else { d.types[p.Type] = map[string]*pb.Pet{ p.Id: p, } } v := d.birthday.Get(birthdayGet{p}) if v == nil { d.birthday.Insert(birthdays{p.Id: p}) continue } v.(birthdays)[p.Id] = p } } // DeletePets implements stroage.Data.DeletePets(). func (d *Data) DeletePets(ctx context.Context, ids []string) error { e := log.NewEvent("mem.data.DeletePets()") defer e.Done(ctx) start := time.Now() defer func() { e.Add("latency.ns", time.Since(start)) }() d.mu.Lock() defer d.mu.Unlock() for _, id := range ids { p, ok := d.ids[id] if !ok { continue } delete(d.ids, id) if v, ok := d.names[p.Name]; ok { if len(v) == 1 { delete(d.names, p.Name) } else { delete(v, id) } } if v, ok := d.types[p.Type]; ok { if len(v) == 1 { delete(d.types, p.Type) } else { delete(v, id) } } v := d.birthday.Get(birthdayGet{p}) if v == nil { continue } if len(v.(birthdays)) == 1 { d.birthday.Delete(birthdayGet{p}) } delete(v.(birthdays), p.Id) } return nil } // SearchPets implements storage.Data.SearchPets(). func (d *Data) SearchPets(ctx context.Context, filter *pb.SearchPetsReq) chan storage.SearchItem { petsCh := make(chan storage.SearchItem, 1) go func() { defer close(petsCh) d.searchPets(ctx, filter, petsCh) }() return petsCh } func (d *Data) searchPets(ctx context.Context, filter *pb.SearchPetsReq, out chan storage.SearchItem) { e := log.NewEvent("mem.data.searchPets()") defer e.Done(ctx) d.mu.RLock() defer d.mu.RUnlock() filters := 0 if len(filter.Names) > 0 { e.Add("filterNames", true) filters++ } if len(filter.Types) > 0 { e.Add("filterTypes", true) filters++ } if filter.BirthdateRange != nil { e.Add("filterBirthday", true) filters++ } // They didn't provide filters, so just return everything. if filters == 0 { e.Add("returnAll", true) d.returnAll(ctx, out) return } searchCh := make(chan []string, len(d.searches)) wg := sync.WaitGroup{} wg.Add(len(d.searches)) goCount := 0 // Spin off our searches. for _, search := range d.searches { goCount++ search := search go func() { defer wg.Done() r := search(ctx, filter) select { case <-ctx.Done(): case searchCh <- r: } }() } e.Add("search.goroutines", goCount) // Wait for our searches to complete then close our searchCh. go func() { wg.Wait(); close(searchCh) }() // Collect all IDs from searches and count them. When one hits // the total number of filters send the matching pet to the caller. m := map[string]int{} matchCh := make(chan string, 1) go func() { defer close(matchCh) for ids := range searchCh { for _, id := range ids { count := m[id] count++ m[id] = count if count == filters { matchCh <- id } } } }() // This handles all our matches getting returned. valCount := 0 latency := 0 defer func() { if valCount > 0 { e.Add("upstream.recv.latency.avg.ns", latency/valCount) } }() for { select { case <-ctx.Done(): return case id, ok := <-matchCh: if !ok { return } start := time.Now() out <- storage.SearchItem{Pet: d.ids[id]} valCount++ latency += int(time.Since(start)) } } } // returnAll streams all the pets that we have. func (d *Data) returnAll(ctx context.Context, out chan storage.SearchItem) { e := log.NewEvent("mem.data.returnAll()") defer e.Done(ctx) start := time.Now() count := 0 defer func() { e.Add("latency.ns", int(time.Since(start))) e.Add("count", count) }() for _, p := range d.ids { count++ select { case <-ctx.Done(): return case out <- storage.SearchItem{Pet: p}: } } } // byNames returns IDs of pets that have the names matched in the filter. func (d *Data) byNames(ctx context.Context, filter *pb.SearchPetsReq) []string { if len(filter.Names) == 0 { return nil } e := log.NewEvent("mem.data.byNames()") defer e.Done(ctx) start := time.Now() count := 0 defer func() { e.Add("latency.ns", int(time.Since(start))) e.Add("count", count) }() var ids []string for _, n := range filter.Names { count++ if ctx.Err() != nil { return nil } p, ok := d.names[n] if !ok { continue } for id := range p { ids = append(ids, id) } } return ids } // byTypes returns IDs of pets that have the types matched in the filter. func (d *Data) byTypes(ctx context.Context, filter *pb.SearchPetsReq) []string { if len(filter.Types) == 0 { return nil } e := log.NewEvent("mem.data.byTypes()") defer e.Done(ctx) start := time.Now() count := 0 defer func() { e.Add("latency.ns", int(time.Since(start))) e.Add("count", count) }() var ids []string for _, t := range filter.Types { count++ if ctx.Err() != nil { return nil } p, ok := d.types[t] if !ok { continue } for id := range p { ids = append(ids, id) } } return ids } // byBirthdays returns IDs of pets that have the birthdays matched in the filter. func (d *Data) byBirthdays(ctx context.Context, filter *pb.SearchPetsReq) []string { if filter.BirthdateRange == nil { return nil } e := log.NewEvent("mem.data.byBirthdays()") defer e.Done(ctx) start := time.Now() count := 0 defer func() { e.Add("latency.ns", int(time.Since(start))) e.Add("count", count) }() var ids []string d.birthday.DoRange( func(c llrb.Comparable) (done bool) { for _, p := range c.(birthdays) { if ctx.Err() != nil { return true } ids = append(ids, p.Id) } return }, birthdayGet{&pb.Pet{Birthday: filter.BirthdateRange.Start}}, birthdayGet{&pb.Pet{Birthday: filter.BirthdateRange.End}}, ) if ctx.Err() != nil { return nil } count = len(ids) return ids } ================================================ FILE: chapter/14/petstore-operator/client/internal/server/storage/mem/mem_test.go ================================================ package mem import ( "context" "sort" "strconv" "testing" "github.com/kylelemons/godebug/pretty" dpb "google.golang.org/genproto/googleapis/type/date" "google.golang.org/protobuf/proto" "github.com/PacktPublishing/Go-for-DevOps/chapter/14/petstore-operator/client/internal/server/storage" pb "github.com/PacktPublishing/Go-for-DevOps/chapter/14/petstore-operator/client/proto" ) // This tests we implement the interface. var _ storage.Data = &Data{} var pets = []*pb.Pet{ { Id: "0", Name: "Adam", Type: pb.PetType_PTCanine, Birthday: &dpb.Date{Month: 1, Day: 1, Year: 2020}, }, { Id: "1", Name: "Becky", Type: pb.PetType_PTFeline, Birthday: &dpb.Date{Month: 2, Day: 1, Year: 2020}, }, { Id: "2", Name: "Calvin", Type: pb.PetType_PTFeline, Birthday: &dpb.Date{Month: 2, Day: 2, Year: 2020}, }, { Id: "3", Name: "David", Type: pb.PetType_PTBird, Birthday: &dpb.Date{Month: 2, Day: 2, Year: 2021}, }, { Id: "4", Name: "Elaine", Type: pb.PetType_PTReptile, Birthday: &dpb.Date{Month: 2, Day: 2, Year: 2021}, }, { Id: "5", Name: "Elaine", Type: pb.PetType_PTReptile, Birthday: &dpb.Date{Month: 2, Day: 3, Year: 2021}, }, } // makePets takes the global "pets" var and clones everything in it and puts it into // a *Data so we have test data. func makePets() *Data { d := New() n := []*pb.Pet{} for _, p := range pets { n = append(n, proto.Clone(p).(*pb.Pet)) } d.AddPets(context.Background(), n) return d } func TestByNames(t *testing.T) { d := makePets() got := d.byNames(context.Background(), &pb.SearchPetsReq{Names: []string{"David", "Elaine"}}) sort.Strings(got) want := []string{"3", "4", "5"} if diff := pretty.Compare(want, got); diff != "" { t.Errorf("TestByNames: -want/+got:\n%s", diff) } } func TestByTypes(t *testing.T) { d := makePets() got := d.byTypes(context.Background(), &pb.SearchPetsReq{Types: []pb.PetType{pb.PetType_PTCanine, pb.PetType_PTReptile}}) sort.Strings(got) want := []string{"0", "4", "5"} if diff := pretty.Compare(want, got); diff != "" { t.Errorf("TestByTypes: -want/+got:\n%s", diff) } } func TestByBirthdays(t *testing.T) { d := makePets() got := d.byBirthdays( context.Background(), &pb.SearchPetsReq{ BirthdateRange: &pb.DateRange{ Start: &dpb.Date{Month: 2, Day: 1, Year: 2020}, End: &dpb.Date{Month: 2, Day: 3, Year: 2021}, }, }, ) sort.Strings(got) want := []string{"1", "2", "3", "4"} if diff := pretty.Compare(want, got); diff != "" { t.Errorf("TestByBirthdays: -want/+got:\n%s", diff) } } func TestDeletePets(t *testing.T) { d := makePets() deletions := []string{"3", "5", "20"} if err := d.DeletePets(context.Background(), deletions); err != nil { t.Fatalf("TestDeletePets: got err == %v, want err == nil", err) } // Don't check the last deletion, it is only there to make sure // a non-existent value doesn't do anything. for _, id := range deletions[:len(deletions)-1] { if _, ok := d.ids[id]; ok { t.Errorf("TestDeletePets: found ids[%s]", id) } i, _ := strconv.Atoi(id) if m, ok := d.names[pets[i].Name]; ok { if _, ok := m[id]; ok { t.Errorf("TestDeletePets: found(%s) in names", id) } } if m, ok := d.types[pets[i].Type]; ok { if _, ok := m[id]; ok { t.Errorf("TestDeletePets: found(%s) in types", id) } } v := d.birthday.Get(birthdayGet{pets[i]}) if v != nil { if _, ok := v.(birthdays)[id]; ok { t.Errorf("TestDeletePets: found(%s) in birthday tree", id) } } } } func TestSearchPets(t *testing.T) { d := makePets() ch := d.SearchPets( context.Background(), &pb.SearchPetsReq{ Names: []string{ "Becky", "Calvin", "David", "Elaine", }, Types: []pb.PetType{ pb.PetType_PTReptile, pb.PetType_PTFeline, }, BirthdateRange: &pb.DateRange{ Start: &dpb.Date{Month: 2, Day: 2, Year: 2021}, End: &dpb.Date{Month: 2, Day: 3, Year: 2021}, }, }, ) got := []storage.SearchItem{} for item := range ch { got = append(got, item) } want := []storage.SearchItem{{Pet: pets[4]}} config := pretty.Config{TrackCycles: true} if diff := config.Compare(want, got); diff != "" { t.Errorf("TestSearchPets: -want/+got:\n%s", diff) } } ================================================ FILE: chapter/14/petstore-operator/client/internal/server/storage/storage.go ================================================ package storage import ( "context" "strings" "time" dpb "google.golang.org/genproto/googleapis/type/date" "github.com/PacktPublishing/Go-for-DevOps/chapter/14/petstore-operator/client/internal/server/errors" pb "github.com/PacktPublishing/Go-for-DevOps/chapter/14/petstore-operator/client/proto" ) // Data represents our data storage. type Data interface { // AddPets adds pet entries into storage. AddPets(ctx context.Context, pets []*pb.Pet) error // UpdatePets updates pet entries in storage. UpdatePets(ctx context.Context, pets []*pb.Pet) error // DeletePets deletes pets in storage by their ID. Will not error // on IDs not found. DeletePets(ctx context.Context, ids []string) error // SearchPets searches storage for pet entries that match the // filter. SearchPets(ctx context.Context, filter *pb.SearchPetsReq) chan SearchItem } // SearchItem is an item returned by a search. type SearchItem struct { // Pet is the pet that matched the search filters. Pet *pb.Pet // Error indicates that there was an error. If set the channel // will close after this entry. Error error } // ValidatePet validates that *pb.Pet has valid fields. func ValidatePet(ctx context.Context, p *pb.Pet, forUpdate bool) error { if forUpdate && p.Id == "" { return errors.New(ctx, "updates must have the Id field set") } else { if !forUpdate && p.Id != "" { return errors.New(ctx, "cannot set the Id field") } } p.Name = strings.TrimSpace(p.Name) if p.Name == "" { return errors.New(ctx, "cannot have a pet without a name") } if p.Type == pb.PetType_PTUnknown { return errors.New(ctx, "cannot have an unknown pet type") } _, err := BirthdayToTime(ctx, p.Birthday) if err != nil { return errors.Errorf(ctx, "pet(%s) had an error in its birthday: %w", p.Name, err) } return nil } // BirthdayToTime converts the *pb.Pet.Birthday field to a time.Time object. func BirthdayToTime(ctx context.Context, d *dpb.Date) (time.Time, error) { if d.Month < 1 || d.Month > 12 { return time.Time{}, errors.Errorf(ctx, "month must be 1-12, was %d", d.Month) } if d.Day < 1 || d.Day > 31 { return time.Time{}, errors.Errorf(ctx, "day(%d) was invalid", d.Day) } t := time.Date(int(d.Year), time.Month(d.Month), int(d.Day), 0, 0, 0, 0, time.UTC) if t.Month() != time.Month(d.Month) { return time.Time{}, errors.Errorf(ctx, "month %v does not have %d days", time.Month(d.Month), d.Day) } return t, nil } ================================================ FILE: chapter/14/petstore-operator/client/internal/server/telemetry/metrics/metrics.go ================================================ /* Package metrics provides setup of metrics that can be used internally to measure various application states. All metrics for the application are defined here and other applications use this package to grab the metrics and use them. This package will also report any metric that is not used in the first 10 seconds after the app has started to prevent useless metrics from existing, as all metrics should be grabbed by that time. In a package you want to set metrics, you can do it as follows: var addCount metrics.Int64Counter func init() { addCounter = metrics.Get.Int64("petstore/server/AddPets/requests") } ... func (s *Server) AddPets(ctx context.Context, req *pb.AddPetsReq) (*pb.AddpetsResp, error) { ... // Do this if you have multiple changes that don't require special labels per update. metrics.Meter.RecordBatch(ctx, nil, addCounter.Measure(ctx, 1)) // Do this if you only need to make one change or need special labels. addCounter.Add(ctx, 1, attribute.String("label", "value") ... } To cause metrics to be exported package main(): func main() { ... stop, err := metrics.Start(ctx, metrics.OTELGRPC{Addr: "ip:port"}) if err != nil { log.Fatal(err) } defer stop() ... } */ package metrics import ( "html/template" "sort" "strings" "sync" "time" "github.com/PacktPublishing/Go-for-DevOps/chapter/14/petstore-operator/client/internal/server/log" "go.opentelemetry.io/otel/metric" "go.opentelemetry.io/otel/metric/global" ) type metricType int const ( unknown = 0 mtInt64 = 1 mtInt64Hist = 2 mtInt64UD = 3 ) type metricDef struct { mtype metricType name string desc string } var metrics = []metricDef{ // Histograms {mtInt64Hist, "petstore/server/AddPets/latency", "The latency of an AddPets() request in nanoseconds"}, {mtInt64Hist, "petstore/server/DeletePets/latency", "The latency of an DeletePets() request in nanoseconds"}, {mtInt64Hist, "petstore/server/UpdatePets/latency", "The latency of an UpdatePets() request in nanoseconds"}, {mtInt64Hist, "petstore/server/SearchPets/latency", "The latency of a SearchPets() request in nanoseconds"}, // Counters {mtInt64, "petstore/server/AddPets/requests", "The total requests made to AddPets()"}, {mtInt64, "petstore/server/DeletePets/requests", "The total requests made to DeletePets()"}, {mtInt64, "petstore/server/UpdatePets/requests", "The total requests made to UpdatePets()"}, {mtInt64, "petstore/server/SearchPets/requests", "The total requests made to SearchPets()"}, {mtInt64, "petstore/server/totals/requests", "The total requests made to the server"}, {mtInt64, "petstore/server/AddPets/errors", "The total error count"}, {mtInt64, "petstore/server/DeletePets/errors", "The total error couunt"}, {mtInt64, "petstore/server/UpdatePets/errors", "The total error count"}, {mtInt64, "petstore/server/SearchPets/errors", "The total error count"}, {mtInt64, "petstore/server/totals/errors", "The total error count for all RPCs"}, // UpDown Counters {mtInt64UD, "petstore/server/AddPets/current", "The amount of requests currently being proccessed"}, {mtInt64UD, "petstore/server/DeletePets/current", "The amount of requests currently being proccessed"}, {mtInt64UD, "petstore/server/UpdatePets/current", "The amount of requests currently being proccessed"}, {mtInt64UD, "petstore/server/SearchPets/current", "The amount of requests currently being proccessed"}, } // Meter is the meter for the petstore. var Meter = global.Meter("petstore") // Get is used to lookup metrics by name. var Get = newLookups() var unusedMetricsTmpl = template.Must( template.New("").Parse( ` The following metrics appeart to be unused: {{- range .}} {{.}} {{- end }} `, ), ) // Lookups provides lookups for metrics based on their names. type Lookups struct { mtInt64Hist map[string]metric.Int64Histogram mtInt64UD map[string]metric.Int64UpDownCounter mtInt64 map[string]metric.Int64Counter mu sync.Mutex used map[string]bool } func newLookups() *Lookups { l := &Lookups{ mtInt64Hist: map[string]metric.Int64Histogram{}, mtInt64: map[string]metric.Int64Counter{}, mtInt64UD: map[string]metric.Int64UpDownCounter{}, used: map[string]bool{}, } exists := map[string]bool{} for _, m := range metrics { if m.mtype == unknown { log.Logger.Fatalf("metric with type(%v) cannot be added", m.mtype) } if m.name == "" { log.Logger.Fatalf("metric cannot be missing a name") } if m.desc == "" { log.Logger.Fatalf("metric cannot be missing a desc") } if exists[m.name] { log.Logger.Fatalf("cannot have two metrics with same name(%s)", m.name) } exists[m.name] = true switch m.mtype { case mtInt64Hist: l.mtInt64Hist[m.name] = metric.Must(Meter).NewInt64Histogram(m.name, metric.WithDescription(m.desc)) case mtInt64UD: l.mtInt64UD[m.name] = metric.Must(Meter).NewInt64UpDownCounter(m.name, metric.WithDescription(m.desc)) case mtInt64: l.mtInt64[m.name] = metric.Must(Meter).NewInt64Counter(m.name, metric.WithDescription(m.desc)) default: log.Logger.Fatalf("bug: we defined a metric type(%v) without adding support", m.mtype) } } go func() { time.Sleep(10 * time.Second) unused := l.unused() s := strings.Builder{} if err := unusedMetricsTmpl.Execute(&s, unused); err != nil { log.Logger.Fatalf("unusedMetricTmpl execute error: %s", err) } log.Logger.Println(s.String()) }() return l } // Int64 grabs the Int64Counter metric named "s". If not found, panics. func (l *Lookups) Int64(s string) metric.Int64Counter { l.mu.Lock() defer l.mu.Unlock() m, ok := l.mtInt64[s] if !ok { log.Logger.Fatalf("int64 metric(%s) is not defined", s) } l.used[s] = true return m } // Int64s grabs a list of Int64Counters. func (l *Lookups) Int64s(s ...string) []metric.Int64Counter { v := make([]metric.Int64Counter, 0, len(s)) for _, name := range s { v = append(v, l.Int64(name)) } return v } // Int64UD grabs the Int64UpDownCounter metric named "s". If not found, panics. func (l *Lookups) Int64UD(s string) metric.Int64UpDownCounter { l.mu.Lock() defer l.mu.Unlock() m, ok := l.mtInt64UD[s] if !ok { log.Logger.Fatalf("int64ud metric(%s) is not defined", s) } l.used[s] = true return m } // Int64UDs grabs a list of Int64UpDownCounters. func (l *Lookups) Int64UDs(s ...string) []metric.Int64UpDownCounter { v := make([]metric.Int64UpDownCounter, 0, len(s)) for _, name := range s { v = append(v, l.Int64UD(name)) } return v } // Int64Hist grabs the Int64Histogram metric named "s". If not found, panics. func (l *Lookups) Int64Hist(s string) metric.Int64Histogram { l.mu.Lock() defer l.mu.Unlock() m, ok := l.mtInt64Hist[s] if !ok { log.Logger.Fatalf("int64 histogram metric(%s) is not defined", s) } l.used[s] = true return m } func (l *Lookups) Int64Hists(s ...string) []metric.Int64Histogram { v := make([]metric.Int64Histogram, 0, len(s)) for _, name := range s { v = append(v, l.Int64Hist(name)) } return v } func (l *Lookups) unused() []string { l.mu.Lock() defer l.mu.Unlock() unused := []string{} for k := range l.mtInt64Hist { if !l.used[k] { unused = append(unused, k) } } for k := range l.mtInt64 { if !l.used[k] { unused = append(unused, k) } } sort.Strings(unused) return unused } ================================================ FILE: chapter/14/petstore-operator/client/internal/server/telemetry/metrics/start.go ================================================ package metrics import ( "context" "fmt" "time" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/exporters/otlp/otlpmetric" "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc" "go.opentelemetry.io/otel/metric/global" "go.opentelemetry.io/otel/sdk/metric/controller/basic" processor "go.opentelemetry.io/otel/sdk/metric/processor/basic" "go.opentelemetry.io/otel/sdk/metric/selector/simple" ) // Controller represents the controller to send metrics to. type Controller interface { isController() } // OTELGRPC represents exporting to the "go.opentelemetry.io/otel/sdk/metric/controller/basic" controller. type OTELGRPC struct { // Addr is the local address to export on. Addr string } func (o OTELGRPC) isController() {} // Stop is used to stop OTEL metric handling. type Stop func() // Start is used to start OTEL metric handling. func Start(ctx context.Context, c Controller) (Stop, error) { control, err := newController(ctx, c) if err != nil { return nil, err } err = control.Start(ctx) if err != nil { return nil, err } return func() { ctx, cancel := context.WithTimeout(ctx, 1*time.Second) defer cancel() if err := control.Stop(ctx); err != nil { otel.Handle(err) } }, nil } func newController(ctx context.Context, c Controller) (*basic.Controller, error) { switch v := c.(type) { case OTELGRPC: return otelGRPC(ctx, v) } return nil, fmt.Errorf("%T is not a valid Controller", c) } func otelGRPC(ctx context.Context, args OTELGRPC) (*basic.Controller, error) { metricClient := otlpmetricgrpc.NewClient( otlpmetricgrpc.WithInsecure(), otlpmetricgrpc.WithEndpoint(args.Addr), ) metricExp, err := otlpmetric.New(ctx, metricClient) if err != nil { return nil, fmt.Errorf("Failed to create the collector metric exporter") } pusher := basic.New( processor.NewFactory( simple.NewWithHistogramDistribution(), metricExp, ), basic.WithExporter(metricExp), basic.WithCollectPeriod(10*time.Second), ) global.SetMeterProvider(pusher) return pusher, nil } ================================================ FILE: chapter/14/petstore-operator/client/internal/server/telemetry/tracing/sampler/sampler.go ================================================ /* Package sampler offers a Sampler that looks for a TraceID.Valid() == true or a gRPC metadata key called "trace" and if they exist will sample. Otherwise it looks to a child Sampler to determine based upon whatever sampler algorithm is used. In addition we offer the ability to switch out the underlying sampler at anytime in a thread-safe way. You can construct a new Sampler like so: s, err := New(trace.NeverSample) if err != nil { // Do something } The above Sampler would only trace if a TraceID.Valid() == true or gRCP metadate key called "trace" existed. If we want to trace 1% of the time as well, we can do the following: s, err := New(trace.TraceIDRatioBased(.01)) if err != nil { // Do something } */ package sampler import ( "fmt" "sync/atomic" "go.opentelemetry.io/otel/sdk/trace" otelTrace "go.opentelemetry.io/otel/trace" "google.golang.org/grpc/metadata" ) const desc = `This sampler samples if TracdID.Valid(), gRPC metadata contains key "trace" or the child sampler decides to sample` // Sampler decides whether a trace should be sampled and exported. This sampler will sample if // paramters TraceID.Valid() == true or the Context contains gRPC metadata that has key "trace" (it doesn't care about values). type Sampler struct { // child stores a *trace.Sampler. trace.Sampler is an interface. Because atomic.Value cares about // the underlying type, you can't just store trace.Sampler. So we do a pointer, which is the only valid // use of *interface I've ever seen. child atomic.Value // *trace.Sampler } // New creates a new Sampler with the child Sampler used if TraceID.Valid() == false and gRPC metadata does not contain // key "trace". func New(child trace.Sampler) (*Sampler, error) { if child == nil { return nil, fmt.Errorf("child cannot == nil") } s := &Sampler{} s.child.Store(&child) return s, nil } // ShouldSample implements trace.Sampler.ShouldSample. func (s *Sampler) ShouldSample(p trace.SamplingParameters) trace.SamplingResult { psc := otelTrace.SpanContextFromContext(p.ParentContext) if psc.IsValid() { if psc.IsRemote() { if psc.IsSampled() { return trace.SamplingResult{ Decision: trace.RecordAndSample, Tracestate: psc.TraceState(), } } } if psc.IsSampled() { return trace.SamplingResult{ Decision: trace.RecordAndSample, Tracestate: psc.TraceState(), } } } md, ok := metadata.FromIncomingContext(p.ParentContext) if !ok { return (*s.child.Load().(*trace.Sampler)).ShouldSample(p) } if _, ok := md["trace"]; ok { psc := otelTrace.SpanContextFromContext(p.ParentContext) return trace.SamplingResult{ Decision: trace.RecordAndSample, Tracestate: psc.TraceState(), } } return (*s.child.Load().(*trace.Sampler)).ShouldSample(p) } // Description implements trace.Sampler.Description(). func (s *Sampler) Description() string { return desc } // Switch switches the underlying trace.Sampler. func (s *Sampler) Switch(sampler trace.Sampler) { if sampler == nil { panic("cannot call Switch() with a nil Sampler") } s.child.Store(&sampler) } ================================================ FILE: chapter/14/petstore-operator/client/internal/server/telemetry/tracing/tracing.go ================================================ /* Package tracing provides functions for starting and stopping our Open Telemetry tracing. This package is intended to be used from main and is simple to use. We offer a few choices on where traces export to. Here is an example to trace to stderr for all requests: func main() { ctx := context.Background() // Set us up to always sample. The "trace" package is: "petstore/server/SearchPets/latency" tracing.Sampler.Switch(trace.AlwaysSample()) // Start our tracing and pass the empty Stderr tracing arguments. // Stderr{} has no required fields. stop, err := tracing.Start(ctx, tracing.Stderr{}) if err != nil { log.Fatalf("problem starting telemetry: %s", err) } // Stop kills our exporter when main() ends. defer stop() } */ package tracing import ( "context" "fmt" "io" "log" "os" "time" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/exporters/otlp/otlptrace" "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc" "go.opentelemetry.io/otel/exporters/stdout/stdouttrace" "go.opentelemetry.io/otel/propagation" "go.opentelemetry.io/otel/sdk/resource" sdktrace "go.opentelemetry.io/otel/sdk/trace" semconv "go.opentelemetry.io/otel/semconv/v1.4.0" "go.opentelemetry.io/otel/trace" "google.golang.org/grpc" "github.com/PacktPublishing/Go-for-DevOps/chapter/14/petstore-operator/client/internal/server/telemetry/tracing/sampler" ) // Tracer is the tracer initialized by Start(). var ( // Tracer is the tracer initialized by Start(). Tracer trace.Tracer // *sdktrace.TracerProvider //otlptrace.Exporter // Sampler is our *sampler.Sampler used by the Tracer. Sampler *sampler.Sampler ) func init() { s, err := sampler.New(sdktrace.TraceIDRatioBased(1)) if err != nil { panic(err) } Sampler = s } // Exporter represents the exporter to send telemetry to. type Exporter interface { isExporter() } // OTELGRPC represents exporting to the go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc exporter. type OTELGRPC struct { // Addr is the local address to export on. Addr string } func (o OTELGRPC) isExporter() {} // Stderr exports trace data to os.Stderr. type Stderr struct{} func (s Stderr) isExporter() {} // File exports trace data to a file. If the file exists, it is overwritten. type File struct { // Path is the path to the file. Path string } func (f File) isExporter() {} // Stop stops our Open Telemetry exporter. type Stop func() // Start creates the OTEL exporter and configures the trace providers. // It returns a Stop() which will stop the exporter. func Start(ctx context.Context, e Exporter) (Stop, error) { log.Println("Sampler: ", Sampler) tp, err := newTraceExporter(ctx, e) if err != nil { return nil, err } Tracer = tp.Tracer("petstore") return func() { var cancel context.CancelFunc ctx, cancel = context.WithTimeout(ctx, 1*time.Second) defer cancel() if err := tp.Shutdown(ctx); err != nil { otel.Handle(err) } }, nil } // newTracerExporter creates an OTLP exporter with our tracer information. func newTraceExporter(ctx context.Context, e Exporter) (*sdktrace.TracerProvider, error) { var exp sdktrace.SpanExporter var err error switch v := e.(type) { case OTELGRPC: exp, err = otelGRPC(ctx, v) case Stderr: exp, err = newFileExporter(os.Stderr) case File: f, err := os.Create(v.Path) if err != nil { return nil, err } exp, err = newFileExporter(f) default: return nil, fmt.Errorf("%T is not a valid Exporter", e) } res, err := resource.New( ctx, resource.WithFromEnv(), resource.WithProcess(), resource.WithTelemetrySDK(), resource.WithHost(), resource.WithAttributes( // the service name used to display traces in backends semconv.ServiceNameKey.String("petstore"), ), ) if err != nil { return nil, err } // set global propagator to tracecontext (the default is no-op). otel.SetTextMapPropagator(propagation.TraceContext{}) prov := sdktrace.NewTracerProvider( sdktrace.WithSampler(Sampler), sdktrace.WithBatcher(exp), sdktrace.WithResource(res), ) otel.SetTracerProvider(prov) return prov, nil } // newFileExporter creates an exporter that writes to a file. func newFileExporter(w io.Writer) (sdktrace.SpanExporter, error) { return stdouttrace.New( stdouttrace.WithWriter(w), stdouttrace.WithPrettyPrint(), ) } func otelGRPC(ctx context.Context, e OTELGRPC) (sdktrace.SpanExporter, error) { //(*otlptrace.Exporter, error) { exp, err := otlptrace.New( ctx, otlptracegrpc.NewClient( otlptracegrpc.WithInsecure(), otlptracegrpc.WithEndpoint(e.Addr), otlptracegrpc.WithDialOption(grpc.WithBlock()), ), ) if err != nil { return nil, err } return exp, nil } ================================================ FILE: chapter/14/petstore-operator/client/proto/buf.gen.yaml ================================================ version: v1 plugins: - name: go out: ./ opt: - paths=source_relative - name: go-grpc out: ./ opt: - paths=source_relative ================================================ FILE: chapter/14/petstore-operator/client/proto/buf.yaml ================================================ version: v1 deps: - buf.build/googleapis/googleapis lint: use: - DEFAULT breaking: use: - FILE ================================================ FILE: chapter/14/petstore-operator/client/proto/petstore.pb.go ================================================ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.25.0-devel // protoc v3.18.0 // source: petstore.proto package proto import ( date "google.golang.org/genproto/googleapis/type/date" 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) ) // Desribes the type of pets. type PetType int32 const ( // The type was not set. PetType_PTUnknown PetType = 0 // The pet is a canine. PetType_PTCanine PetType = 1 // The pet is a feline. PetType_PTFeline PetType = 2 // The pet is a bird. PetType_PTBird PetType = 3 // The pet is a reptile. PetType_PTReptile PetType = 4 ) // Enum value maps for PetType. var ( PetType_name = map[int32]string{ 0: "PTUnknown", 1: "PTCanine", 2: "PTFeline", 3: "PTBird", 4: "PTReptile", } PetType_value = map[string]int32{ "PTUnknown": 0, "PTCanine": 1, "PTFeline": 2, "PTBird": 3, "PTReptile": 4, } ) func (x PetType) Enum() *PetType { p := new(PetType) *p = x return p } func (x PetType) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } func (PetType) Descriptor() protoreflect.EnumDescriptor { return file_petstore_proto_enumTypes[0].Descriptor() } func (PetType) Type() protoreflect.EnumType { return &file_petstore_proto_enumTypes[0] } func (x PetType) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } // Deprecated: Use PetType.Descriptor instead. func (PetType) EnumDescriptor() ([]byte, []int) { return file_petstore_proto_rawDescGZIP(), []int{0} } // Types of OTEL sampling we support. type SamplerType int32 const ( SamplerType_STUnknown SamplerType = 0 SamplerType_STNever SamplerType = 1 SamplerType_STAlways SamplerType = 2 SamplerType_STFloat SamplerType = 3 ) // Enum value maps for SamplerType. var ( SamplerType_name = map[int32]string{ 0: "STUnknown", 1: "STNever", 2: "STAlways", 3: "STFloat", } SamplerType_value = map[string]int32{ "STUnknown": 0, "STNever": 1, "STAlways": 2, "STFloat": 3, } ) func (x SamplerType) Enum() *SamplerType { p := new(SamplerType) *p = x return p } func (x SamplerType) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } func (SamplerType) Descriptor() protoreflect.EnumDescriptor { return file_petstore_proto_enumTypes[1].Descriptor() } func (SamplerType) Type() protoreflect.EnumType { return &file_petstore_proto_enumTypes[1] } func (x SamplerType) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } // Deprecated: Use SamplerType.Descriptor instead. func (SamplerType) EnumDescriptor() ([]byte, []int) { return file_petstore_proto_rawDescGZIP(), []int{1} } // Represents a range of dates. type DateRange struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // When to start the range, this is inclusive. Start *date.Date `protobuf:"bytes,1,opt,name=start,proto3" json:"start,omitempty"` // When to end the range, this is exclusive. End *date.Date `protobuf:"bytes,2,opt,name=end,proto3" json:"end,omitempty"` } func (x *DateRange) Reset() { *x = DateRange{} if protoimpl.UnsafeEnabled { mi := &file_petstore_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *DateRange) String() string { return protoimpl.X.MessageStringOf(x) } func (*DateRange) ProtoMessage() {} func (x *DateRange) ProtoReflect() protoreflect.Message { mi := &file_petstore_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 DateRange.ProtoReflect.Descriptor instead. func (*DateRange) Descriptor() ([]byte, []int) { return file_petstore_proto_rawDescGZIP(), []int{0} } func (x *DateRange) GetStart() *date.Date { if x != nil { return x.Start } return nil } func (x *DateRange) GetEnd() *date.Date { if x != nil { return x.End } return nil } // Represents a unique pet. type Pet struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // A UUIDv4 for this pet. This can never be set on an AddPet(). Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` // The name of the pet. Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` // The type of pet. Type PetType `protobuf:"varint,3,opt,name=type,proto3,enum=petstore.PetType" json:"type,omitempty"` // The pet's birthday. Birthday *date.Date `protobuf:"bytes,4,opt,name=birthday,proto3" json:"birthday,omitempty"` } func (x *Pet) Reset() { *x = Pet{} if protoimpl.UnsafeEnabled { mi := &file_petstore_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Pet) String() string { return protoimpl.X.MessageStringOf(x) } func (*Pet) ProtoMessage() {} func (x *Pet) ProtoReflect() protoreflect.Message { mi := &file_petstore_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 Pet.ProtoReflect.Descriptor instead. func (*Pet) Descriptor() ([]byte, []int) { return file_petstore_proto_rawDescGZIP(), []int{1} } func (x *Pet) GetId() string { if x != nil { return x.Id } return "" } func (x *Pet) GetName() string { if x != nil { return x.Name } return "" } func (x *Pet) GetType() PetType { if x != nil { return x.Type } return PetType_PTUnknown } func (x *Pet) GetBirthday() *date.Date { if x != nil { return x.Birthday } return nil } // The request used to add a pets to the system. type AddPetsReq struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // The pet information to add. Pet.id must not be set. Pets []*Pet `protobuf:"bytes,1,rep,name=pets,proto3" json:"pets,omitempty"` } func (x *AddPetsReq) Reset() { *x = AddPetsReq{} if protoimpl.UnsafeEnabled { mi := &file_petstore_proto_msgTypes[2] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *AddPetsReq) String() string { return protoimpl.X.MessageStringOf(x) } func (*AddPetsReq) ProtoMessage() {} func (x *AddPetsReq) ProtoReflect() protoreflect.Message { mi := &file_petstore_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 AddPetsReq.ProtoReflect.Descriptor instead. func (*AddPetsReq) Descriptor() ([]byte, []int) { return file_petstore_proto_rawDescGZIP(), []int{2} } func (x *AddPetsReq) GetPets() []*Pet { if x != nil { return x.Pets } return nil } // The response do AddPets(). type AddPetsResp struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // The IDs of the pets that were added. Ids []string `protobuf:"bytes,1,rep,name=ids,proto3" json:"ids,omitempty"` } func (x *AddPetsResp) Reset() { *x = AddPetsResp{} if protoimpl.UnsafeEnabled { mi := &file_petstore_proto_msgTypes[3] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *AddPetsResp) String() string { return protoimpl.X.MessageStringOf(x) } func (*AddPetsResp) ProtoMessage() {} func (x *AddPetsResp) ProtoReflect() protoreflect.Message { mi := &file_petstore_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 AddPetsResp.ProtoReflect.Descriptor instead. func (*AddPetsResp) Descriptor() ([]byte, []int) { return file_petstore_proto_rawDescGZIP(), []int{3} } func (x *AddPetsResp) GetIds() []string { if x != nil { return x.Ids } return nil } // The request used to update pets in the system. type UpdatePetsReq struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // The pet information to update. Pet.id must be set. Pets []*Pet `protobuf:"bytes,1,rep,name=pets,proto3" json:"pets,omitempty"` } func (x *UpdatePetsReq) Reset() { *x = UpdatePetsReq{} if protoimpl.UnsafeEnabled { mi := &file_petstore_proto_msgTypes[4] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *UpdatePetsReq) String() string { return protoimpl.X.MessageStringOf(x) } func (*UpdatePetsReq) ProtoMessage() {} func (x *UpdatePetsReq) ProtoReflect() protoreflect.Message { mi := &file_petstore_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 UpdatePetsReq.ProtoReflect.Descriptor instead. func (*UpdatePetsReq) Descriptor() ([]byte, []int) { return file_petstore_proto_rawDescGZIP(), []int{4} } func (x *UpdatePetsReq) GetPets() []*Pet { if x != nil { return x.Pets } return nil } // The response do UpdatePets(). type UpdatePetsResp struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields } func (x *UpdatePetsResp) Reset() { *x = UpdatePetsResp{} if protoimpl.UnsafeEnabled { mi := &file_petstore_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *UpdatePetsResp) String() string { return protoimpl.X.MessageStringOf(x) } func (*UpdatePetsResp) ProtoMessage() {} func (x *UpdatePetsResp) ProtoReflect() protoreflect.Message { mi := &file_petstore_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 UpdatePetsResp.ProtoReflect.Descriptor instead. func (*UpdatePetsResp) Descriptor() ([]byte, []int) { return file_petstore_proto_rawDescGZIP(), []int{5} } // Used to indicate which pets to delete. This is an all or nothing request. type DeletePetsReq struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // The IDs of the pets to delete. Ids []string `protobuf:"bytes,1,rep,name=ids,proto3" json:"ids,omitempty"` } func (x *DeletePetsReq) Reset() { *x = DeletePetsReq{} if protoimpl.UnsafeEnabled { mi := &file_petstore_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *DeletePetsReq) String() string { return protoimpl.X.MessageStringOf(x) } func (*DeletePetsReq) ProtoMessage() {} func (x *DeletePetsReq) ProtoReflect() protoreflect.Message { mi := &file_petstore_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 DeletePetsReq.ProtoReflect.Descriptor instead. func (*DeletePetsReq) Descriptor() ([]byte, []int) { return file_petstore_proto_rawDescGZIP(), []int{6} } func (x *DeletePetsReq) GetIds() []string { if x != nil { return x.Ids } return nil } // The response to a DeletePet(). type DeletePetsResp struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields } func (x *DeletePetsResp) Reset() { *x = DeletePetsResp{} if protoimpl.UnsafeEnabled { mi := &file_petstore_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *DeletePetsResp) String() string { return protoimpl.X.MessageStringOf(x) } func (*DeletePetsResp) ProtoMessage() {} func (x *DeletePetsResp) ProtoReflect() protoreflect.Message { mi := &file_petstore_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 DeletePetsResp.ProtoReflect.Descriptor instead. func (*DeletePetsResp) Descriptor() ([]byte, []int) { return file_petstore_proto_rawDescGZIP(), []int{7} } // The request to search for pets. type SearchPetsReq struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // Pet names to filter by. Names []string `protobuf:"bytes,1,rep,name=names,proto3" json:"names,omitempty"` // Pet types to filter by. Types []PetType `protobuf:"varint,2,rep,packed,name=types,proto3,enum=petstore.PetType" json:"types,omitempty"` // Birthdays to filter by. BirthdateRange *DateRange `protobuf:"bytes,3,opt,name=birthdate_range,json=birthdateRange,proto3" json:"birthdate_range,omitempty"` } func (x *SearchPetsReq) Reset() { *x = SearchPetsReq{} if protoimpl.UnsafeEnabled { mi := &file_petstore_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *SearchPetsReq) String() string { return protoimpl.X.MessageStringOf(x) } func (*SearchPetsReq) ProtoMessage() {} func (x *SearchPetsReq) ProtoReflect() protoreflect.Message { mi := &file_petstore_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 SearchPetsReq.ProtoReflect.Descriptor instead. func (*SearchPetsReq) Descriptor() ([]byte, []int) { return file_petstore_proto_rawDescGZIP(), []int{8} } func (x *SearchPetsReq) GetNames() []string { if x != nil { return x.Names } return nil } func (x *SearchPetsReq) GetTypes() []PetType { if x != nil { return x.Types } return nil } func (x *SearchPetsReq) GetBirthdateRange() *DateRange { if x != nil { return x.BirthdateRange } return nil } type Sampler struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // The type of sampling to change to. Type SamplerType `protobuf:"varint,1,opt,name=type,proto3,enum=petstore.SamplerType" json:"type,omitempty"` // This is the sampling rate if type == STFloat. Values must be // > 0 and <= 1.0 . FloatValue float64 `protobuf:"fixed64,2,opt,name=float_value,json=floatValue,proto3" json:"float_value,omitempty"` } func (x *Sampler) Reset() { *x = Sampler{} if protoimpl.UnsafeEnabled { mi := &file_petstore_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Sampler) String() string { return protoimpl.X.MessageStringOf(x) } func (*Sampler) ProtoMessage() {} func (x *Sampler) ProtoReflect() protoreflect.Message { mi := &file_petstore_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 Sampler.ProtoReflect.Descriptor instead. func (*Sampler) Descriptor() ([]byte, []int) { return file_petstore_proto_rawDescGZIP(), []int{9} } func (x *Sampler) GetType() SamplerType { if x != nil { return x.Type } return SamplerType_STUnknown } func (x *Sampler) GetFloatValue() float64 { if x != nil { return x.FloatValue } return 0 } // Used to request we change the OTEL sampling. type ChangeSamplerReq struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Sampler *Sampler `protobuf:"bytes,1,opt,name=sampler,proto3" json:"sampler,omitempty"` } func (x *ChangeSamplerReq) Reset() { *x = ChangeSamplerReq{} if protoimpl.UnsafeEnabled { mi := &file_petstore_proto_msgTypes[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *ChangeSamplerReq) String() string { return protoimpl.X.MessageStringOf(x) } func (*ChangeSamplerReq) ProtoMessage() {} func (x *ChangeSamplerReq) ProtoReflect() protoreflect.Message { mi := &file_petstore_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 ChangeSamplerReq.ProtoReflect.Descriptor instead. func (*ChangeSamplerReq) Descriptor() ([]byte, []int) { return file_petstore_proto_rawDescGZIP(), []int{10} } func (x *ChangeSamplerReq) GetSampler() *Sampler { if x != nil { return x.Sampler } return nil } // The response to a sampling change. type ChangeSamplerResp struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields } func (x *ChangeSamplerResp) Reset() { *x = ChangeSamplerResp{} if protoimpl.UnsafeEnabled { mi := &file_petstore_proto_msgTypes[11] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *ChangeSamplerResp) String() string { return protoimpl.X.MessageStringOf(x) } func (*ChangeSamplerResp) ProtoMessage() {} func (x *ChangeSamplerResp) ProtoReflect() protoreflect.Message { mi := &file_petstore_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 ChangeSamplerResp.ProtoReflect.Descriptor instead. func (*ChangeSamplerResp) Descriptor() ([]byte, []int) { return file_petstore_proto_rawDescGZIP(), []int{11} } var File_petstore_proto protoreflect.FileDescriptor var file_petstore_proto_rawDesc = []byte{ 0x0a, 0x0e, 0x70, 0x65, 0x74, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x08, 0x70, 0x65, 0x74, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x1a, 0x16, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x74, 0x79, 0x70, 0x65, 0x2f, 0x64, 0x61, 0x74, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x59, 0x0a, 0x09, 0x44, 0x61, 0x74, 0x65, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x27, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x2e, 0x44, 0x61, 0x74, 0x65, 0x52, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x12, 0x23, 0x0a, 0x03, 0x65, 0x6e, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x2e, 0x44, 0x61, 0x74, 0x65, 0x52, 0x03, 0x65, 0x6e, 0x64, 0x22, 0x7f, 0x0a, 0x03, 0x50, 0x65, 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, 0x25, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x11, 0x2e, 0x70, 0x65, 0x74, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x50, 0x65, 0x74, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x2d, 0x0a, 0x08, 0x62, 0x69, 0x72, 0x74, 0x68, 0x64, 0x61, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x2e, 0x44, 0x61, 0x74, 0x65, 0x52, 0x08, 0x62, 0x69, 0x72, 0x74, 0x68, 0x64, 0x61, 0x79, 0x22, 0x2f, 0x0a, 0x0a, 0x41, 0x64, 0x64, 0x50, 0x65, 0x74, 0x73, 0x52, 0x65, 0x71, 0x12, 0x21, 0x0a, 0x04, 0x70, 0x65, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x70, 0x65, 0x74, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x50, 0x65, 0x74, 0x52, 0x04, 0x70, 0x65, 0x74, 0x73, 0x22, 0x1f, 0x0a, 0x0b, 0x41, 0x64, 0x64, 0x50, 0x65, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x12, 0x10, 0x0a, 0x03, 0x69, 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x03, 0x69, 0x64, 0x73, 0x22, 0x32, 0x0a, 0x0d, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x50, 0x65, 0x74, 0x73, 0x52, 0x65, 0x71, 0x12, 0x21, 0x0a, 0x04, 0x70, 0x65, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x70, 0x65, 0x74, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x50, 0x65, 0x74, 0x52, 0x04, 0x70, 0x65, 0x74, 0x73, 0x22, 0x10, 0x0a, 0x0e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x50, 0x65, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x22, 0x21, 0x0a, 0x0d, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x65, 0x74, 0x73, 0x52, 0x65, 0x71, 0x12, 0x10, 0x0a, 0x03, 0x69, 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x03, 0x69, 0x64, 0x73, 0x22, 0x10, 0x0a, 0x0e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x65, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x22, 0x8c, 0x01, 0x0a, 0x0d, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x50, 0x65, 0x74, 0x73, 0x52, 0x65, 0x71, 0x12, 0x14, 0x0a, 0x05, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x05, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x12, 0x27, 0x0a, 0x05, 0x74, 0x79, 0x70, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0e, 0x32, 0x11, 0x2e, 0x70, 0x65, 0x74, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x50, 0x65, 0x74, 0x54, 0x79, 0x70, 0x65, 0x52, 0x05, 0x74, 0x79, 0x70, 0x65, 0x73, 0x12, 0x3c, 0x0a, 0x0f, 0x62, 0x69, 0x72, 0x74, 0x68, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x65, 0x74, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x44, 0x61, 0x74, 0x65, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x0e, 0x62, 0x69, 0x72, 0x74, 0x68, 0x64, 0x61, 0x74, 0x65, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x22, 0x55, 0x0a, 0x07, 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x72, 0x12, 0x29, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x15, 0x2e, 0x70, 0x65, 0x74, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x72, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x01, 0x52, 0x0a, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x3f, 0x0a, 0x10, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x72, 0x52, 0x65, 0x71, 0x12, 0x2b, 0x0a, 0x07, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x70, 0x65, 0x74, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x72, 0x52, 0x07, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x72, 0x22, 0x13, 0x0a, 0x11, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x2a, 0x4f, 0x0a, 0x07, 0x50, 0x65, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0d, 0x0a, 0x09, 0x50, 0x54, 0x55, 0x6e, 0x6b, 0x6e, 0x6f, 0x77, 0x6e, 0x10, 0x00, 0x12, 0x0c, 0x0a, 0x08, 0x50, 0x54, 0x43, 0x61, 0x6e, 0x69, 0x6e, 0x65, 0x10, 0x01, 0x12, 0x0c, 0x0a, 0x08, 0x50, 0x54, 0x46, 0x65, 0x6c, 0x69, 0x6e, 0x65, 0x10, 0x02, 0x12, 0x0a, 0x0a, 0x06, 0x50, 0x54, 0x42, 0x69, 0x72, 0x64, 0x10, 0x03, 0x12, 0x0d, 0x0a, 0x09, 0x50, 0x54, 0x52, 0x65, 0x70, 0x74, 0x69, 0x6c, 0x65, 0x10, 0x04, 0x2a, 0x44, 0x0a, 0x0b, 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x72, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0d, 0x0a, 0x09, 0x53, 0x54, 0x55, 0x6e, 0x6b, 0x6e, 0x6f, 0x77, 0x6e, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x53, 0x54, 0x4e, 0x65, 0x76, 0x65, 0x72, 0x10, 0x01, 0x12, 0x0c, 0x0a, 0x08, 0x53, 0x54, 0x41, 0x6c, 0x77, 0x61, 0x79, 0x73, 0x10, 0x02, 0x12, 0x0b, 0x0a, 0x07, 0x53, 0x54, 0x46, 0x6c, 0x6f, 0x61, 0x74, 0x10, 0x03, 0x32, 0xd0, 0x02, 0x0a, 0x08, 0x50, 0x65, 0x74, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x12, 0x38, 0x0a, 0x07, 0x41, 0x64, 0x64, 0x50, 0x65, 0x74, 0x73, 0x12, 0x14, 0x2e, 0x70, 0x65, 0x74, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x41, 0x64, 0x64, 0x50, 0x65, 0x74, 0x73, 0x52, 0x65, 0x71, 0x1a, 0x15, 0x2e, 0x70, 0x65, 0x74, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x41, 0x64, 0x64, 0x50, 0x65, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x12, 0x41, 0x0a, 0x0a, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x50, 0x65, 0x74, 0x73, 0x12, 0x17, 0x2e, 0x70, 0x65, 0x74, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x50, 0x65, 0x74, 0x73, 0x52, 0x65, 0x71, 0x1a, 0x18, 0x2e, 0x70, 0x65, 0x74, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x50, 0x65, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x12, 0x41, 0x0a, 0x0a, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x65, 0x74, 0x73, 0x12, 0x17, 0x2e, 0x70, 0x65, 0x74, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x65, 0x74, 0x73, 0x52, 0x65, 0x71, 0x1a, 0x18, 0x2e, 0x70, 0x65, 0x74, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x65, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x12, 0x38, 0x0a, 0x0a, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x50, 0x65, 0x74, 0x73, 0x12, 0x17, 0x2e, 0x70, 0x65, 0x74, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x50, 0x65, 0x74, 0x73, 0x52, 0x65, 0x71, 0x1a, 0x0d, 0x2e, 0x70, 0x65, 0x74, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x50, 0x65, 0x74, 0x22, 0x00, 0x30, 0x01, 0x12, 0x4a, 0x0a, 0x0d, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x72, 0x12, 0x1a, 0x2e, 0x70, 0x65, 0x74, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x72, 0x52, 0x65, 0x71, 0x1a, 0x1b, 0x2e, 0x70, 0x65, 0x74, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x42, 0x30, 0x5a, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x50, 0x61, 0x63, 0x6b, 0x74, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x69, 0x6e, 0x67, 0x2f, 0x47, 0x6f, 0x2d, 0x66, 0x6f, 0x72, 0x2d, 0x44, 0x65, 0x76, 0x4f, 0x70, 0x73, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( file_petstore_proto_rawDescOnce sync.Once file_petstore_proto_rawDescData = file_petstore_proto_rawDesc ) func file_petstore_proto_rawDescGZIP() []byte { file_petstore_proto_rawDescOnce.Do(func() { file_petstore_proto_rawDescData = protoimpl.X.CompressGZIP(file_petstore_proto_rawDescData) }) return file_petstore_proto_rawDescData } var file_petstore_proto_enumTypes = make([]protoimpl.EnumInfo, 2) var file_petstore_proto_msgTypes = make([]protoimpl.MessageInfo, 12) var file_petstore_proto_goTypes = []interface{}{ (PetType)(0), // 0: petstore.PetType (SamplerType)(0), // 1: petstore.SamplerType (*DateRange)(nil), // 2: petstore.DateRange (*Pet)(nil), // 3: petstore.Pet (*AddPetsReq)(nil), // 4: petstore.AddPetsReq (*AddPetsResp)(nil), // 5: petstore.AddPetsResp (*UpdatePetsReq)(nil), // 6: petstore.UpdatePetsReq (*UpdatePetsResp)(nil), // 7: petstore.UpdatePetsResp (*DeletePetsReq)(nil), // 8: petstore.DeletePetsReq (*DeletePetsResp)(nil), // 9: petstore.DeletePetsResp (*SearchPetsReq)(nil), // 10: petstore.SearchPetsReq (*Sampler)(nil), // 11: petstore.Sampler (*ChangeSamplerReq)(nil), // 12: petstore.ChangeSamplerReq (*ChangeSamplerResp)(nil), // 13: petstore.ChangeSamplerResp (*date.Date)(nil), // 14: google.type.Date } var file_petstore_proto_depIdxs = []int32{ 14, // 0: petstore.DateRange.start:type_name -> google.type.Date 14, // 1: petstore.DateRange.end:type_name -> google.type.Date 0, // 2: petstore.Pet.type:type_name -> petstore.PetType 14, // 3: petstore.Pet.birthday:type_name -> google.type.Date 3, // 4: petstore.AddPetsReq.pets:type_name -> petstore.Pet 3, // 5: petstore.UpdatePetsReq.pets:type_name -> petstore.Pet 0, // 6: petstore.SearchPetsReq.types:type_name -> petstore.PetType 2, // 7: petstore.SearchPetsReq.birthdate_range:type_name -> petstore.DateRange 1, // 8: petstore.Sampler.type:type_name -> petstore.SamplerType 11, // 9: petstore.ChangeSamplerReq.sampler:type_name -> petstore.Sampler 4, // 10: petstore.PetStore.AddPets:input_type -> petstore.AddPetsReq 6, // 11: petstore.PetStore.UpdatePets:input_type -> petstore.UpdatePetsReq 8, // 12: petstore.PetStore.DeletePets:input_type -> petstore.DeletePetsReq 10, // 13: petstore.PetStore.SearchPets:input_type -> petstore.SearchPetsReq 12, // 14: petstore.PetStore.ChangeSampler:input_type -> petstore.ChangeSamplerReq 5, // 15: petstore.PetStore.AddPets:output_type -> petstore.AddPetsResp 7, // 16: petstore.PetStore.UpdatePets:output_type -> petstore.UpdatePetsResp 9, // 17: petstore.PetStore.DeletePets:output_type -> petstore.DeletePetsResp 3, // 18: petstore.PetStore.SearchPets:output_type -> petstore.Pet 13, // 19: petstore.PetStore.ChangeSampler:output_type -> petstore.ChangeSamplerResp 15, // [15:20] is the sub-list for method output_type 10, // [10:15] is the sub-list for method input_type 10, // [10:10] is the sub-list for extension type_name 10, // [10:10] is the sub-list for extension extendee 0, // [0:10] is the sub-list for field type_name } func init() { file_petstore_proto_init() } func file_petstore_proto_init() { if File_petstore_proto != nil { return } if !protoimpl.UnsafeEnabled { file_petstore_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*DateRange); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_petstore_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Pet); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_petstore_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*AddPetsReq); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_petstore_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*AddPetsResp); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_petstore_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*UpdatePetsReq); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_petstore_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*UpdatePetsResp); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_petstore_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*DeletePetsReq); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_petstore_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*DeletePetsResp); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_petstore_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*SearchPetsReq); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_petstore_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Sampler); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_petstore_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*ChangeSamplerReq); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_petstore_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*ChangeSamplerResp); 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_petstore_proto_rawDesc, NumEnums: 2, NumMessages: 12, NumExtensions: 0, NumServices: 1, }, GoTypes: file_petstore_proto_goTypes, DependencyIndexes: file_petstore_proto_depIdxs, EnumInfos: file_petstore_proto_enumTypes, MessageInfos: file_petstore_proto_msgTypes, }.Build() File_petstore_proto = out.File file_petstore_proto_rawDesc = nil file_petstore_proto_goTypes = nil file_petstore_proto_depIdxs = nil } ================================================ FILE: chapter/14/petstore-operator/client/proto/petstore.proto ================================================ syntax = "proto3"; package petstore; option go_package = "github.com/PacktPublishing/Go-for-DevOps/proto"; import "google/type/date.proto"; // Desribes the type of pets. enum PetType { // The type was not set. PTUnknown = 0; // The pet is a canine. PTCanine = 1; // The pet is a feline. PTFeline = 2; // The pet is a bird. PTBird = 3; // The pet is a reptile. PTReptile = 4; } // Represents a range of dates. message DateRange { // When to start the range, this is inclusive. google.type.Date start = 1; // When to end the range, this is exclusive. google.type.Date end = 2; } // Represents a unique pet. message Pet { // A UUIDv4 for this pet. This can never be set on an AddPet(). string id = 1; // The name of the pet. string name = 2; // The type of pet. PetType type = 3; // The pet's birthday. google.type.Date birthday = 4; } // The request used to add a pets to the system. message AddPetsReq { // The pet information to add. Pet.id must not be set. repeated Pet pets = 1; } // The response do AddPets(). message AddPetsResp { // The IDs of the pets that were added. repeated string ids = 1; } // The request used to update pets in the system. message UpdatePetsReq { // The pet information to update. Pet.id must be set. repeated Pet pets = 1; } // The response do UpdatePets(). message UpdatePetsResp {} // Used to indicate which pets to delete. This is an all or nothing request. message DeletePetsReq { // The IDs of the pets to delete. repeated string ids = 1; } // The response to a DeletePet(). message DeletePetsResp{} // The request to search for pets. message SearchPetsReq { // Pet names to filter by. repeated string names = 1; // Pet types to filter by. repeated PetType types = 2; // Birthdays to filter by. DateRange birthdate_range = 3; } // Types of OTEL sampling we support. enum SamplerType { STUnknown = 0; STNever = 1; STAlways = 2; STFloat = 3; } message Sampler { // The type of sampling to change to. SamplerType type = 1; // This is the sampling rate if type == STFloat. Values must be // > 0 and <= 1.0 . double float_value = 2; } // Used to request we change the OTEL sampling. message ChangeSamplerReq { Sampler sampler = 1; } // The response to a sampling change. message ChangeSamplerResp{} service PetStore { // Adds pets to the pet store. rpc AddPets(AddPetsReq) returns (AddPetsResp) {}; // Updates pets entries in the store. rpc UpdatePets(UpdatePetsReq) returns (UpdatePetsResp) {}; // Deletes pets from the pet store. rpc DeletePets(DeletePetsReq) returns (DeletePetsResp) {}; // Finds pets in the pet store. rpc SearchPets(SearchPetsReq) returns (stream Pet) {}; // These are for management. In real life I might break this into a new server that is // serving on SSH so that it has to be auth'd and reachable only from in my network // or only if auth'd by something. // Changes the OTEL sampling type. rpc ChangeSampler(ChangeSamplerReq) returns (ChangeSamplerResp) {}; } ================================================ FILE: chapter/14/petstore-operator/client/proto/petstore_grpc.pb.go ================================================ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. package proto 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.32.0 or later. const _ = grpc.SupportPackageIsVersion7 // PetStoreClient is the client API for PetStore 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 PetStoreClient interface { // Adds pets to the pet store. AddPets(ctx context.Context, in *AddPetsReq, opts ...grpc.CallOption) (*AddPetsResp, error) // Updates pets entries in the store. UpdatePets(ctx context.Context, in *UpdatePetsReq, opts ...grpc.CallOption) (*UpdatePetsResp, error) // Deletes pets from the pet store. DeletePets(ctx context.Context, in *DeletePetsReq, opts ...grpc.CallOption) (*DeletePetsResp, error) // Finds pets in the pet store. SearchPets(ctx context.Context, in *SearchPetsReq, opts ...grpc.CallOption) (PetStore_SearchPetsClient, error) // Changes the OTEL sampling type. ChangeSampler(ctx context.Context, in *ChangeSamplerReq, opts ...grpc.CallOption) (*ChangeSamplerResp, error) } type petStoreClient struct { cc grpc.ClientConnInterface } func NewPetStoreClient(cc grpc.ClientConnInterface) PetStoreClient { return &petStoreClient{cc} } func (c *petStoreClient) AddPets(ctx context.Context, in *AddPetsReq, opts ...grpc.CallOption) (*AddPetsResp, error) { out := new(AddPetsResp) err := c.cc.Invoke(ctx, "/petstore.PetStore/AddPets", in, out, opts...) if err != nil { return nil, err } return out, nil } func (c *petStoreClient) UpdatePets(ctx context.Context, in *UpdatePetsReq, opts ...grpc.CallOption) (*UpdatePetsResp, error) { out := new(UpdatePetsResp) err := c.cc.Invoke(ctx, "/petstore.PetStore/UpdatePets", in, out, opts...) if err != nil { return nil, err } return out, nil } func (c *petStoreClient) DeletePets(ctx context.Context, in *DeletePetsReq, opts ...grpc.CallOption) (*DeletePetsResp, error) { out := new(DeletePetsResp) err := c.cc.Invoke(ctx, "/petstore.PetStore/DeletePets", in, out, opts...) if err != nil { return nil, err } return out, nil } func (c *petStoreClient) SearchPets(ctx context.Context, in *SearchPetsReq, opts ...grpc.CallOption) (PetStore_SearchPetsClient, error) { stream, err := c.cc.NewStream(ctx, &PetStore_ServiceDesc.Streams[0], "/petstore.PetStore/SearchPets", opts...) if err != nil { return nil, err } x := &petStoreSearchPetsClient{stream} if err := x.ClientStream.SendMsg(in); err != nil { return nil, err } if err := x.ClientStream.CloseSend(); err != nil { return nil, err } return x, nil } type PetStore_SearchPetsClient interface { Recv() (*Pet, error) grpc.ClientStream } type petStoreSearchPetsClient struct { grpc.ClientStream } func (x *petStoreSearchPetsClient) Recv() (*Pet, error) { m := new(Pet) if err := x.ClientStream.RecvMsg(m); err != nil { return nil, err } return m, nil } func (c *petStoreClient) ChangeSampler(ctx context.Context, in *ChangeSamplerReq, opts ...grpc.CallOption) (*ChangeSamplerResp, error) { out := new(ChangeSamplerResp) err := c.cc.Invoke(ctx, "/petstore.PetStore/ChangeSampler", in, out, opts...) if err != nil { return nil, err } return out, nil } // PetStoreServer is the server API for PetStore service. // All implementations must embed UnimplementedPetStoreServer // for forward compatibility type PetStoreServer interface { // Adds pets to the pet store. AddPets(context.Context, *AddPetsReq) (*AddPetsResp, error) // Updates pets entries in the store. UpdatePets(context.Context, *UpdatePetsReq) (*UpdatePetsResp, error) // Deletes pets from the pet store. DeletePets(context.Context, *DeletePetsReq) (*DeletePetsResp, error) // Finds pets in the pet store. SearchPets(*SearchPetsReq, PetStore_SearchPetsServer) error // Changes the OTEL sampling type. ChangeSampler(context.Context, *ChangeSamplerReq) (*ChangeSamplerResp, error) mustEmbedUnimplementedPetStoreServer() } // UnimplementedPetStoreServer must be embedded to have forward compatible implementations. type UnimplementedPetStoreServer struct { } func (UnimplementedPetStoreServer) AddPets(context.Context, *AddPetsReq) (*AddPetsResp, error) { return nil, status.Errorf(codes.Unimplemented, "method AddPets not implemented") } func (UnimplementedPetStoreServer) UpdatePets(context.Context, *UpdatePetsReq) (*UpdatePetsResp, error) { return nil, status.Errorf(codes.Unimplemented, "method UpdatePets not implemented") } func (UnimplementedPetStoreServer) DeletePets(context.Context, *DeletePetsReq) (*DeletePetsResp, error) { return nil, status.Errorf(codes.Unimplemented, "method DeletePets not implemented") } func (UnimplementedPetStoreServer) SearchPets(*SearchPetsReq, PetStore_SearchPetsServer) error { return status.Errorf(codes.Unimplemented, "method SearchPets not implemented") } func (UnimplementedPetStoreServer) ChangeSampler(context.Context, *ChangeSamplerReq) (*ChangeSamplerResp, error) { return nil, status.Errorf(codes.Unimplemented, "method ChangeSampler not implemented") } func (UnimplementedPetStoreServer) mustEmbedUnimplementedPetStoreServer() {} // UnsafePetStoreServer may be embedded to opt out of forward compatibility for this service. // Use of this interface is not recommended, as added methods to PetStoreServer will // result in compilation errors. type UnsafePetStoreServer interface { mustEmbedUnimplementedPetStoreServer() } func RegisterPetStoreServer(s grpc.ServiceRegistrar, srv PetStoreServer) { s.RegisterService(&PetStore_ServiceDesc, srv) } func _PetStore_AddPets_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(AddPetsReq) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(PetStoreServer).AddPets(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: "/petstore.PetStore/AddPets", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(PetStoreServer).AddPets(ctx, req.(*AddPetsReq)) } return interceptor(ctx, in, info, handler) } func _PetStore_UpdatePets_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(UpdatePetsReq) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(PetStoreServer).UpdatePets(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: "/petstore.PetStore/UpdatePets", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(PetStoreServer).UpdatePets(ctx, req.(*UpdatePetsReq)) } return interceptor(ctx, in, info, handler) } func _PetStore_DeletePets_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(DeletePetsReq) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(PetStoreServer).DeletePets(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: "/petstore.PetStore/DeletePets", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(PetStoreServer).DeletePets(ctx, req.(*DeletePetsReq)) } return interceptor(ctx, in, info, handler) } func _PetStore_SearchPets_Handler(srv interface{}, stream grpc.ServerStream) error { m := new(SearchPetsReq) if err := stream.RecvMsg(m); err != nil { return err } return srv.(PetStoreServer).SearchPets(m, &petStoreSearchPetsServer{stream}) } type PetStore_SearchPetsServer interface { Send(*Pet) error grpc.ServerStream } type petStoreSearchPetsServer struct { grpc.ServerStream } func (x *petStoreSearchPetsServer) Send(m *Pet) error { return x.ServerStream.SendMsg(m) } func _PetStore_ChangeSampler_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(ChangeSamplerReq) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(PetStoreServer).ChangeSampler(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: "/petstore.PetStore/ChangeSampler", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(PetStoreServer).ChangeSampler(ctx, req.(*ChangeSamplerReq)) } return interceptor(ctx, in, info, handler) } // PetStore_ServiceDesc is the grpc.ServiceDesc for PetStore service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) var PetStore_ServiceDesc = grpc.ServiceDesc{ ServiceName: "petstore.PetStore", HandlerType: (*PetStoreServer)(nil), Methods: []grpc.MethodDesc{ { MethodName: "AddPets", Handler: _PetStore_AddPets_Handler, }, { MethodName: "UpdatePets", Handler: _PetStore_UpdatePets_Handler, }, { MethodName: "DeletePets", Handler: _PetStore_DeletePets_Handler, }, { MethodName: "ChangeSampler", Handler: _PetStore_ChangeSampler_Handler, }, }, Streams: []grpc.StreamDesc{ { StreamName: "SearchPets", Handler: _PetStore_SearchPets_Handler, ServerStreams: true, }, }, Metadata: "petstore.proto", } ================================================ FILE: chapter/14/petstore-operator/config/crd/bases/petstore.example.com_pets.yaml ================================================ --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: controller-gen.kubebuilder.io/version: v0.8.0 creationTimestamp: null name: pets.petstore.example.com spec: group: petstore.example.com names: kind: Pet listKind: PetList plural: pets singular: pet scope: Namespaced versions: - name: v1alpha1 schema: openAPIV3Schema: description: Pet is the Schema for the pets API properties: apiVersion: description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' type: string kind: description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' type: string metadata: type: object spec: description: PetSpec defines the desired state of Pet properties: birthday: description: Birthday is the date the pet was born format: date-time type: string name: description: Name is the name of the pet type: string type: description: Type is the type of pet enum: - dog - cat - bird - reptile type: string required: - birthday - name - type type: object status: description: PetStatus defines the observed state of Pet properties: id: description: ID is the unique identifier created by the service for the pet type: string type: object type: object served: true storage: true subresources: status: {} status: acceptedNames: kind: "" plural: "" conditions: [] storedVersions: [] ================================================ FILE: chapter/14/petstore-operator/config/crd/kustomization.yaml ================================================ # This kustomization.yaml is not intended to be run by itself, # since it depends on service name and namespace that are out of this kustomize package. # It should be run by config/default resources: - bases/petstore.example.com_pets.yaml #+kubebuilder:scaffold:crdkustomizeresource patchesStrategicMerge: # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix. # patches here are for enabling the conversion webhook for each CRD #- patches/webhook_in_pets.yaml #+kubebuilder:scaffold:crdkustomizewebhookpatch # [CERTMANAGER] To enable cert-manager, uncomment all the sections with [CERTMANAGER] prefix. # patches here are for enabling the CA injection for each CRD #- patches/cainjection_in_pets.yaml #+kubebuilder:scaffold:crdkustomizecainjectionpatch # the following config is for teaching kustomize how to do kustomization for CRDs. configurations: - kustomizeconfig.yaml ================================================ FILE: chapter/14/petstore-operator/config/crd/kustomizeconfig.yaml ================================================ # This file is for teaching kustomize how to substitute name and namespace reference in CRD nameReference: - kind: Service version: v1 fieldSpecs: - kind: CustomResourceDefinition version: v1 group: apiextensions.k8s.io path: spec/conversion/webhook/clientConfig/service/name namespace: - kind: CustomResourceDefinition version: v1 group: apiextensions.k8s.io path: spec/conversion/webhook/clientConfig/service/namespace create: false varReference: - path: metadata/annotations ================================================ FILE: chapter/14/petstore-operator/config/crd/patches/cainjection_in_pets.yaml ================================================ # The following patch adds a directive for certmanager to inject CA into the CRD apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) name: pets.petstore.example.com ================================================ FILE: chapter/14/petstore-operator/config/crd/patches/webhook_in_pets.yaml ================================================ # The following patch enables a conversion webhook for the CRD apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: name: pets.petstore.example.com spec: conversion: strategy: Webhook webhook: clientConfig: service: namespace: system name: webhook-service path: /convert conversionReviewVersions: - v1 ================================================ FILE: chapter/14/petstore-operator/config/default/kustomization.yaml ================================================ # Adds namespace to all resources. namespace: petstore-operator-system # Value of this field is prepended to the # names of all resources, e.g. a deployment named # "wordpress" becomes "alices-wordpress". # Note that it should also match with the prefix (text before '-') of the namespace # field above. namePrefix: petstore-operator- # Labels to add to all resources and selectors. #commonLabels: # someName: someValue bases: - ../crd - ../rbac - ../manager # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in # crd/kustomization.yaml #- ../webhook # [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER'. 'WEBHOOK' components are required. #- ../certmanager # [PROMETHEUS] To enable prometheus monitor, uncomment all sections with 'PROMETHEUS'. #- ../prometheus patchesStrategicMerge: # Protect the /metrics endpoint by putting it behind auth. # If you want your controller-manager to expose the /metrics # endpoint w/o any authn/z, please comment the following line. - manager_auth_proxy_patch.yaml # Mount the controller config file for loading manager configurations # through a ComponentConfig type #- manager_config_patch.yaml # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in # crd/kustomization.yaml #- manager_webhook_patch.yaml # [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER'. # Uncomment 'CERTMANAGER' sections in crd/kustomization.yaml to enable the CA injection in the admission webhooks. # 'CERTMANAGER' needs to be enabled to use ca injection #- webhookcainjection_patch.yaml # the following config is for teaching kustomize how to do var substitution vars: # [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER' prefix. #- name: CERTIFICATE_NAMESPACE # namespace of the certificate CR # objref: # kind: Certificate # group: cert-manager.io # version: v1 # name: serving-cert # this name should match the one in certificate.yaml # fieldref: # fieldpath: metadata.namespace #- name: CERTIFICATE_NAME # objref: # kind: Certificate # group: cert-manager.io # version: v1 # name: serving-cert # this name should match the one in certificate.yaml #- name: SERVICE_NAMESPACE # namespace of the service # objref: # kind: Service # version: v1 # name: webhook-service # fieldref: # fieldpath: metadata.namespace #- name: SERVICE_NAME # objref: # kind: Service # version: v1 # name: webhook-service ================================================ FILE: chapter/14/petstore-operator/config/default/manager_auth_proxy_patch.yaml ================================================ # This patch inject a sidecar container which is a HTTP proxy for the # controller manager, it performs RBAC authorization against the Kubernetes API using SubjectAccessReviews. apiVersion: apps/v1 kind: Deployment metadata: name: controller-manager namespace: system spec: template: spec: containers: - name: kube-rbac-proxy image: gcr.io/kubebuilder/kube-rbac-proxy:v0.8.0 args: - "--secure-listen-address=0.0.0.0:8443" - "--upstream=http://127.0.0.1:8080/" - "--logtostderr=true" - "--v=0" ports: - containerPort: 8443 protocol: TCP name: https resources: limits: cpu: 500m memory: 128Mi requests: cpu: 5m memory: 64Mi - name: manager args: - "--health-probe-bind-address=:8081" - "--metrics-bind-address=127.0.0.1:8080" - "--leader-elect" ================================================ FILE: chapter/14/petstore-operator/config/default/manager_config_patch.yaml ================================================ apiVersion: apps/v1 kind: Deployment metadata: name: controller-manager namespace: system spec: template: spec: containers: - name: manager args: - "--config=controller_manager_config.yaml" volumeMounts: - name: manager-config mountPath: /controller_manager_config.yaml subPath: controller_manager_config.yaml volumes: - name: manager-config configMap: name: manager-config ================================================ FILE: chapter/14/petstore-operator/config/manager/controller_manager_config.yaml ================================================ apiVersion: controller-runtime.sigs.k8s.io/v1alpha1 kind: ControllerManagerConfig health: healthProbeBindAddress: :8081 metrics: bindAddress: 127.0.0.1:8080 webhook: port: 9443 leaderElection: leaderElect: true resourceName: 9c3fedb1.example.com ================================================ FILE: chapter/14/petstore-operator/config/manager/kustomization.yaml ================================================ resources: - manager.yaml generatorOptions: disableNameSuffixHash: true configMapGenerator: - name: manager-config files: - controller_manager_config.yaml ================================================ FILE: chapter/14/petstore-operator/config/manager/manager.yaml ================================================ apiVersion: v1 kind: Namespace metadata: labels: control-plane: controller-manager name: system --- apiVersion: apps/v1 kind: Deployment metadata: name: controller-manager namespace: system labels: control-plane: controller-manager spec: selector: matchLabels: control-plane: controller-manager replicas: 1 template: metadata: annotations: kubectl.kubernetes.io/default-container: manager labels: control-plane: controller-manager spec: securityContext: runAsNonRoot: true containers: - command: - /manager args: - --leader-elect image: controller:latest name: manager securityContext: allowPrivilegeEscalation: false livenessProbe: httpGet: path: /healthz port: 8081 initialDelaySeconds: 15 periodSeconds: 20 readinessProbe: httpGet: path: /readyz port: 8081 initialDelaySeconds: 5 periodSeconds: 10 # TODO(user): Configure the resources accordingly based on the project requirements. # More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ resources: limits: cpu: 500m memory: 128Mi requests: cpu: 10m memory: 64Mi serviceAccountName: controller-manager terminationGracePeriodSeconds: 10 ================================================ FILE: chapter/14/petstore-operator/config/manifests/kustomization.yaml ================================================ # These resources constitute the fully configured set of manifests # used to generate the 'manifests/' directory in a bundle. resources: - bases/petstore-operator.clusterserviceversion.yaml - ../default - ../samples - ../scorecard # [WEBHOOK] To enable webhooks, uncomment all the sections with [WEBHOOK] prefix. # Do NOT uncomment sections with prefix [CERTMANAGER], as OLM does not support cert-manager. # These patches remove the unnecessary "cert" volume and its manager container volumeMount. #patchesJson6902: #- target: # group: apps # version: v1 # kind: Deployment # name: controller-manager # namespace: system # patch: |- # # Remove the manager container's "cert" volumeMount, since OLM will create and mount a set of certs. # # Update the indices in this path if adding or removing containers/volumeMounts in the manager's Deployment. # - op: remove # path: /spec/template/spec/containers/1/volumeMounts/0 # # Remove the "cert" volume, since OLM will create and mount a set of certs. # # Update the indices in this path if adding or removing volumes in the manager's Deployment. # - op: remove # path: /spec/template/spec/volumes/0 ================================================ FILE: chapter/14/petstore-operator/config/petstore-service/service.yaml ================================================ --- apiVersion: v1 kind: Namespace metadata: name: petstore --- apiVersion: v1 kind: Service metadata: name: petstore-service namespace: petstore spec: selector: app: petstore-service ports: - name: grpc protocol: TCP port: 6742 targetPort: 6742 --- apiVersion: apps/v1 kind: Deployment metadata: name: petstore-service namespace: petstore labels: app: petstore-service spec: replicas: 1 selector: matchLabels: app: petstore-service template: metadata: labels: app: petstore-service spec: containers: - name: service image: petstore:latest command: [ "/go/bin/petstore" ] args: [ "--localDebug" ] ports: - containerPort: 6742 hostPort: 6742 name: grpc ================================================ FILE: chapter/14/petstore-operator/config/prometheus/kustomization.yaml ================================================ resources: - monitor.yaml ================================================ FILE: chapter/14/petstore-operator/config/prometheus/monitor.yaml ================================================ # Prometheus Monitor Service (Metrics) apiVersion: monitoring.coreos.com/v1 kind: ServiceMonitor metadata: labels: control-plane: controller-manager name: controller-manager-metrics-monitor namespace: system spec: endpoints: - path: /metrics port: https scheme: https bearerTokenFile: /var/run/secrets/kubernetes.io/serviceaccount/token tlsConfig: insecureSkipVerify: true selector: matchLabels: control-plane: controller-manager ================================================ FILE: chapter/14/petstore-operator/config/rbac/auth_proxy_client_clusterrole.yaml ================================================ apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: name: metrics-reader rules: - nonResourceURLs: - "/metrics" verbs: - get ================================================ FILE: chapter/14/petstore-operator/config/rbac/auth_proxy_role.yaml ================================================ apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: name: proxy-role rules: - apiGroups: - authentication.k8s.io resources: - tokenreviews verbs: - create - apiGroups: - authorization.k8s.io resources: - subjectaccessreviews verbs: - create ================================================ FILE: chapter/14/petstore-operator/config/rbac/auth_proxy_role_binding.yaml ================================================ apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: proxy-rolebinding roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: proxy-role subjects: - kind: ServiceAccount name: controller-manager namespace: system ================================================ FILE: chapter/14/petstore-operator/config/rbac/auth_proxy_service.yaml ================================================ apiVersion: v1 kind: Service metadata: labels: control-plane: controller-manager name: controller-manager-metrics-service namespace: system spec: ports: - name: https port: 8443 protocol: TCP targetPort: https selector: control-plane: controller-manager ================================================ FILE: chapter/14/petstore-operator/config/rbac/kustomization.yaml ================================================ resources: # All RBAC will be applied under this service account in # the deployment namespace. You may comment out this resource # if your manager will use a service account that exists at # runtime. Be sure to update RoleBinding and ClusterRoleBinding # subjects if changing service account names. - service_account.yaml - role.yaml - role_binding.yaml - leader_election_role.yaml - leader_election_role_binding.yaml # Comment the following 4 lines if you want to disable # the auth proxy (https://github.com/brancz/kube-rbac-proxy) # which protects your /metrics endpoint. - auth_proxy_service.yaml - auth_proxy_role.yaml - auth_proxy_role_binding.yaml - auth_proxy_client_clusterrole.yaml ================================================ FILE: chapter/14/petstore-operator/config/rbac/leader_election_role.yaml ================================================ # permissions to do leader election. apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: name: leader-election-role rules: - apiGroups: - "" resources: - configmaps verbs: - get - list - watch - create - update - patch - delete - apiGroups: - coordination.k8s.io resources: - leases verbs: - get - list - watch - create - update - patch - delete - apiGroups: - "" resources: - events verbs: - create - patch ================================================ FILE: chapter/14/petstore-operator/config/rbac/leader_election_role_binding.yaml ================================================ apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: name: leader-election-rolebinding roleRef: apiGroup: rbac.authorization.k8s.io kind: Role name: leader-election-role subjects: - kind: ServiceAccount name: controller-manager namespace: system ================================================ FILE: chapter/14/petstore-operator/config/rbac/pet_editor_role.yaml ================================================ # permissions for end users to edit pets. apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: name: pet-editor-role rules: - apiGroups: - petstore.example.com resources: - pets verbs: - create - delete - get - list - patch - update - watch - apiGroups: - petstore.example.com resources: - pets/status verbs: - get ================================================ FILE: chapter/14/petstore-operator/config/rbac/pet_viewer_role.yaml ================================================ # permissions for end users to view pets. apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: name: pet-viewer-role rules: - apiGroups: - petstore.example.com resources: - pets verbs: - get - list - watch - apiGroups: - petstore.example.com resources: - pets/status verbs: - get ================================================ FILE: chapter/14/petstore-operator/config/rbac/role.yaml ================================================ --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: creationTimestamp: null name: manager-role rules: - apiGroups: - petstore.example.com resources: - pets verbs: - create - delete - get - list - patch - update - watch - apiGroups: - petstore.example.com resources: - pets/finalizers verbs: - update - apiGroups: - petstore.example.com resources: - pets/status verbs: - get - patch - update ================================================ FILE: chapter/14/petstore-operator/config/rbac/role_binding.yaml ================================================ apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: manager-rolebinding roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: manager-role subjects: - kind: ServiceAccount name: controller-manager namespace: system ================================================ FILE: chapter/14/petstore-operator/config/rbac/service_account.yaml ================================================ apiVersion: v1 kind: ServiceAccount metadata: name: controller-manager namespace: system ================================================ FILE: chapter/14/petstore-operator/config/samples/kustomization.yaml ================================================ ## Append samples you want in your CSV to this file as resources ## resources: - petstore_v1alpha1_pet.yaml #+kubebuilder:scaffold:manifestskustomizesamples ================================================ FILE: chapter/14/petstore-operator/config/samples/petstore_v1alpha1_pet.yaml ================================================ --- apiVersion: petstore.example.com/v1alpha1 kind: Pet metadata: name: pet-sample1 spec: name: Thor type: dog birthday: 2021-04-01T00:00:00Z --- apiVersion: petstore.example.com/v1alpha1 kind: Pet metadata: name: pet-sample2 spec: name: Tron type: cat birthday: 2020-06-25T00:00:00Z ================================================ FILE: chapter/14/petstore-operator/config/scorecard/bases/config.yaml ================================================ apiVersion: scorecard.operatorframework.io/v1alpha3 kind: Configuration metadata: name: config stages: - parallel: true tests: [] ================================================ FILE: chapter/14/petstore-operator/config/scorecard/kustomization.yaml ================================================ resources: - bases/config.yaml patchesJson6902: - path: patches/basic.config.yaml target: group: scorecard.operatorframework.io version: v1alpha3 kind: Configuration name: config - path: patches/olm.config.yaml target: group: scorecard.operatorframework.io version: v1alpha3 kind: Configuration name: config #+kubebuilder:scaffold:patchesJson6902 ================================================ FILE: chapter/14/petstore-operator/config/scorecard/patches/basic.config.yaml ================================================ - op: add path: /stages/0/tests/- value: entrypoint: - scorecard-test - basic-check-spec image: quay.io/operator-framework/scorecard-test:v1.18.0 labels: suite: basic test: basic-check-spec-test ================================================ FILE: chapter/14/petstore-operator/config/scorecard/patches/olm.config.yaml ================================================ - op: add path: /stages/0/tests/- value: entrypoint: - scorecard-test - olm-bundle-validation image: quay.io/operator-framework/scorecard-test:v1.18.0 labels: suite: olm test: olm-bundle-validation-test - op: add path: /stages/0/tests/- value: entrypoint: - scorecard-test - olm-crds-have-validation image: quay.io/operator-framework/scorecard-test:v1.18.0 labels: suite: olm test: olm-crds-have-validation-test - op: add path: /stages/0/tests/- value: entrypoint: - scorecard-test - olm-crds-have-resources image: quay.io/operator-framework/scorecard-test:v1.18.0 labels: suite: olm test: olm-crds-have-resources-test - op: add path: /stages/0/tests/- value: entrypoint: - scorecard-test - olm-spec-descriptors image: quay.io/operator-framework/scorecard-test:v1.18.0 labels: suite: olm test: olm-spec-descriptors-test - op: add path: /stages/0/tests/- value: entrypoint: - scorecard-test - olm-status-descriptors image: quay.io/operator-framework/scorecard-test:v1.18.0 labels: suite: olm test: olm-status-descriptors-test ================================================ FILE: chapter/14/petstore-operator/controllers/pet_controller.go ================================================ /* Copyright 2022. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 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 controllers import ( "context" "github.com/pkg/errors" "google.golang.org/genproto/googleapis/type/date" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "sigs.k8s.io/cluster-api/util/patch" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/reconcile" petstorev1 "github.com/PacktPublishing/Go-for-DevOps/chapter/14/petstore-operator/api/v1alpha1" psclient "github.com/PacktPublishing/Go-for-DevOps/chapter/14/petstore-operator/client" pb "github.com/PacktPublishing/Go-for-DevOps/chapter/14/petstore-operator/client/proto" ) const ( PetFinalizer = "pet.petstore.example.com" ) // PetReconciler reconciles a Pet object type PetReconciler struct { client.Client Scheme *runtime.Scheme } //+kubebuilder:rbac:groups=petstore.example.com,resources=pets,verbs=get;list;watch;create;update;patch;delete //+kubebuilder:rbac:groups=petstore.example.com,resources=pets/status,verbs=get;update;patch //+kubebuilder:rbac:groups=petstore.example.com,resources=pets/finalizers,verbs=update // Reconcile moves the current state of the pet to be the desired state described in the pet.spec. func (r *PetReconciler) Reconcile(ctx context.Context, req ctrl.Request) (result ctrl.Result, errResult error) { logger := log.FromContext(ctx) pet := &petstorev1.Pet{} if err := r.Get(ctx, req.NamespacedName, pet); err != nil { if apierrors.IsNotFound(err) { logger.Info("object was not found") return reconcile.Result{}, nil } logger.Error(err, "failed to fetch pet from API server") // this will cause this pet resource to be requeued return ctrl.Result{}, err } helper, err := patch.NewHelper(pet, r.Client) if err != nil { return ctrl.Result{}, errors.Wrap(err, "failed to create patch helper") } defer func() { // patch the resource stored in the API server if something changed. if err := helper.Patch(ctx, pet); err != nil { errResult = err } }() if pet.DeletionTimestamp.IsZero() { // the pet is not marked for delete reconcile desired state return r.ReconcileNormal(ctx, pet) } // pet has been marked for delete, so delete from the petstore return r.ReconcileDelete(ctx, pet) } // ReconcileNormal will ensure the finalizer and save the desired state to the petstore. func (r *PetReconciler) ReconcileNormal(ctx context.Context, pet *petstorev1.Pet) (ctrl.Result, error) { logger := ctrl.LoggerFrom(ctx).WithValues("pet", pet.Spec.Name, "id", pet.Status.ID) // add a finalizer to ensure we clean up before K8s garbage collects logger.Info("ensuring finalizer") controllerutil.AddFinalizer(pet, PetFinalizer) psc, err := getPetstoreClient() if err != nil { return ctrl.Result{}, errors.Wrap(err, "unable to construct petstore client") } logger.Info("finding pets in store") psPet, err := findPetInStore(ctx, psc, pet) if err != nil { return ctrl.Result{}, errors.Wrap(err, "failed trying to find pet in pet store") } if psPet == nil { logger.Info("psPet was not nil") // no pet was found, so we must create a pet in the pet store err := createPetInStore(ctx, pet, psc) return ctrl.Result{}, err } // pet was found, so we need to update the pet in the pet store if err := updatePetInStore(ctx, psc, pet, psPet.Pet); err != nil { logger.Info("updating pet in store") return ctrl.Result{}, err } return ctrl.Result{}, nil } // ReconcileDelete deletes the pet from the petstore and removes the finalizer. func (r *PetReconciler) ReconcileDelete(ctx context.Context, pet *petstorev1.Pet) (ctrl.Result, error) { psc, err := getPetstoreClient() if err != nil { return ctrl.Result{}, errors.Wrap(err, "unable to construct petstore client") } if pet.Status.ID != "" { if err := psc.DeletePets(ctx, []string{pet.Status.ID}); err != nil { return ctrl.Result{}, errors.Wrap(err, "failed to delete pet") } } // remove finalizer, so K8s can garbage collect the resource. controllerutil.RemoveFinalizer(pet, PetFinalizer) return ctrl.Result{}, nil } func createPetInStore(ctx context.Context, pet *petstorev1.Pet, psc *psclient.Client) error { pbPet := &pb.Pet{ Name: pet.Spec.Name, Type: petTypeToProtoPetType(pet.Spec.Type), Birthday: timeToPbDate(pet.Spec.Birthday), } ids, err := psc.AddPets(ctx, []*pb.Pet{pbPet}) if err != nil { return errors.Wrap(err, "failed to create new pet in store") } pet.Status.ID = ids[0] return nil } func updatePetInStore(ctx context.Context, psc *psclient.Client, pet *petstorev1.Pet, pbPet *pb.Pet) error { pbPet.Name = pet.Spec.Name pbPet.Type = petTypeToProtoPetType(pet.Spec.Type) pbPet.Birthday = timeToPbDate(pet.Spec.Birthday) if err := psc.UpdatePets(ctx, []*pb.Pet{pbPet}); err != nil { return errors.Wrap(err, "failed to update the pet in the store") } return nil } // SetupWithManager sets up the controller with the Manager. func (r *PetReconciler) SetupWithManager(mgr ctrl.Manager) error { return ctrl.NewControllerManagedBy(mgr). For(&petstorev1.Pet{}). Complete(r) } // findPetInStore searches the pet store for a pet that matches the custom resource pet. func findPetInStore(ctx context.Context, psc *psclient.Client, pet *petstorev1.Pet) (*psclient.Pet, error) { petsChan, err := psc.SearchPets(ctx, &pb.SearchPetsReq{ Names: []string{pet.Spec.Name}, Types: []pb.PetType{petTypeToProtoPetType(pet.Spec.Type)}, }) if err != nil { return nil, errors.Wrap(err, "failed searching for pet") } for pbPet := range petsChan { if pbPet.Error() != nil { logger := ctrl.LoggerFrom(ctx) logger.Error(err, "search chan error") continue } if pbPet.Id == pet.Status.ID { return &pbPet, nil } } return nil, nil } func petTypeToProtoPetType(petType petstorev1.PetType) pb.PetType { switch petType { case petstorev1.DogPetType: return pb.PetType_PTCanine case petstorev1.CatPetType: return pb.PetType_PTFeline case petstorev1.BirdPetType: return pb.PetType_PTBird default: return pb.PetType_PTReptile } } func timeToPbDate(t metav1.Time) *date.Date { return &date.Date{ Year: int32(t.Year()), Month: int32(t.Month()), Day: int32(t.Day()), } } func getPetstoreClient() (*psclient.Client, error) { return psclient.New("petstore-service.petstore.svc.cluster.local:6742") } ================================================ FILE: chapter/14/petstore-operator/controllers/suite_test.go ================================================ /* Copyright 2022. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 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 controllers import ( "path/filepath" "testing" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" "k8s.io/client-go/kubernetes/scheme" "k8s.io/client-go/rest" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/envtest" "sigs.k8s.io/controller-runtime/pkg/envtest/printer" logf "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/log/zap" petstorev1alpha1 "github.com/PacktPublishing/Go-for-DevOps/chapter/14/petstore-operator/api/v1alpha1" //+kubebuilder:scaffold:imports ) // These tests use Ginkgo (BDD-style Go testing framework). Refer to // http://onsi.github.io/ginkgo/ to learn more about Ginkgo. var cfg *rest.Config var k8sClient client.Client var testEnv *envtest.Environment func TestAPIs(t *testing.T) { RegisterFailHandler(Fail) RunSpecsWithDefaultAndCustomReporters(t, "Controller Suite", []Reporter{printer.NewlineReporter{}}) } var _ = BeforeSuite(func() { logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true))) By("bootstrapping test environment") testEnv = &envtest.Environment{ CRDDirectoryPaths: []string{filepath.Join("..", "config", "crd", "bases")}, ErrorIfCRDPathMissing: true, } cfg, err := testEnv.Start() Expect(err).NotTo(HaveOccurred()) Expect(cfg).NotTo(BeNil()) err = petstorev1alpha1.AddToScheme(scheme.Scheme) Expect(err).NotTo(HaveOccurred()) //+kubebuilder:scaffold:scheme k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme}) Expect(err).NotTo(HaveOccurred()) Expect(k8sClient).NotTo(BeNil()) }, 60) var _ = AfterSuite(func() { By("tearing down the test environment") err := testEnv.Stop() Expect(err).NotTo(HaveOccurred()) }) ================================================ FILE: chapter/14/petstore-operator/go.mod ================================================ module github.com/PacktPublishing/Go-for-DevOps/chapter/14/petstore-operator go 1.17 require ( github.com/biogo/store v0.0.0-20201120204734-aad293a2328f github.com/kylelemons/godebug v1.1.0 github.com/onsi/ginkgo v1.16.5 github.com/onsi/gomega v1.17.0 github.com/pkg/errors v0.9.1 go.opentelemetry.io/otel v1.4.1 go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.27.0 go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v0.27.0 go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.4.1 go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.4.1 go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.4.1 go.opentelemetry.io/otel/metric v0.27.0 go.opentelemetry.io/otel/sdk v1.4.1 go.opentelemetry.io/otel/sdk/metric v0.27.0 go.opentelemetry.io/otel/trace v1.4.1 google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2 google.golang.org/grpc v1.44.0 google.golang.org/protobuf v1.27.1 k8s.io/apimachinery v0.23.0 k8s.io/client-go v0.23.0 sigs.k8s.io/cluster-api v1.1.2 sigs.k8s.io/controller-runtime v0.11.1 ) require ( cloud.google.com/go v0.93.3 // indirect github.com/Azure/go-autorest v14.2.0+incompatible // indirect github.com/Azure/go-autorest/autorest v0.11.18 // indirect github.com/Azure/go-autorest/autorest/adal v0.9.13 // indirect github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect github.com/Azure/go-autorest/logger v0.2.1 // indirect github.com/Azure/go-autorest/tracing v0.6.0 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/blang/semver v3.5.1+incompatible // indirect github.com/cenkalti/backoff/v4 v4.1.2 // indirect github.com/cespare/xxhash/v2 v2.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/evanphx/json-patch v4.12.0+incompatible // indirect github.com/form3tech-oss/jwt-go v3.2.3+incompatible // indirect github.com/fsnotify/fsnotify v1.5.1 // indirect github.com/go-logr/logr v1.2.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-logr/zapr v1.2.0 // indirect github.com/gobuffalo/flect v0.2.4 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.2 // indirect github.com/google/go-cmp v0.5.7 // indirect github.com/google/gofuzz v1.2.0 // indirect github.com/google/uuid v1.1.2 // indirect github.com/googleapis/gnostic v0.5.5 // indirect github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect github.com/imdario/mergo v0.3.12 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/nxadm/tail v1.4.8 // indirect github.com/prometheus/client_golang v1.11.0 // indirect github.com/prometheus/client_model v0.2.0 // indirect github.com/prometheus/common v0.28.0 // indirect github.com/prometheus/procfs v0.6.0 // indirect github.com/spf13/pflag v1.0.5 // indirect go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.4.1 // indirect go.opentelemetry.io/otel/internal/metric v0.27.0 // indirect go.opentelemetry.io/proto/otlp v0.12.0 // indirect go.uber.org/atomic v1.7.0 // indirect go.uber.org/multierr v1.6.0 // indirect go.uber.org/zap v1.19.1 // indirect golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 // indirect golang.org/x/net v0.0.0-20210825183410-e898025ed96a // indirect golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect golang.org/x/sys v0.0.0-20211029165221-6e7872819dc8 // indirect golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b // indirect golang.org/x/text v0.3.7 // indirect golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect google.golang.org/appengine v1.6.7 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect k8s.io/api v0.23.0 // indirect k8s.io/apiextensions-apiserver v0.23.0 // indirect k8s.io/component-base v0.23.0 // indirect k8s.io/klog/v2 v2.30.0 // indirect k8s.io/kube-openapi v0.0.0-20211115234752-e816edb12b65 // indirect k8s.io/utils v0.0.0-20210930125809-cb0fa318a74b // indirect sigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6 // indirect sigs.k8s.io/structured-merge-diff/v4 v4.2.0 // indirect sigs.k8s.io/yaml v1.3.0 // indirect ) ================================================ FILE: chapter/14/petstore-operator/go.sum ================================================ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY= cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM= cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY= cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ= cloud.google.com/go v0.93.3 h1:wPBktZFzYBcCZVARvwVKqH1uEj+aLXofJEtrb4oOsio= cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= cloud.google.com/go/firestore v1.6.0/go.mod h1:afJwI0vaXwAG54kI7A//lP/lSPDkQORQuMkv56TxEPU= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/Azure/go-ansiterm v0.0.0-20210608223527-2377c96fe795/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs= github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= github.com/Azure/go-autorest/autorest v0.11.18 h1:90Y4srNYrwOtAgVo3ndrQkTYn6kf1Eg/AjTFJ8Is2aM= github.com/Azure/go-autorest/autorest v0.11.18/go.mod h1:dSiJPy22c3u0OtOKDNttNgqpNFY/GeWa7GH/Pz56QRA= github.com/Azure/go-autorest/autorest/adal v0.9.13 h1:Mp5hbtOePIzM8pJVRa3YLrWWmZtoxRXqUEzCfJt3+/Q= github.com/Azure/go-autorest/autorest/adal v0.9.13/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M= github.com/Azure/go-autorest/autorest/date v0.3.0 h1:7gUk1U5M/CQbp9WoqinNzJar+8KY+LPI6wiWrP/myHw= github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= github.com/Azure/go-autorest/autorest/mocks v0.4.1 h1:K0laFcLE6VLTOwNgSxaGbUcLPuGXlNkbVvq4cW4nIHk= github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= github.com/Azure/go-autorest/logger v0.2.1 h1:IG7i4p/mDa2Ce4TRyAO8IHnVhAVF3RFU+ZtXWSmf4Tg= github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo= github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd/go.mod h1:64YHyfSL2R96J44Nlwm39UHepQbyR5q10x7iYa1ks2E= github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ= github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE= github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/ajeddeloh/go-json v0.0.0-20160803184958-73d058cf8437/go.mod h1:otnto4/Icqn88WCcM4bhIJNSgsh9VLBuspyyCfvof9c= github.com/ajeddeloh/go-json v0.0.0-20200220154158-5ae607161559/go.mod h1:otnto4/Icqn88WCcM4bhIJNSgsh9VLBuspyyCfvof9c= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= github.com/alecthomas/units v0.0.0-20210208195552-ff826a37aa15/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/antlr/antlr4/runtime/Go/antlr v0.0.0-20210826220005-b48c857c3a0e h1:GCzyKMDDjSGnlpl3clrdAK7I1AaVoaiKDOYkUzChZzg= github.com/antlr/antlr4/runtime/Go/antlr v0.0.0-20210826220005-b48c857c3a0e/go.mod h1:F7bn7fEU90QkQ3tnmaTx3LTKLEDqnwWODIYppRQ5hnY= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a h1:idn718Q4B6AGu/h5Sxe66HYVdqdGu2l9Iebqhi/AEoA= github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/aws/aws-sdk-go v1.8.39/go.mod h1:ZRmQr0FajVIyZ4ZzBYKG5P3ZqPz9IHG41ZoMu1ADI3k= github.com/benbjohnson/clock v1.0.3/go.mod h1:bGMdMPoPVvcYyt1gHDf4J2KE153Yf9BuiUKYMaxlTDM= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A= github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/biogo/store v0.0.0-20201120204734-aad293a2328f h1:+6okTAeUsUrdQr/qN7fIODzowrjjCrnJDg/gkYqcSXY= github.com/biogo/store v0.0.0-20201120204734-aad293a2328f/go.mod h1:z52shMwD6SGwRg2iYFjjDwX5Ene4ENTw6HfXraUy/08= github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM= github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ= github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= github.com/cenkalti/backoff/v4 v4.1.2 h1:6Yo7N8UP2K6LWZnW94DLVSSrbobcWdVzAYOisuDPIFo= github.com/cenkalti/backoff/v4 v4.1.2/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/certifi/gocertifi v0.0.0-20191021191039-0944d244cd40/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA= github.com/certifi/gocertifi v0.0.0-20200922220541-2c3bb06c6054/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA= github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chai2010/gettext-go v0.0.0-20160711120539-c6fed771bfd5/go.mod h1:/iP1qXHoty45bqomnu2LM+VVyAEdWN+vtSHGlQgyxbw= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= 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/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cockroachdb/datadriven v0.0.0-20200714090401-bf6692d28da5/go.mod h1:h6jFvWxBdQXxjopDMZyH2UVceIRfR84bdzbkoKrsWNo= github.com/cockroachdb/errors v1.2.4/go.mod h1:rQD95gz6FARkaKkQXUksEje/d9a6wBJoCr5oaCLELYA= github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f/go.mod h1:i/u985jwjWRlyHXQbwatDASoW0RMlZ/3i9yJHE2xLkI= github.com/coredns/caddy v1.1.0 h1:ezvsPrT/tA/7pYDBZxu0cT0VmWk75AfIaf6GSYCNMf0= github.com/coredns/caddy v1.1.0/go.mod h1:A6ntJQlAWuQfFlsd9hvigKbo2WS0VUs2l1e2F+BawD4= github.com/coredns/corefile-migration v1.0.14 h1:Tz3WZhoj2NdP8drrQH86NgnCng+VrPjNeg2Oe1ALKag= github.com/coredns/corefile-migration v1.0.14/go.mod h1:XnhgULOEouimnzgn0t4WPuFDN2/PJQcTxdWKC5eXNGE= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= github.com/coreos/go-semver v0.1.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd v0.0.0-20181031085051-9002847aa142/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 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/daviddengcn/go-colortext v0.0.0-20160507010035-511bcaf42ccd/go.mod h1:dv4zxwHi5C/8AeI+4gX4dCWOIvNi7I6JCSX0HvlKPgE= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/docker/distribution v2.7.1+incompatible h1:a5mlkVzth6W5A4fOsS3D2EO5BUmsJpcB+cRlLU7cSug= github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= github.com/drone/envsubst/v2 v2.0.0-20210730161058-179042472c46/go.mod h1:esf2rsHFNlZlxsqsZDojNBcnNs5REqIvRrWRHqX0vEU= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= 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.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/evanphx/json-patch v0.5.2/go.mod h1:ZWS5hhDbVDyob71nXKNL0+PWn6ToqBHMikGIFbs31qQ= github.com/evanphx/json-patch v4.11.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch v4.12.0+incompatible h1:4onqiflcdA9EOZ4RxV643DvftH5pOlLGNtQ5lPWQu84= github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch/v5 v5.6.0 h1:b91NhWfaz02IuVxO9faSllyAtNXHMPkC5J8sJCLunww= github.com/evanphx/json-patch/v5 v5.6.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2VvlbKOFpnXhI9Bw4= github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d/go.mod h1:ZZMPRZwes7CROmyNKgQzC3XPs6L/G2EJLHddWejkmf4= github.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwoZc+/fpc= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/flatcar-linux/container-linux-config-transpiler v0.9.2/go.mod h1:AGVTulMzeIKwurV9ExYH3UiokET1Ur65g+EIeRDMwzM= github.com/flatcar-linux/ignition v0.36.1/go.mod h1:0jS5n4AopgOdwgi7QDo5MFgkMx/fQUDYjuxlGJC1Txg= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= github.com/form3tech-oss/jwt-go v3.2.3+incompatible h1:7ZaBxOI7TMoYBfyA3cQHErNNyAWIKUMIwqxEtgHOs5c= github.com/form3tech-oss/jwt-go v3.2.3+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWpgI= github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU= github.com/fvbommel/sortorder v1.0.1/go.mod h1:uk88iVf1ovNn1iLfgUVU2F9o5eO30ui720w+kxuqRs0= github.com/getkin/kin-openapi v0.76.0/go.mod h1:660oXbgy5JFMKreazJaQTw7o+X00qeSyhcnluiMv+Xg= github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-ini/ini v1.25.4/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.2 h1:ahHml/yUpnlb96Rp8HCvtYVPY8ZYpxq3g7UYchIYwbs= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= 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-logr/zapr v1.2.0 h1:n4JnPI1T3Qq1SFEi/F8rwLrZERp2bso19PJZDB9dayk= github.com/go-logr/zapr v1.2.0/go.mod h1:Qa4Bsj2Vb+FAVeAKsLD8RLQ+YRJB8YDmOAKxaBQf7Ro= github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY= github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= github.com/go-openapi/jsonreference v0.19.5 h1:1WJP/wi4OjB4iV8KVbH73rQaoialJrqv8gitZLxGLtM= github.com/go-openapi/jsonreference v0.19.5/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg= github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.19.14 h1:gm3vOOXfiuw5i9p5N9xJvfjvuofpyvLA9Wr6QfK5Fng= github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/gobuffalo/flect v0.2.4 h1:BSYA8+T60cdyq+vynaSUjqSVI9mDEg9ZfQUXKmfjo4I= github.com/gobuffalo/flect v0.2.4/go.mod h1:1ZyCLIbg0YD7sDkzvFdPoOydPtD8y9JQnrOROolUcM8= github.com/godbus/dbus v0.0.0-20181025153459-66d97aec3384/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= 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.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golangplus/testing v0.0.0-20180327235837-af21d9c3145e/go.mod h1:0AA//k/eakGydO4jKRoRL2j92ZKSzTgj9tclaCrvXHk= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= github.com/google/cel-go v0.9.0 h1:u1hg7lcZ/XWw2d3aV1jFS30ijQQ6q0/h1C2ZBeBD1gY= github.com/google/cel-go v0.9.0/go.mod h1:U7ayypeSkw23szu4GaQTPJGx66c20mx8JklMSxrmI1w= github.com/google/cel-spec v0.6.0/go.mod h1:Nwjgxy5CbjlPrtCWjeDjUyKMl8w41YBYGjsyDdqk0xA= 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.4.1/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.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.2/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.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o= github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= github.com/google/go-github/v33 v33.0.0/go.mod h1:GMdDnVZY/2TsWgp/lkYnpSAh6TrzhANBBwm6k6TTEXg= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= github.com/googleapis/gnostic v0.5.1/go.mod h1:6U4PtQXGIEt/Z3h5MAT7FNofLnw9vXk2cUuW7uA/OeU= github.com/googleapis/gnostic v0.5.5 h1:9fHAtK0uDfpveeqqo1hkEZJcFvYXAiCN3UutL8F9xHw= github.com/googleapis/gnostic v0.5.5/go.mod h1:7+EbHbldMins07ALC74bsA81Ovc97DwqyJO1AENw9kA= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gosuri/uitable v0.0.4/go.mod h1:tKR86bXuXPZazfOTG1FIzvjIdXzd0mo4Vtn16vt0PJo= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= github.com/hashicorp/consul/api v1.10.1/go.mod h1:XjsvQN+RJGWI2TWy1/kqaE16HrR2J/FWgkYjdZQsX9M= github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= github.com/hashicorp/consul/sdk v0.8.0/go.mod h1:GBvyrGALthsZObzUGsfgHZQDXjg4lOjagTIwIR1vPms= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= github.com/hashicorp/mdns v1.0.1/go.mod h1:4gW7WsVCke5TE7EPeYliwHlRUyBtfCwuFwuMg2DmyNY= github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= github.com/hashicorp/memberlist v0.2.2/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/hashicorp/serf v0.9.5/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE= github.com/lithammer/dedent v1.1.0/go.mod h1:jrXYCQtgg0nJiN+StA2KgR7w6CiQNv9Fd/Z9BP0jIOc= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA= github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 h1:I0XW9+e1XWDxdcEniV4rQAIOPUGDq67JSCiRCgGCZLI= github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI= github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.4.2 h1:6h7AQ0yhTcIsmFmnAwQls75jp2Gzs4iB8W7pjMO+rqo= github.com/mitchellh/mapstructure v1.4.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= github.com/moby/term v0.0.0-20210610120745-9d4ed1856297/go.mod h1:vgPCkQMyxTZ7IDy8SXRufE172gr8+K/JE/7hHFxHW3A= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00/go.mod h1:Pm3mSP3c5uWn86xMLZ5Sa7JB9GsEZySvHYXCTK4E9q4= github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.17.0 h1:9Luw4uT5HTjHTN8+aNcSThgH1vdXnmdJ8xIfZ4wyTRE= github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pborman/uuid v0.0.0-20170612153648-e790cca94e6c/go.mod h1:VyrYX9gd7irzKovcSS6BIIEwPRkP2Wm2m9ufcdFSJ34= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= github.com/pin/tftp v2.1.0+incompatible/go.mod h1:xVpZOMCXTy+A5QMjEVN0Glwa1sUvaJhFXbr/aAxuxGY= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= 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/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s= github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= github.com/prometheus/client_golang v1.11.0 h1:HNkLOAEQMIDv/K+04rukrLx6ch7msSRwf3/SASFAGtQ= github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= github.com/prometheus/common v0.28.0 h1:vGVfV9KrDTvWt5boZO0I19g2E3CsWfpPPKZM9dt3mEw= github.com/prometheus/common v0.28.0/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.6.0 h1:mxy4L2jP6qMonqmq+aTtOx1ifVWUgG/TAmntgbh3xv4= github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/sagikazarmark/crypt v0.1.0/go.mod h1:B/mN0msZuINBtQ1zZLEQcegFJJf9vnYIR88KRMEuODE= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sigma/bdoor v0.0.0-20160202064022-babf2a4017b0/go.mod h1:WBu7REWbxC/s/J06jsk//d+9DOz9BbsmcIrimuGRFbs= github.com/sigma/vmw-guestinfo v0.0.0-20160204083807-95dd4126d6e8/go.mod h1:JrRFFC0veyh0cibh0DAhriSY7/gV3kDdNaVUOmfx01U= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/assertions v1.2.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/smartystreets/goconvey v1.7.2/go.mod h1:Vw0tHAZW6lzCRk3xgdin6fKYcG+G3Pg9vgXWeJpQFMM= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo= github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns= github.com/spf13/viper v1.9.0/go.mod h1:+i6ajR7OX2XaiBkrcZJFK21htRk7eDeLg7+O6bhUPP4= github.com/stoewer/go-strcase v1.2.0 h1:Z2iHWqGXH00XYgqDmNgQbIBxf3wrNq0F3feEy0ainaU= github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= github.com/valyala/fastjson v1.6.3/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY= github.com/vincent-petithory/dataurl v1.0.0/go.mod h1:FHafX5vmDzyP+1CQATJn7WFKc9CvnvxyvZy6I1MrG/U= github.com/vmware/vmw-guestinfo v0.0.0-20170707015358-25eff159a728/go.mod h1:x9oS4Wk2s2u4tS29nEaDLdzvuHdB19CvSGJjPgkZJNk= github.com/vmware/vmw-ovflib v0.0.0-20170608004843-1f217b9dc714/go.mod h1:jiPk45kn7klhByRvUq5i2vo1RtHKBHj+iWGFpxbXuuI= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca/go.mod h1:ce1O1j6UtZfjr22oyGxGLbauSBp2YVXpARAosm7dHBg= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4= go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= go.etcd.io/etcd/api/v3 v3.5.1/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= go.etcd.io/etcd/client/pkg/v3 v3.5.1/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= go.etcd.io/etcd/client/v3 v3.5.0/go.mod h1:AIKXXVX/DQXtfTEqBryiLTUXwON+GuvO6Z7lLS/oTh0= go.etcd.io/etcd/client/v3 v3.5.1/go.mod h1:OnjH4M8OnAotwaB2l9bVgZzRFKru7/ZMoS46OtKyd3Q= go.etcd.io/etcd/pkg/v3 v3.5.0/go.mod h1:UzJGatBQ1lXChBkQF0AuAtkRQMYnHubxAEYIrC3MSsE= go.etcd.io/etcd/raft/v3 v3.5.0/go.mod h1:UFOHSIvO/nKwd4lhkwabrTD3cqW5yVyYYf/KlD00Szc= go.etcd.io/etcd/server/v3 v3.5.0/go.mod h1:3Ah5ruV+M+7RZr0+Y/5mNLwC+eQlni+mQmOVdCRJoS4= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= go.opentelemetry.io/contrib v0.20.0/go.mod h1:G/EtFaa6qaN7+LxqfIAT3GiZa7Wv5DTBUzl5H4LY0Kc= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.20.0/go.mod h1:oVGt1LRbBOBq1A5BQLlUg9UaU/54aiHw8cgjV3aWZ/E= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.20.0/go.mod h1:2AboqHi0CiIZU0qwhtUfCYD1GeUzvvIXWNkhDt7ZMG4= go.opentelemetry.io/otel v0.20.0/go.mod h1:Y3ugLH2oa81t5QO+Lty+zXf8zC9L26ax4Nzoxm/dooo= go.opentelemetry.io/otel v1.4.0/go.mod h1:jeAqMFKy2uLIxCtKxoFj0FAL5zAPKQagc3+GtBWakzk= go.opentelemetry.io/otel v1.4.1 h1:QbINgGDDcoQUoMJa2mMaWno49lja9sHwp6aoa2n3a4g= go.opentelemetry.io/otel v1.4.1/go.mod h1:StM6F/0fSwpd8dKWDCdRr7uRvEPYdW0hBSlbdTiUde4= go.opentelemetry.io/otel/exporters/otlp v0.20.0 h1:PTNgq9MRmQqqJY0REVbZFvwkYOA85vbdQU/nVfxDyqg= go.opentelemetry.io/otel/exporters/otlp v0.20.0/go.mod h1:YIieizyaN77rtLJra0buKiNBOm9XQfkPEKBeuhoMwAM= go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.4.0/go.mod h1:VpP4/RMn8bv8gNo9uK7/IMY4mtWLELsS+JIP0inH0h4= go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.4.1 h1:imIM3vRDMyZK1ypQlQlO+brE22I9lRhJsBDXpDWjlz8= go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.4.1/go.mod h1:VpP4/RMn8bv8gNo9uK7/IMY4mtWLELsS+JIP0inH0h4= go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.27.0 h1:t1aPfMj5oZzv2EaRmdC2QPQg1a7MaBjraOh4Hjwuia8= go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.27.0/go.mod h1:aZnoYVx7GIuMROciGC3cjZhYxMD/lKroRJUnFY0afu0= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v0.27.0 h1:RJURCSrqUjJiCY3GuFCVP2EPKOQLwNXQ4FI3aH2KoHg= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v0.27.0/go.mod h1:LIc1eCpkU94tPnXxH40ya41Oyxm7sL+oDvxCYPFpnV8= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.4.1 h1:WPpPsAAs8I2rA47v5u0558meKmmwm1Dj99ZbqCV8sZ8= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.4.1/go.mod h1:o5RW5o2pKpJLD5dNTCmjF1DorYwMeFJmb/rKr5sLaa8= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.4.1 h1:AxqDiGk8CorEXStMDZF5Hz9vo9Z7ZZ+I5m8JRl/ko40= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.4.1/go.mod h1:c6E4V3/U+miqjs/8l950wggHGL1qzlp0Ypj9xoGrPqo= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.4.1 h1:yaXaoJjXaJqRnsfW9HrN7pGb7bzcEn31Rk6yo2LFaWo= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.4.1/go.mod h1:BFiGsTMZdqtxufux8ANXuMeRz9dMPVFdJZadUWDFD7o= go.opentelemetry.io/otel/internal/metric v0.27.0 h1:9dAVGAfFiiEq5NVB9FUJ5et+btbDQAUIJehJ+ikyryk= go.opentelemetry.io/otel/internal/metric v0.27.0/go.mod h1:n1CVxRqKqYZtqyTh9U/onvKapPGv7y/rpyOTI+LFNzw= go.opentelemetry.io/otel/metric v0.20.0/go.mod h1:598I5tYlH1vzBjn+BTuhzTCSb/9debfNp6R3s7Pr1eU= go.opentelemetry.io/otel/metric v0.27.0 h1:HhJPsGhJoKRSegPQILFbODU56NS/L1UE4fS1sC5kIwQ= go.opentelemetry.io/otel/metric v0.27.0/go.mod h1:raXDJ7uP2/Jc0nVZWQjJtzoyssOYWu/+pjZqRzfvZ7g= go.opentelemetry.io/otel/oteltest v0.20.0/go.mod h1:L7bgKf9ZB7qCwT9Up7i9/pn0PWIa9FqQ2IQ8LoxiGnw= go.opentelemetry.io/otel/sdk v0.20.0/go.mod h1:g/IcepuwNsoiX5Byy2nNV0ySUF1em498m7hBWC279Yc= go.opentelemetry.io/otel/sdk v1.4.0/go.mod h1:71GJPNJh4Qju6zJuYl1CrYtXbrgfau/M9UAggqiy1UE= go.opentelemetry.io/otel/sdk v1.4.1 h1:J7EaW71E0v87qflB4cDolaqq3AcujGrtyIPGQoZOB0Y= go.opentelemetry.io/otel/sdk v1.4.1/go.mod h1:NBwHDgDIBYjwK2WNu1OPgsIc2IJzmBXNnvIJxJc8BpE= go.opentelemetry.io/otel/sdk/export/metric v0.20.0/go.mod h1:h7RBNMsDJ5pmI1zExLi+bJK+Dr8NQCh0qGhm1KDnNlE= go.opentelemetry.io/otel/sdk/metric v0.20.0/go.mod h1:knxiS8Xd4E/N+ZqKmUPf3gTTZ4/0TjTXukfxjzSTpHE= go.opentelemetry.io/otel/sdk/metric v0.27.0 h1:CDEu96Js5IP7f4bJ8eimxF09V5hKYmE7CeyKSjmAL1s= go.opentelemetry.io/otel/sdk/metric v0.27.0/go.mod h1:lOgrT5C3ORdbqp2LsDrx+pBj6gbZtQ5Omk27vH3EaW0= go.opentelemetry.io/otel/trace v0.20.0/go.mod h1:6GjCW8zgDjwGHGa6GkyeB8+/5vjT16gUEi0Nf1iBdgw= go.opentelemetry.io/otel/trace v1.4.0/go.mod h1:uc3eRsqDfWs9R7b92xbQbU42/eTNz4N+gLP8qJCi4aE= go.opentelemetry.io/otel/trace v1.4.1 h1:O+16qcdTrT7zxv2J6GejTPFinSwA++cYerC5iSiF8EQ= go.opentelemetry.io/otel/trace v1.4.1/go.mod h1:iYEVbroFCNut9QkwEczV9vMRPHNKSSwYZjulEtsmhFc= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.opentelemetry.io/proto/otlp v0.12.0 h1:CMJ/3Wp7iOWES+CYLfnBv+DVmPbB+kmy9PJ92XvlR6c= go.opentelemetry.io/proto/otlp v0.12.0/go.mod h1:TsIjwGWIx5VFYv9KGVlOpxoBl5Dy+63SUguV7GGvlSQ= go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5/go.mod h1:nmDLcffg48OtT/PSW0Hg7FvpRQsQh5OSqIylirxKC7o= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.uber.org/goleak v1.1.12 h1:gZAh5/EyT/HQwlpkCy6wTpqfH9H8Lz8zbm3dZh+OyzA= go.uber.org/goleak v1.1.12/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= go.uber.org/zap v1.19.0/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= go.uber.org/zap v1.19.1 h1:ue41HOKd1vGURxrmeKIgELGb3jPW9DMUDGtsinblHwI= go.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI= go4.org v0.0.0-20160314031811-03efcb870d84/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE= go4.org v0.0.0-20201209231011-d4a079459e60/go.mod h1:CIiUVy99QCPfoE13bO4EZaz5GZMZXMSBGhxRdsvzbkg= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 h1:HWj/xjIHfjYU5nVXpTM0s39J9CbLn7Cc5a7IC5rwsMQ= golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= 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-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 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-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/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-20190320064053-1272bf9dcd53/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210825183410-e898025ed96a h1:bRuuGXV8wwSdGTB+CtJf+FjgO1APK1CoO39T4BN/XBw= golang.org/x/net v0.0.0-20210825183410-e898025ed96a/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 h1:RerP+noqYHUQ8CMRcPlC2nvTa4dcBIjegkuWdcUDuqg= golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= 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-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/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.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/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-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191002063906-3421d5a6bb1c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211029165221-6e7872819dc8 h1:M69LAlWZCshgp0QSzyDcSsSIejIEeuaCVpmwcKwyLMk= golang.org/x/sys v0.0.0-20211029165221-6e7872819dc8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b h1:9zKuko04nR4gjZ4+DNjHqRlAJqbJETHwiNKDqTfOjfE= golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20190321115727-fe223c5a2583/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac h1:7zkz7BUtwNFFqcowJ+RIgu2MaV/MapERkDIy+mwPyjs= golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/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-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200505023115-26f46d2f7ef8/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.6-0.20210820212750-d4cc65f0b2ff/go.mod h1:YD9qOF0M9xpSpdWTBbzEl5e/RnCefISl8E5Noe10jFM= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gomodules.xyz/jsonpatch/v2 v2.2.0 h1:4pT439QV83L+G9FkcCriY6EkpcK6r6bK+A5FBUMI7qY= gomodules.xyz/jsonpatch/v2 v2.2.0/go.mod h1:WXp+iVDkoLQqPudfQ9GBlwB2eZ5DKOnjQZCYdOS8GPY= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= google.golang.org/api v0.44.0/go.mod h1:EBOGZqzyhtvMDoxwS97ctnh0zUmYY6CxqXsc1AvkYD8= google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo= google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4= google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw= google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU= google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k= google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= 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/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201102152239-715cce707fb0/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A= google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24= google.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w= google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2 h1:NHN4wOCScVzKhPenJ2dt+BTs3X/XkBVI/Rh4iDt55T8= google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= 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.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= google.golang.org/grpc v1.43.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= google.golang.org/grpc v1.44.0 h1:weqSxi/TMs1SqFRMHCtBgXRs8k3X39QIDEZ0pRcttUg= google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= 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.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.63.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= k8s.io/api v0.23.0 h1:WrL1gb73VSC8obi8cuYETJGXEoFNEh3LU0Pt+Sokgro= k8s.io/api v0.23.0/go.mod h1:8wmDdLBHBNxtOIytwLstXt5E9PddnZb0GaMcqsvDBpg= k8s.io/apiextensions-apiserver v0.23.0 h1:uii8BYmHYiT2ZTAJxmvc3X8UhNYMxl2A0z0Xq3Pm+WY= k8s.io/apiextensions-apiserver v0.23.0/go.mod h1:xIFAEEDlAZgpVBl/1VSjGDmLoXAWRG40+GsWhKhAxY4= k8s.io/apimachinery v0.23.0 h1:mIfWRMjBuMdolAWJ3Fd+aPTMv3X9z+waiARMpvvb0HQ= k8s.io/apimachinery v0.23.0/go.mod h1:fFCTTBKvKcwTPFzjlcxp91uPFZr+JA0FubU4fLzzFYc= k8s.io/apiserver v0.23.0 h1:Ds/QveXWi9aJ8ISB0CJa4zBNc5njxAs5u3rmMIexqCY= k8s.io/apiserver v0.23.0/go.mod h1:Cec35u/9zAepDPPFyT+UMrgqOCjgJ5qtfVJDxjZYmt4= k8s.io/cli-runtime v0.23.0/go.mod h1:B5N3YH0KP1iKr6gEuJ/RRmGjO0mJQ/f/JrsmEiPQAlU= k8s.io/client-go v0.23.0 h1:vcsOqyPq7XV3QmQRCBH/t9BICJM9Q1M18qahjv+rebY= k8s.io/client-go v0.23.0/go.mod h1:hrDnpnK1mSr65lHHcUuIZIXDgEbzc7/683c6hyG4jTA= k8s.io/cluster-bootstrap v0.23.0 h1:8pZuuAWPoygewSNB4IddX3HBwXcQkPDXL/ca7GtGf4o= k8s.io/cluster-bootstrap v0.23.0/go.mod h1:VltEnKWfrRTiKgOXp3ts3vh7yqNlH6KFKFflo9GtCBg= k8s.io/code-generator v0.23.0/go.mod h1:vQvOhDXhuzqiVfM/YHp+dmg10WDZCchJVObc9MvowsE= k8s.io/component-base v0.23.0 h1:UAnyzjvVZ2ZR1lF35YwtNY6VMN94WtOnArcXBu34es8= k8s.io/component-base v0.23.0/go.mod h1:DHH5uiFvLC1edCpvcTDV++NKULdYYU6pR9Tt3HIKMKI= k8s.io/component-helpers v0.23.0/go.mod h1:liXMh6FZS4qamKtMJQ7uLHnFe3tlC86RX5mJEk/aerg= k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/gengo v0.0.0-20210813121822-485abfe95c7c/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= k8s.io/klog/v2 v2.30.0 h1:bUO6drIvCIsvZ/XFgfxoGFQU/a4Qkh0iAlvUR7vlHJw= k8s.io/klog/v2 v2.30.0/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e/go.mod h1:vHXdDvt9+2spS2Rx9ql3I8tycm3H9FDfdUoIuKCefvw= k8s.io/kube-openapi v0.0.0-20211115234752-e816edb12b65 h1:E3J9oCLlaobFUqsjG9DfKbP2BmgwBL2p7pn0A3dG9W4= k8s.io/kube-openapi v0.0.0-20211115234752-e816edb12b65/go.mod h1:sX9MT8g7NVZM5lVL/j8QyCCJe8YSMW30QvGZWaCIDIk= k8s.io/kubectl v0.23.0/go.mod h1:TfcGEs3u4dkmoC2eku1GYymdGaMtPMcaLLFrX/RB2kI= k8s.io/metrics v0.23.0/go.mod h1:NDiZTwppEtAuKJ1Rxt3S4dhyRzdp6yUcJf0vo023dPo= k8s.io/utils v0.0.0-20210802155522-efc7438f0176/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20210930125809-cb0fa318a74b h1:wxEMGetGMur3J1xuGLQY7GEQYg9bZxKn3tKo5k/eYcs= k8s.io/utils v0.0.0-20210930125809-cb0fa318a74b/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.25/go.mod h1:Mlj9PNLmG9bZ6BHFwFKDo5afkpWyUISkb9Me0GnK66I= sigs.k8s.io/cluster-api v1.1.2 h1:v00hk4crISOo2sKBhvOq0PC375BPk79Cpflt3Jtn7k8= sigs.k8s.io/cluster-api v1.1.2/go.mod h1:aq0b2tkMHZDTnuLEU7KOZOiQ5Pg82s3vh/KH/X6c/mM= sigs.k8s.io/controller-runtime v0.11.1 h1:7YIHT2QnHJArj/dk9aUkYhfqfK5cIxPOX5gPECfdZLU= sigs.k8s.io/controller-runtime v0.11.1/go.mod h1:KKwLiTooNGu+JmLZGn9Sl3Gjmfj66eMbCQznLP5zcqA= sigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6 h1:fD1pz4yfdADVNfFmcP2aBEtudwUQ1AlLnRBALr33v3s= sigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6/go.mod h1:p4QtZmO4uMYipTQNzagwnNoseA6OxSUutVw05NhYDRs= sigs.k8s.io/kustomize/api v0.10.1/go.mod h1:2FigT1QN6xKdcnGS2Ppp1uIWrtWN28Ms8A3OZUZhwr8= sigs.k8s.io/kustomize/cmd/config v0.10.2/go.mod h1:K2aW7nXJ0AaT+VA/eO0/dzFLxmpFcTzudmAgDwPY1HQ= sigs.k8s.io/kustomize/kustomize/v4 v4.4.1/go.mod h1:qOKJMMz2mBP+vcS7vK+mNz4HBLjaQSWRY22EF6Tb7Io= sigs.k8s.io/kustomize/kyaml v0.13.0/go.mod h1:FTJxEZ86ScK184NpGSAQcfEqee0nul8oLCK30D47m4E= sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= sigs.k8s.io/structured-merge-diff/v4 v4.1.2/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4= sigs.k8s.io/structured-merge-diff/v4 v4.2.0 h1:kDvPBbnPk+qYmkHmSo8vKGp438IASWofnbbUKDE/bv0= sigs.k8s.io/structured-merge-diff/v4 v4.2.0/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4= sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= ================================================ FILE: chapter/14/petstore-operator/hack/boilerplate.go.txt ================================================ /* Copyright 2022. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ ================================================ FILE: chapter/14/petstore-operator/main.go ================================================ /* Copyright 2022. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 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 ( "flag" "os" // Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.) // to ensure that exec-entrypoint and run can make use of them. _ "k8s.io/client-go/plugin/pkg/client/auth" "k8s.io/apimachinery/pkg/runtime" utilruntime "k8s.io/apimachinery/pkg/util/runtime" clientgoscheme "k8s.io/client-go/kubernetes/scheme" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/healthz" "sigs.k8s.io/controller-runtime/pkg/log/zap" petstorev1alpha1 "github.com/PacktPublishing/Go-for-DevOps/chapter/14/petstore-operator/api/v1alpha1" "github.com/PacktPublishing/Go-for-DevOps/chapter/14/petstore-operator/controllers" //+kubebuilder:scaffold:imports ) var ( scheme = runtime.NewScheme() setupLog = ctrl.Log.WithName("setup") ) func init() { utilruntime.Must(clientgoscheme.AddToScheme(scheme)) utilruntime.Must(petstorev1alpha1.AddToScheme(scheme)) //+kubebuilder:scaffold:scheme } func main() { var metricsAddr string var enableLeaderElection bool var probeAddr string flag.StringVar(&metricsAddr, "metrics-bind-address", ":8080", "The address the metric endpoint binds to.") flag.StringVar(&probeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.") flag.BoolVar(&enableLeaderElection, "leader-elect", false, "Enable leader election for controller manager. "+ "Enabling this will ensure there is only one active controller manager.") opts := zap.Options{ Development: true, } opts.BindFlags(flag.CommandLine) flag.Parse() ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts))) mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{ Scheme: scheme, MetricsBindAddress: metricsAddr, Port: 9443, HealthProbeBindAddress: probeAddr, LeaderElection: enableLeaderElection, LeaderElectionID: "9c3fedb1.example.com", }) if err != nil { setupLog.Error(err, "unable to start manager") os.Exit(1) } if err = (&controllers.PetReconciler{ Client: mgr.GetClient(), Scheme: mgr.GetScheme(), }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "Pet") os.Exit(1) } //+kubebuilder:scaffold:builder if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil { setupLog.Error(err, "unable to set up health check") os.Exit(1) } if err := mgr.AddReadyzCheck("readyz", healthz.Ping); err != nil { setupLog.Error(err, "unable to set up ready check") os.Exit(1) } setupLog.Info("starting manager") if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil { setupLog.Error(err, "problem running manager") os.Exit(1) } } ================================================ FILE: chapter/14/workloads/Readme.md ================================================ # Interacting with Kubernetes Workloads In this example, we'll explore how to interact with the Kubernetes API using a Go client to create a namespace, ingress, service, and deployment to run an Nginx hello world service. The service runs locally in KinD streaming logs from the pods in the service to STDOUT. After the service is started, open a browser to http://localhost:8080/hello. You should see the request stream to STDOUT, and you should be greeted with a page describing the request and the server that served it. If you refresh the page, you should see the server name change indicating the requests are load balancing across the 2 pod replicas in the deployment. ## Required tools - docker - kubectl - kind ## Running the code ```shell kind create cluster --name workloads --config kind-config.yaml kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/main/deploy/static/provider/kind/deploy.yaml kubectl wait --namespace ingress-nginx \ --for=condition=ready pod \ --selector=app.kubernetes.io/component=controller \ --timeout=90s go run . ``` ## Deleting the KinD cluster Deleting the KinD cluster will clean up all resources used for this example. ```shell kind delete cluster --name workloads ``` ================================================ FILE: chapter/14/workloads/go.mod ================================================ module github.com/Go-for-DevOps/chapter/14/workloads go 1.17 require ( github.com/Azure/go-autorest/autorest/to v0.4.0 k8s.io/api v0.23.4 k8s.io/apimachinery v0.23.4 k8s.io/client-go v0.23.4 ) require ( github.com/Azure/go-autorest v14.2.0+incompatible // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/go-logr/logr v1.2.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/protobuf v1.5.2 // indirect github.com/google/go-cmp v0.5.5 // indirect github.com/google/gofuzz v1.1.0 // indirect github.com/googleapis/gnostic v0.5.5 // indirect github.com/imdario/mergo v0.3.5 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/spf13/pflag v1.0.5 // indirect golang.org/x/net v0.0.0-20211209124913-491a49abca63 // indirect golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f // indirect golang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e // indirect golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b // indirect golang.org/x/text v0.3.7 // indirect golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/protobuf v1.27.1 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect k8s.io/klog/v2 v2.30.0 // indirect k8s.io/kube-openapi v0.0.0-20211115234752-e816edb12b65 // indirect k8s.io/utils v0.0.0-20211116205334-6203023598ed // indirect sigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6 // indirect sigs.k8s.io/structured-merge-diff/v4 v4.2.1 // indirect sigs.k8s.io/yaml v1.2.0 // indirect ) ================================================ FILE: chapter/14/workloads/go.sum ================================================ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs= github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= github.com/Azure/go-autorest/autorest v0.11.18 h1:90Y4srNYrwOtAgVo3ndrQkTYn6kf1Eg/AjTFJ8Is2aM= github.com/Azure/go-autorest/autorest v0.11.18/go.mod h1:dSiJPy22c3u0OtOKDNttNgqpNFY/GeWa7GH/Pz56QRA= github.com/Azure/go-autorest/autorest/adal v0.9.13/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M= github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= github.com/Azure/go-autorest/autorest/to v0.4.0 h1:oXVqrxakqqV1UZdSazDOPOLvOIz+XA683u8EctwboHk= github.com/Azure/go-autorest/autorest/to v0.4.0/go.mod h1:fE8iZBn7LQR7zH/9XU2NcPR4o9jEImooCeWJcYV/zLE= github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= 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/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 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/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= 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.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= github.com/form3tech-oss/jwt-go v3.2.3+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/getkin/kin-openapi v0.76.0/go.mod h1:660oXbgy5JFMKreazJaQTw7o+X00qeSyhcnluiMv+Xg= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= github.com/go-logr/logr v1.2.0 h1:QK40JKJyMdUDz+h+xvCsru/bJhvG0UxvePV0ufL/AcE= github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= 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.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= 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.4.1/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.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.2/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.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g= github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/gnostic v0.5.1/go.mod h1:6U4PtQXGIEt/Z3h5MAT7FNofLnw9vXk2cUuW7uA/OeU= github.com/googleapis/gnostic v0.5.5 h1:9fHAtK0uDfpveeqqo1hkEZJcFvYXAiCN3UutL8F9xHw= github.com/googleapis/gnostic v0.5.5/go.mod h1:7+EbHbldMins07ALC74bsA81Ovc97DwqyJO1AENw9kA= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imdario/mergo v0.3.5 h1:JboBksRwiiAJWvIYJVo46AfV+IAIKZpfrSzVKj42R4Q= github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.14.0 h1:2mOpI4JVVPBN+WQRa0WKH2eXR+Ey+uK4n7Zj0aYpIQA= github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 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/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= 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-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 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-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/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-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20211209124913-491a49abca63 h1:iocB37TsdFuN6IBRZ+ry36wrkoV51/tl5vOWqkcPGvY= golang.org/x/net v0.0.0-20211209124913-491a49abca63/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f h1:Qmd2pbz05z7z6lm0DrgQVVPuBm92jqujBKMHMOlOQEw= golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= 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-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/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.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/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-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e h1:XMgFehsDnnLGtjvjOfqWSUzt0alpTR1RSEuznObga2c= golang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b h1:9zKuko04nR4gjZ4+DNjHqRlAJqbJETHwiNKDqTfOjfE= golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac h1:7zkz7BUtwNFFqcowJ+RIgu2MaV/MapERkDIy+mwPyjs= golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 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-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200505023115-26f46d2f7ef8/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= 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/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= 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.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= 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.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/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-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= k8s.io/api v0.23.4 h1:85gnfXQOWbJa1SiWGpE9EEtHs0UVvDyIsSMpEtl2D4E= k8s.io/api v0.23.4/go.mod h1:i77F4JfyNNrhOjZF7OwwNJS5Y1S9dpwvb9iYRYRczfI= k8s.io/apimachinery v0.23.4 h1:fhnuMd/xUL3Cjfl64j5ULKZ1/J9n8NuQEgNL+WXWfdM= k8s.io/apimachinery v0.23.4/go.mod h1:BEuFMMBaIbcOqVIJqNZJXGFTP4W6AycEpb5+m/97hrM= k8s.io/client-go v0.23.4 h1:YVWvPeerA2gpUudLelvsolzH7c2sFoXXR5wM/sWqNFU= k8s.io/client-go v0.23.4/go.mod h1:PKnIL4pqLuvYUK1WU7RLTMYKPiIh7MYShLshtRY9cj0= k8s.io/gengo v0.0.0-20210813121822-485abfe95c7c/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= k8s.io/klog/v2 v2.30.0 h1:bUO6drIvCIsvZ/XFgfxoGFQU/a4Qkh0iAlvUR7vlHJw= k8s.io/klog/v2 v2.30.0/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= k8s.io/kube-openapi v0.0.0-20211115234752-e816edb12b65 h1:E3J9oCLlaobFUqsjG9DfKbP2BmgwBL2p7pn0A3dG9W4= k8s.io/kube-openapi v0.0.0-20211115234752-e816edb12b65/go.mod h1:sX9MT8g7NVZM5lVL/j8QyCCJe8YSMW30QvGZWaCIDIk= k8s.io/utils v0.0.0-20210802155522-efc7438f0176/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20211116205334-6203023598ed h1:ck1fRPWPJWsMd8ZRFsWc6mh/zHp5fZ/shhbrgPUxDAE= k8s.io/utils v0.0.0-20211116205334-6203023598ed/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= sigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6 h1:fD1pz4yfdADVNfFmcP2aBEtudwUQ1AlLnRBALr33v3s= sigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6/go.mod h1:p4QtZmO4uMYipTQNzagwnNoseA6OxSUutVw05NhYDRs= sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= sigs.k8s.io/structured-merge-diff/v4 v4.2.1 h1:bKCqE9GvQ5tiVHn5rfn1r+yao3aLQEaLzkkmAkf+A6Y= sigs.k8s.io/structured-merge-diff/v4 v4.2.1/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4= sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q= sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= ================================================ FILE: chapter/14/workloads/kind-config.yaml ================================================ kind: Cluster apiVersion: kind.x-k8s.io/v1alpha4 nodes: - role: control-plane kubeadmConfigPatches: - | kind: InitConfiguration nodeRegistration: kubeletExtraArgs: node-labels: "ingress-ready=true" extraPortMappings: - containerPort: 80 hostPort: 8080 protocol: TCP - containerPort: 443 hostPort: 8081 protocol: TCP ================================================ FILE: chapter/14/workloads/main.go ================================================ package main import ( "context" "flag" "fmt" "os" "os/signal" "path" "path/filepath" "syscall" "time" "github.com/Azure/go-autorest/autorest/to" appv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" netv1 "k8s.io/api/networking/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" "k8s.io/client-go/tools/clientcmd" "k8s.io/client-go/util/homedir" ) func main() { ctx, cancel := context.WithCancel(context.Background()) defer cancel() clientSet := getClientSet() // create a namespace named "foo" and delete it when main exits nsFoo := createNamespace(ctx, clientSet, "foo") defer func() { deleteNamespace(ctx, clientSet, nsFoo) }() // create an nginx deployment named "hello-world" in the nsFoo namespace deployNginx(ctx, clientSet, nsFoo, "hello-world") fmt.Printf("You can now see your running service: http://localhost:8080/hello\n\n") // listen to pod logs from namespace foo listenToPodLogs(ctx, clientSet, nsFoo, "hello-world") // wait for ctrl-c to exit the program waitForExitSignal() } func createNamespace(ctx context.Context, clientSet *kubernetes.Clientset, name string) *corev1.Namespace { fmt.Printf("Creating namespace %q.\n\n", name) ns := &corev1.Namespace{ ObjectMeta: metav1.ObjectMeta{ Name: name, }, } ns, err := clientSet.CoreV1().Namespaces().Create(ctx, ns, metav1.CreateOptions{}) panicIfError(err) return ns } func deleteNamespace(ctx context.Context, clientSet *kubernetes.Clientset, ns *corev1.Namespace) { fmt.Printf("\n\nDeleting namespace %q.\n", ns.Name) panicIfError(clientSet.CoreV1().Namespaces().Delete(ctx, ns.Name, metav1.DeleteOptions{})) } func deployNginx(ctx context.Context, clientSet *kubernetes.Clientset, ns *corev1.Namespace, name string) { deployment := createNginxDeployment(ctx, clientSet, ns, name) waitForReadyReplicas(ctx, clientSet, deployment) createNginxService(ctx, clientSet, ns, name) createNginxIngress(ctx, clientSet, ns, name) } func createNginxDeployment(ctx context.Context, clientSet *kubernetes.Clientset, ns *corev1.Namespace, name string) *appv1.Deployment { var ( matchLabel = map[string]string{"app": "nginx"} objMeta = metav1.ObjectMeta{ Name: name, Namespace: ns.Name, Labels: matchLabel, } ) deployment := &appv1.Deployment{ ObjectMeta: objMeta, Spec: appv1.DeploymentSpec{ Replicas: to.Int32Ptr(2), Selector: &metav1.LabelSelector{MatchLabels: matchLabel}, Template: corev1.PodTemplateSpec{ ObjectMeta: metav1.ObjectMeta{ Labels: matchLabel, }, Spec: corev1.PodSpec{ Containers: []corev1.Container{ { Name: name, Image: "nginxdemos/hello:latest", Ports: []corev1.ContainerPort{ { ContainerPort: 80, }, }, }, }, }, }, }, } deployment, err := clientSet.AppsV1().Deployments(ns.Name).Create(ctx, deployment, metav1.CreateOptions{}) panicIfError(err) return deployment } func waitForReadyReplicas(ctx context.Context, clientSet *kubernetes.Clientset, deployment *appv1.Deployment) { fmt.Printf("Waiting for ready replicas in deployment %q\n", deployment.Name) for { expectedReplicas := *deployment.Spec.Replicas readyReplicas := getReadyReplicasForDeployment(ctx, clientSet, deployment) if readyReplicas == expectedReplicas { fmt.Printf("replicas are ready!\n\n") break } fmt.Printf("replicas are not ready yet. %d/%d\n", readyReplicas, expectedReplicas) time.Sleep(1 * time.Second) } } func getReadyReplicasForDeployment(ctx context.Context, clientSet *kubernetes.Clientset, deployment *appv1.Deployment) int32 { dep, err := clientSet.AppsV1().Deployments(deployment.Namespace).Get(ctx, deployment.Name, metav1.GetOptions{}) panicIfError(err) return dep.Status.ReadyReplicas } func createNginxService(ctx context.Context, clientSet *kubernetes.Clientset, ns *corev1.Namespace, name string) { var ( matchLabel = map[string]string{"app": "nginx"} objMeta = metav1.ObjectMeta{ Name: name, Namespace: ns.Name, Labels: matchLabel, } ) service := &corev1.Service{ ObjectMeta: objMeta, Spec: corev1.ServiceSpec{ Selector: matchLabel, Ports: []corev1.ServicePort{ { Port: 80, Protocol: corev1.ProtocolTCP, Name: "http", }, }, }, } service, err := clientSet.CoreV1().Services(ns.Name).Create(ctx, service, metav1.CreateOptions{}) panicIfError(err) } func createNginxIngress(ctx context.Context, clientSet *kubernetes.Clientset, ns *corev1.Namespace, name string) { var ( prefix = netv1.PathTypePrefix objMeta = metav1.ObjectMeta{ Name: name, Namespace: ns.Name, } ingressPath = netv1.HTTPIngressPath{ PathType: &prefix, Path: "/hello", Backend: netv1.IngressBackend{ Service: &netv1.IngressServiceBackend{ Name: name, Port: netv1.ServiceBackendPort{ Name: "http", }, }, }, } ) ingress := &netv1.Ingress{ ObjectMeta: objMeta, Spec: netv1.IngressSpec{ Rules: []netv1.IngressRule{ { IngressRuleValue: netv1.IngressRuleValue{ HTTP: &netv1.HTTPIngressRuleValue{ Paths: []netv1.HTTPIngressPath{ingressPath}, }, }, }, }, }, } ingress, err := clientSet.NetworkingV1().Ingresses(ns.Name).Create(ctx, ingress, metav1.CreateOptions{}) panicIfError(err) } func listenToPodLogs(ctx context.Context, clientSet *kubernetes.Clientset, ns *corev1.Namespace, containerName string) { // list all the pods in namespace foo podList := listPods(ctx, clientSet, ns) for _, pod := range podList.Items { podName := pod.Name go func() { opts := &corev1.PodLogOptions{ Container: containerName, Follow: true, } podLogs, err := clientSet.CoreV1().Pods(ns.Name).GetLogs(podName, opts).Stream(ctx) panicIfError(err) _, _ = os.Stdout.ReadFrom(podLogs) }() } } func listPods(ctx context.Context, clientSet *kubernetes.Clientset, ns *corev1.Namespace) *corev1.PodList { podList, err := clientSet.CoreV1().Pods(ns.Name).List(ctx, metav1.ListOptions{}) panicIfError(err) fmt.Printf("Listing pods in %q namespace.\n", ns.Name) for _, pod := range podList.Items { fmt.Printf("# Pod: \n## namespace/name: %q\n## spec.containers[0].name: %q\n## spec.containers[0].image: %q\n", path.Join(pod.Namespace, pod.Name), pod.Spec.Containers[0].Name, pod.Spec.Containers[0].Image) } fmt.Printf("\n\n") return podList } func waitForExitSignal() { fmt.Printf("Type ctrl-c to exit\n\n") sigs := make(chan os.Signal, 1) signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) <-sigs } func getClientSet() *kubernetes.Clientset { var kubeconfig *string if home := homedir.HomeDir(); home != "" { kubeconfig = flag.String("kubeconfig", filepath.Join(home, ".kube", "config"), "(optional) absolute path to the kubeconfig file") } else { kubeconfig = flag.String("kubeconfig", "", "absolute path to the kubeconfig file") } flag.Parse() // use the current context in kubeconfig config, err := clientcmd.BuildConfigFromFlags("", *kubeconfig) panicIfError(err) // create the clientSet cs, err := kubernetes.NewForConfig(config) panicIfError(err) return cs } func panicIfError(err error) { if err != nil { panic(err.Error()) } } ================================================ FILE: chapter/15/.gitignore ================================================ .env .ssh bin ================================================ FILE: chapter/15/cloud-init/init.yml ================================================ #cloud-config package_upgrade: true packages: - nginx - golang runcmd: - echo "hello world" ================================================ FILE: chapter/15/cmd/compute/main.go ================================================ package main import ( "bufio" "context" "fmt" "os" "strings" "github.com/joho/godotenv" "github.com/PacktPublishing/Go-for-DevOps/chapter/15/pkg/helpers" "github.com/PacktPublishing/Go-for-DevOps/chapter/15/pkg/mgmt" ) func init() { _ = godotenv.Load() } func main() { subscriptionID := helpers.MustGetenv("AZURE_SUBSCRIPTION_ID") sshPubKeyPath := helpers.MustGetenv("SSH_PUBLIC_KEY_PATH") factory := mgmt.NewVirtualMachineFactory(subscriptionID, sshPubKeyPath) fmt.Println("Staring to build Azure resources...") stack := factory.CreateVirtualMachineStack(context.Background(), "southcentralus") var ( admin = stack.VirtualMachine.Properties.OSProfile.AdminUsername ipAddress = stack.PublicIP.Properties.IPAddress sshIdentityPath = strings.TrimRight(sshPubKeyPath, ".pub") ) fmt.Printf("Connect with: `ssh -i %s %s@%s`\n\n", sshIdentityPath, *admin, *ipAddress) fmt.Println("Press enter to delete the infrastructure.") reader := bufio.NewReader(os.Stdin) _, _ = reader.ReadString('\n') factory.DestroyVirtualMachineStack(context.Background(), stack) } ================================================ FILE: chapter/15/cmd/storage/main.go ================================================ package main import ( "bufio" "context" "fmt" "io/ioutil" "os" "path" "time" "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob" "github.com/joho/godotenv" . "github.com/PacktPublishing/Go-for-DevOps/chapter/15/pkg/helpers" "github.com/PacktPublishing/Go-for-DevOps/chapter/15/pkg/mgmt" ) func init() { _ = godotenv.Load() } func main() { subscriptionID := MustGetenv("AZURE_SUBSCRIPTION_ID") factory := mgmt.NewStorageFactory(subscriptionID) fmt.Println("Staring to build Azure resources...") stack := factory.CreateStorageStack(context.Background(), "southcentralus") uploadBlobs(stack) printSASUris(stack) fmt.Println("Press enter to delete the infrastructure.") reader := bufio.NewReader(os.Stdin) _, _ = reader.ReadString('\n') factory.DestroyStorageStack(context.Background(), stack) } func uploadBlobs(stack *mgmt.StorageStack) { serviceClient := stack.ServiceClient() containerClient, err := serviceClient.NewContainerClient("jd-imgs") HandleErr(err) fmt.Printf("Creating a new container \"jd-imgs\" in the Storage Account...\n") _, err = containerClient.Create(context.Background(), nil) HandleErr(err) fmt.Printf("Reading all files ./blobs...\n") files, err := ioutil.ReadDir("./blobs") HandleErr(err) for _, file := range files { fmt.Printf("Uploading file %q to container jd-imgs...\n", file.Name()) blobClient := HandleErrWithResult(containerClient.NewBlockBlobClient(file.Name())) osFile := HandleErrWithResult(os.Open(path.Join("./blobs", file.Name()))) _ = HandleErrWithResult(blobClient.UploadFile(context.Background(), osFile, azblob.UploadOption{})) } } func printSASUris(stack *mgmt.StorageStack) { serviceClient := stack.ServiceClient() containerClient, err := serviceClient.NewContainerClient("jd-imgs") HandleErr(err) fmt.Printf("\nGenerating readonly links to blobs that expire in 2 hours...\n") files := HandleErrWithResult(ioutil.ReadDir("./blobs")) for _, file := range files { blobClient := HandleErrWithResult(containerClient.NewBlockBlobClient(file.Name())) permissions := azblob.BlobSASPermissions{ Read: true, } now := time.Now().UTC() sasQuery := HandleErrWithResult(blobClient.GetSASToken(permissions, now, now.Add(2*time.Hour))) fmt.Println(blobClient.URL() + "?" + sasQuery.Encode()) } } ================================================ FILE: chapter/15/go.mod ================================================ module github.com/PacktPublishing/Go-for-DevOps/chapter/15 go 1.18 require ( github.com/Azure/azure-sdk-for-go/sdk/azcore v0.23.0 github.com/Azure/azure-sdk-for-go/sdk/azidentity v0.14.0 github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute v0.6.0 github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork v0.4.0 github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v0.4.0 github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v0.6.0 github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v0.4.0 github.com/joho/godotenv v1.4.0 github.com/mitchellh/go-homedir v1.1.0 github.com/yelinaung/go-haikunator v0.0.0-20150320004105-1249cae259af ) require ( github.com/Azure/azure-sdk-for-go/sdk/internal v0.9.2 // indirect github.com/AzureAD/microsoft-authentication-library-for-go v0.4.0 // indirect github.com/golang-jwt/jwt v3.2.1+incompatible // indirect github.com/google/uuid v1.1.1 // indirect github.com/kylelemons/godebug v1.1.0 // indirect github.com/pkg/browser v0.0.0-20210115035449-ce105d075bb4 // indirect golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897 // indirect golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f // indirect golang.org/x/sys v0.0.0-20211019181941-9d821ace8654 // indirect golang.org/x/text v0.3.7 // indirect ) ================================================ FILE: chapter/15/go.sum ================================================ github.com/Azure/azure-sdk-for-go/sdk/azcore v0.23.0 h1:D7l5jspkc4kwBYRWoZE4DQnu6LVpLwDsMZjBKS4wZLQ= github.com/Azure/azure-sdk-for-go/sdk/azcore v0.23.0/go.mod h1:w5pDIZuawUmY3Bj4tVx3Xb8KS96ToB0j315w9rqpAg0= github.com/Azure/azure-sdk-for-go/sdk/azidentity v0.14.0 h1:NVS/4LOQfkBpk+B1VopIzv1ptmYeEskA8w/3K/w7vjo= github.com/Azure/azure-sdk-for-go/sdk/azidentity v0.14.0/go.mod h1:RG0cZndeZM17StwohYclmcXSr4oOJ8b1I5hB8llIc6Y= github.com/Azure/azure-sdk-for-go/sdk/internal v0.9.2 h1:Px2KVERcYEg2Lv25AqC2hVr0xUWaq94wuEObLIkYzmA= github.com/Azure/azure-sdk-for-go/sdk/internal v0.9.2/go.mod h1:CdSJQNNzZhCkwDaV27XV1w48ZBPtxe7mlrZAsPNxD5g= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute v0.6.0 h1:p65zCzT+l0eeC94l+Ax1cMsZyrLKzEN0sNesJKEUeAA= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute v0.6.0/go.mod h1:F1Y6OEs1Q0IoRrlkw9loA1PZ/wnCKNb3kPLkZ4Uqk+s= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/internal v0.3.0 h1:gYG7I0WxtCnHSWOFLbWCHLBWr+yzI5UeU9KuGZajf5U= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork v0.4.0 h1:VNxCIK7KSqUWIRd8f+0NmDfwcLesigWKNffSMJ6uYIo= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork v0.4.0/go.mod h1:AzPq7EbLxPd3Q2v82xnS3ngMYjcEjoAQqfER9JYkbzg= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v0.4.0 h1:TSBK+EXu33WBNrYhWmOecKJAl4z1Z5dtbDmqOzd1zGk= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v0.4.0/go.mod h1:tt77DwGu+r0Ued27YQPhiW8h8YWpYwpfOfi5uRpRMTg= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v0.6.0 h1:ukJ+nvDsWdEWCrlvR3xDJDiHLcTuxHuX7/gErD33T70= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v0.6.0/go.mod h1:flXFQOQ2GUYDHAMwynF6VeCmy5b+nnc/jd5J0njl13o= github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v0.4.0 h1:0nJeKDmB7a1a8RDMjTltahlPsaNlWjq/LpkZleSwINk= github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v0.4.0/go.mod h1:mbwxKc/fW+IkF0GG591MuXw0KuEQBDkeRoZ9vmVJPxg= github.com/AzureAD/microsoft-authentication-library-for-go v0.4.0 h1:WVsrXCnHlDDX8ls+tootqRE87/hL9S/g4ewig9RsD/c= github.com/AzureAD/microsoft-authentication-library-for-go v0.4.0/go.mod h1:Vt9sXTKwMyGcOxSmLDMnGPgqsUg7m8pe215qMLrDXw4= 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/dnaeon/go-vcr v1.1.0 h1:ReYa/UBrRyQdant9B4fNHGoCNKw6qh6P0fsdGmZpR7c= github.com/dnaeon/go-vcr v1.1.0/go.mod h1:M7tiix8f0r6mKKJ3Yq/kqU1OYf3MnfmBWVbPx/yU9ko= github.com/golang-jwt/jwt v3.2.1+incompatible h1:73Z+4BJcrTC+KczS6WvTPvRGOp1WmfEP4Q1lOd9Z/+c= github.com/golang-jwt/jwt v3.2.1+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= github.com/golang-jwt/jwt/v4 v4.2.0 h1:besgBTC8w8HjP6NzQdxwKH9Z5oQMZ24ThTrHp3cZ8eU= github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/joho/godotenv v1.4.0 h1:3l4+N6zfMWnkbPEXKng2o2/MR5mSwTrBih4ZEkkz1lg= github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8= github.com/montanaflynn/stats v0.6.6/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow= github.com/pkg/browser v0.0.0-20210115035449-ce105d075bb4 h1:Qj1ukM4GlMWXNdMBuXcXfz/Kw9s1qm0CLY32QxuSImI= github.com/pkg/browser v0.0.0-20210115035449-ce105d075bb4/go.mod h1:N6UoU20jOqggOuDwUaBQpluzLNDqif3kq9z2wpdYEfQ= 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/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.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= github.com/yelinaung/go-haikunator v0.0.0-20150320004105-1249cae259af h1:q8vvhFu/wCz94XJxzF0hCzfNxIb6E+dTRIsh9vsWAsI= github.com/yelinaung/go-haikunator v0.0.0-20150320004105-1249cae259af/go.mod h1:jGDZu6LyiOPbvJqHW0320zIqCODGq8zYdVS0ZE6Jlso= 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.0.0-20201016220609-9e8e0b390897 h1:pLI5jrR7OSLijeIDcmRxNmw2api+jEfxLoykJVice/E= golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f h1:OfiFi4JbukWwe3lzw+xunroH1mnC1e2Gy5cxNJApiSY= golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 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-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20211019181941-9d821ace8654 h1:id054HUawV2/6IGm2IV8KZQjqtwAOo2CYlOToYqa0d0= golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 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.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= ================================================ FILE: chapter/15/pkg/helpers/helpers.go ================================================ package helpers import ( "context" "log" "os" "time" "github.com/Azure/azure-sdk-for-go/sdk/azcore" "github.com/Azure/azure-sdk-for-go/sdk/azcore/arm" armruntime "github.com/Azure/azure-sdk-for-go/sdk/azcore/arm/runtime" "github.com/Azure/azure-sdk-for-go/sdk/azidentity" ) const ( DefaultPollingFreq = 10 * time.Second ) type ClientBuilderFunc[T any] func(string, azcore.TokenCredential, *arm.ClientOptions) (*T, error) func MustGetenv(key string) string { val := os.Getenv(key) if val == "" { log.Fatalf("please add your %s to the .env file", key) } return val } func BuildClient[T any](subID string, cred *azidentity.DefaultAzureCredential, builderFunc ClientBuilderFunc[T]) *T { return HandleErrWithResult(builderFunc(subID, cred, nil)) } func HandleErrPoller[T any](ctx context.Context, poller *armruntime.Poller[T]) T { res, err := poller.PollUntilDone(ctx, DefaultPollingFreq) HandleErr(err) return res } func HandleErrWithResult[T any](result T, err error) T { HandleErr(err) return result } func HandleErr(err error) { if err != nil { panic(err) } } ================================================ FILE: chapter/15/pkg/mgmt/compute.go ================================================ package mgmt import ( "context" "encoding/base64" "fmt" "io/ioutil" "time" "github.com/Azure/azure-sdk-for-go/sdk/azcore" "github.com/Azure/azure-sdk-for-go/sdk/azcore/arm" "github.com/Azure/azure-sdk-for-go/sdk/azcore/to" "github.com/Azure/azure-sdk-for-go/sdk/azidentity" "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute" "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork" "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources" "github.com/mitchellh/go-homedir" "github.com/yelinaung/go-haikunator" . "github.com/PacktPublishing/Go-for-DevOps/chapter/15/pkg/helpers" ) var ( haiku = haikunator.New(time.Now().UnixNano()) ) type VirtualMachineFactory struct { subscriptionID string sshPubKeyPath string cred azcore.TokenCredential groupsClient *armresources.ResourceGroupsClient vmClient *armcompute.VirtualMachinesClient vnetClient *armnetwork.VirtualNetworksClient subnetClient *armnetwork.SubnetsClient nicClient *armnetwork.InterfacesClient nsgClient *armnetwork.SecurityGroupsClient pipClient *armnetwork.PublicIPAddressesClient } // NewVirtualMachineFactory instantiates an Azure VirtualMachine factory func NewVirtualMachineFactory(subscriptionID, sshPubKeyPath string) *VirtualMachineFactory { cred := HandleErrWithResult(azidentity.NewDefaultAzureCredential(nil)) return &VirtualMachineFactory{ cred: cred, subscriptionID: subscriptionID, sshPubKeyPath: sshPubKeyPath, groupsClient: BuildClient(subscriptionID, cred, armresources.NewResourceGroupsClient), vmClient: BuildClient(subscriptionID, cred, armcompute.NewVirtualMachinesClient), vnetClient: BuildClient(subscriptionID, cred, armnetwork.NewVirtualNetworksClient), subnetClient: BuildClient(subscriptionID, cred, armnetwork.NewSubnetsClient), nsgClient: BuildClient(subscriptionID, cred, armnetwork.NewSecurityGroupsClient), nicClient: BuildClient(subscriptionID, cred, armnetwork.NewInterfacesClient), pipClient: BuildClient(subscriptionID, cred, armnetwork.NewPublicIPAddressesClient), } } type VirtualMachineStack struct { Location string sshKeyPath string name string ResourceGroup armresources.ResourceGroup VirtualNetwork armnetwork.VirtualNetwork SecurityGroup armnetwork.SecurityGroup VirtualMachine armcompute.VirtualMachine NetworkInterface armnetwork.Interface PublicIP armnetwork.PublicIPAddress } // CreateVirtualMachineStack creates a virtual machine and networking within a resource group func (vmf *VirtualMachineFactory) CreateVirtualMachineStack(ctx context.Context, location string) *VirtualMachineStack { stack := &VirtualMachineStack{ Location: location, name: haiku.Haikunate(), sshKeyPath: HandleErrWithResult(homedir.Expand(vmf.sshPubKeyPath)), } stack.ResourceGroup = vmf.createResourceGroup(ctx, stack.name, stack.Location) stack.SecurityGroup = vmf.createSecurityGroup(ctx, stack.name, stack.Location) stack.VirtualNetwork = vmf.createVirtualNetwork(ctx, stack) stack.VirtualMachine = vmf.createVirtualMachine(ctx, stack) stack.NetworkInterface = vmf.getFirstNetworkInterface(ctx, stack) stack.PublicIP = vmf.getPublicIPAddress(ctx, stack) return stack } // DestroyVirtualMachineStack deletes a virtual machine and networking within a resource group. // This function does not wait for completion. Once the delete operation is accepted, the function returns. func (vmf *VirtualMachineFactory) DestroyVirtualMachineStack(ctx context.Context, vmStack *VirtualMachineStack) { _, err := vmf.groupsClient.BeginDelete(ctx, vmStack.name, nil) HandleErr(err) } // createResourceGroup creates an Azure resource by name and in a given location func (vmf *VirtualMachineFactory) createResourceGroup(ctx context.Context, name, location string) armresources.ResourceGroup { param := armresources.ResourceGroup{ Location: to.Ptr(location), } fmt.Printf("Building an Azure Resource Group named %q...\n", name) res, err := vmf.groupsClient.CreateOrUpdate(ctx, name, param, nil) HandleErr(err) return res.ResourceGroup } // createVirtualNetwork creates an Azure Virtual Network with a 10.0.0.0/16 CIDR with a 10.0.0.0/24 subnet func (vmf *VirtualMachineFactory) createVirtualNetwork(ctx context.Context, vmStack *VirtualMachineStack) armnetwork.VirtualNetwork { param := armnetwork.VirtualNetwork{ Location: to.Ptr(vmStack.Location), Name: to.Ptr(vmStack.name + "-vnet"), Properties: &armnetwork.VirtualNetworkPropertiesFormat{ AddressSpace: &armnetwork.AddressSpace{ AddressPrefixes: []*string{to.Ptr("10.0.0.0/16")}, }, Subnets: []*armnetwork.Subnet{ { Name: to.Ptr("subnet1"), Properties: &armnetwork.SubnetPropertiesFormat{ AddressPrefix: to.Ptr("10.0.0.0/24"), NetworkSecurityGroup: &vmStack.SecurityGroup, }, }, }, }, } fmt.Printf("Building an Azure Virtual Network named %q...\n", *param.Name) poller, err := vmf.vnetClient.BeginCreateOrUpdate(ctx, vmStack.name, *param.Name, param, nil) HandleErr(err) res := HandleErrPoller(ctx, poller) return res.VirtualNetwork } // createSecurityGroup creates an Azure Network Security Group to allow SSH on port 22 func (vmf *VirtualMachineFactory) createSecurityGroup(ctx context.Context, name, location string) armnetwork.SecurityGroup { param := armnetwork.SecurityGroup{ Location: to.Ptr(location), Name: to.Ptr(name + "-nsg"), Properties: &armnetwork.SecurityGroupPropertiesFormat{ SecurityRules: []*armnetwork.SecurityRule{ { Name: to.Ptr("ssh"), Properties: &armnetwork.SecurityRulePropertiesFormat{ Access: to.Ptr(armnetwork.SecurityRuleAccessAllow), Direction: to.Ptr(armnetwork.SecurityRuleDirectionInbound), Protocol: to.Ptr(armnetwork.SecurityRuleProtocolAsterisk), Description: to.Ptr("allow ssh on 22"), DestinationAddressPrefix: to.Ptr("*"), DestinationPortRange: to.Ptr("22"), Priority: to.Ptr(int32(101)), SourcePortRange: to.Ptr("*"), SourceAddressPrefix: to.Ptr("*"), }, }, }, }, } fmt.Printf("Building an Azure Network Security Group named %q...\n", *param.Name) poller, err := vmf.nsgClient.BeginCreateOrUpdate(ctx, name, *param.Name, param, nil) HandleErr(err) res := HandleErrPoller(ctx, poller) return res.SecurityGroup } // createVirtualMachine creates an Azure Virtual Machine func (vmf *VirtualMachineFactory) createVirtualMachine(ctx context.Context, vmStack *VirtualMachineStack) armcompute.VirtualMachine { param := linuxVM(vmStack) fmt.Printf("Building an Azure Virtual Machine named %q...\n", *param.Name) poller, err := vmf.vmClient.BeginCreateOrUpdate(ctx, vmStack.name, *param.Name, param, nil) HandleErr(err) res := HandleErrPoller(ctx, poller) return res.VirtualMachine } // getFirstNetworkInterface returns the first network interface on the vmStack Virtual Machine func (vmf *VirtualMachineFactory) getFirstNetworkInterface(ctx context.Context, vmStack *VirtualMachineStack) armnetwork.Interface { iface := vmStack.VirtualMachine.Properties.NetworkProfile.NetworkInterfaces[0] parsed := HandleErrWithResult(arm.ParseResourceID(*iface.ID)) fmt.Printf("Fetching the first Network Interface named %q connected to the VM...\n", parsed.Name) res := HandleErrWithResult(vmf.nicClient.Get(ctx, vmStack.name, parsed.Name, nil)) return res.Interface } // getFirstNetworkInterface returns the first network interface on the vmStack Virtual Machine func (vmf *VirtualMachineFactory) getPublicIPAddress(ctx context.Context, vmStack *VirtualMachineStack) armnetwork.PublicIPAddress { pipName := vmStack.NetworkInterface.Properties.IPConfigurations[0].Properties.PublicIPAddress.Name fmt.Printf("Fetching the Public IP Address named %q connected to the VM...\n", *pipName) res := HandleErrWithResult(vmf.pipClient.Get(ctx, vmStack.name, *pipName, nil)) return res.PublicIPAddress } // linuxVM builds a Linux Virtual Machine structure func linuxVM(vmStack *VirtualMachineStack) armcompute.VirtualMachine { return armcompute.VirtualMachine{ Location: to.Ptr(vmStack.Location), Name: to.Ptr(vmStack.name + "-vm"), Properties: &armcompute.VirtualMachineProperties{ HardwareProfile: &armcompute.HardwareProfile{ VMSize: to.Ptr(armcompute.VirtualMachineSizeTypesStandardD2SV3), }, NetworkProfile: networkProfile(vmStack), OSProfile: linuxOSProfile(vmStack), StorageProfile: &armcompute.StorageProfile{ ImageReference: &armcompute.ImageReference{ Publisher: to.Ptr("Canonical"), Offer: to.Ptr("UbuntuServer"), SKU: to.Ptr("18.04-LTS"), Version: to.Ptr("latest"), }, }, }, } } // networkProfile builds a Virtual Machine network profile requesting a public IP func networkProfile(vmStack *VirtualMachineStack) *armcompute.NetworkProfile { firstSubnet := vmStack.VirtualNetwork.Properties.Subnets[0] return &armcompute.NetworkProfile{ NetworkAPIVersion: to.Ptr(armcompute.NetworkAPIVersionTwoThousandTwenty1101), NetworkInterfaceConfigurations: []*armcompute.VirtualMachineNetworkInterfaceConfiguration{ { Name: to.Ptr(vmStack.name + "-nic"), Properties: &armcompute.VirtualMachineNetworkInterfaceConfigurationProperties{ IPConfigurations: []*armcompute.VirtualMachineNetworkInterfaceIPConfiguration{ { Name: to.Ptr(vmStack.name + "-nic-conf"), Properties: &armcompute.VirtualMachineNetworkInterfaceIPConfigurationProperties{ Primary: to.Ptr(true), Subnet: &armcompute.SubResource{ ID: firstSubnet.ID, }, PublicIPAddressConfiguration: &armcompute.VirtualMachinePublicIPAddressConfiguration{ Name: to.Ptr(vmStack.name + "-pip"), Properties: &armcompute.VirtualMachinePublicIPAddressConfigurationProperties{ PublicIPAllocationMethod: to.Ptr(armcompute.PublicIPAllocationMethodStatic), PublicIPAddressVersion: to.Ptr(armcompute.IPVersionsIPv4), }, }, }, }, }, Primary: to.Ptr(true), }, }, }, } } // linuxOSProfile creates an Azure VM OS profile with only SSH access for the devops admin user func linuxOSProfile(vmStack *VirtualMachineStack) *armcompute.OSProfile { sshKeyData := HandleErrWithResult(ioutil.ReadFile(vmStack.sshKeyPath)) cloudInitContent := HandleErrWithResult(ioutil.ReadFile("./cloud-init/init.yml")) b64EncodedInitScript := base64.StdEncoding.EncodeToString(cloudInitContent) return &armcompute.OSProfile{ AdminUsername: to.Ptr("devops"), ComputerName: to.Ptr(vmStack.name), CustomData: to.Ptr(b64EncodedInitScript), LinuxConfiguration: &armcompute.LinuxConfiguration{ DisablePasswordAuthentication: to.Ptr(true), SSH: &armcompute.SSHConfiguration{ PublicKeys: []*armcompute.SSHPublicKey{ { Path: to.Ptr("/home/devops/.ssh/authorized_keys"), KeyData: to.Ptr(string(sshKeyData)), }, }, }, }, } } ================================================ FILE: chapter/15/pkg/mgmt/storage.go ================================================ package mgmt import ( "context" "fmt" "strings" "github.com/Azure/azure-sdk-for-go/sdk/azcore" "github.com/Azure/azure-sdk-for-go/sdk/azcore/to" "github.com/Azure/azure-sdk-for-go/sdk/azidentity" "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources" "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage" "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob" . "github.com/PacktPublishing/Go-for-DevOps/chapter/15/pkg/helpers" ) type StorageStack struct { Location string name string Cred azcore.TokenCredential ResourceGroup armresources.ResourceGroup Account armstorage.Account AccountKey *armstorage.AccountKey } type StorageFactory struct { subscriptionID string cred azcore.TokenCredential groupsClient *armresources.ResourceGroupsClient storageClient *armstorage.AccountsClient } // NewStorageFactory instantiates an Azure Storage factory for building an Azure Storage playground func NewStorageFactory(subscriptionID string) *StorageFactory { cred := HandleErrWithResult(azidentity.NewDefaultAzureCredential(nil)) return &StorageFactory{ cred: cred, subscriptionID: subscriptionID, groupsClient: BuildClient(subscriptionID, cred, armresources.NewResourceGroupsClient), storageClient: BuildClient(subscriptionID, cred, armstorage.NewAccountsClient), } } func (sf *StorageFactory) CreateStorageStack(ctx context.Context, location string) *StorageStack { stack := &StorageStack{ name: haiku.Haikunate(), } stack.ResourceGroup = sf.createResourceGroup(ctx, stack.name, location) stack.Account = sf.createStorageAccount(ctx, stack.name, location) stack.AccountKey = sf.getPrimaryAccountKey(ctx, stack) return stack } func (sf *StorageFactory) DestroyStorageStack(ctx context.Context, stack *StorageStack) { _, err := sf.groupsClient.BeginDelete(ctx, stack.name, nil) HandleErr(err) } // createResourceGroup creates an Azure resource by name and in a given location func (sf *StorageFactory) createResourceGroup(ctx context.Context, name, location string) armresources.ResourceGroup { param := armresources.ResourceGroup{ Location: to.Ptr(location), } fmt.Printf("Building an Azure Resource Group named %q...\n", name) res, err := sf.groupsClient.CreateOrUpdate(ctx, name, param, nil) HandleErr(err) return res.ResourceGroup } // createStorageAccount creates an Azure Storage Account func (sf *StorageFactory) createStorageAccount(ctx context.Context, name, location string) armstorage.Account { param := armstorage.AccountCreateParameters{ Location: to.Ptr(location), Kind: to.Ptr(armstorage.KindBlockBlobStorage), SKU: &armstorage.SKU{ Name: to.Ptr(armstorage.SKUNamePremiumLRS), Tier: to.Ptr(armstorage.SKUTierPremium), }, } accountName := strings.Replace(name, "-", "", -1) fmt.Printf("Building an Azure Storage Account named %q...\n", accountName) poller, err := sf.storageClient.BeginCreate(ctx, name, accountName, param, nil) HandleErr(err) res := HandleErrPoller(ctx, poller) return res.Account } func (sf *StorageFactory) getPrimaryAccountKey(ctx context.Context, stack *StorageStack) *armstorage.AccountKey { fmt.Printf("Fetching the Azure Storage Account shared key...\n") res, err := sf.storageClient.ListKeys(ctx, stack.name, *stack.Account.Name, nil) HandleErr(err) return res.Keys[0] } func (ss *StorageStack) ServiceClient() *azblob.ServiceClient { cred := HandleErrWithResult(azblob.NewSharedKeyCredential(*ss.Account.Name, *ss.AccountKey.Value)) blobURI := *ss.Account.Properties.PrimaryEndpoints.Blob client, err := azblob.NewServiceClientWithSharedKey(blobURI, cred, nil) HandleErr(err) return client } ================================================ FILE: chapter/15/readme.md ================================================ # Programming the Cloud This chapter illustrates the basics of manipulating and using cloud infrastructure. We build two stacks of infrastructure, a Virtual Machine and related infra, and a blob storage account and related infra. Through this code, we can learn about the Azure control plane (Azure Resource Manager) and the Azure Storage blob data plane. **NOTE:** Please DO NOT handle errors like shown in these examples. The error handling is abbreviated to make the code more concise for readability in the book. Panic is not your friend! ## Tools needed - Azure CLI ## Getting Started The following steps will help you get started running the examples. Each of the steps should be run from the root directory of this chapter. ### Generating SSH keys Lets create an SSH key to use for logging into our Virtual Machine. ```shell $ mkdir .ssh $ ssh-keygen -t rsa -b 4096 Generating public/private rsa key pair. Enter file in which to save the key (/Users/user/.ssh/id_rsa): ./.ssh/id_rsa Enter passphrase (empty for no passphrase): Enter same passphrase again: Your identification has been saved in ./.ssh/id_rsa Your public key has been saved in ./.ssh/id_rsa.pub The key fingerprint is: SHA256:WtZQylkZaSjC24LWDw78wqqz8PJm+1eas54xy6Dk0ws user@computer The key's randomart image is: +---[RSA 4096]----+ | . .++ | | o ...=+ | | . o + .=. | | = = . o | | o + + S . | | o o .+. | |..Eo. ++ | |=++o.o== | |+B*+o+*o | +----[SHA256]-----+ ``` ### Login to the Azure CLI Now that we have our SSH key setup, lets login to the Azure CLI. ```shell az login ``` ### Create .env file The following command will create a .env file which is used to load environment vars in the examples. ```shell echo -e "AZURE_SUBSCRIPTION_ID=$(az account show --query 'id' -o tsv)\nSSH_PUBLIC_KEY_PATH=./.ssh/id_rsa.pub" >> .env ``` ## Run the examples Each of the examples below when executed will describe what operations are taking place and prompt you to interact with the infrastructure provisioned. Once done playing with the provisioned infra, just press enter in the terminal and the infrastructure will be destroyed. ### Building an Azure Virtual Machine and related infrastructure This example will build an Azure Resource Group, networking infra, and a Virtual Machine. After the VM is built, you can SSH into the machine and explore. The provisioned Virtual Machine runs the cloud-init provisioning script in `./cloud-init/init.yml` upon creation. ```shell go run ./cmd/compute/main.go ``` ### Building an Azure Storage account, related infra, and storing files This example will build an Azure Resource Group and a blob Storage Account which we will use to store some local images from the `./blobs` directory in the private cloud blob store. The example will then print signed URIs which will grant limited access to the images stored in the storage account. ```shell go run ./cmd/storage/main.go ``` ================================================ FILE: chapter/16/workflow/README.md ================================================ # A Generic Workflow Service ![Diskerase runthrough](docs/images/client_runthrough.gif) (Our diskerase client running a workflow on the server) ## Introduction The example code layed out in this directory represents a generic workflow execution service. This service receives a protocol buffer to a gRPC service that represents the type of work we want to do (a WorkReq). This is another example of separating the work to be done into two parts: * Creation of the work in one program * Execution of work in another This allows centralization of all work done into a single system that can have security, emergency stop capabilities, policies, ... in a single location. Clients that create work can have their own logic and tests. This benefits with: * Central place to create reusable components * Central policy enforcement * One system authorized for changes instead of multiple * Work logic is a separate system from work execution * One place to stop bad things when they are occuring The work is defined in `Block`s with one block executed at at time. Inside the `Block`s are `Job`s, which are the actions that are taken. Those will be executed concurrently within some rate limit you define for the `Block`. Each `WorkReq` that is sent to the service is checked against a set of policies. If no policies are defined, the `WorkReq` is rejected. If the `WorkReq` violates a policy, it is rejected. Policies can be used to sanity check a `WorkReq`. Once a `WorkReq` is received, a unique ID is generated and returned to the client. To execute that `WorkReq`, a second call to the server is made. The server provides an RPC endoint to recover the status of the `WorkReq` for watching workflows execute. `Job`s and `Policies` can be added to the system to expand its capabilities. We include some sample data that is used to represents "sites", or places where machines are located. We also include data that represents "machines" at those sites. These obey some naming conventions and I have included the generators I used to make this fake data. These stand in for what would probably be a database or services that would hold authoritative information. Finally I include a client that build `WorkReq` protocol buffers and call the service for a sample satellite disk erasure. You can use this to test that policies such as the token bucket work. You can alter these to try to defy the policies on the server. ## What this isn't This is an example of a generic workflow system to demonstrate concepts from our chapter on "Designing for Chaos". This isn't a production quality service. If it has tests, they are not comprehensive. Unfortunately I have another full time job, so testing suffered for these book examples (something I would not do in my real job. Tests, tests and more tests!). Other things that make it non-production quality: * If we have a server restart, we cannot resume running workflows, we leave half eaten carcasses of workflows just laying around * There is no security, so anyone could call this service. By default it starts on 127.0.0.1:8080 and doesn't have Jobs that do anything bad, but if you decide to change that, you need security * Backend storage is local files in a temp directory * Failures do not have some maximum count, they only stop work if a Job decideds they are fatal * We don't write creations, start and end times * There is no web interface * Didn't provide a workflow killer except through emergency stop * No pause capabilities * No workflow cloning tools * ... The one example workflow I put in here is a diskerase for satellite datacenters. The `diskErase` `Job` isn't real, it just sleeps. The other jobs are simply looking at files representing information about fake sites and machines. These jobs could be made real, but for this demo I didn't want to actually mutate anything real. You could turn this into a real system, but it would need some more bells and whistles. This is a very lightweight version of a system I developed at Google. That service could handle service failures, restarts, horizontally scale and lots had lots of helpful packages... This is not that system. ## Structure overview ``` ├── client ├── configs ├── data │ ├── generators │ │ └── mk │ └── packages │ └── sites ├── internal │ ├── es │ ├── policy │ │ ├── config │ │ └── register │ │ ├── restrictjobtypes │ │ ├── sameargs │ │ └── startorend │ ├── service │ │ ├── executor │ │ └── jobs │ │ └── register │ │ ├── diskerase │ │ ├── sleep │ │ ├── tokenbucket │ │ └── validatedecom │ └── token ├── proto └── samples └── diskerase └── cmd ``` * `client/` contains a client library for talking to the service * `configs/` contains server configuration files, like our policies and emergency stop * `data/` contains fake data related to fake datacenters and machines * `generators/` has programs that generate our fake data * `packages/` has packages for reading our fake data * `internal/` contains the server's internal packages * `es/` provides a package for reading emergency stop data * `policy/` defines our policy engine and registered policies * `config/` has a policy configuration file reader * `register/` has a policy register and sub-directories containing policies in the system * `service/` contains the service implementation * `executor/` holds the main execution engine for all workflows * `jobs` contains our job execution engine and all defined jobs in the system * `register/` has a job regiter and sub-directories containing jobs defined for the system * `token/` has a token bucket implemention * `proto/` has the protocol buffer implementations used in the service, including how to define a workflow request * `samples/` contains sample workflow creation programs that can submit to the workflow service * `diskerase/` contains a client for creating satellite disk erase workflows for the service to execute ## Finding Jobs that are available The Jobs you can call are defined in: `internal/service/jobs/register/...` Each file header in the directory will give informations such as: ``` Register name: "diskErase" Args: "machine"(mandatory): The name of the machine, like "aa01" or "ab02" "site"(mandatory): The name of the site, like "aaa" or "aba" Result: Erases a disk on a machine, except this is a demo, so it really just sleeps for 30 seconds. ``` This let's you know what arguements to use with a `Job` you define. So if this was a `Job` I wanted to call, I might do: ```go job := &pb.Job{ Name: "diskErase", Args: map[string]string{ "machine": "aa01", "site": "aba02", } } ``` You can see the `samples/diskerase` sample program to see a client program in action. ## Where to find policies All policy implementations are define at: `internal/policy/register/...` In each file you will see a call called: `policy.Register("startOrEnd", p)` where "startOrEnd" is the name of the policy. The `struct` called `Settings` will give all the settings for a policy to be applied to a workflow. Policies to apply to a workflow are defined in: `configs/policies.json` You must have a policy entry for every type of `WorkReq` you want to submit inside `configs/policies.json`. This is checked against `WorkReq.Name`. ## A satellite disk erasure client You can find our example client that submits a datacenter satellite to have its disks erased at: `samples/diskerase` The following are instructions on running our `diskerase` client against the workflow system (remember that this code doesn't actually erase any disks or do any real work, we are just faking it). * Open a terminal * Enter the `workflow/` directory * Type: `go run workflow.go` You should see some startup information like so: ``` Registered Job: diskErase Registered Job: sleep Registered Job: tokenBucket Registered Job: validateDecom Registered Policy: restrictJobTypes Registered Policy: sameArgs Registered Policy: startOrEnd Workflow Storage is at: /var/folders/rd/hbhb8s197633_f8ncy6fmpqr0000gn/T/workflows Server started ``` Now that we have the service running, let's start a satellite workflow: * Open another terminal * Enter the `workflow/samples/diskerase/` directory * Type: `go run diskerase.go eraseSatellite aap` This asks our `diskerase` client to create a `pb.WorkReq` representing a disk erasure for cluster "aap". Our client will do some pre-checks and then create the `pb.WorkReq`, submit it to the system and then ask the system to execute it. A file: "submit.log" will be created that holds any UUIDs for workflow you create. It will then display a message like so: ![Diskerase status](docs/images/diskerase_status.png) Once it has finished, you can access the full proto JSON output with: `go run diskerase.go statusProto [workflow id]` Or if you cancel out and want to resume watching, you can do: `go run diskerase.go status [workflow id]` ## Some cool things to try Now that you have seen the client and server, you can watch some of the concepts from the chaos chapter in action by trying to do things that you shouldn't. Here are a few things to try out: ### Run a diskerase and then try to run another diskerase This is the simplest thing to try, as it requires no changes to files. This should trigger the token bucket in the pre-conidtions block and fail. Only 1 of these can be triggered per hour. ### Run a diskerase on a non-satellite datacenter This should fail because the tool only supports satellite datacenter types. ### Run a diskerase on a non-decom'd satellite This should fail various pre-checks because it is not in a decom state. ### Make changes to es.json Change `configs/es.json` so that the `diskErase` entry has `stop` instead of `go` while running a workflow. `es.go` checks that file every 10 seconds and the display refreshes every 10 seconds. You can watch the workflow stop. You can try other things here like erasing the entry, which will have the same effect (or not having it in the right JSON format). You can erase the entry and try submitting a new workflow, which will get denied. ### Make changes to the diskerase Jobs You can change the Jobs that the `diskerase` client creates. You could add machines not in the same site, or remove precondition checks. These should violate policies and reject your jobs. ### Change the diskerase pb.WorkReq.Name This should cause the service to not have a policy and automatically reject the workflow. ================================================ FILE: chapter/16/workflow/client/client.go ================================================ /* Package client provides access to the workflow service. You can use this client to: Submit a *pb.WorkReq to the service Execute a *pb.WorkReq previously submitted Get the status of a *pb.WorkReq See the README.md in the root workflow/ directory for more information. SECURITY NOTICE: As this is an example for a book and is meant to be run in a secure environment, we use grpc.WithInsecure(). Aka, not production ready. */ package client import ( "context" "sync" "time" "github.com/cenkalti/backoff" "github.com/sony/gobreaker" "google.golang.org/grpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" "google.golang.org/protobuf/proto" pb "github.com/PacktPublishing/Go-for-DevOps/chapter/16/workflow/proto" ) // Workflow represents our Workflow client. It uses a builtin circuit breaker wrapping // an exponential backoff. The context passed to any method should be the maximum time you are // willing to wait including all retries. If a call returns an error because the context expires, // the circuit breaker will trip. Errors without gRPC status codes or with status codes of // DeadlineExceeded or ResourceExhausted are considered fatal errors. Fatal errors do not // get retries and do not trip the circuit breaker. type Workflow struct { conn *grpc.ClientConn client pb.WorkflowClient cb *gobreaker.CircuitBreaker retryPool sync.Pool } // New creates a new Workflow instance. func New(addr string) (*Workflow, error) { conn, err := grpc.Dial(addr, grpc.WithInsecure()) if err != nil { return nil, err } return &Workflow{ conn: conn, client: pb.NewWorkflowClient(conn), cb: gobreaker.NewCircuitBreaker( gobreaker.Settings{ MaxRequests: 1, Interval: 20 * time.Second, Timeout: 30 * time.Second, ReadyToTrip: func(c gobreaker.Counts) bool { return c.ConsecutiveFailures > 1 }, IsSuccessful: func(err error) bool { if isFatal(err) { return true } return false }, }, ), retryPool: sync.Pool{ New: func() interface{} { return backoff.NewExponentialBackOff() }, }, }, nil } // Submit submits a pb.WorkReq to the server. If successful an ID will be returned that // represents the pb.WorkReq on the server. This can be used in an Exec() call to execute // the pb.WorkReq. func (w *Workflow) Submit(ctx context.Context, req *pb.WorkReq) (string, error) { caller := func(ctx context.Context, req proto.Message) (proto.Message, error) { r := req.(*pb.WorkReq) return w.client.Submit(ctx, r) } resp, err := w.call(ctx, req, caller) if err != nil { return "", err } return resp.(*pb.WorkResp).Id, nil } // Exec causes the server to execute a pb.WorkReq that was previously accepted by the server // via a Submit() call. func (w *Workflow) Exec(ctx context.Context, id string) error { caller := func(ctx context.Context, req proto.Message) (proto.Message, error) { r := req.(*pb.ExecReq) return w.client.Exec(ctx, r) } _, err := w.call(ctx, &pb.ExecReq{Id: id}, caller) if err != nil { return err } return nil } // Status returns the status of a pb.WorkReq that was submitted to the server via the Submit() // call. func (w *Workflow) Status(ctx context.Context, id string) (*pb.StatusResp, error) { caller := func(ctx context.Context, req proto.Message) (proto.Message, error) { r := req.(*pb.StatusReq) return w.client.Status(ctx, r) } resp, err := w.call(ctx, &pb.StatusReq{Id: id}, caller) if err != nil { return nil, err } return resp.(*pb.StatusResp), nil } type grpcCall = func(context.Context, proto.Message) (proto.Message, error) // call generically calls any non-streaming gRPC endpoint that is contained within "call". // This method will default to a timeout of 30 seconds unless Context has a deadline. // We also have use a circuit breaker surrounding an exponential backoff on our calls. We // only do retries when we receive a generic error (one that does not contain a gRPC status code) // or status code: DeadlineExceeded, ResourceExhausted. func (w *Workflow) call(ctx context.Context, req proto.Message, call grpcCall) (proto.Message, error) { if _, ok := ctx.Deadline(); !ok { var cancel context.CancelFunc ctx, cancel = context.WithTimeout(ctx, 30*time.Second) defer cancel() } var resp proto.Message op := func() error { var err error resp, err = call(ctx, req) return err } retry := func() error { back := w.retryPool.Get().(*backoff.ExponentialBackOff) defer w.retryPool.Put(back) defer back.Reset() // Execute our op() func until the context is cancelled with exponential backoff. for { err := op() if err == nil { return nil } if isFatal(err) { return err } // We manually are going to do our backoff instead of using backoff.Retry() // because we want to differentiate between fatal errors that should never // get retried and errors that just need backing off. backoff := back.NextBackOff() deadline, _ := ctx.Deadline() if time.Now().Add(backoff).After(deadline) { // This happens when our next retry would happen after our deadline. return context.DeadlineExceeded } // Sleep before our next retry on whatever backoff we were given or // until our context is cancelled by the user. timer := time.NewTimer(backoff) select { case <-ctx.Done(): timer.Stop() return err case <-timer.C: } } } _, err := w.cb.Execute( func() (interface{}, error) { return nil, retry() }, ) if err != nil { return nil, err } return resp, nil } func isFatal(err error) bool { if s, ok := status.FromError(err); ok { switch s.Code() { case codes.DeadlineExceeded, codes.ResourceExhausted: return false } } return true } ================================================ FILE: chapter/16/workflow/configs/README ================================================ This directory contains configuration files for running our service. ================================================ FILE: chapter/16/workflow/configs/es.json ================================================ { "Name": "SatelliteDiskErase", "Status": "go" } ================================================ FILE: chapter/16/workflow/configs/policies.json ================================================ { "Name": "SatelliteDiskErase", "Policies": [ { "Name": "restrictJobTypes", "Settings": { "AllowedJobs": [ "validateDecom", "diskErase", "sleep", "tokenBucket" ] } }, { "Name": "sameArgs", "Settings": { "Jobs": { "diskErase": [ "Site" ] } } }, { "Name": "startOrEnd", "Settings": { "JobName": "tokenBucket", "MustArgs": { "bucket": "diskEraseSatellite" }, "Start": true, "AllowedBeforeOrAfter": [ "validateDecom" ] } } ] } ================================================ FILE: chapter/16/workflow/data/README.md ================================================ # Data directory This simply contains some sample data that represents fictional datacenter sites and fictional machine information that is contained in those sites. Two subdirectories are contained here: * `generators/` contains programs that are used to generate these data files * `packages/` contains packages that access these data files In the real world the data would be in some service that you query for this information. But for our purposes, this gives us some fake data to be able to generate work to do. ================================================ FILE: chapter/16/workflow/data/generators/README ================================================ This directory simply contains programs used to generate example data files. ================================================ FILE: chapter/16/workflow/data/generators/mk/README ================================================ This program simply creates a file that contains data about our sites and machines that are in those sites. There are sites that are not satellites in the sites file, but no correpsonding machines as those aren't needed. The generated files are in the data/ directory for this exercise. ================================================ FILE: chapter/16/workflow/data/generators/mk/mk.go ================================================ package main import ( "encoding/json" "os" ) type site struct { Name string Type string Status string } type machine struct { Name string Site string } func main() { first := byte(97) second := byte(97) third := byte(97) sites := []site{} for i := 0; i < 100; i++ { t := "cluster" if i%3 == 0 { t = "satellite" } site := site{ Name: string(append([]byte{}, first, second, third)), Type: t, Status: "inService", } switch site.Name { case "aap", "adg", "adv": site.Status = "decom" } sites = append(sites, site) if third < 122 { third++ } else { third = 97 second++ if second == 122 { second = 97 first++ } } } machines := []machine{} for _, site := range sites { if site.Type == "satellite" { first := byte(97) second := byte(97) a := byte(48) b := byte(48) for i := 0; i < 50; i++ { name := append([]byte{}, first, second, a, b) machines = append( machines, machine{Name: string(name), Site: site.Name}, ) b++ if b > 57 { a = 48 b = 48 second++ if second > 122 { first++ second = 97 } } } } } sitef, err := os.OpenFile("sites.json", os.O_WRONLY+os.O_CREATE, 0600) if err != nil { panic(err) } defer sitef.Close() machinef, err := os.OpenFile("machines.json", os.O_WRONLY+os.O_CREATE, 0600) if err != nil { panic(err) } defer machinef.Close() encSites := json.NewEncoder(sitef) for _, s := range sites { if err := encSites.Encode(s); err != nil { panic(err) } } encMachines := json.NewEncoder(machinef) for _, m := range machines { if err := encMachines.Encode(m); err != nil { panic(err) } } } ================================================ FILE: chapter/16/workflow/data/machines.json ================================================ {"Name":"aa00","Site":"aaa"} {"Name":"aa01","Site":"aaa"} {"Name":"aa02","Site":"aaa"} {"Name":"aa03","Site":"aaa"} {"Name":"aa04","Site":"aaa"} {"Name":"aa05","Site":"aaa"} {"Name":"aa06","Site":"aaa"} {"Name":"aa07","Site":"aaa"} {"Name":"aa08","Site":"aaa"} {"Name":"aa09","Site":"aaa"} {"Name":"ab00","Site":"aaa"} {"Name":"ab01","Site":"aaa"} {"Name":"ab02","Site":"aaa"} {"Name":"ab03","Site":"aaa"} {"Name":"ab04","Site":"aaa"} {"Name":"ab05","Site":"aaa"} {"Name":"ab06","Site":"aaa"} {"Name":"ab07","Site":"aaa"} {"Name":"ab08","Site":"aaa"} {"Name":"ab09","Site":"aaa"} {"Name":"ac00","Site":"aaa"} {"Name":"ac01","Site":"aaa"} {"Name":"ac02","Site":"aaa"} {"Name":"ac03","Site":"aaa"} {"Name":"ac04","Site":"aaa"} {"Name":"ac05","Site":"aaa"} {"Name":"ac06","Site":"aaa"} {"Name":"ac07","Site":"aaa"} {"Name":"ac08","Site":"aaa"} {"Name":"ac09","Site":"aaa"} {"Name":"ad00","Site":"aaa"} {"Name":"ad01","Site":"aaa"} {"Name":"ad02","Site":"aaa"} {"Name":"ad03","Site":"aaa"} {"Name":"ad04","Site":"aaa"} {"Name":"ad05","Site":"aaa"} {"Name":"ad06","Site":"aaa"} {"Name":"ad07","Site":"aaa"} {"Name":"ad08","Site":"aaa"} {"Name":"ad09","Site":"aaa"} {"Name":"ae00","Site":"aaa"} {"Name":"ae01","Site":"aaa"} {"Name":"ae02","Site":"aaa"} {"Name":"ae03","Site":"aaa"} {"Name":"ae04","Site":"aaa"} {"Name":"ae05","Site":"aaa"} {"Name":"ae06","Site":"aaa"} {"Name":"ae07","Site":"aaa"} {"Name":"ae08","Site":"aaa"} {"Name":"ae09","Site":"aaa"} {"Name":"aa00","Site":"aad"} {"Name":"aa01","Site":"aad"} {"Name":"aa02","Site":"aad"} {"Name":"aa03","Site":"aad"} {"Name":"aa04","Site":"aad"} {"Name":"aa05","Site":"aad"} {"Name":"aa06","Site":"aad"} {"Name":"aa07","Site":"aad"} {"Name":"aa08","Site":"aad"} {"Name":"aa09","Site":"aad"} {"Name":"ab00","Site":"aad"} {"Name":"ab01","Site":"aad"} {"Name":"ab02","Site":"aad"} {"Name":"ab03","Site":"aad"} {"Name":"ab04","Site":"aad"} {"Name":"ab05","Site":"aad"} {"Name":"ab06","Site":"aad"} {"Name":"ab07","Site":"aad"} {"Name":"ab08","Site":"aad"} {"Name":"ab09","Site":"aad"} {"Name":"ac00","Site":"aad"} {"Name":"ac01","Site":"aad"} {"Name":"ac02","Site":"aad"} {"Name":"ac03","Site":"aad"} {"Name":"ac04","Site":"aad"} {"Name":"ac05","Site":"aad"} {"Name":"ac06","Site":"aad"} {"Name":"ac07","Site":"aad"} {"Name":"ac08","Site":"aad"} {"Name":"ac09","Site":"aad"} {"Name":"ad00","Site":"aad"} {"Name":"ad01","Site":"aad"} {"Name":"ad02","Site":"aad"} {"Name":"ad03","Site":"aad"} {"Name":"ad04","Site":"aad"} {"Name":"ad05","Site":"aad"} {"Name":"ad06","Site":"aad"} {"Name":"ad07","Site":"aad"} {"Name":"ad08","Site":"aad"} {"Name":"ad09","Site":"aad"} {"Name":"ae00","Site":"aad"} {"Name":"ae01","Site":"aad"} {"Name":"ae02","Site":"aad"} {"Name":"ae03","Site":"aad"} {"Name":"ae04","Site":"aad"} {"Name":"ae05","Site":"aad"} {"Name":"ae06","Site":"aad"} {"Name":"ae07","Site":"aad"} {"Name":"ae08","Site":"aad"} {"Name":"ae09","Site":"aad"} {"Name":"aa00","Site":"aag"} {"Name":"aa01","Site":"aag"} {"Name":"aa02","Site":"aag"} {"Name":"aa03","Site":"aag"} {"Name":"aa04","Site":"aag"} {"Name":"aa05","Site":"aag"} {"Name":"aa06","Site":"aag"} {"Name":"aa07","Site":"aag"} {"Name":"aa08","Site":"aag"} {"Name":"aa09","Site":"aag"} {"Name":"ab00","Site":"aag"} {"Name":"ab01","Site":"aag"} {"Name":"ab02","Site":"aag"} {"Name":"ab03","Site":"aag"} {"Name":"ab04","Site":"aag"} {"Name":"ab05","Site":"aag"} {"Name":"ab06","Site":"aag"} {"Name":"ab07","Site":"aag"} {"Name":"ab08","Site":"aag"} {"Name":"ab09","Site":"aag"} {"Name":"ac00","Site":"aag"} {"Name":"ac01","Site":"aag"} {"Name":"ac02","Site":"aag"} {"Name":"ac03","Site":"aag"} {"Name":"ac04","Site":"aag"} {"Name":"ac05","Site":"aag"} {"Name":"ac06","Site":"aag"} {"Name":"ac07","Site":"aag"} {"Name":"ac08","Site":"aag"} {"Name":"ac09","Site":"aag"} {"Name":"ad00","Site":"aag"} {"Name":"ad01","Site":"aag"} {"Name":"ad02","Site":"aag"} {"Name":"ad03","Site":"aag"} {"Name":"ad04","Site":"aag"} {"Name":"ad05","Site":"aag"} {"Name":"ad06","Site":"aag"} {"Name":"ad07","Site":"aag"} {"Name":"ad08","Site":"aag"} {"Name":"ad09","Site":"aag"} {"Name":"ae00","Site":"aag"} {"Name":"ae01","Site":"aag"} {"Name":"ae02","Site":"aag"} {"Name":"ae03","Site":"aag"} {"Name":"ae04","Site":"aag"} {"Name":"ae05","Site":"aag"} {"Name":"ae06","Site":"aag"} {"Name":"ae07","Site":"aag"} {"Name":"ae08","Site":"aag"} {"Name":"ae09","Site":"aag"} {"Name":"aa00","Site":"aaj"} {"Name":"aa01","Site":"aaj"} {"Name":"aa02","Site":"aaj"} {"Name":"aa03","Site":"aaj"} {"Name":"aa04","Site":"aaj"} {"Name":"aa05","Site":"aaj"} {"Name":"aa06","Site":"aaj"} {"Name":"aa07","Site":"aaj"} {"Name":"aa08","Site":"aaj"} {"Name":"aa09","Site":"aaj"} {"Name":"ab00","Site":"aaj"} {"Name":"ab01","Site":"aaj"} {"Name":"ab02","Site":"aaj"} {"Name":"ab03","Site":"aaj"} {"Name":"ab04","Site":"aaj"} {"Name":"ab05","Site":"aaj"} {"Name":"ab06","Site":"aaj"} {"Name":"ab07","Site":"aaj"} {"Name":"ab08","Site":"aaj"} {"Name":"ab09","Site":"aaj"} {"Name":"ac00","Site":"aaj"} {"Name":"ac01","Site":"aaj"} {"Name":"ac02","Site":"aaj"} {"Name":"ac03","Site":"aaj"} {"Name":"ac04","Site":"aaj"} {"Name":"ac05","Site":"aaj"} {"Name":"ac06","Site":"aaj"} {"Name":"ac07","Site":"aaj"} {"Name":"ac08","Site":"aaj"} {"Name":"ac09","Site":"aaj"} {"Name":"ad00","Site":"aaj"} {"Name":"ad01","Site":"aaj"} {"Name":"ad02","Site":"aaj"} {"Name":"ad03","Site":"aaj"} {"Name":"ad04","Site":"aaj"} {"Name":"ad05","Site":"aaj"} {"Name":"ad06","Site":"aaj"} {"Name":"ad07","Site":"aaj"} {"Name":"ad08","Site":"aaj"} {"Name":"ad09","Site":"aaj"} {"Name":"ae00","Site":"aaj"} {"Name":"ae01","Site":"aaj"} {"Name":"ae02","Site":"aaj"} {"Name":"ae03","Site":"aaj"} {"Name":"ae04","Site":"aaj"} {"Name":"ae05","Site":"aaj"} {"Name":"ae06","Site":"aaj"} {"Name":"ae07","Site":"aaj"} {"Name":"ae08","Site":"aaj"} {"Name":"ae09","Site":"aaj"} {"Name":"aa00","Site":"aam"} {"Name":"aa01","Site":"aam"} {"Name":"aa02","Site":"aam"} {"Name":"aa03","Site":"aam"} {"Name":"aa04","Site":"aam"} {"Name":"aa05","Site":"aam"} {"Name":"aa06","Site":"aam"} {"Name":"aa07","Site":"aam"} {"Name":"aa08","Site":"aam"} {"Name":"aa09","Site":"aam"} {"Name":"ab00","Site":"aam"} {"Name":"ab01","Site":"aam"} {"Name":"ab02","Site":"aam"} {"Name":"ab03","Site":"aam"} {"Name":"ab04","Site":"aam"} {"Name":"ab05","Site":"aam"} {"Name":"ab06","Site":"aam"} {"Name":"ab07","Site":"aam"} {"Name":"ab08","Site":"aam"} {"Name":"ab09","Site":"aam"} {"Name":"ac00","Site":"aam"} {"Name":"ac01","Site":"aam"} {"Name":"ac02","Site":"aam"} {"Name":"ac03","Site":"aam"} {"Name":"ac04","Site":"aam"} {"Name":"ac05","Site":"aam"} {"Name":"ac06","Site":"aam"} {"Name":"ac07","Site":"aam"} {"Name":"ac08","Site":"aam"} {"Name":"ac09","Site":"aam"} {"Name":"ad00","Site":"aam"} {"Name":"ad01","Site":"aam"} {"Name":"ad02","Site":"aam"} {"Name":"ad03","Site":"aam"} {"Name":"ad04","Site":"aam"} {"Name":"ad05","Site":"aam"} {"Name":"ad06","Site":"aam"} {"Name":"ad07","Site":"aam"} {"Name":"ad08","Site":"aam"} {"Name":"ad09","Site":"aam"} {"Name":"ae00","Site":"aam"} {"Name":"ae01","Site":"aam"} {"Name":"ae02","Site":"aam"} {"Name":"ae03","Site":"aam"} {"Name":"ae04","Site":"aam"} {"Name":"ae05","Site":"aam"} {"Name":"ae06","Site":"aam"} {"Name":"ae07","Site":"aam"} {"Name":"ae08","Site":"aam"} {"Name":"ae09","Site":"aam"} {"Name":"aa00","Site":"aap"} {"Name":"aa01","Site":"aap"} {"Name":"aa02","Site":"aap"} {"Name":"aa03","Site":"aap"} {"Name":"aa04","Site":"aap"} {"Name":"aa05","Site":"aap"} {"Name":"aa06","Site":"aap"} {"Name":"aa07","Site":"aap"} {"Name":"aa08","Site":"aap"} {"Name":"aa09","Site":"aap"} {"Name":"ab00","Site":"aap"} {"Name":"ab01","Site":"aap"} {"Name":"ab02","Site":"aap"} {"Name":"ab03","Site":"aap"} {"Name":"ab04","Site":"aap"} {"Name":"ab05","Site":"aap"} {"Name":"ab06","Site":"aap"} {"Name":"ab07","Site":"aap"} {"Name":"ab08","Site":"aap"} {"Name":"ab09","Site":"aap"} {"Name":"ac00","Site":"aap"} {"Name":"ac01","Site":"aap"} {"Name":"ac02","Site":"aap"} {"Name":"ac03","Site":"aap"} {"Name":"ac04","Site":"aap"} {"Name":"ac05","Site":"aap"} {"Name":"ac06","Site":"aap"} {"Name":"ac07","Site":"aap"} {"Name":"ac08","Site":"aap"} {"Name":"ac09","Site":"aap"} {"Name":"ad00","Site":"aap"} {"Name":"ad01","Site":"aap"} {"Name":"ad02","Site":"aap"} {"Name":"ad03","Site":"aap"} {"Name":"ad04","Site":"aap"} {"Name":"ad05","Site":"aap"} {"Name":"ad06","Site":"aap"} {"Name":"ad07","Site":"aap"} {"Name":"ad08","Site":"aap"} {"Name":"ad09","Site":"aap"} {"Name":"ae00","Site":"aap"} {"Name":"ae01","Site":"aap"} {"Name":"ae02","Site":"aap"} {"Name":"ae03","Site":"aap"} {"Name":"ae04","Site":"aap"} {"Name":"ae05","Site":"aap"} {"Name":"ae06","Site":"aap"} {"Name":"ae07","Site":"aap"} {"Name":"ae08","Site":"aap"} {"Name":"ae09","Site":"aap"} {"Name":"aa00","Site":"aas"} {"Name":"aa01","Site":"aas"} {"Name":"aa02","Site":"aas"} {"Name":"aa03","Site":"aas"} {"Name":"aa04","Site":"aas"} {"Name":"aa05","Site":"aas"} {"Name":"aa06","Site":"aas"} {"Name":"aa07","Site":"aas"} {"Name":"aa08","Site":"aas"} {"Name":"aa09","Site":"aas"} {"Name":"ab00","Site":"aas"} {"Name":"ab01","Site":"aas"} {"Name":"ab02","Site":"aas"} {"Name":"ab03","Site":"aas"} {"Name":"ab04","Site":"aas"} {"Name":"ab05","Site":"aas"} {"Name":"ab06","Site":"aas"} {"Name":"ab07","Site":"aas"} {"Name":"ab08","Site":"aas"} {"Name":"ab09","Site":"aas"} {"Name":"ac00","Site":"aas"} {"Name":"ac01","Site":"aas"} {"Name":"ac02","Site":"aas"} {"Name":"ac03","Site":"aas"} {"Name":"ac04","Site":"aas"} {"Name":"ac05","Site":"aas"} {"Name":"ac06","Site":"aas"} {"Name":"ac07","Site":"aas"} {"Name":"ac08","Site":"aas"} {"Name":"ac09","Site":"aas"} {"Name":"ad00","Site":"aas"} {"Name":"ad01","Site":"aas"} {"Name":"ad02","Site":"aas"} {"Name":"ad03","Site":"aas"} {"Name":"ad04","Site":"aas"} {"Name":"ad05","Site":"aas"} {"Name":"ad06","Site":"aas"} {"Name":"ad07","Site":"aas"} {"Name":"ad08","Site":"aas"} {"Name":"ad09","Site":"aas"} {"Name":"ae00","Site":"aas"} {"Name":"ae01","Site":"aas"} {"Name":"ae02","Site":"aas"} {"Name":"ae03","Site":"aas"} {"Name":"ae04","Site":"aas"} {"Name":"ae05","Site":"aas"} {"Name":"ae06","Site":"aas"} {"Name":"ae07","Site":"aas"} {"Name":"ae08","Site":"aas"} {"Name":"ae09","Site":"aas"} {"Name":"aa00","Site":"aav"} {"Name":"aa01","Site":"aav"} {"Name":"aa02","Site":"aav"} {"Name":"aa03","Site":"aav"} {"Name":"aa04","Site":"aav"} {"Name":"aa05","Site":"aav"} {"Name":"aa06","Site":"aav"} {"Name":"aa07","Site":"aav"} {"Name":"aa08","Site":"aav"} {"Name":"aa09","Site":"aav"} {"Name":"ab00","Site":"aav"} {"Name":"ab01","Site":"aav"} {"Name":"ab02","Site":"aav"} {"Name":"ab03","Site":"aav"} {"Name":"ab04","Site":"aav"} {"Name":"ab05","Site":"aav"} {"Name":"ab06","Site":"aav"} {"Name":"ab07","Site":"aav"} {"Name":"ab08","Site":"aav"} {"Name":"ab09","Site":"aav"} {"Name":"ac00","Site":"aav"} {"Name":"ac01","Site":"aav"} {"Name":"ac02","Site":"aav"} {"Name":"ac03","Site":"aav"} {"Name":"ac04","Site":"aav"} {"Name":"ac05","Site":"aav"} {"Name":"ac06","Site":"aav"} {"Name":"ac07","Site":"aav"} {"Name":"ac08","Site":"aav"} {"Name":"ac09","Site":"aav"} {"Name":"ad00","Site":"aav"} {"Name":"ad01","Site":"aav"} {"Name":"ad02","Site":"aav"} {"Name":"ad03","Site":"aav"} {"Name":"ad04","Site":"aav"} {"Name":"ad05","Site":"aav"} {"Name":"ad06","Site":"aav"} {"Name":"ad07","Site":"aav"} {"Name":"ad08","Site":"aav"} {"Name":"ad09","Site":"aav"} {"Name":"ae00","Site":"aav"} {"Name":"ae01","Site":"aav"} {"Name":"ae02","Site":"aav"} {"Name":"ae03","Site":"aav"} {"Name":"ae04","Site":"aav"} {"Name":"ae05","Site":"aav"} {"Name":"ae06","Site":"aav"} {"Name":"ae07","Site":"aav"} {"Name":"ae08","Site":"aav"} {"Name":"ae09","Site":"aav"} {"Name":"aa00","Site":"aay"} {"Name":"aa01","Site":"aay"} {"Name":"aa02","Site":"aay"} {"Name":"aa03","Site":"aay"} {"Name":"aa04","Site":"aay"} {"Name":"aa05","Site":"aay"} {"Name":"aa06","Site":"aay"} {"Name":"aa07","Site":"aay"} {"Name":"aa08","Site":"aay"} {"Name":"aa09","Site":"aay"} {"Name":"ab00","Site":"aay"} {"Name":"ab01","Site":"aay"} {"Name":"ab02","Site":"aay"} {"Name":"ab03","Site":"aay"} {"Name":"ab04","Site":"aay"} {"Name":"ab05","Site":"aay"} {"Name":"ab06","Site":"aay"} {"Name":"ab07","Site":"aay"} {"Name":"ab08","Site":"aay"} {"Name":"ab09","Site":"aay"} {"Name":"ac00","Site":"aay"} {"Name":"ac01","Site":"aay"} {"Name":"ac02","Site":"aay"} {"Name":"ac03","Site":"aay"} {"Name":"ac04","Site":"aay"} {"Name":"ac05","Site":"aay"} {"Name":"ac06","Site":"aay"} {"Name":"ac07","Site":"aay"} {"Name":"ac08","Site":"aay"} {"Name":"ac09","Site":"aay"} {"Name":"ad00","Site":"aay"} {"Name":"ad01","Site":"aay"} {"Name":"ad02","Site":"aay"} {"Name":"ad03","Site":"aay"} {"Name":"ad04","Site":"aay"} {"Name":"ad05","Site":"aay"} {"Name":"ad06","Site":"aay"} {"Name":"ad07","Site":"aay"} {"Name":"ad08","Site":"aay"} {"Name":"ad09","Site":"aay"} {"Name":"ae00","Site":"aay"} {"Name":"ae01","Site":"aay"} {"Name":"ae02","Site":"aay"} {"Name":"ae03","Site":"aay"} {"Name":"ae04","Site":"aay"} {"Name":"ae05","Site":"aay"} {"Name":"ae06","Site":"aay"} {"Name":"ae07","Site":"aay"} {"Name":"ae08","Site":"aay"} {"Name":"ae09","Site":"aay"} {"Name":"aa00","Site":"abb"} {"Name":"aa01","Site":"abb"} {"Name":"aa02","Site":"abb"} {"Name":"aa03","Site":"abb"} {"Name":"aa04","Site":"abb"} {"Name":"aa05","Site":"abb"} {"Name":"aa06","Site":"abb"} {"Name":"aa07","Site":"abb"} {"Name":"aa08","Site":"abb"} {"Name":"aa09","Site":"abb"} {"Name":"ab00","Site":"abb"} {"Name":"ab01","Site":"abb"} {"Name":"ab02","Site":"abb"} {"Name":"ab03","Site":"abb"} {"Name":"ab04","Site":"abb"} {"Name":"ab05","Site":"abb"} {"Name":"ab06","Site":"abb"} {"Name":"ab07","Site":"abb"} {"Name":"ab08","Site":"abb"} {"Name":"ab09","Site":"abb"} {"Name":"ac00","Site":"abb"} {"Name":"ac01","Site":"abb"} {"Name":"ac02","Site":"abb"} {"Name":"ac03","Site":"abb"} {"Name":"ac04","Site":"abb"} {"Name":"ac05","Site":"abb"} {"Name":"ac06","Site":"abb"} {"Name":"ac07","Site":"abb"} {"Name":"ac08","Site":"abb"} {"Name":"ac09","Site":"abb"} {"Name":"ad00","Site":"abb"} {"Name":"ad01","Site":"abb"} {"Name":"ad02","Site":"abb"} {"Name":"ad03","Site":"abb"} {"Name":"ad04","Site":"abb"} {"Name":"ad05","Site":"abb"} {"Name":"ad06","Site":"abb"} {"Name":"ad07","Site":"abb"} {"Name":"ad08","Site":"abb"} {"Name":"ad09","Site":"abb"} {"Name":"ae00","Site":"abb"} {"Name":"ae01","Site":"abb"} {"Name":"ae02","Site":"abb"} {"Name":"ae03","Site":"abb"} {"Name":"ae04","Site":"abb"} {"Name":"ae05","Site":"abb"} {"Name":"ae06","Site":"abb"} {"Name":"ae07","Site":"abb"} {"Name":"ae08","Site":"abb"} {"Name":"ae09","Site":"abb"} {"Name":"aa00","Site":"abe"} {"Name":"aa01","Site":"abe"} {"Name":"aa02","Site":"abe"} {"Name":"aa03","Site":"abe"} {"Name":"aa04","Site":"abe"} {"Name":"aa05","Site":"abe"} {"Name":"aa06","Site":"abe"} {"Name":"aa07","Site":"abe"} {"Name":"aa08","Site":"abe"} {"Name":"aa09","Site":"abe"} {"Name":"ab00","Site":"abe"} {"Name":"ab01","Site":"abe"} {"Name":"ab02","Site":"abe"} {"Name":"ab03","Site":"abe"} {"Name":"ab04","Site":"abe"} {"Name":"ab05","Site":"abe"} {"Name":"ab06","Site":"abe"} {"Name":"ab07","Site":"abe"} {"Name":"ab08","Site":"abe"} {"Name":"ab09","Site":"abe"} {"Name":"ac00","Site":"abe"} {"Name":"ac01","Site":"abe"} {"Name":"ac02","Site":"abe"} {"Name":"ac03","Site":"abe"} {"Name":"ac04","Site":"abe"} {"Name":"ac05","Site":"abe"} {"Name":"ac06","Site":"abe"} {"Name":"ac07","Site":"abe"} {"Name":"ac08","Site":"abe"} {"Name":"ac09","Site":"abe"} {"Name":"ad00","Site":"abe"} {"Name":"ad01","Site":"abe"} {"Name":"ad02","Site":"abe"} {"Name":"ad03","Site":"abe"} {"Name":"ad04","Site":"abe"} {"Name":"ad05","Site":"abe"} {"Name":"ad06","Site":"abe"} {"Name":"ad07","Site":"abe"} {"Name":"ad08","Site":"abe"} {"Name":"ad09","Site":"abe"} {"Name":"ae00","Site":"abe"} {"Name":"ae01","Site":"abe"} {"Name":"ae02","Site":"abe"} {"Name":"ae03","Site":"abe"} {"Name":"ae04","Site":"abe"} {"Name":"ae05","Site":"abe"} {"Name":"ae06","Site":"abe"} {"Name":"ae07","Site":"abe"} {"Name":"ae08","Site":"abe"} {"Name":"ae09","Site":"abe"} {"Name":"aa00","Site":"abh"} {"Name":"aa01","Site":"abh"} {"Name":"aa02","Site":"abh"} {"Name":"aa03","Site":"abh"} {"Name":"aa04","Site":"abh"} {"Name":"aa05","Site":"abh"} {"Name":"aa06","Site":"abh"} {"Name":"aa07","Site":"abh"} {"Name":"aa08","Site":"abh"} {"Name":"aa09","Site":"abh"} {"Name":"ab00","Site":"abh"} {"Name":"ab01","Site":"abh"} {"Name":"ab02","Site":"abh"} {"Name":"ab03","Site":"abh"} {"Name":"ab04","Site":"abh"} {"Name":"ab05","Site":"abh"} {"Name":"ab06","Site":"abh"} {"Name":"ab07","Site":"abh"} {"Name":"ab08","Site":"abh"} {"Name":"ab09","Site":"abh"} {"Name":"ac00","Site":"abh"} {"Name":"ac01","Site":"abh"} {"Name":"ac02","Site":"abh"} {"Name":"ac03","Site":"abh"} {"Name":"ac04","Site":"abh"} {"Name":"ac05","Site":"abh"} {"Name":"ac06","Site":"abh"} {"Name":"ac07","Site":"abh"} {"Name":"ac08","Site":"abh"} {"Name":"ac09","Site":"abh"} {"Name":"ad00","Site":"abh"} {"Name":"ad01","Site":"abh"} {"Name":"ad02","Site":"abh"} {"Name":"ad03","Site":"abh"} {"Name":"ad04","Site":"abh"} {"Name":"ad05","Site":"abh"} {"Name":"ad06","Site":"abh"} {"Name":"ad07","Site":"abh"} {"Name":"ad08","Site":"abh"} {"Name":"ad09","Site":"abh"} {"Name":"ae00","Site":"abh"} {"Name":"ae01","Site":"abh"} {"Name":"ae02","Site":"abh"} {"Name":"ae03","Site":"abh"} {"Name":"ae04","Site":"abh"} {"Name":"ae05","Site":"abh"} {"Name":"ae06","Site":"abh"} {"Name":"ae07","Site":"abh"} {"Name":"ae08","Site":"abh"} {"Name":"ae09","Site":"abh"} {"Name":"aa00","Site":"abk"} {"Name":"aa01","Site":"abk"} {"Name":"aa02","Site":"abk"} {"Name":"aa03","Site":"abk"} {"Name":"aa04","Site":"abk"} {"Name":"aa05","Site":"abk"} {"Name":"aa06","Site":"abk"} {"Name":"aa07","Site":"abk"} {"Name":"aa08","Site":"abk"} {"Name":"aa09","Site":"abk"} {"Name":"ab00","Site":"abk"} {"Name":"ab01","Site":"abk"} {"Name":"ab02","Site":"abk"} {"Name":"ab03","Site":"abk"} {"Name":"ab04","Site":"abk"} {"Name":"ab05","Site":"abk"} {"Name":"ab06","Site":"abk"} {"Name":"ab07","Site":"abk"} {"Name":"ab08","Site":"abk"} {"Name":"ab09","Site":"abk"} {"Name":"ac00","Site":"abk"} {"Name":"ac01","Site":"abk"} {"Name":"ac02","Site":"abk"} {"Name":"ac03","Site":"abk"} {"Name":"ac04","Site":"abk"} {"Name":"ac05","Site":"abk"} {"Name":"ac06","Site":"abk"} {"Name":"ac07","Site":"abk"} {"Name":"ac08","Site":"abk"} {"Name":"ac09","Site":"abk"} {"Name":"ad00","Site":"abk"} {"Name":"ad01","Site":"abk"} {"Name":"ad02","Site":"abk"} {"Name":"ad03","Site":"abk"} {"Name":"ad04","Site":"abk"} {"Name":"ad05","Site":"abk"} {"Name":"ad06","Site":"abk"} {"Name":"ad07","Site":"abk"} {"Name":"ad08","Site":"abk"} {"Name":"ad09","Site":"abk"} {"Name":"ae00","Site":"abk"} {"Name":"ae01","Site":"abk"} {"Name":"ae02","Site":"abk"} {"Name":"ae03","Site":"abk"} {"Name":"ae04","Site":"abk"} {"Name":"ae05","Site":"abk"} {"Name":"ae06","Site":"abk"} {"Name":"ae07","Site":"abk"} {"Name":"ae08","Site":"abk"} {"Name":"ae09","Site":"abk"} {"Name":"aa00","Site":"abn"} {"Name":"aa01","Site":"abn"} {"Name":"aa02","Site":"abn"} {"Name":"aa03","Site":"abn"} {"Name":"aa04","Site":"abn"} {"Name":"aa05","Site":"abn"} {"Name":"aa06","Site":"abn"} {"Name":"aa07","Site":"abn"} {"Name":"aa08","Site":"abn"} {"Name":"aa09","Site":"abn"} {"Name":"ab00","Site":"abn"} {"Name":"ab01","Site":"abn"} {"Name":"ab02","Site":"abn"} {"Name":"ab03","Site":"abn"} {"Name":"ab04","Site":"abn"} {"Name":"ab05","Site":"abn"} {"Name":"ab06","Site":"abn"} {"Name":"ab07","Site":"abn"} {"Name":"ab08","Site":"abn"} {"Name":"ab09","Site":"abn"} {"Name":"ac00","Site":"abn"} {"Name":"ac01","Site":"abn"} {"Name":"ac02","Site":"abn"} {"Name":"ac03","Site":"abn"} {"Name":"ac04","Site":"abn"} {"Name":"ac05","Site":"abn"} {"Name":"ac06","Site":"abn"} {"Name":"ac07","Site":"abn"} {"Name":"ac08","Site":"abn"} {"Name":"ac09","Site":"abn"} {"Name":"ad00","Site":"abn"} {"Name":"ad01","Site":"abn"} {"Name":"ad02","Site":"abn"} {"Name":"ad03","Site":"abn"} {"Name":"ad04","Site":"abn"} {"Name":"ad05","Site":"abn"} {"Name":"ad06","Site":"abn"} {"Name":"ad07","Site":"abn"} {"Name":"ad08","Site":"abn"} {"Name":"ad09","Site":"abn"} {"Name":"ae00","Site":"abn"} {"Name":"ae01","Site":"abn"} {"Name":"ae02","Site":"abn"} {"Name":"ae03","Site":"abn"} {"Name":"ae04","Site":"abn"} {"Name":"ae05","Site":"abn"} {"Name":"ae06","Site":"abn"} {"Name":"ae07","Site":"abn"} {"Name":"ae08","Site":"abn"} {"Name":"ae09","Site":"abn"} {"Name":"aa00","Site":"abq"} {"Name":"aa01","Site":"abq"} {"Name":"aa02","Site":"abq"} {"Name":"aa03","Site":"abq"} {"Name":"aa04","Site":"abq"} {"Name":"aa05","Site":"abq"} {"Name":"aa06","Site":"abq"} {"Name":"aa07","Site":"abq"} {"Name":"aa08","Site":"abq"} {"Name":"aa09","Site":"abq"} {"Name":"ab00","Site":"abq"} {"Name":"ab01","Site":"abq"} {"Name":"ab02","Site":"abq"} {"Name":"ab03","Site":"abq"} {"Name":"ab04","Site":"abq"} {"Name":"ab05","Site":"abq"} {"Name":"ab06","Site":"abq"} {"Name":"ab07","Site":"abq"} {"Name":"ab08","Site":"abq"} {"Name":"ab09","Site":"abq"} {"Name":"ac00","Site":"abq"} {"Name":"ac01","Site":"abq"} {"Name":"ac02","Site":"abq"} {"Name":"ac03","Site":"abq"} {"Name":"ac04","Site":"abq"} {"Name":"ac05","Site":"abq"} {"Name":"ac06","Site":"abq"} {"Name":"ac07","Site":"abq"} {"Name":"ac08","Site":"abq"} {"Name":"ac09","Site":"abq"} {"Name":"ad00","Site":"abq"} {"Name":"ad01","Site":"abq"} {"Name":"ad02","Site":"abq"} {"Name":"ad03","Site":"abq"} {"Name":"ad04","Site":"abq"} {"Name":"ad05","Site":"abq"} {"Name":"ad06","Site":"abq"} {"Name":"ad07","Site":"abq"} {"Name":"ad08","Site":"abq"} {"Name":"ad09","Site":"abq"} {"Name":"ae00","Site":"abq"} {"Name":"ae01","Site":"abq"} {"Name":"ae02","Site":"abq"} {"Name":"ae03","Site":"abq"} {"Name":"ae04","Site":"abq"} {"Name":"ae05","Site":"abq"} {"Name":"ae06","Site":"abq"} {"Name":"ae07","Site":"abq"} {"Name":"ae08","Site":"abq"} {"Name":"ae09","Site":"abq"} {"Name":"aa00","Site":"abt"} {"Name":"aa01","Site":"abt"} {"Name":"aa02","Site":"abt"} {"Name":"aa03","Site":"abt"} {"Name":"aa04","Site":"abt"} {"Name":"aa05","Site":"abt"} {"Name":"aa06","Site":"abt"} {"Name":"aa07","Site":"abt"} {"Name":"aa08","Site":"abt"} {"Name":"aa09","Site":"abt"} {"Name":"ab00","Site":"abt"} {"Name":"ab01","Site":"abt"} {"Name":"ab02","Site":"abt"} {"Name":"ab03","Site":"abt"} {"Name":"ab04","Site":"abt"} {"Name":"ab05","Site":"abt"} {"Name":"ab06","Site":"abt"} {"Name":"ab07","Site":"abt"} {"Name":"ab08","Site":"abt"} {"Name":"ab09","Site":"abt"} {"Name":"ac00","Site":"abt"} {"Name":"ac01","Site":"abt"} {"Name":"ac02","Site":"abt"} {"Name":"ac03","Site":"abt"} {"Name":"ac04","Site":"abt"} {"Name":"ac05","Site":"abt"} {"Name":"ac06","Site":"abt"} {"Name":"ac07","Site":"abt"} {"Name":"ac08","Site":"abt"} {"Name":"ac09","Site":"abt"} {"Name":"ad00","Site":"abt"} {"Name":"ad01","Site":"abt"} {"Name":"ad02","Site":"abt"} {"Name":"ad03","Site":"abt"} {"Name":"ad04","Site":"abt"} {"Name":"ad05","Site":"abt"} {"Name":"ad06","Site":"abt"} {"Name":"ad07","Site":"abt"} {"Name":"ad08","Site":"abt"} {"Name":"ad09","Site":"abt"} {"Name":"ae00","Site":"abt"} {"Name":"ae01","Site":"abt"} {"Name":"ae02","Site":"abt"} {"Name":"ae03","Site":"abt"} {"Name":"ae04","Site":"abt"} {"Name":"ae05","Site":"abt"} {"Name":"ae06","Site":"abt"} {"Name":"ae07","Site":"abt"} {"Name":"ae08","Site":"abt"} {"Name":"ae09","Site":"abt"} {"Name":"aa00","Site":"abw"} {"Name":"aa01","Site":"abw"} {"Name":"aa02","Site":"abw"} {"Name":"aa03","Site":"abw"} {"Name":"aa04","Site":"abw"} {"Name":"aa05","Site":"abw"} {"Name":"aa06","Site":"abw"} {"Name":"aa07","Site":"abw"} {"Name":"aa08","Site":"abw"} {"Name":"aa09","Site":"abw"} {"Name":"ab00","Site":"abw"} {"Name":"ab01","Site":"abw"} {"Name":"ab02","Site":"abw"} {"Name":"ab03","Site":"abw"} {"Name":"ab04","Site":"abw"} {"Name":"ab05","Site":"abw"} {"Name":"ab06","Site":"abw"} {"Name":"ab07","Site":"abw"} {"Name":"ab08","Site":"abw"} {"Name":"ab09","Site":"abw"} {"Name":"ac00","Site":"abw"} {"Name":"ac01","Site":"abw"} {"Name":"ac02","Site":"abw"} {"Name":"ac03","Site":"abw"} {"Name":"ac04","Site":"abw"} {"Name":"ac05","Site":"abw"} {"Name":"ac06","Site":"abw"} {"Name":"ac07","Site":"abw"} {"Name":"ac08","Site":"abw"} {"Name":"ac09","Site":"abw"} {"Name":"ad00","Site":"abw"} {"Name":"ad01","Site":"abw"} {"Name":"ad02","Site":"abw"} {"Name":"ad03","Site":"abw"} {"Name":"ad04","Site":"abw"} {"Name":"ad05","Site":"abw"} {"Name":"ad06","Site":"abw"} {"Name":"ad07","Site":"abw"} {"Name":"ad08","Site":"abw"} {"Name":"ad09","Site":"abw"} {"Name":"ae00","Site":"abw"} {"Name":"ae01","Site":"abw"} {"Name":"ae02","Site":"abw"} {"Name":"ae03","Site":"abw"} {"Name":"ae04","Site":"abw"} {"Name":"ae05","Site":"abw"} {"Name":"ae06","Site":"abw"} {"Name":"ae07","Site":"abw"} {"Name":"ae08","Site":"abw"} {"Name":"ae09","Site":"abw"} {"Name":"aa00","Site":"abz"} {"Name":"aa01","Site":"abz"} {"Name":"aa02","Site":"abz"} {"Name":"aa03","Site":"abz"} {"Name":"aa04","Site":"abz"} {"Name":"aa05","Site":"abz"} {"Name":"aa06","Site":"abz"} {"Name":"aa07","Site":"abz"} {"Name":"aa08","Site":"abz"} {"Name":"aa09","Site":"abz"} {"Name":"ab00","Site":"abz"} {"Name":"ab01","Site":"abz"} {"Name":"ab02","Site":"abz"} {"Name":"ab03","Site":"abz"} {"Name":"ab04","Site":"abz"} {"Name":"ab05","Site":"abz"} {"Name":"ab06","Site":"abz"} {"Name":"ab07","Site":"abz"} {"Name":"ab08","Site":"abz"} {"Name":"ab09","Site":"abz"} {"Name":"ac00","Site":"abz"} {"Name":"ac01","Site":"abz"} {"Name":"ac02","Site":"abz"} {"Name":"ac03","Site":"abz"} {"Name":"ac04","Site":"abz"} {"Name":"ac05","Site":"abz"} {"Name":"ac06","Site":"abz"} {"Name":"ac07","Site":"abz"} {"Name":"ac08","Site":"abz"} {"Name":"ac09","Site":"abz"} {"Name":"ad00","Site":"abz"} {"Name":"ad01","Site":"abz"} {"Name":"ad02","Site":"abz"} {"Name":"ad03","Site":"abz"} {"Name":"ad04","Site":"abz"} {"Name":"ad05","Site":"abz"} {"Name":"ad06","Site":"abz"} {"Name":"ad07","Site":"abz"} {"Name":"ad08","Site":"abz"} {"Name":"ad09","Site":"abz"} {"Name":"ae00","Site":"abz"} {"Name":"ae01","Site":"abz"} {"Name":"ae02","Site":"abz"} {"Name":"ae03","Site":"abz"} {"Name":"ae04","Site":"abz"} {"Name":"ae05","Site":"abz"} {"Name":"ae06","Site":"abz"} {"Name":"ae07","Site":"abz"} {"Name":"ae08","Site":"abz"} {"Name":"ae09","Site":"abz"} {"Name":"aa00","Site":"acc"} {"Name":"aa01","Site":"acc"} {"Name":"aa02","Site":"acc"} {"Name":"aa03","Site":"acc"} {"Name":"aa04","Site":"acc"} {"Name":"aa05","Site":"acc"} {"Name":"aa06","Site":"acc"} {"Name":"aa07","Site":"acc"} {"Name":"aa08","Site":"acc"} {"Name":"aa09","Site":"acc"} {"Name":"ab00","Site":"acc"} {"Name":"ab01","Site":"acc"} {"Name":"ab02","Site":"acc"} {"Name":"ab03","Site":"acc"} {"Name":"ab04","Site":"acc"} {"Name":"ab05","Site":"acc"} {"Name":"ab06","Site":"acc"} {"Name":"ab07","Site":"acc"} {"Name":"ab08","Site":"acc"} {"Name":"ab09","Site":"acc"} {"Name":"ac00","Site":"acc"} {"Name":"ac01","Site":"acc"} {"Name":"ac02","Site":"acc"} {"Name":"ac03","Site":"acc"} {"Name":"ac04","Site":"acc"} {"Name":"ac05","Site":"acc"} {"Name":"ac06","Site":"acc"} {"Name":"ac07","Site":"acc"} {"Name":"ac08","Site":"acc"} {"Name":"ac09","Site":"acc"} {"Name":"ad00","Site":"acc"} {"Name":"ad01","Site":"acc"} {"Name":"ad02","Site":"acc"} {"Name":"ad03","Site":"acc"} {"Name":"ad04","Site":"acc"} {"Name":"ad05","Site":"acc"} {"Name":"ad06","Site":"acc"} {"Name":"ad07","Site":"acc"} {"Name":"ad08","Site":"acc"} {"Name":"ad09","Site":"acc"} {"Name":"ae00","Site":"acc"} {"Name":"ae01","Site":"acc"} {"Name":"ae02","Site":"acc"} {"Name":"ae03","Site":"acc"} {"Name":"ae04","Site":"acc"} {"Name":"ae05","Site":"acc"} {"Name":"ae06","Site":"acc"} {"Name":"ae07","Site":"acc"} {"Name":"ae08","Site":"acc"} {"Name":"ae09","Site":"acc"} {"Name":"aa00","Site":"acf"} {"Name":"aa01","Site":"acf"} {"Name":"aa02","Site":"acf"} {"Name":"aa03","Site":"acf"} {"Name":"aa04","Site":"acf"} {"Name":"aa05","Site":"acf"} {"Name":"aa06","Site":"acf"} {"Name":"aa07","Site":"acf"} {"Name":"aa08","Site":"acf"} {"Name":"aa09","Site":"acf"} {"Name":"ab00","Site":"acf"} {"Name":"ab01","Site":"acf"} {"Name":"ab02","Site":"acf"} {"Name":"ab03","Site":"acf"} {"Name":"ab04","Site":"acf"} {"Name":"ab05","Site":"acf"} {"Name":"ab06","Site":"acf"} {"Name":"ab07","Site":"acf"} {"Name":"ab08","Site":"acf"} {"Name":"ab09","Site":"acf"} {"Name":"ac00","Site":"acf"} {"Name":"ac01","Site":"acf"} {"Name":"ac02","Site":"acf"} {"Name":"ac03","Site":"acf"} {"Name":"ac04","Site":"acf"} {"Name":"ac05","Site":"acf"} {"Name":"ac06","Site":"acf"} {"Name":"ac07","Site":"acf"} {"Name":"ac08","Site":"acf"} {"Name":"ac09","Site":"acf"} {"Name":"ad00","Site":"acf"} {"Name":"ad01","Site":"acf"} {"Name":"ad02","Site":"acf"} {"Name":"ad03","Site":"acf"} {"Name":"ad04","Site":"acf"} {"Name":"ad05","Site":"acf"} {"Name":"ad06","Site":"acf"} {"Name":"ad07","Site":"acf"} {"Name":"ad08","Site":"acf"} {"Name":"ad09","Site":"acf"} {"Name":"ae00","Site":"acf"} {"Name":"ae01","Site":"acf"} {"Name":"ae02","Site":"acf"} {"Name":"ae03","Site":"acf"} {"Name":"ae04","Site":"acf"} {"Name":"ae05","Site":"acf"} {"Name":"ae06","Site":"acf"} {"Name":"ae07","Site":"acf"} {"Name":"ae08","Site":"acf"} {"Name":"ae09","Site":"acf"} {"Name":"aa00","Site":"aci"} {"Name":"aa01","Site":"aci"} {"Name":"aa02","Site":"aci"} {"Name":"aa03","Site":"aci"} {"Name":"aa04","Site":"aci"} {"Name":"aa05","Site":"aci"} {"Name":"aa06","Site":"aci"} {"Name":"aa07","Site":"aci"} {"Name":"aa08","Site":"aci"} {"Name":"aa09","Site":"aci"} {"Name":"ab00","Site":"aci"} {"Name":"ab01","Site":"aci"} {"Name":"ab02","Site":"aci"} {"Name":"ab03","Site":"aci"} {"Name":"ab04","Site":"aci"} {"Name":"ab05","Site":"aci"} {"Name":"ab06","Site":"aci"} {"Name":"ab07","Site":"aci"} {"Name":"ab08","Site":"aci"} {"Name":"ab09","Site":"aci"} {"Name":"ac00","Site":"aci"} {"Name":"ac01","Site":"aci"} {"Name":"ac02","Site":"aci"} {"Name":"ac03","Site":"aci"} {"Name":"ac04","Site":"aci"} {"Name":"ac05","Site":"aci"} {"Name":"ac06","Site":"aci"} {"Name":"ac07","Site":"aci"} {"Name":"ac08","Site":"aci"} {"Name":"ac09","Site":"aci"} {"Name":"ad00","Site":"aci"} {"Name":"ad01","Site":"aci"} {"Name":"ad02","Site":"aci"} {"Name":"ad03","Site":"aci"} {"Name":"ad04","Site":"aci"} {"Name":"ad05","Site":"aci"} {"Name":"ad06","Site":"aci"} {"Name":"ad07","Site":"aci"} {"Name":"ad08","Site":"aci"} {"Name":"ad09","Site":"aci"} {"Name":"ae00","Site":"aci"} {"Name":"ae01","Site":"aci"} {"Name":"ae02","Site":"aci"} {"Name":"ae03","Site":"aci"} {"Name":"ae04","Site":"aci"} {"Name":"ae05","Site":"aci"} {"Name":"ae06","Site":"aci"} {"Name":"ae07","Site":"aci"} {"Name":"ae08","Site":"aci"} {"Name":"ae09","Site":"aci"} {"Name":"aa00","Site":"acl"} {"Name":"aa01","Site":"acl"} {"Name":"aa02","Site":"acl"} {"Name":"aa03","Site":"acl"} {"Name":"aa04","Site":"acl"} {"Name":"aa05","Site":"acl"} {"Name":"aa06","Site":"acl"} {"Name":"aa07","Site":"acl"} {"Name":"aa08","Site":"acl"} {"Name":"aa09","Site":"acl"} {"Name":"ab00","Site":"acl"} {"Name":"ab01","Site":"acl"} {"Name":"ab02","Site":"acl"} {"Name":"ab03","Site":"acl"} {"Name":"ab04","Site":"acl"} {"Name":"ab05","Site":"acl"} {"Name":"ab06","Site":"acl"} {"Name":"ab07","Site":"acl"} {"Name":"ab08","Site":"acl"} {"Name":"ab09","Site":"acl"} {"Name":"ac00","Site":"acl"} {"Name":"ac01","Site":"acl"} {"Name":"ac02","Site":"acl"} {"Name":"ac03","Site":"acl"} {"Name":"ac04","Site":"acl"} {"Name":"ac05","Site":"acl"} {"Name":"ac06","Site":"acl"} {"Name":"ac07","Site":"acl"} {"Name":"ac08","Site":"acl"} {"Name":"ac09","Site":"acl"} {"Name":"ad00","Site":"acl"} {"Name":"ad01","Site":"acl"} {"Name":"ad02","Site":"acl"} {"Name":"ad03","Site":"acl"} {"Name":"ad04","Site":"acl"} {"Name":"ad05","Site":"acl"} {"Name":"ad06","Site":"acl"} {"Name":"ad07","Site":"acl"} {"Name":"ad08","Site":"acl"} {"Name":"ad09","Site":"acl"} {"Name":"ae00","Site":"acl"} {"Name":"ae01","Site":"acl"} {"Name":"ae02","Site":"acl"} {"Name":"ae03","Site":"acl"} {"Name":"ae04","Site":"acl"} {"Name":"ae05","Site":"acl"} {"Name":"ae06","Site":"acl"} {"Name":"ae07","Site":"acl"} {"Name":"ae08","Site":"acl"} {"Name":"ae09","Site":"acl"} {"Name":"aa00","Site":"aco"} {"Name":"aa01","Site":"aco"} {"Name":"aa02","Site":"aco"} {"Name":"aa03","Site":"aco"} {"Name":"aa04","Site":"aco"} {"Name":"aa05","Site":"aco"} {"Name":"aa06","Site":"aco"} {"Name":"aa07","Site":"aco"} {"Name":"aa08","Site":"aco"} {"Name":"aa09","Site":"aco"} {"Name":"ab00","Site":"aco"} {"Name":"ab01","Site":"aco"} {"Name":"ab02","Site":"aco"} {"Name":"ab03","Site":"aco"} {"Name":"ab04","Site":"aco"} {"Name":"ab05","Site":"aco"} {"Name":"ab06","Site":"aco"} {"Name":"ab07","Site":"aco"} {"Name":"ab08","Site":"aco"} {"Name":"ab09","Site":"aco"} {"Name":"ac00","Site":"aco"} {"Name":"ac01","Site":"aco"} {"Name":"ac02","Site":"aco"} {"Name":"ac03","Site":"aco"} {"Name":"ac04","Site":"aco"} {"Name":"ac05","Site":"aco"} {"Name":"ac06","Site":"aco"} {"Name":"ac07","Site":"aco"} {"Name":"ac08","Site":"aco"} {"Name":"ac09","Site":"aco"} {"Name":"ad00","Site":"aco"} {"Name":"ad01","Site":"aco"} {"Name":"ad02","Site":"aco"} {"Name":"ad03","Site":"aco"} {"Name":"ad04","Site":"aco"} {"Name":"ad05","Site":"aco"} {"Name":"ad06","Site":"aco"} {"Name":"ad07","Site":"aco"} {"Name":"ad08","Site":"aco"} {"Name":"ad09","Site":"aco"} {"Name":"ae00","Site":"aco"} {"Name":"ae01","Site":"aco"} {"Name":"ae02","Site":"aco"} {"Name":"ae03","Site":"aco"} {"Name":"ae04","Site":"aco"} {"Name":"ae05","Site":"aco"} {"Name":"ae06","Site":"aco"} {"Name":"ae07","Site":"aco"} {"Name":"ae08","Site":"aco"} {"Name":"ae09","Site":"aco"} {"Name":"aa00","Site":"acr"} {"Name":"aa01","Site":"acr"} {"Name":"aa02","Site":"acr"} {"Name":"aa03","Site":"acr"} {"Name":"aa04","Site":"acr"} {"Name":"aa05","Site":"acr"} {"Name":"aa06","Site":"acr"} {"Name":"aa07","Site":"acr"} {"Name":"aa08","Site":"acr"} {"Name":"aa09","Site":"acr"} {"Name":"ab00","Site":"acr"} {"Name":"ab01","Site":"acr"} {"Name":"ab02","Site":"acr"} {"Name":"ab03","Site":"acr"} {"Name":"ab04","Site":"acr"} {"Name":"ab05","Site":"acr"} {"Name":"ab06","Site":"acr"} {"Name":"ab07","Site":"acr"} {"Name":"ab08","Site":"acr"} {"Name":"ab09","Site":"acr"} {"Name":"ac00","Site":"acr"} {"Name":"ac01","Site":"acr"} {"Name":"ac02","Site":"acr"} {"Name":"ac03","Site":"acr"} {"Name":"ac04","Site":"acr"} {"Name":"ac05","Site":"acr"} {"Name":"ac06","Site":"acr"} {"Name":"ac07","Site":"acr"} {"Name":"ac08","Site":"acr"} {"Name":"ac09","Site":"acr"} {"Name":"ad00","Site":"acr"} {"Name":"ad01","Site":"acr"} {"Name":"ad02","Site":"acr"} {"Name":"ad03","Site":"acr"} {"Name":"ad04","Site":"acr"} {"Name":"ad05","Site":"acr"} {"Name":"ad06","Site":"acr"} {"Name":"ad07","Site":"acr"} {"Name":"ad08","Site":"acr"} {"Name":"ad09","Site":"acr"} {"Name":"ae00","Site":"acr"} {"Name":"ae01","Site":"acr"} {"Name":"ae02","Site":"acr"} {"Name":"ae03","Site":"acr"} {"Name":"ae04","Site":"acr"} {"Name":"ae05","Site":"acr"} {"Name":"ae06","Site":"acr"} {"Name":"ae07","Site":"acr"} {"Name":"ae08","Site":"acr"} {"Name":"ae09","Site":"acr"} {"Name":"aa00","Site":"acu"} {"Name":"aa01","Site":"acu"} {"Name":"aa02","Site":"acu"} {"Name":"aa03","Site":"acu"} {"Name":"aa04","Site":"acu"} {"Name":"aa05","Site":"acu"} {"Name":"aa06","Site":"acu"} {"Name":"aa07","Site":"acu"} {"Name":"aa08","Site":"acu"} {"Name":"aa09","Site":"acu"} {"Name":"ab00","Site":"acu"} {"Name":"ab01","Site":"acu"} {"Name":"ab02","Site":"acu"} {"Name":"ab03","Site":"acu"} {"Name":"ab04","Site":"acu"} {"Name":"ab05","Site":"acu"} {"Name":"ab06","Site":"acu"} {"Name":"ab07","Site":"acu"} {"Name":"ab08","Site":"acu"} {"Name":"ab09","Site":"acu"} {"Name":"ac00","Site":"acu"} {"Name":"ac01","Site":"acu"} {"Name":"ac02","Site":"acu"} {"Name":"ac03","Site":"acu"} {"Name":"ac04","Site":"acu"} {"Name":"ac05","Site":"acu"} {"Name":"ac06","Site":"acu"} {"Name":"ac07","Site":"acu"} {"Name":"ac08","Site":"acu"} {"Name":"ac09","Site":"acu"} {"Name":"ad00","Site":"acu"} {"Name":"ad01","Site":"acu"} {"Name":"ad02","Site":"acu"} {"Name":"ad03","Site":"acu"} {"Name":"ad04","Site":"acu"} {"Name":"ad05","Site":"acu"} {"Name":"ad06","Site":"acu"} {"Name":"ad07","Site":"acu"} {"Name":"ad08","Site":"acu"} {"Name":"ad09","Site":"acu"} {"Name":"ae00","Site":"acu"} {"Name":"ae01","Site":"acu"} {"Name":"ae02","Site":"acu"} {"Name":"ae03","Site":"acu"} {"Name":"ae04","Site":"acu"} {"Name":"ae05","Site":"acu"} {"Name":"ae06","Site":"acu"} {"Name":"ae07","Site":"acu"} {"Name":"ae08","Site":"acu"} {"Name":"ae09","Site":"acu"} {"Name":"aa00","Site":"acx"} {"Name":"aa01","Site":"acx"} {"Name":"aa02","Site":"acx"} {"Name":"aa03","Site":"acx"} {"Name":"aa04","Site":"acx"} {"Name":"aa05","Site":"acx"} {"Name":"aa06","Site":"acx"} {"Name":"aa07","Site":"acx"} {"Name":"aa08","Site":"acx"} {"Name":"aa09","Site":"acx"} {"Name":"ab00","Site":"acx"} {"Name":"ab01","Site":"acx"} {"Name":"ab02","Site":"acx"} {"Name":"ab03","Site":"acx"} {"Name":"ab04","Site":"acx"} {"Name":"ab05","Site":"acx"} {"Name":"ab06","Site":"acx"} {"Name":"ab07","Site":"acx"} {"Name":"ab08","Site":"acx"} {"Name":"ab09","Site":"acx"} {"Name":"ac00","Site":"acx"} {"Name":"ac01","Site":"acx"} {"Name":"ac02","Site":"acx"} {"Name":"ac03","Site":"acx"} {"Name":"ac04","Site":"acx"} {"Name":"ac05","Site":"acx"} {"Name":"ac06","Site":"acx"} {"Name":"ac07","Site":"acx"} {"Name":"ac08","Site":"acx"} {"Name":"ac09","Site":"acx"} {"Name":"ad00","Site":"acx"} {"Name":"ad01","Site":"acx"} {"Name":"ad02","Site":"acx"} {"Name":"ad03","Site":"acx"} {"Name":"ad04","Site":"acx"} {"Name":"ad05","Site":"acx"} {"Name":"ad06","Site":"acx"} {"Name":"ad07","Site":"acx"} {"Name":"ad08","Site":"acx"} {"Name":"ad09","Site":"acx"} {"Name":"ae00","Site":"acx"} {"Name":"ae01","Site":"acx"} {"Name":"ae02","Site":"acx"} {"Name":"ae03","Site":"acx"} {"Name":"ae04","Site":"acx"} {"Name":"ae05","Site":"acx"} {"Name":"ae06","Site":"acx"} {"Name":"ae07","Site":"acx"} {"Name":"ae08","Site":"acx"} {"Name":"ae09","Site":"acx"} {"Name":"aa00","Site":"ada"} {"Name":"aa01","Site":"ada"} {"Name":"aa02","Site":"ada"} {"Name":"aa03","Site":"ada"} {"Name":"aa04","Site":"ada"} {"Name":"aa05","Site":"ada"} {"Name":"aa06","Site":"ada"} {"Name":"aa07","Site":"ada"} {"Name":"aa08","Site":"ada"} {"Name":"aa09","Site":"ada"} {"Name":"ab00","Site":"ada"} {"Name":"ab01","Site":"ada"} {"Name":"ab02","Site":"ada"} {"Name":"ab03","Site":"ada"} {"Name":"ab04","Site":"ada"} {"Name":"ab05","Site":"ada"} {"Name":"ab06","Site":"ada"} {"Name":"ab07","Site":"ada"} {"Name":"ab08","Site":"ada"} {"Name":"ab09","Site":"ada"} {"Name":"ac00","Site":"ada"} {"Name":"ac01","Site":"ada"} {"Name":"ac02","Site":"ada"} {"Name":"ac03","Site":"ada"} {"Name":"ac04","Site":"ada"} {"Name":"ac05","Site":"ada"} {"Name":"ac06","Site":"ada"} {"Name":"ac07","Site":"ada"} {"Name":"ac08","Site":"ada"} {"Name":"ac09","Site":"ada"} {"Name":"ad00","Site":"ada"} {"Name":"ad01","Site":"ada"} {"Name":"ad02","Site":"ada"} {"Name":"ad03","Site":"ada"} {"Name":"ad04","Site":"ada"} {"Name":"ad05","Site":"ada"} {"Name":"ad06","Site":"ada"} {"Name":"ad07","Site":"ada"} {"Name":"ad08","Site":"ada"} {"Name":"ad09","Site":"ada"} {"Name":"ae00","Site":"ada"} {"Name":"ae01","Site":"ada"} {"Name":"ae02","Site":"ada"} {"Name":"ae03","Site":"ada"} {"Name":"ae04","Site":"ada"} {"Name":"ae05","Site":"ada"} {"Name":"ae06","Site":"ada"} {"Name":"ae07","Site":"ada"} {"Name":"ae08","Site":"ada"} {"Name":"ae09","Site":"ada"} {"Name":"aa00","Site":"add"} {"Name":"aa01","Site":"add"} {"Name":"aa02","Site":"add"} {"Name":"aa03","Site":"add"} {"Name":"aa04","Site":"add"} {"Name":"aa05","Site":"add"} {"Name":"aa06","Site":"add"} {"Name":"aa07","Site":"add"} {"Name":"aa08","Site":"add"} {"Name":"aa09","Site":"add"} {"Name":"ab00","Site":"add"} {"Name":"ab01","Site":"add"} {"Name":"ab02","Site":"add"} {"Name":"ab03","Site":"add"} {"Name":"ab04","Site":"add"} {"Name":"ab05","Site":"add"} {"Name":"ab06","Site":"add"} {"Name":"ab07","Site":"add"} {"Name":"ab08","Site":"add"} {"Name":"ab09","Site":"add"} {"Name":"ac00","Site":"add"} {"Name":"ac01","Site":"add"} {"Name":"ac02","Site":"add"} {"Name":"ac03","Site":"add"} {"Name":"ac04","Site":"add"} {"Name":"ac05","Site":"add"} {"Name":"ac06","Site":"add"} {"Name":"ac07","Site":"add"} {"Name":"ac08","Site":"add"} {"Name":"ac09","Site":"add"} {"Name":"ad00","Site":"add"} {"Name":"ad01","Site":"add"} {"Name":"ad02","Site":"add"} {"Name":"ad03","Site":"add"} {"Name":"ad04","Site":"add"} {"Name":"ad05","Site":"add"} {"Name":"ad06","Site":"add"} {"Name":"ad07","Site":"add"} {"Name":"ad08","Site":"add"} {"Name":"ad09","Site":"add"} {"Name":"ae00","Site":"add"} {"Name":"ae01","Site":"add"} {"Name":"ae02","Site":"add"} {"Name":"ae03","Site":"add"} {"Name":"ae04","Site":"add"} {"Name":"ae05","Site":"add"} {"Name":"ae06","Site":"add"} {"Name":"ae07","Site":"add"} {"Name":"ae08","Site":"add"} {"Name":"ae09","Site":"add"} {"Name":"aa00","Site":"adg"} {"Name":"aa01","Site":"adg"} {"Name":"aa02","Site":"adg"} {"Name":"aa03","Site":"adg"} {"Name":"aa04","Site":"adg"} {"Name":"aa05","Site":"adg"} {"Name":"aa06","Site":"adg"} {"Name":"aa07","Site":"adg"} {"Name":"aa08","Site":"adg"} {"Name":"aa09","Site":"adg"} {"Name":"ab00","Site":"adg"} {"Name":"ab01","Site":"adg"} {"Name":"ab02","Site":"adg"} {"Name":"ab03","Site":"adg"} {"Name":"ab04","Site":"adg"} {"Name":"ab05","Site":"adg"} {"Name":"ab06","Site":"adg"} {"Name":"ab07","Site":"adg"} {"Name":"ab08","Site":"adg"} {"Name":"ab09","Site":"adg"} {"Name":"ac00","Site":"adg"} {"Name":"ac01","Site":"adg"} {"Name":"ac02","Site":"adg"} {"Name":"ac03","Site":"adg"} {"Name":"ac04","Site":"adg"} {"Name":"ac05","Site":"adg"} {"Name":"ac06","Site":"adg"} {"Name":"ac07","Site":"adg"} {"Name":"ac08","Site":"adg"} {"Name":"ac09","Site":"adg"} {"Name":"ad00","Site":"adg"} {"Name":"ad01","Site":"adg"} {"Name":"ad02","Site":"adg"} {"Name":"ad03","Site":"adg"} {"Name":"ad04","Site":"adg"} {"Name":"ad05","Site":"adg"} {"Name":"ad06","Site":"adg"} {"Name":"ad07","Site":"adg"} {"Name":"ad08","Site":"adg"} {"Name":"ad09","Site":"adg"} {"Name":"ae00","Site":"adg"} {"Name":"ae01","Site":"adg"} {"Name":"ae02","Site":"adg"} {"Name":"ae03","Site":"adg"} {"Name":"ae04","Site":"adg"} {"Name":"ae05","Site":"adg"} {"Name":"ae06","Site":"adg"} {"Name":"ae07","Site":"adg"} {"Name":"ae08","Site":"adg"} {"Name":"ae09","Site":"adg"} {"Name":"aa00","Site":"adj"} {"Name":"aa01","Site":"adj"} {"Name":"aa02","Site":"adj"} {"Name":"aa03","Site":"adj"} {"Name":"aa04","Site":"adj"} {"Name":"aa05","Site":"adj"} {"Name":"aa06","Site":"adj"} {"Name":"aa07","Site":"adj"} {"Name":"aa08","Site":"adj"} {"Name":"aa09","Site":"adj"} {"Name":"ab00","Site":"adj"} {"Name":"ab01","Site":"adj"} {"Name":"ab02","Site":"adj"} {"Name":"ab03","Site":"adj"} {"Name":"ab04","Site":"adj"} {"Name":"ab05","Site":"adj"} {"Name":"ab06","Site":"adj"} {"Name":"ab07","Site":"adj"} {"Name":"ab08","Site":"adj"} {"Name":"ab09","Site":"adj"} {"Name":"ac00","Site":"adj"} {"Name":"ac01","Site":"adj"} {"Name":"ac02","Site":"adj"} {"Name":"ac03","Site":"adj"} {"Name":"ac04","Site":"adj"} {"Name":"ac05","Site":"adj"} {"Name":"ac06","Site":"adj"} {"Name":"ac07","Site":"adj"} {"Name":"ac08","Site":"adj"} {"Name":"ac09","Site":"adj"} {"Name":"ad00","Site":"adj"} {"Name":"ad01","Site":"adj"} {"Name":"ad02","Site":"adj"} {"Name":"ad03","Site":"adj"} {"Name":"ad04","Site":"adj"} {"Name":"ad05","Site":"adj"} {"Name":"ad06","Site":"adj"} {"Name":"ad07","Site":"adj"} {"Name":"ad08","Site":"adj"} {"Name":"ad09","Site":"adj"} {"Name":"ae00","Site":"adj"} {"Name":"ae01","Site":"adj"} {"Name":"ae02","Site":"adj"} {"Name":"ae03","Site":"adj"} {"Name":"ae04","Site":"adj"} {"Name":"ae05","Site":"adj"} {"Name":"ae06","Site":"adj"} {"Name":"ae07","Site":"adj"} {"Name":"ae08","Site":"adj"} {"Name":"ae09","Site":"adj"} {"Name":"aa00","Site":"adm"} {"Name":"aa01","Site":"adm"} {"Name":"aa02","Site":"adm"} {"Name":"aa03","Site":"adm"} {"Name":"aa04","Site":"adm"} {"Name":"aa05","Site":"adm"} {"Name":"aa06","Site":"adm"} {"Name":"aa07","Site":"adm"} {"Name":"aa08","Site":"adm"} {"Name":"aa09","Site":"adm"} {"Name":"ab00","Site":"adm"} {"Name":"ab01","Site":"adm"} {"Name":"ab02","Site":"adm"} {"Name":"ab03","Site":"adm"} {"Name":"ab04","Site":"adm"} {"Name":"ab05","Site":"adm"} {"Name":"ab06","Site":"adm"} {"Name":"ab07","Site":"adm"} {"Name":"ab08","Site":"adm"} {"Name":"ab09","Site":"adm"} {"Name":"ac00","Site":"adm"} {"Name":"ac01","Site":"adm"} {"Name":"ac02","Site":"adm"} {"Name":"ac03","Site":"adm"} {"Name":"ac04","Site":"adm"} {"Name":"ac05","Site":"adm"} {"Name":"ac06","Site":"adm"} {"Name":"ac07","Site":"adm"} {"Name":"ac08","Site":"adm"} {"Name":"ac09","Site":"adm"} {"Name":"ad00","Site":"adm"} {"Name":"ad01","Site":"adm"} {"Name":"ad02","Site":"adm"} {"Name":"ad03","Site":"adm"} {"Name":"ad04","Site":"adm"} {"Name":"ad05","Site":"adm"} {"Name":"ad06","Site":"adm"} {"Name":"ad07","Site":"adm"} {"Name":"ad08","Site":"adm"} {"Name":"ad09","Site":"adm"} {"Name":"ae00","Site":"adm"} {"Name":"ae01","Site":"adm"} {"Name":"ae02","Site":"adm"} {"Name":"ae03","Site":"adm"} {"Name":"ae04","Site":"adm"} {"Name":"ae05","Site":"adm"} {"Name":"ae06","Site":"adm"} {"Name":"ae07","Site":"adm"} {"Name":"ae08","Site":"adm"} {"Name":"ae09","Site":"adm"} {"Name":"aa00","Site":"adp"} {"Name":"aa01","Site":"adp"} {"Name":"aa02","Site":"adp"} {"Name":"aa03","Site":"adp"} {"Name":"aa04","Site":"adp"} {"Name":"aa05","Site":"adp"} {"Name":"aa06","Site":"adp"} {"Name":"aa07","Site":"adp"} {"Name":"aa08","Site":"adp"} {"Name":"aa09","Site":"adp"} {"Name":"ab00","Site":"adp"} {"Name":"ab01","Site":"adp"} {"Name":"ab02","Site":"adp"} {"Name":"ab03","Site":"adp"} {"Name":"ab04","Site":"adp"} {"Name":"ab05","Site":"adp"} {"Name":"ab06","Site":"adp"} {"Name":"ab07","Site":"adp"} {"Name":"ab08","Site":"adp"} {"Name":"ab09","Site":"adp"} {"Name":"ac00","Site":"adp"} {"Name":"ac01","Site":"adp"} {"Name":"ac02","Site":"adp"} {"Name":"ac03","Site":"adp"} {"Name":"ac04","Site":"adp"} {"Name":"ac05","Site":"adp"} {"Name":"ac06","Site":"adp"} {"Name":"ac07","Site":"adp"} {"Name":"ac08","Site":"adp"} {"Name":"ac09","Site":"adp"} {"Name":"ad00","Site":"adp"} {"Name":"ad01","Site":"adp"} {"Name":"ad02","Site":"adp"} {"Name":"ad03","Site":"adp"} {"Name":"ad04","Site":"adp"} {"Name":"ad05","Site":"adp"} {"Name":"ad06","Site":"adp"} {"Name":"ad07","Site":"adp"} {"Name":"ad08","Site":"adp"} {"Name":"ad09","Site":"adp"} {"Name":"ae00","Site":"adp"} {"Name":"ae01","Site":"adp"} {"Name":"ae02","Site":"adp"} {"Name":"ae03","Site":"adp"} {"Name":"ae04","Site":"adp"} {"Name":"ae05","Site":"adp"} {"Name":"ae06","Site":"adp"} {"Name":"ae07","Site":"adp"} {"Name":"ae08","Site":"adp"} {"Name":"ae09","Site":"adp"} {"Name":"aa00","Site":"ads"} {"Name":"aa01","Site":"ads"} {"Name":"aa02","Site":"ads"} {"Name":"aa03","Site":"ads"} {"Name":"aa04","Site":"ads"} {"Name":"aa05","Site":"ads"} {"Name":"aa06","Site":"ads"} {"Name":"aa07","Site":"ads"} {"Name":"aa08","Site":"ads"} {"Name":"aa09","Site":"ads"} {"Name":"ab00","Site":"ads"} {"Name":"ab01","Site":"ads"} {"Name":"ab02","Site":"ads"} {"Name":"ab03","Site":"ads"} {"Name":"ab04","Site":"ads"} {"Name":"ab05","Site":"ads"} {"Name":"ab06","Site":"ads"} {"Name":"ab07","Site":"ads"} {"Name":"ab08","Site":"ads"} {"Name":"ab09","Site":"ads"} {"Name":"ac00","Site":"ads"} {"Name":"ac01","Site":"ads"} {"Name":"ac02","Site":"ads"} {"Name":"ac03","Site":"ads"} {"Name":"ac04","Site":"ads"} {"Name":"ac05","Site":"ads"} {"Name":"ac06","Site":"ads"} {"Name":"ac07","Site":"ads"} {"Name":"ac08","Site":"ads"} {"Name":"ac09","Site":"ads"} {"Name":"ad00","Site":"ads"} {"Name":"ad01","Site":"ads"} {"Name":"ad02","Site":"ads"} {"Name":"ad03","Site":"ads"} {"Name":"ad04","Site":"ads"} {"Name":"ad05","Site":"ads"} {"Name":"ad06","Site":"ads"} {"Name":"ad07","Site":"ads"} {"Name":"ad08","Site":"ads"} {"Name":"ad09","Site":"ads"} {"Name":"ae00","Site":"ads"} {"Name":"ae01","Site":"ads"} {"Name":"ae02","Site":"ads"} {"Name":"ae03","Site":"ads"} {"Name":"ae04","Site":"ads"} {"Name":"ae05","Site":"ads"} {"Name":"ae06","Site":"ads"} {"Name":"ae07","Site":"ads"} {"Name":"ae08","Site":"ads"} {"Name":"ae09","Site":"ads"} {"Name":"aa00","Site":"adv"} {"Name":"aa01","Site":"adv"} {"Name":"aa02","Site":"adv"} {"Name":"aa03","Site":"adv"} {"Name":"aa04","Site":"adv"} {"Name":"aa05","Site":"adv"} {"Name":"aa06","Site":"adv"} {"Name":"aa07","Site":"adv"} {"Name":"aa08","Site":"adv"} {"Name":"aa09","Site":"adv"} {"Name":"ab00","Site":"adv"} {"Name":"ab01","Site":"adv"} {"Name":"ab02","Site":"adv"} {"Name":"ab03","Site":"adv"} {"Name":"ab04","Site":"adv"} {"Name":"ab05","Site":"adv"} {"Name":"ab06","Site":"adv"} {"Name":"ab07","Site":"adv"} {"Name":"ab08","Site":"adv"} {"Name":"ab09","Site":"adv"} {"Name":"ac00","Site":"adv"} {"Name":"ac01","Site":"adv"} {"Name":"ac02","Site":"adv"} {"Name":"ac03","Site":"adv"} {"Name":"ac04","Site":"adv"} {"Name":"ac05","Site":"adv"} {"Name":"ac06","Site":"adv"} {"Name":"ac07","Site":"adv"} {"Name":"ac08","Site":"adv"} {"Name":"ac09","Site":"adv"} {"Name":"ad00","Site":"adv"} {"Name":"ad01","Site":"adv"} {"Name":"ad02","Site":"adv"} {"Name":"ad03","Site":"adv"} {"Name":"ad04","Site":"adv"} {"Name":"ad05","Site":"adv"} {"Name":"ad06","Site":"adv"} {"Name":"ad07","Site":"adv"} {"Name":"ad08","Site":"adv"} {"Name":"ad09","Site":"adv"} {"Name":"ae00","Site":"adv"} {"Name":"ae01","Site":"adv"} {"Name":"ae02","Site":"adv"} {"Name":"ae03","Site":"adv"} {"Name":"ae04","Site":"adv"} {"Name":"ae05","Site":"adv"} {"Name":"ae06","Site":"adv"} {"Name":"ae07","Site":"adv"} {"Name":"ae08","Site":"adv"} {"Name":"ae09","Site":"adv"} ================================================ FILE: chapter/16/workflow/data/packages/sites/sites.go ================================================ /* Package sites contains types, functions and methods for reading and interpreting data about sites contained in data files sites.json and machines.json This data can be accessed through the global variable "Data". */ package sites import ( "encoding/json" "fmt" "os" "path/filepath" "regexp" ) var ( siteNameRE = regexp.MustCompile(`[a-z][a-z][a-z]`) machineNameRE = regexp.MustCompile(`[a-z][a-z]0[0-9]`) ) // Data holds the site data and is how to access the data. // Note: in a real system, the data here would always be a copy. That way // different things accessing it could not change it for others. var Data SiteData // Init initializes our Data given the data location. Call from main(). func Init(loc string) { sd, err := newSiteData(loc) if err != nil { panic(err) } Data = sd } // Site represents an individual site where we have machines located. type Site struct { // Name is the name of the site. Name string // Type is the type of site. Type string // Status is the status of the site. Status string // Machines are a list of machines in the site. Machines []Machine } // Validate validates Site's fields are valid. func (s Site) Validate() error { if !siteNameRE.MatchString(s.Name) { return fmt.Errorf(".Name(%s) is not a valid Site name", s.Name) } switch s.Type { case "satellite", "cluster": default: return fmt.Errorf("site has .Type(%s) that is invalid", s.Type) } switch s.Status { case "inService", "decom", "removed": default: return fmt.Errorf("site has .Status(%s) that is invalid", s.Status) } return nil } // Machine represents a physical machine located in a site. type Machine struct { // Name is the name of the machine. Name string // Site is the site the machine is located at. Site string } // FullName retrieves the globally unique name for the machine. func (m Machine) FullName() string { return m.Name + "." + m.Site } // Validate validates a Machine's fields. func (m Machine) Validate() error { if !machineNameRE.MatchString(m.Name) { return fmt.Errorf(".Name(%s) is not a valid Machine name", m.Name) } if !siteNameRE.MatchString(m.Site) { return fmt.Errorf(".Site(%s) is not a valid Site name for a Machine to belong to", m.Site) } return nil } // SiteData contains information on our sites and the machines that are located in those sites. type SiteData struct { // Sites is mapping of all sites by name. Sites map[string]Site // Machines is a mapping of all machines by their full name (aa01.aaa). Machines map[string]Machine } // newSiteData creates a new SiteData instance by reading sites.json and machines.json from "dir". func newSiteData(dir string) (SiteData, error) { sf, err := os.Open(filepath.Join(dir, "sites.json")) if err != nil { return SiteData{}, err } defer sf.Close() mf, err := os.Open(filepath.Join(dir, "machines.json")) if err != nil { return SiteData{}, err } defer mf.Close() sd := SiteData{Sites: map[string]Site{}, Machines: map[string]Machine{}} sitesDec := json.NewDecoder(sf) for sitesDec.More() { s := Site{} if err := sitesDec.Decode(&s); err != nil { return SiteData{}, err } if err := s.Validate(); err != nil { return SiteData{}, err } sd.Sites[s.Name] = s } machinesDec := json.NewDecoder(mf) for machinesDec.More() { m := Machine{} if err := machinesDec.Decode(&m); err != nil { return SiteData{}, err } if err := m.Validate(); err != nil { return SiteData{}, err } s, ok := sd.Sites[m.Site] if !ok { return SiteData{}, fmt.Errorf("Machine(%s) has Site(%s) that was not found in our sites.json file", m.Name, m.Site) } s.Machines = append(s.Machines, m) sd.Sites[m.Site] = s sd.Machines[m.FullName()] = m } return sd, nil } ================================================ FILE: chapter/16/workflow/data/sites.json ================================================ {"Name":"aaa","Type":"satellite","Status":"inService"} {"Name":"aab","Type":"cluster","Status":"inService"} {"Name":"aac","Type":"cluster","Status":"inService"} {"Name":"aad","Type":"satellite","Status":"inService"} {"Name":"aae","Type":"cluster","Status":"inService"} {"Name":"aaf","Type":"cluster","Status":"inService"} {"Name":"aag","Type":"satellite","Status":"inService"} {"Name":"aah","Type":"cluster","Status":"inService"} {"Name":"aai","Type":"cluster","Status":"inService"} {"Name":"aaj","Type":"satellite","Status":"inService"} {"Name":"aak","Type":"cluster","Status":"inService"} {"Name":"aal","Type":"cluster","Status":"inService"} {"Name":"aam","Type":"satellite","Status":"inService"} {"Name":"aan","Type":"cluster","Status":"inService"} {"Name":"aao","Type":"cluster","Status":"inService"} {"Name":"aap","Type":"satellite","Status":"decom"} {"Name":"aaq","Type":"cluster","Status":"inService"} {"Name":"aar","Type":"cluster","Status":"inService"} {"Name":"aas","Type":"satellite","Status":"inService"} {"Name":"aat","Type":"cluster","Status":"inService"} {"Name":"aau","Type":"cluster","Status":"inService"} {"Name":"aav","Type":"satellite","Status":"inService"} {"Name":"aaw","Type":"cluster","Status":"inService"} {"Name":"aax","Type":"cluster","Status":"inService"} {"Name":"aay","Type":"satellite","Status":"inService"} {"Name":"aaz","Type":"cluster","Status":"inService"} {"Name":"aba","Type":"cluster","Status":"inService"} {"Name":"abb","Type":"satellite","Status":"inService"} {"Name":"abc","Type":"cluster","Status":"inService"} {"Name":"abd","Type":"cluster","Status":"inService"} {"Name":"abe","Type":"satellite","Status":"inService"} {"Name":"abf","Type":"cluster","Status":"inService"} {"Name":"abg","Type":"cluster","Status":"inService"} {"Name":"abh","Type":"satellite","Status":"inService"} {"Name":"abi","Type":"cluster","Status":"inService"} {"Name":"abj","Type":"cluster","Status":"inService"} {"Name":"abk","Type":"satellite","Status":"inService"} {"Name":"abl","Type":"cluster","Status":"inService"} {"Name":"abm","Type":"cluster","Status":"inService"} {"Name":"abn","Type":"satellite","Status":"inService"} {"Name":"abo","Type":"cluster","Status":"inService"} {"Name":"abp","Type":"cluster","Status":"inService"} {"Name":"abq","Type":"satellite","Status":"inService"} {"Name":"abr","Type":"cluster","Status":"inService"} {"Name":"abs","Type":"cluster","Status":"inService"} {"Name":"abt","Type":"satellite","Status":"inService"} {"Name":"abu","Type":"cluster","Status":"inService"} {"Name":"abv","Type":"cluster","Status":"inService"} {"Name":"abw","Type":"satellite","Status":"inService"} {"Name":"abx","Type":"cluster","Status":"inService"} {"Name":"aby","Type":"cluster","Status":"inService"} {"Name":"abz","Type":"satellite","Status":"inService"} {"Name":"aca","Type":"cluster","Status":"inService"} {"Name":"acb","Type":"cluster","Status":"inService"} {"Name":"acc","Type":"satellite","Status":"inService"} {"Name":"acd","Type":"cluster","Status":"inService"} {"Name":"ace","Type":"cluster","Status":"inService"} {"Name":"acf","Type":"satellite","Status":"inService"} {"Name":"acg","Type":"cluster","Status":"inService"} {"Name":"ach","Type":"cluster","Status":"inService"} {"Name":"aci","Type":"satellite","Status":"inService"} {"Name":"acj","Type":"cluster","Status":"inService"} {"Name":"ack","Type":"cluster","Status":"inService"} {"Name":"acl","Type":"satellite","Status":"inService"} {"Name":"acm","Type":"cluster","Status":"inService"} {"Name":"acn","Type":"cluster","Status":"inService"} {"Name":"aco","Type":"satellite","Status":"inService"} {"Name":"acp","Type":"cluster","Status":"inService"} {"Name":"acq","Type":"cluster","Status":"inService"} {"Name":"acr","Type":"satellite","Status":"inService"} {"Name":"acs","Type":"cluster","Status":"inService"} {"Name":"act","Type":"cluster","Status":"inService"} {"Name":"acu","Type":"satellite","Status":"inService"} {"Name":"acv","Type":"cluster","Status":"inService"} {"Name":"acw","Type":"cluster","Status":"inService"} {"Name":"acx","Type":"satellite","Status":"inService"} {"Name":"acy","Type":"cluster","Status":"inService"} {"Name":"acz","Type":"cluster","Status":"inService"} {"Name":"ada","Type":"satellite","Status":"inService"} {"Name":"adb","Type":"cluster","Status":"inService"} {"Name":"adc","Type":"cluster","Status":"inService"} {"Name":"add","Type":"satellite","Status":"inService"} {"Name":"ade","Type":"cluster","Status":"inService"} {"Name":"adf","Type":"cluster","Status":"inService"} {"Name":"adg","Type":"satellite","Status":"decom"} {"Name":"adh","Type":"cluster","Status":"inService"} {"Name":"adi","Type":"cluster","Status":"inService"} {"Name":"adj","Type":"satellite","Status":"inService"} {"Name":"adk","Type":"cluster","Status":"inService"} {"Name":"adl","Type":"cluster","Status":"inService"} {"Name":"adm","Type":"satellite","Status":"inService"} {"Name":"adn","Type":"cluster","Status":"inService"} {"Name":"ado","Type":"cluster","Status":"inService"} {"Name":"adp","Type":"satellite","Status":"inService"} {"Name":"adq","Type":"cluster","Status":"inService"} {"Name":"adr","Type":"cluster","Status":"inService"} {"Name":"ads","Type":"satellite","Status":"inService"} {"Name":"adt","Type":"cluster","Status":"inService"} {"Name":"adu","Type":"cluster","Status":"inService"} {"Name":"adv","Type":"satellite","Status":"decom"} ================================================ FILE: chapter/16/workflow/internal/es/es.go ================================================ /* Package es contains an emergency stop implementation. This data is read from es.json file every 10 seconds. If the data changes, subscribers will receive an update. Using this is simple: ch, cancel := es.Data.Subscribe("SatelliteDiskErase") defer cancel() if <-ch != es.Go { // Do something } select { case <-ch: log.Println("ES changed to Stop state ") } */ package es import ( "encoding/json" "fmt" "log" "os" "strings" "sync" "sync/atomic" "time" ) // Data is how to access the emergency stop information. var Data *Reader func init() { d, err := newReader() if err != nil { panic(err) } Data = d } // Status indicates the emergency stop status. type Status string const ( // Unknown means the status was not set. Unknown Status = "" // Go indicates the matching workflow can execute. Go Status = "go" // Stop indicates that the matching workflow should not execute and // existing ones should be stopped. Stop Status = "stop" ) // Info is the emergency stop information for a particular entry in our es.json file. type Info struct { // Name is the WorkReq type. Name string // Status is the emergency stop status. Status Status } func (i Info) validate() error { i.Name = strings.TrimSpace(i.Name) if i.Name == "" { return fmt.Errorf("es.json: rule with empty name, ignored") } switch i.Status { case "go", "stop": default: return fmt.Errorf("es.json: rule(%s) has invalid Status(%s), ignored", i.Name, i.Status) } return nil } // Reader reads the es.json file at intervals and makes the data and changes to the data // available. type Reader struct { entries atomic.Value // map[string]Info mu sync.Mutex subscribers map[string][]chan Status } func newReader() (*Reader, error) { r := &Reader{subscribers: map[string][]chan Status{}} m, err := r.load() if err != nil { return nil, err } r.entries.Store(m) go r.loop() return r, nil } // Cancel is used to cancel your subscription. type Cancel func() // Subscribe returns a channel that sends a Status whenever a ES entry changes status. // This will send the initial Status immediately. If the name is not found, this will // send Stop and close the channel. If there is a transition to Stop, the channel will // be closed once this is sent. Once you either receive a Stop or are no longer interested // in listening, simply call Cancel(). func (r *Reader) Subscribe(name string) (chan Status, Cancel) { i, ok := r.entries.Load().(map[string]Info)[name] if !ok || i.Status != Go { ch := make(chan Status, 1) ch <- Stop close(ch) return ch, func() {} } r.mu.Lock() defer r.mu.Unlock() ch := make(chan Status, 1) ch <- Go l := r.subscribers[name] l = append(l, ch) r.subscribers[name] = l // This removes the channel when it is no longer needed because no // one is listening. cancel := func() { r.mu.Lock() defer r.mu.Unlock() l := make([]chan Status, 0, len(r.subscribers[name])-1) for _, stored := range r.subscribers[name] { if stored == ch { continue } l = append(l, stored) } r.subscribers[name] = l } return ch, cancel } // Status returns the ES status for the named workflow. func (r *Reader) Status(name string) Status { m := r.entries.Load().(map[string]Info) switch m[name].Status { case Go: return Go } return Stop } // loop reads the es.json file in every 10 seconds and updates subscribers of changes // from Go status to Stop status. func (r *Reader) loop() { for _ = range time.Tick(10 * time.Second) { newInfos, err := r.load() if err != nil { // This means the file was malformed or missing. In these // cases we stop all work. r.mu.Lock() for name := range r.subscribers { r.sendStop(name) } r.mu.Unlock() continue } for name, info := range r.entries.Load().(map[string]Info) { newInfo, ok := newInfos[name] if !ok { r.sendStop(name) continue } if info.Status == Go && newInfo.Status != Go { r.sendStop(name) continue } } r.entries.Store(newInfos) } } // sendStop sends a Stop State change to all subscriber to a name. func (r *Reader) sendStop(name string) { r.mu.Lock() defer r.mu.Unlock() chans := r.subscribers[name] for _, ch := range chans { for { select { case ch <- Stop: break default: // If somehow the channel is full, remove the old entry // and then loop and add the most recent one. select { case <-ch: default: } } } close(ch) delete(r.subscribers, name) } } // load loads the current es.json values and returns them. Any error is an indication // that the file could not be read. func (r *Reader) load() (map[string]Info, error) { f, err := os.Open("configs/es.json") if err != nil { return map[string]Info{}, fmt.Errorf("could not open configs/es.json: %w", err) } dec := json.NewDecoder(f) dec.DisallowUnknownFields() m := map[string]Info{} for dec.More() { info := Info{} if err := dec.Decode(&info); err != nil { r.entries.Store(map[string]Info{}) return map[string]Info{}, fmt.Errorf("es.json file is badly formatted, all jobs moving into stop state") } if _, ok := m[info.Name]; ok { log.Printf("es.json file has two definitions(%s) with the same name, ignoring the second", info.Name) continue } m[info.Name] = info } return m, nil } ================================================ FILE: chapter/16/workflow/internal/policy/config/config.go ================================================ /* Package config stores the policy configuration Go representation, a global variable called Policies that is for reading the Config as it is updated on disk and configuration validation to make sure errors don't slip in to the Config. A configuration is stored in JSON and looks like: { "Name": "SateliteDiskErase", "Policies": [ { "Name": "restrictJobTypes", "Settings": { "AllowedJobs": [ "JTValidateDecom", "JTDiskErase", "JTSleep", "JTGetTokenFromBucket" ] } } ] } ... */ package config import ( "encoding/json" "fmt" "os" "reflect" "strings" "sync/atomic" "time" "github.com/PacktPublishing/Go-for-DevOps/chapter/16/workflow/internal/policy" ) // Policies provides the policy Reader that can be used to read the current policy. var Policies *Reader // Init is called in main to initialize our reads of the policy file. It is called // manually instead of init() to guarantee other init() statements are run first. func Init() { r, err := newReader("configs/policies.json") if err != nil { panic(err) } Policies = r } // Config represents a policy config that is stored on disk. type Config struct { // Workflows stored by name. Workflows map[string]Workflow // Set if the last load of a new Config had errors. This Config will not // have any errors if this is set. err error } func newConfig() Config { return Config{ Workflows: map[string]Workflow{}, } } func (c Config) validate() error { for k, w := range c.Workflows { if err := w.validate(); err != nil { return err } c.Workflows[k] = w // In case there were any changes. } return nil } // Workflow stores a Workflow name and the Policies for that Workflow. type Workflow struct { // Name is the name of the Workflow. Name string // Policies are the Policies to be applied to that Workflow. Policies []Policy } func (w Workflow) validate() error { w.Name = strings.TrimSpace(w.Name) if w.Name == "" { return fmt.Errorf("Workflow cannot have an empty Name field") } for i, p := range w.Policies { if err := p.validate(); err != nil { return fmt.Errorf("Workflow(%s): %s", w.Name, err) } w.Policies[i] = p // Stores the Policy that has SettingsTyped stored } return nil } // Policy is the policy to apply to a Workflow. type Policy struct { // Name is the name of the policy type. Name string // Settings are the settings for that particular policy. We store this as // a RawMessage so that we can parse it into the typed version on a second pass. Settings json.RawMessage // SettingsTyped is the parsed version of the Settings. This is not stored in the Config. SettingsTyped policy.Settings `json:"-"` } func (p *Policy) validate() error { p.Name = strings.TrimSpace(p.Name) if p.Name == "" { return fmt.Errorf("Policy cannot have an empty Name field") } s, err := policy.GetSettings(p.Name) if err != nil { return err } // This section is going to be confusing, as it is using an advanced topic called // runtime reflection. This is a topic that really can be its own book. Suffice it to say, // I wanted every registered implementation of our policy.Settings to be a struct{}, not // a *struct. This makes it easy to make copies of it without worrying about accidental // modification. But, you can only unmarshal JSON into a *struct. Because all these specific // Settings are stored inside an interface called policy.Settings, you can't do: // &s, because that would be the address of the interface, not the underlying value. // Confused?? Yeah, I know....... // So here we are going to use the reflect package to create a pointer to the specific // value the user put in the interface. Then we are going to unmarshal into that. // Then we are going to convert that back to a policy.Settings interface. // Don't spend a bunch of time here, reflection is something better left avoided if you can. val := reflect.ValueOf(s) ptr := reflect.New(val.Type()) if err := json.Unmarshal(p.Settings, ptr.Interface()); err != nil { return fmt.Errorf("policy(%s) could not unmarshal its Settings: %s", p.Name, err) } p.SettingsTyped = ptr.Elem().Interface().(policy.Settings) if err := p.SettingsTyped.Validate(); err != nil { return fmt.Errorf("policy(%s) Settings did not validate: %s", p.Name, err) } return nil } // Reader is used to read the current policy configuration. The // configuration is checked for updates every 10 seconds and if there // is a valid configuration, it is updated. If not, an error is recorded. // Once a Reader is returned by New(), it guarantees to always return a Config. // If there is an error also returned, then the Config is the last known good // Config. type Reader struct { loc string conf atomic.Value // Config } // Read reads the latest Config we have. If an error is returned, the Config will // be valid, but it will be the last known good Config instead of the broken latest // Config. func (r *Reader) Read() (Config, error) { c := r.conf.Load().(Config) return c, c.err } func (r *Reader) update() { for _ = range time.Tick(10 * time.Second) { if err := r.load(); err != nil { c := r.conf.Load().(Config) c.err = err r.conf.Store(c) } } } func (r *Reader) load() error { f, err := os.Open(r.loc) if err != nil { return fmt.Errorf("cannot access policy config(%s): %w", r.loc, err) } defer f.Close() c := newConfig() dec := json.NewDecoder(f) dec.DisallowUnknownFields() for dec.More() { w := Workflow{} if err := dec.Decode(&w); err != nil { return fmt.Errorf("policy on disk could not be JSON decoded: %w", err) } w.Name = strings.TrimSpace(w.Name) if w.Name == "" { return fmt.Errorf("Workflow cannot have an empty Name field") } if _, ok := c.Workflows[w.Name]; ok { return fmt.Errorf("cannot have two sets of Workflow policies for %q", w.Name) } c.Workflows[w.Name] = w } if err := c.validate(); err != nil { return fmt.Errorf("policy config had an error: %s", err) } r.conf.Store(c) return nil } // newReader returns a Reader that can grab the latest Config on disk. func newReader(loc string) (*Reader, error) { r := &Reader{loc: loc} if err := r.load(); err != nil { return nil, err } go r.update() return r, nil } ================================================ FILE: chapter/16/workflow/internal/policy/policy.go ================================================ /* Package policy provides policy primatives, policy registration and functions to run policies against a WorkReq that is submitted to the system. */ package policy import ( "context" "fmt" "log" "reflect" "strings" "sync" "google.golang.org/protobuf/proto" pb "github.com/PacktPublishing/Go-for-DevOps/chapter/16/workflow/proto" ) var policies = map[string]registration{} type registration struct { policy Policy settings Settings } // Setting holds a struct that is used to hold the Policy settings that are invoked. // These are defined per policy. This should always be a struct and not a *struct. type Settings interface { // Validate validates the Settings for a Policy. Validate() error } // Register registers a policy by name with an empty Settings that will be copied // to provide the Settings we will read out of the config file. func Register(name string, p Policy, s Settings) { name = strings.TrimSpace(name) if name == "" { panic("cannot register a policy with an empty name") } if _, ok := policies[name]; ok { panic(fmt.Sprintf("cannot register two policies with the same name(%s)", name)) } if p == nil { panic("cannot register a nil policy") } if s == nil { panic("cannot register a policy with a nil setting") } if reflect.ValueOf(s).Kind() != reflect.Struct { panic(fmt.Sprintf("cannot register a policy(%s) with settings that are not a struct", name)) } log.Println("Registered Policy: ", name) policies[name] = registration{policy: p, settings: s} } // GetSettings fetches the Settings for a named Policy. func GetSettings(name string) (Settings, error) { r, ok := policies[name] if !ok { return nil, fmt.Errorf("policy(%s) cannot be found", name) } return r.settings, nil } // Policy represents a policy that is defined to check a WorkReq is compliant. type Policy interface { // Run runs the policy against a request with settings that are specific // to the policy. settings can be nil for certain implementations. Run(ctx context.Context, req *pb.WorkReq, settings Settings) error } // PolicyArgs detail a policy and settings to use to invoke it. type PolicyArgs struct { // Name of the policy in the registry. Name string // Settings for that policy invocation. Settings Settings } // Run runs all policies runners that are passed concurrently. func Run(ctx context.Context, req *pb.WorkReq, args ...PolicyArgs) error { if len(args) == 0 { return nil } var cancel context.CancelFunc ctx, cancel = context.WithCancel(ctx) defer cancel() // Make a deep clone so that no policy is able to make changes. creq := proto.Clone(req).(*pb.WorkReq) // Validate that all the policies actually exist and build runners // to simply run them against the settings in the next part. runners := make([]func() error, 0, len(args)) for _, arg := range args { arg := arg r, ok := policies[arg.Name] if !ok { return fmt.Errorf("policy(%s) does not exist", arg.Name) } runners = append( runners, func() error { err := r.policy.Run(ctx, creq, arg.Settings) if err != nil { return fmt.Errorf("policy(%s) violation: %w", arg.Name, err) } return nil }, ) } wg := sync.WaitGroup{} ch := make(chan error, 1) // Run all policy invocations concurrently. wg.Add(len(runners)) for _, r := range runners { r := r go func() { defer wg.Done() if err := r(); err != nil { select { case ch <- err: cancel() default: } return } }() } wg.Wait() select { case err := <-ch: return err default: } if !proto.Equal(req, creq) { return fmt.Errorf("a policy tried to modify a request: this is not allowed as it is a security violation") } return nil } ================================================ FILE: chapter/16/workflow/internal/policy/register/README ================================================ This directory contains policies that are registered with the system. Policies can then be assigned to workflow types to put guardrails on a set of work to be performed. ================================================ FILE: chapter/16/workflow/internal/policy/register/restrictjobtypes/restrictjobtypes.go ================================================ /* Package restrictjobtypes provides a policy that can be invoked to ensure that a WorkReq only contains jobs of certain types. Any job outside these types will cause a policy violation. */ package restrictjobtypes import ( "context" "fmt" "github.com/PacktPublishing/Go-for-DevOps/chapter/16/workflow/internal/policy" "github.com/PacktPublishing/Go-for-DevOps/chapter/16/workflow/internal/service/jobs" pb "github.com/PacktPublishing/Go-for-DevOps/chapter/16/workflow/proto" ) // This registers our policy with the service. func init() { p, err := New() if err != nil { panic(err) } policy.Register("restrictJobTypes", p, Settings{}) } // Settings provides settings for a specific implementation of our Policy. type Settings struct { AllowedJobs []string } // Validate implements policy.Settings.Validate(). func (s Settings) Validate() error { for _, n := range s.AllowedJobs { _, err := jobs.GetJob(n) if err != nil { return fmt.Errorf("allowed job(%s) is not registered in the system", n) } } return nil } func (s Settings) allowed(name string) bool { for _, jn := range s.AllowedJobs { if jn == name { return true } } return false } // Policy implements policy.Policy. type Policy struct{} // New is the constructor for Policy. func New() (Policy, error) { return Policy{}, nil } // Run implements Policy.Run(). func (p Policy) Run(ctx context.Context, req *pb.WorkReq, settings policy.Settings) error { const errMsg = "block(%d)/job(%d) is a type(%s) that is not allowed" s, ok := settings.(Settings) if !ok { return fmt.Errorf("settings were not valid type, were %T", settings) } for blockNum, block := range req.Blocks { for jobNum, job := range block.Jobs { if ctx.Err() != nil { return ctx.Err() } if !s.allowed(job.Name) { return fmt.Errorf(errMsg, blockNum, jobNum, job.Name) } } } return nil } ================================================ FILE: chapter/16/workflow/internal/policy/register/sameargs/sameargs.go ================================================ /* Package sameargs defines a generic policy that can be used to look at Jobs of certain types and validate that every Job of that type has certain arguments that are the same for every invocation. This can be used, for example, to restrict something to working on one service, router, site, region, ... */ package sameargs import ( "context" "fmt" "github.com/PacktPublishing/Go-for-DevOps/chapter/16/workflow/internal/policy" "github.com/PacktPublishing/Go-for-DevOps/chapter/16/workflow/internal/service/jobs" pb "github.com/PacktPublishing/Go-for-DevOps/chapter/16/workflow/proto" ) // This registers our policy with the service. func init() { p, err := New() if err != nil { panic(err) } policy.Register("sameArgs", p, Settings{}) } // ArgKeys are a list of string type ArgKeys []string // Settings provides settings for a specific implementation of our Policy. type Settings struct { // Jobs are a list of Job names we want to check and the args // that must be the same across each Job with that name. Jobs map[string]ArgKeys } // Settings implements policy.Settings.Validate(). func (s Settings) Validate() error { for name := range s.Jobs { if _, err := jobs.GetJob(name); err != nil { return fmt.Errorf("Job(%s) was not found", name) } } return nil } // checkJob returns true if we have a setting corresponding to the JobType. func (s Settings) checkJob(name string) bool { _, ok := s.Jobs[name] return ok } // needKey simply looks at our Jobs argument and determines if we care about // a specific arg key for a JobType. func (s Settings) needKey(name string, k string) bool { keys, ok := s.Jobs[name] if !ok { return false } for _, key := range keys { if k == key { return true } } return false } // sameCheck holds a mapping of JobType that holds args we care about and // the value that should be the same through every instance. type sameCheck map[string]map[string]string // isSame checks that a key for a JobType has the same value as "v". If // a value hasn't been stored, it is stored and used on every future check. func (s sameCheck) isSame(name, k, v string) bool { kv, ok := s[name] if !ok { s[name] = map[string]string{k: v} return true } stored, ok := kv[k] if !ok { s[name][k] = v return true } if stored == v { return true } return false } // Policy implements policy.Policy. type Policy struct{} // New is the constructor for Polixy. func New() (Policy, error) { return Policy{}, nil } // Run implements Policy.Run(). func (p Policy) Run(ctx context.Context, req *pb.WorkReq, settings policy.Settings) error { s, ok := settings.(Settings) if !ok { return fmt.Errorf("settings were not valid type, were %T", settings) } same := sameCheck{} for blockNum, block := range req.Blocks { for jobNum, job := range block.Jobs { if ctx.Err() != nil { return ctx.Err() } if !s.checkJob(job.Name) { continue } if err := p.argSame(s, job, same, blockNum, jobNum); err != nil { return err } } } return nil } func (p Policy) argSame(settings Settings, job *pb.Job, same sameCheck, blockNum, jobNum int) error { const policyErrMsg = "block(%d)/job(%d) violated rule: setting(%s) is different for this job" for k, v := range job.Args { if settings.needKey(job.Name, k) { // Only check if we care about the key if !same.isSame(job.Name, k, v) { return fmt.Errorf(policyErrMsg, blockNum, jobNum, k) } } } return nil } ================================================ FILE: chapter/16/workflow/internal/policy/register/startorend/startOrEnd.go ================================================ /* Package startorend implements a policy that can be used to check that a WorkReq has a particular Job in the first block or the last block with certain settings. Inaddition it can allow for certain other jobs to be before of after it (depending on other settings). This is useful when you need certain cleanup Jobs, health checks or init jobs to be present. */ package startorend import ( "context" "fmt" "github.com/PacktPublishing/Go-for-DevOps/chapter/16/workflow/internal/policy" "github.com/PacktPublishing/Go-for-DevOps/chapter/16/workflow/internal/service/jobs" pb "github.com/PacktPublishing/Go-for-DevOps/chapter/16/workflow/proto" ) // This registers our policy with the service. func init() { p, err := New() if err != nil { panic(err) } policy.Register("startOrEnd", p, Settings{}) } // Settings provides settings for a specific implementation of our Policy. type Settings struct { // JobName is the name of the job that must be present. JobName string // MustArgs indicate arguments that must be set to a certain setting. MustArgs map[string]string // Start indicates this must be one of the first jobs. Start bool // End indicates this must be one of the end jobs. End bool // AllowedBeforeOrAfter are job types that are allowed before // this job if Start == true, or after this job if End == true. AllowedBeforeOrAfter []string allowed map[string]bool } func (s Settings) Validate() error { if _, err := jobs.GetJob(s.JobName); err != nil { return fmt.Errorf("Job(%s) is invalid", s.JobName) } if s.Start && s.End { return fmt.Errorf("Start and End cannot both be true") } if !s.Start && !s.End { return fmt.Errorf("either Start of End must be set") } for _, name := range s.AllowedBeforeOrAfter { if _, err := jobs.GetJob(name); err != nil { return fmt.Errorf("AllowedBeforeOrAfter had Job(%s) that is invalid", name) } } return nil } func (s Settings) compile() Settings { s.allowed = map[string]bool{} for _, name := range s.AllowedBeforeOrAfter { s.allowed[name] = true } return s } // Policy implements policy.Policy. type Policy struct { } // New is the constructor for Policy. func New() (Policy, error) { return Policy{}, nil } // Run implements Policy.Run(). func (p Policy) Run(ctx context.Context, req *pb.WorkReq, settings policy.Settings) error { s, ok := settings.(Settings) if !ok { return fmt.Errorf("settings were not valid type, were %T", settings) } return p.eachWorkReq(ctx, req, s.compile()) } func (p Policy) eachWorkReq(ctx context.Context, req *pb.WorkReq, s Settings) error { if s.Start { err := p.startOfBlock(ctx, req.Blocks[0].Jobs, s) if err != nil { return fmt.Errorf("requires Job(%s) in the first block: %s", s.JobName, err) } return err } err := p.endOfBlock(ctx, req.Blocks[len(req.Blocks)-1].Jobs, s) if err != nil { err = fmt.Errorf("requires Job(%s) in the last block: %s", s.JobName, err) return err } return nil } func (p Policy) startOfBlock(ctx context.Context, block []*pb.Job, s Settings) error { for _, job := range block { if job.Name == s.JobName { return p.mustHave(ctx, job, s) } if s.allowed[job.Name] { continue } return fmt.Errorf("not found at the beginning of the block") } return fmt.Errorf("not found in the block at all") } func (p Policy) endOfBlock(ctx context.Context, block []*pb.Job, s Settings) error { for i := len(block); i > 0; i-- { job := block[i] if job.Name == s.JobName { return p.mustHave(ctx, job, s) } if s.allowed[job.Name] { continue } return fmt.Errorf("not found at the beginning of the block") } return fmt.Errorf("not found in the block at all") } func (p Policy) mustHave(ctx context.Context, job *pb.Job, s Settings) error { for k, v := range s.MustArgs { has, ok := job.Args[k] if !ok { return fmt.Errorf("found, but required arg(%s) was not found", k) } if v != has { return fmt.Errorf("found,but required arg(%s) found has incorrect value(got %q, want %q)", k, has, v) } } return nil } ================================================ FILE: chapter/16/workflow/internal/service/executor/executor.go ================================================ /* Package executor provides the Work type which is used to execute a pb.WorkReq. This package is the meat of the engine. To create a Work object, simply: work := executor.New(req, status} After creating a Work object, validate it: if err := work.Validate(); err !=nil { // Do something } To run the Work object, do: ch := work.Run() Once Run() returns, the pb.Status object passed will contain the results of running the WorkReq. */ package executor import ( "context" "fmt" "log" "sync" "time" "github.com/PacktPublishing/Go-for-DevOps/chapter/16/workflow/internal/es" "github.com/PacktPublishing/Go-for-DevOps/chapter/16/workflow/internal/policy" "github.com/PacktPublishing/Go-for-DevOps/chapter/16/workflow/internal/policy/config" "github.com/PacktPublishing/Go-for-DevOps/chapter/16/workflow/internal/service/jobs" "google.golang.org/protobuf/proto" pb "github.com/PacktPublishing/Go-for-DevOps/chapter/16/workflow/proto" ) // Work is an executor for executing a WorkReq received by the server. type Work struct { req *pb.WorkReq mu sync.Mutex status *pb.StatusResp ch chan *pb.StatusResp } // New is the constructor for Work. func New(req *pb.WorkReq, status *pb.StatusResp) *Work { return &Work{ req: req, status: status, ch: make(chan *pb.StatusResp, 1), } } // Run validates that a WorkReq is correct and passed policy, then executes it. func (w *Work) Run(ctx context.Context) chan *pb.StatusResp { w.setWorkStatus(pb.Status_StatusRunning, false) go func() { defer close(w.ch) esCh, cancelES := es.Data.Subscribe(w.req.Name) defer cancelES() if <-esCh != es.Go { w.setWorkStatus(pb.Status_StatusFailed, true) return } var cancel context.CancelFunc ctx, cancel = context.WithCancel(ctx) defer cancel() // If we get an emergency stop, cancel our context. // If the context gets cancelled, then just exit. go func() { select { case <-ctx.Done(): return case <-esCh: log.Println("Emergency Stop called on running workflow type ", w.req.Name) w.setWorkStatus(pb.Status_StatusFailed, true) cancel() } }() // Loop through each block one at a time and execute the Jobs located in them // at the rate limit defined for the block. for i, block := range w.req.Blocks { if ctx.Err() != nil { break } stat := w.status.Blocks[i] if err := w.runJobs(ctx, block, stat); err != nil { break } } // Record our final state based on if any of our blocks failed. completed := true for _, block := range w.status.Blocks { if block.Status == pb.Status_StatusFailed { completed = false w.setWorkStatus(pb.Status_StatusFailed, false) } } if completed { w.setWorkStatus(pb.Status_StatusCompleted, false) } }() return w.ch } func (w *Work) setWorkStatus(status pb.Status, esStopped bool) { w.mu.Lock() w.status.Status = status w.status.WasEsStopped = esStopped w.sendStatus(w.status) w.mu.Unlock() } func (w *Work) setBlockStatus(block *pb.BlockStatus, status pb.Status) { w.mu.Lock() block.Status = status w.sendStatus(w.status) w.mu.Unlock() } func (w *Work) setJobStatus(job *pb.JobStatus, status pb.Status, err string) { w.mu.Lock() job.Status = status job.Error = err w.sendStatus(w.status) w.mu.Unlock() } // sendStatus sends the status of the WorkReq on our output channel. If the channel // is currently blocked with another status update, it removes that update for the newer one. func (w *Work) sendStatus(status *pb.StatusResp) { // We clone our status to prevent any concurrent access issues once the lock around // sendStatus is released. status = proto.Clone(status).(*pb.StatusResp) for { select { case w.ch <- status: return default: select { case <-w.ch: default: } } } } func (w *Work) runJobs(ctx context.Context, block *pb.Block, blockStatus *pb.BlockStatus) error { ctx, cancel := context.WithCancel(ctx) // Setup our rate limiter. limit := block.RateLimit if limit < 1 { limit = 1 } rateLimiter := make(chan struct{}, int(limit)) w.setBlockStatus(blockStatus, pb.Status_StatusRunning) // Execute our Jobs. wg := sync.WaitGroup{} for i, job := range block.Jobs { i := i job := job select { case rateLimiter <- struct{}{}: case <-ctx.Done(): } if ctx.Err() != nil { break } wg.Add(1) go func() { defer wg.Done() defer func() { <-rateLimiter }() js := blockStatus.Jobs[i] j, err := jobs.GetJob(job.Name) if err != nil { cancel() w.setJobStatus(js, pb.Status_StatusFailed, fmt.Sprintf("a Job(%s) passed validation but when ran could not be found, bug?", job.Name)) return } w.setJobStatus(js, pb.Status_StatusRunning, "") err = j.Run(ctx, job) if err != nil { if jobs.IsFatal(err) { cancel() } w.setJobStatus(js, pb.Status_StatusFailed, err.Error()) return } w.setJobStatus(js, pb.Status_StatusCompleted, "") }() } wg.Wait() // If any Job failed, the block failed. for _, js := range blockStatus.Jobs { if js.Status == pb.Status_StatusFailed { w.setBlockStatus(blockStatus, pb.Status_StatusFailed) return ctx.Err() } } w.setBlockStatus(blockStatus, pb.Status_StatusCompleted) return ctx.Err() } // Validate validates that a WorkReq is valid. This will check that basic values are set correctly // and run all policies for this Workflow. func Validate(ctx context.Context, req *pb.WorkReq) error { for blockNum, b := range req.Blocks { if len(b.Jobs) == 0 { return fmt.Errorf("Block(%d) had 0 jobs", blockNum) } for jobNum, j := range b.Jobs { job, err := jobs.GetJob(j.Name) if err != nil { return fmt.Errorf("Block(%d) Job(%d) had a invalid Type(%s)", blockNum, jobNum, j.Name) } if err := job.Validate(j); err != nil { return fmt.Errorf("Block(%d) Job(%d)(%s) did not validate: %s)", blockNum, jobNum, j.Name, err) } } } conf, err := config.Policies.Read() if err != nil { log.Println("policy config could not be read: ", err) return fmt.Errorf("cannot read our policies config: %s", err) } workConf, ok := conf.Workflows[req.Name] if !ok { return fmt.Errorf("Workflow does not have an associated policy in the policy configuration file") } args := make([]policy.PolicyArgs, 0, len(workConf.Policies)) for _, p := range workConf.Policies { args = append(args, policy.PolicyArgs{Name: p.Name, Settings: p.SettingsTyped}) } policyContext, cancel := context.WithTimeout(ctx, 30*time.Second) defer cancel() if err := policy.Run(policyContext, req, args...); err != nil { return err } return nil } ================================================ FILE: chapter/16/workflow/internal/service/jobs/jobs.go ================================================ /* Package jobs defines our Job type, which executes work and a registration system for registering Jobs. Packages that contain jobs can register themselves by doing: func init() { jobs.Register("name", job) } If there is a duplicate name, this will panic. Fetching a Job is simply: GetJob(jt string) (Job, error) */ package jobs import ( "context" "errors" "fmt" "log" "strings" pb "github.com/PacktPublishing/Go-for-DevOps/chapter/16/workflow/proto" ) var jobs = map[string]Job{} // Register registers a Job so that it can be executed. func Register(name string, job Job) { name = strings.TrimSpace(name) if name == "" { panic("cannot Register empty JobType") } if _, ok := jobs[name]; ok { panic(fmt.Sprintf("cannot register Job(%s) twice", name)) } log.Println("Registered Job: ", name) jobs[name] = job } // GetJob returns a Job by its type from the registry. func GetJob(name string) (Job, error) { j, ok := jobs[name] if !ok { return nil, fmt.Errorf("Job(%v) not found", name) } return j, nil } // FatalErr is a an error that should terminate a Workflow. type FatalErr struct { err error } // Fatalf creates a fatal error similar to fmt.Errorf(). func Fatalf(msg string, a ...interface{}) FatalErr { return FatalErr{err: fmt.Errorf(msg, a...)} } // IsFatal indicates if an error is fatal. func IsFatal(err error) bool { return errors.Is(err, FatalErr{}) } // Is implements the built in Is method. func (f FatalErr) Is(target error) bool { switch target.(type) { case FatalErr, *FatalErr: return true } return false } // Error() implements error.Error(). func (f FatalErr) Error() string { if f.err == nil { return "" } return f.err.Error() } // Unwrap implements the built in Unwrap method. func (f FatalErr) Unwrap() error { return errors.Unwrap(f.err) } // Job executes some type of work. type Job interface { // Validate validates that the Job settings sent to the server are valid. Validate(job *pb.Job) error // Run runs the Job settings. Run(ctx context.Context, job *pb.Job) error } ================================================ FILE: chapter/16/workflow/internal/service/jobs/register/diskerase/diskerase.go ================================================ /* Package diskerase registers a job that can be used to erase a disk on a machine. As this is just a demo, this really just sleeps for 30 seconds. Register name: "diskErase" Args: "machine"(mandatory): The name of the machine, like "aa01" or "ab02" "site"(mandatory): The name of the site, like "aaa" or "aba" Result: Erases a disk on a machine, except this is a demo, so it really just sleeps for 30 seconds. */ package diskerase import ( "context" "fmt" "time" "github.com/PacktPublishing/Go-for-DevOps/chapter/16/workflow/data/packages/sites" "github.com/PacktPublishing/Go-for-DevOps/chapter/16/workflow/internal/service/jobs" pb "github.com/PacktPublishing/Go-for-DevOps/chapter/16/workflow/proto" ) // This registers our Job on server startup. func init() { jobs.Register("diskErase", newJob()) } type args struct { machine string site string } func (a *args) validate(args map[string]string) error { must := map[string]bool{ "machine": false, "site": false, } for k, v := range args { switch k { case "machine": must["machine"] = true a.machine = v case "site": if _, ok := sites.Data.Sites[v]; !ok { return fmt.Errorf("site(%s) arg was not a valid site", v) } must["site"] = true a.site = v default: return fmt.Errorf("invalid arg(%s)", k) } } for k, v := range must { if !v { return fmt.Errorf("missing required arg(%s)", k) } } fullName := fmt.Sprintf("%s.%s", a.machine, a.site) _, ok := sites.Data.Machines[fullName] if !ok { return fmt.Errorf("invalid arg(machine): machine(%s) does not exist", fullName) } return nil } // Job implements jobs.Job. type Job struct { args args } func newJob() *Job { return &Job{} } // Validate implements jobs.Job.Validate(). func (j *Job) Validate(job *pb.Job) error { a := args{} if err := a.validate(job.Args); err != nil { return err } j.args = a return nil } // Run implements jobs.Job.Run(). func (j *Job) Run(ctx context.Context, job *pb.Job) error { time.Sleep(30 * time.Second) // A crude and inaccurate simulation of a disk erasure return nil } ================================================ FILE: chapter/16/workflow/internal/service/jobs/register/sleep/sleep.go ================================================ /* Package validatedecom registers a job that is used to validate a site is set to state "decom". Register name: "validateDecom" Args: "site"(mandatory): The name of the site, like "aaa" or "aba" "siteType"(mandatory): The type of the site, like "satellite" or "cluster" Result: If the site is not in decom, will return a fatal error. */ package sleep import ( "context" "fmt" "strconv" "time" "github.com/PacktPublishing/Go-for-DevOps/chapter/16/workflow/data/packages/sites" "github.com/PacktPublishing/Go-for-DevOps/chapter/16/workflow/internal/service/jobs" pb "github.com/PacktPublishing/Go-for-DevOps/chapter/16/workflow/proto" ) // This registers our Job on server startup. func init() { jobs.Register("sleep", newJob()) } type args struct { d time.Duration } func (a *args) validate(args map[string]string) error { must := map[string]bool{ "seconds": false, } for k, v := range args { switch k { case "seconds": i, err := strconv.Atoi(v) if err != nil { return fmt.Errorf("arg(seconds) is not an integer(%s)", v) } if i < 1 { return fmt.Errorf("arg(seconds) cannot be less than 1(%d)", i) } must["seconds"] = true a.d = time.Duration(i) * time.Second default: return fmt.Errorf("validateDecom had invalid arg(%s)", k) } } for k, v := range must { if !v { return fmt.Errorf("missing required arg(%s)", k) } } return nil } // Job implements jobs.Job. type Job struct { sites map[string]sites.Site args args } func newJob() *Job { return &Job{} } // Validate implements jobs.Job.Validate(). func (j *Job) Validate(job *pb.Job) error { a := args{} if err := a.validate(job.Args); err != nil { return err } j.args = a return nil } // Run implements jobs.Job.Run(). func (j *Job) Run(ctx context.Context, job *pb.Job) error { time.Sleep(j.args.d) return nil } ================================================ FILE: chapter/16/workflow/internal/service/jobs/register/tokenbucket/tokenbucket.go ================================================ /* Package tokenbucket registers a job that is used to fetch a token from a token bucket. Register name: "tokenBucket" Args: "bucket"(mandatory): The name of the bucket "fatal"(mandatory): true if a failure should cause a fatal error, false if it should block until it gets one Result: If the site is not in decom, will return a fatal error. */ package tokenbucket import ( "context" "fmt" "time" "github.com/PacktPublishing/Go-for-DevOps/chapter/16/workflow/data/packages/sites" "github.com/PacktPublishing/Go-for-DevOps/chapter/16/workflow/internal/service/jobs" "github.com/PacktPublishing/Go-for-DevOps/chapter/16/workflow/internal/token" pb "github.com/PacktPublishing/Go-for-DevOps/chapter/16/workflow/proto" ) var buckets = map[string]*token.Bucket{} // This registers our Job on server startup. func init() { jobs.Register("tokenBucket", newJob()) satDiskErase, err := token.New(1, 1, 30*time.Minute) if err != nil { panic(err) } buckets["diskEraseSatellite"] = satDiskErase } type args struct { bucket string fatal bool } func (a *args) validate(args map[string]string) error { must := map[string]bool{ "bucket": false, "fatal": false, } for k, v := range args { switch k { case "bucket": if _, ok := buckets[v]; !ok { return fmt.Errorf("bucket(%s) was not a valid", v) } must["bucket"] = true a.bucket = v case "fatal": switch v { case "true": a.fatal = true case "false": a.fatal = true default: return fmt.Errorf("arg(fatal) was not true or false, was %q", v) } must["fatal"] = true default: return fmt.Errorf("invalid arg(%s)", k) } } for k, v := range must { if !v { return fmt.Errorf("missing required arg(%s)", k) } } return nil } // Job implements jobs.Job. type Job struct { sites map[string]sites.Site args args } func newJob() *Job { return &Job{} } // Validate implements jobs.Job.Validate(). func (j *Job) Validate(job *pb.Job) error { a := args{} if err := a.validate(job.Args); err != nil { return err } j.args = a return nil } // Run implements jobs.Job.Run(). func (j *Job) Run(ctx context.Context, job *pb.Job) error { if j.args.fatal { var cancel context.CancelFunc ctx, cancel = context.WithTimeout(ctx, 1*time.Second) defer cancel() } if err := buckets[j.args.bucket].Token(ctx); err != nil { if j.args.fatal { return jobs.Fatalf("token(%s) not available", j.args.bucket) } return jobs.Fatalf("workflow cancelled before token(%s) was available", j.args.bucket) } return nil } ================================================ FILE: chapter/16/workflow/internal/service/jobs/register/validatedecom/validatedecom.go ================================================ /* Package validatedecom registers a job that is used to validate a site is set to state "decom". Register name: "validateDecom" Args: "site"(mandatory): The name of the site, like "aaa" or "aba" "siteType"(mandatory): The type of the site, like "satellite" or "cluster" Result: If the site is not in decom, will return a fatal error. */ package validatedecom import ( "context" "fmt" "github.com/PacktPublishing/Go-for-DevOps/chapter/16/workflow/data/packages/sites" "github.com/PacktPublishing/Go-for-DevOps/chapter/16/workflow/internal/service/jobs" pb "github.com/PacktPublishing/Go-for-DevOps/chapter/16/workflow/proto" ) // This registers our Job on server startup. func init() { jobs.Register("validateDecom", newJob(sites.Data.Sites)) } type args struct { site string siteType string } func (a *args) validate(args map[string]string) error { must := map[string]bool{ "site": false, "type": false, } var siteData sites.Site for k, v := range args { switch k { case "site": s, ok := sites.Data.Sites[v] if !ok { return fmt.Errorf("site(%s) was not a valid site", v) } must["site"] = true a.site = v siteData = s case "type": must["type"] = true a.siteType = v default: return fmt.Errorf("invalid arg(%s)", k) } } if siteData.Type != a.siteType { return fmt.Errorf("site(%s) is type(%s), we expected(%s)", a.site, siteData.Type, a.siteType) } if siteData.Status != "decom" { return fmt.Errorf("site(%s) is not in the decom state, was in %q", a.site, siteData.Status) } for k, v := range must { if !v { return fmt.Errorf("missing required arg(%s)", k) } } return nil } // Job implements jobs.Job. type Job struct { sites map[string]sites.Site args args } func newJob(sites map[string]sites.Site) *Job { return &Job{sites: sites} } // Validate implements jobs.Job.Validate(). func (j *Job) Validate(job *pb.Job) error { a := args{} if err := a.validate(job.Args); err != nil { return err } j.args = a return nil } // Run implements jobs.Job.Run(). func (j *Job) Run(ctx context.Context, job *pb.Job) error { site, ok := sites.Data.Sites[j.args.site] if !ok { return jobs.Fatalf("site(%s) is no longer in the sites file", j.args.site) } if site.Status != "decom" { return jobs.Fatalf("site(%s) was transitioned out of decom before Job ran", j.args.site) } return nil } ================================================ FILE: chapter/16/workflow/internal/service/service.go ================================================ // Package service implements our gRPC service called Workflow. package service import ( "context" "errors" "fmt" "log" "os" "path/filepath" "sync" "sync/atomic" "time" "github.com/google/uuid" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" "google.golang.org/protobuf/proto" "github.com/PacktPublishing/Go-for-DevOps/chapter/16/workflow/internal/es" "github.com/PacktPublishing/Go-for-DevOps/chapter/16/workflow/internal/service/executor" pb "github.com/PacktPublishing/Go-for-DevOps/chapter/16/workflow/proto" ) // active provides an entry for an actively executing workflow. type active struct { work *executor.Work status atomic.Value // *pb.StatusResp } // Workflow implements our gRPC service. type Workflow struct { // storageDir is where we store workflow information. storageDir string // mu protects active mu sync.Mutex // active tracks all active work that is occuring. active map[string]*active // Required for gRPC to run, makes sure we have all the methods defined. pb.UnimplementedWorkflowServer } // New creates a new Workflow service. func New(storageDir string) (*Workflow, error) { stat, err := os.Stat(storageDir) if err != nil { return nil, fmt.Errorf("could not stat the workflow storage(%s): %w", storageDir, err) } if !stat.IsDir() { return nil, fmt.Errorf("storageDir(%s) is not a directory", storageDir) } u := "ping_" + uuid.NewString() p := filepath.Join(storageDir, u) f, err := os.OpenFile(p, os.O_CREATE+os.O_RDWR, 0600) if err != nil { return nil, fmt.Errorf("could not open a file in storage(%s) for RDWR: %w", storageDir, err) } f.Close() if err := os.Remove(p); err != nil { return nil, fmt.Errorf("could not remove ping file(%s) in storage(%s)", p, storageDir) } return &Workflow{storageDir: storageDir, active: map[string]*active{}}, nil } var submitRateLimit = make(chan struct{}, 10) // Submit submits a request to run a workflow. func (w *Workflow) Submit(ctx context.Context, req *pb.WorkReq) (*pb.WorkResp, error) { select { case submitRateLimit <- struct{}{}: default: return nil, status.Errorf(codes.ResourceExhausted, "too many requests") } defer func() { <-submitRateLimit }() esStatus := es.Data.Status(req.Name) if esStatus != es.Go { return nil, status.Errorf(codes.Aborted, "emergency stop for(%s) was %s", req.Name, esStatus) } validateCtx, cancel := context.WithTimeout(ctx, 30*time.Second) defer cancel() if err := executor.Validate(validateCtx, req); err != nil { if errors.Is(err, context.Canceled) { return nil, status.Error(codes.DeadlineExceeded, err.Error()) } return nil, status.Error(codes.InvalidArgument, err.Error()) } var ( id string p string ) // Loop until we get a unique ID that doesn't exist on the filesystem. for { u, err := uuid.NewUUID() if err != nil { return nil, status.Errorf(codes.Internal, "problem getting UUIDv1; %s", err.Error()) } id = u.String() p = filepath.Join(w.storageDir, id) _, err = os.Stat(p) // Make sure this doesn't alreay exist. if err != nil { break } } resp := &pb.WorkResp{Id: id} b, err := proto.Marshal(req) if err != nil { return nil, status.Errorf(codes.InvalidArgument, "could not marshal the request: %s", err) } f, err := os.OpenFile(p, os.O_CREATE+os.O_WRONLY, 0600) if err != nil { return nil, status.Errorf(codes.Internal, "could not open file in storageDir(%s): %s", w.storageDir, err) } defer f.Close() if _, err := f.Write(b); err != nil { return nil, status.Errorf(codes.Internal, "problem writing request to storage: %s", err) } return resp, nil } var executeRateLimit = make(chan struct{}, 10) // Exec requests that the system execute a submitted workflow. func (w *Workflow) Exec(ctx context.Context, req *pb.ExecReq) (*pb.ExecResp, error) { select { case executeRateLimit <- struct{}{}: default: return nil, status.Errorf(codes.ResourceExhausted, "too many requests") } defer func() { <-executeRateLimit }() p := filepath.Join(w.storageDir, req.Id) statP := filepath.Join(w.storageDir, req.Id+"_status") w.mu.Lock() defer w.mu.Unlock() _, ok := w.active[req.Id] if ok { return nil, status.Errorf(codes.AlreadyExists, "Workflow(%s) is already running", req.Id) } _, err := os.Stat(statP) if err == nil { return nil, status.Errorf(codes.AlreadyExists, "Workflow(%s) already executing or executed", req.Id) } u, err := uuid.Parse(req.Id) if err != nil { return nil, status.Errorf(codes.InvalidArgument, "Id(%s) is not a valid value: %s", req.Id, err) } t := time.Unix(u.Time().UnixTime()) if time.Now().Sub(t) > 1*time.Hour { return nil, status.Errorf(codes.FailedPrecondition, "Id(%s) is older than 1 hour and cannot be started", req.Id) } b, err := os.ReadFile(p) if err != nil { return nil, status.Errorf(codes.NotFound, "Workflow(%s) not found", req.Id) } workReq := &pb.WorkReq{} if err := proto.Unmarshal(b, workReq); err != nil { return nil, status.Errorf(codes.Internal, "Workflow(%s) could not be unmarshalled: %s", req.Id, err) } esStatus := es.Data.Status(workReq.Name) if esStatus != es.Go { return nil, status.Errorf(codes.Aborted, "emergency stop for(%s) was %s", workReq.Name, esStatus) } // Write our status file to indicate we have started working on this. statusResp := statusFromWork(workReq) statusB, err := proto.Marshal(statusResp) if err != nil { return nil, status.Errorf(codes.InvalidArgument, "could not marshal the work's status proto: %s", err) } if err := os.WriteFile(statP, statusB, 0600); err != nil { return nil, status.Errorf(codes.Internal, "problem writing status to storage: %s", err) } work := executor.New(workReq, statusResp) active := &active{work: work} active.status.Store(proto.Clone(statusResp).(*pb.StatusResp)) w.active[req.Id] = active // Run our work and get the first state change. ch := work.Run(context.Background()) active.status.Store(<-ch) writeIn := statusWriter(statP) // Update our status as it changes in memory and on disk. // Cleanup our list of active work when we are done. go func() { for status := range ch { // Record our status in memory active.status.Store(status) // Record our status on disk. If there is an entry pending, // remove it for the latest entry. select { case writeIn <- status: default: select { case <-writeIn: default: } writeIn <- status } } w.mu.Lock() delete(w.active, req.Id) w.mu.Unlock() }() return &pb.ExecResp{}, nil } var statusRateLimit = make(chan struct{}, 10) // Status is used to query for the status of a workflow. func (w *Workflow) Status(ctx context.Context, req *pb.StatusReq) (*pb.StatusResp, error) { select { case statusRateLimit <- struct{}{}: default: return nil, status.Errorf(codes.ResourceExhausted, "too many requests") } defer func() { <-statusRateLimit }() w.mu.Lock() a := w.active[req.Id] w.mu.Unlock() if a != nil { return a.status.Load().(*pb.StatusResp), nil } // This ID is not currently running, so look in storage. p := filepath.Join(w.storageDir, req.Id+"_status") b, err := os.ReadFile(p) if err != nil { return nil, status.Errorf(codes.InvalidArgument, "work ID(%s) was not found", req.Id) } resp := &pb.StatusResp{} if err := proto.Unmarshal(b, resp); err != nil { return nil, status.Errorf(codes.Internal, "work ID(%s) data was corrupted on disk", req.Id) } return resp, nil } // statusFromWork takes a WorkReq and generates the corresponding StatusResp. func statusFromWork(req *pb.WorkReq) *pb.StatusResp { resp := &pb.StatusResp{Name: req.Name, Desc: req.Desc, Status: pb.Status_StatusNotStarted} for _, b := range req.Blocks { sb := &pb.BlockStatus{ Desc: b.Desc, Status: pb.Status_StatusNotStarted, } for _, j := range b.Jobs { sj := &pb.JobStatus{ Name: j.Name, Desc: j.Desc, Args: j.Args, Status: pb.Status_StatusNotStarted, } sb.Jobs = append(sb.Jobs, sj) } resp.Blocks = append(resp.Blocks, sb) } return resp } func statusWriter(p string) (in chan *pb.StatusResp) { in = make(chan *pb.StatusResp, 1) go func() { for status := range in { b, err := proto.Marshal(status) if err != nil { log.Println("could not marshal a status proto: ", err) continue } if err := os.WriteFile(p, b, 0600); err != nil { log.Println("cannot write a status update to disk, this is bad: ", err) continue } } }() return in } ================================================ FILE: chapter/16/workflow/internal/token/token.go ================================================ // Package token contains a standard token bucket implementation. package token import ( "context" "fmt" "time" ) // Bucket is an implementation of a standard token Bucket. The Bucket is refilled at some interval // to a maximum value. type Bucket struct { // tokens represents an full token Bucket at some size. Every entry into the Bucket // remove capacity. tokens chan struct{} // ticker is a ticket that we use to refresh our tokens at some interval. ticker *time.Ticker } // New creates a Bucket instance. size is how many tokens we can hold. incr is the amount of tokens // to add at a time. interval is how often to add tokens. func New(size, incr int, interval time.Duration) (*Bucket, error) { if size < 1 { return nil, fmt.Errorf("size must be > 1") } if interval < 1*time.Second { return nil, fmt.Errorf("interval must be < 1 second") } if incr < 1 { return nil, fmt.Errorf("incr must be > 0") } b := Bucket{tokens: make(chan struct{}, size), ticker: time.NewTicker(interval)} // This goroutine adds tokens by removing items from our channel. This seems like the // opposite logic of what you'd expect, but this is actually an efficient way of implementing // a token Bucket usign channels. go func() { for _ = range b.ticker.C { for i := 0; i < incr; i++ { select { case <-b.tokens: continue default: } break } } }() return &b, nil } // close stops the token Bucket's goroutine. This should be called before throwing away the Bucket. // If you use the token Bucket after this is called, this can cause major problems like causing the // token() call to block forever, as there are no more tokens being added. func (b *Bucket) Close() { b.ticker.Stop() close(b.tokens) } // token blocks until a token is available or the context is cancelled. An error is only returned // if the context is cancelled. func (b *Bucket) Token(ctx context.Context) error { select { case <-ctx.Done(): return ctx.Err() case b.tokens <- struct{}{}: } return nil } ================================================ FILE: chapter/16/workflow/proto/buf.gen.yaml ================================================ version: v1 plugins: - name: go out: ./ opt: - paths=source_relative - name: go-grpc out: ./ opt: - paths=source_relative ================================================ FILE: chapter/16/workflow/proto/buf.yaml ================================================ version: v1 lint: use: - DEFAULT breaking: use: - FILE ================================================ FILE: chapter/16/workflow/proto/custom.go ================================================ package diskerase import ( "fmt" "strings" "time" "github.com/fatih/color" "github.com/rodaine/table" ) // CLISummary() provides the StatusResp in a summary format that is useful for // viewing in a CLI application. It summarizes all blocks into single lines except // for the block that is currently running. func (x *StatusResp) CLISummary(id string) string { if len(x.Blocks) == 0 { return "no blocks defined" } blockTitle := color.New(color.FgCyan).Add(color.Underline) name := color.New(color.FgGreen) desc := color.New(color.FgYellow) buff := strings.Builder{} buff.WriteString(fmt.Sprintf("Time: %s\n", time.Now().Format(time.RFC1123))) buff.WriteString(fmt.Sprintf("Workflow: %s\n", id)) name.Fprintln(&buff, "Name: "+x.Name) desc.Fprintln(&buff, "Description: "+x.Desc) if i, block := x.findRunning(x.Blocks); i != -1 { blockTitle.Fprintln(&buff, fmt.Sprintf("\nRunning Block(%d): %s", i, block.Desc)) x.writeRunning(&buff, block) } blockTitle.Fprintln(&buff, "\nBlock Summaries") x.writeOthers(&buff, x.Blocks) return buff.String() } func (x *StatusResp) writeOthers(buff *strings.Builder, blocks []*BlockStatus) { headerFmt := color.New(color.FgGreen, color.Underline).SprintfFunc() columnFmt := color.New(color.FgYellow).SprintfFunc() tbl := table.New("Block Number", "Desc", "Status").WithWriter(buff) tbl.WithHeaderFormatter(headerFmt).WithFirstColumnFormatter(columnFmt) for i, block := range blocks { tbl.AddRow(i, block.Desc, block.Status) if block.Status == Status_StatusRunning { continue } } tbl.Print() } func (x *StatusResp) findRunning(blocks []*BlockStatus) (int, *BlockStatus) { for i, b := range blocks { if b.Status == Status_StatusRunning { return i, b } } return -1, nil } func (x *StatusResp) writeRunning(buff *strings.Builder, block *BlockStatus) { headerFmt := color.New(color.FgGreen, color.Underline).SprintfFunc() columnFmt := color.New(color.FgYellow).SprintfFunc() tbl := table.New("Job Number", "Desc", "Status").WithWriter(buff) tbl.WithHeaderFormatter(headerFmt).WithFirstColumnFormatter(columnFmt) for i, job := range block.Jobs { tbl.AddRow(i, job.Desc, job.Status) } tbl.Print() return } ================================================ FILE: chapter/16/workflow/proto/diskerase.pb.go ================================================ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.25.0-devel // protoc v3.18.0 // source: diskerase.proto package diskerase import ( reflect "reflect" sync "sync" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" ) 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) ) // Status details the status of a Block or Job. type Status int32 const ( // Indicates that there is some bug // as the Status for the object was not set. Status_StatusUnknown Status = 0 // The WorkReq, Block or Job has not started execution. Status_StatusNotStarted Status = 1 // The WorkReq, Block or Job is currently executing. Status_StatusRunning Status = 2 // The WorkReq, Block or Job has failed. Status_StatusFailed Status = 3 // The WorkReq, Block or Job has completed. Status_StatusCompleted Status = 4 ) // Enum value maps for Status. var ( Status_name = map[int32]string{ 0: "StatusUnknown", 1: "StatusNotStarted", 2: "StatusRunning", 3: "StatusFailed", 4: "StatusCompleted", } Status_value = map[string]int32{ "StatusUnknown": 0, "StatusNotStarted": 1, "StatusRunning": 2, "StatusFailed": 3, "StatusCompleted": 4, } ) func (x Status) Enum() *Status { p := new(Status) *p = x return p } func (x Status) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } func (Status) Descriptor() protoreflect.EnumDescriptor { return file_diskerase_proto_enumTypes[0].Descriptor() } func (Status) Type() protoreflect.EnumType { return &file_diskerase_proto_enumTypes[0] } func (x Status) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } // Deprecated: Use Status.Descriptor instead. func (Status) EnumDescriptor() ([]byte, []int) { return file_diskerase_proto_rawDescGZIP(), []int{0} } // WorkReq is the definition of some work to be done by the system. type WorkReq struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // This is used to describe the work to be done. This name // must be authorized by having a policy with the same name // in the server's policies.json fie. Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` // A description of what this is doing. Desc string `protobuf:"bytes,2,opt,name=desc,proto3" json:"desc,omitempty"` // These are groupings of Jobs. Each block is executed one at // a time. Blocks []*Block `protobuf:"bytes,3,rep,name=blocks,proto3" json:"blocks,omitempty"` } func (x *WorkReq) Reset() { *x = WorkReq{} if protoimpl.UnsafeEnabled { mi := &file_diskerase_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *WorkReq) String() string { return protoimpl.X.MessageStringOf(x) } func (*WorkReq) ProtoMessage() {} func (x *WorkReq) ProtoReflect() protoreflect.Message { mi := &file_diskerase_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 WorkReq.ProtoReflect.Descriptor instead. func (*WorkReq) Descriptor() ([]byte, []int) { return file_diskerase_proto_rawDescGZIP(), []int{0} } func (x *WorkReq) GetName() string { if x != nil { return x.Name } return "" } func (x *WorkReq) GetDesc() string { if x != nil { return x.Desc } return "" } func (x *WorkReq) GetBlocks() []*Block { if x != nil { return x.Blocks } return nil } // WorkResp details the ID that will be used to refer to a submitted WorkReq. type WorkResp struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // This is the unique ID for this WorkReq. Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` } func (x *WorkResp) Reset() { *x = WorkResp{} if protoimpl.UnsafeEnabled { mi := &file_diskerase_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *WorkResp) String() string { return protoimpl.X.MessageStringOf(x) } func (*WorkResp) ProtoMessage() {} func (x *WorkResp) ProtoReflect() protoreflect.Message { mi := &file_diskerase_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 WorkResp.ProtoReflect.Descriptor instead. func (*WorkResp) Descriptor() ([]byte, []int) { return file_diskerase_proto_rawDescGZIP(), []int{1} } func (x *WorkResp) GetId() string { if x != nil { return x.Id } return "" } // Block is a grouping of Jobs that will be executed concurrently // at some rate. type Block struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // This describes what the Block is doing. Desc string `protobuf:"bytes,1,opt,name=desc,proto3" json:"desc,omitempty"` // The amount of concurrency executions. < 1 will default to 1. RateLimit int32 `protobuf:"varint,2,opt,name=rate_limit,json=rateLimit,proto3" json:"rate_limit,omitempty"` // The Jobs to to execute in this Block. Jobs []*Job `protobuf:"bytes,3,rep,name=jobs,proto3" json:"jobs,omitempty"` } func (x *Block) Reset() { *x = Block{} if protoimpl.UnsafeEnabled { mi := &file_diskerase_proto_msgTypes[2] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Block) String() string { return protoimpl.X.MessageStringOf(x) } func (*Block) ProtoMessage() {} func (x *Block) ProtoReflect() protoreflect.Message { mi := &file_diskerase_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 Block.ProtoReflect.Descriptor instead. func (*Block) Descriptor() ([]byte, []int) { return file_diskerase_proto_rawDescGZIP(), []int{2} } func (x *Block) GetDesc() string { if x != nil { return x.Desc } return "" } func (x *Block) GetRateLimit() int32 { if x != nil { return x.RateLimit } return 0 } func (x *Block) GetJobs() []*Job { if x != nil { return x.Jobs } return nil } // Job refers to a Job action that is defined on the server. type Job struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // This is the name of the Job, which must be registered on // the server. Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` // This is a description of what the job is doing. Desc string `protobuf:"bytes,2,opt,name=desc,proto3" json:"desc,omitempty"` // A mapping of key/value arguments. While the value is a string, // it can represent non-string data and will be converted by the // Job on the server. See the Job definition for a list of arguments // that are mandatory and optional. Args map[string]string `protobuf:"bytes,3,rep,name=args,proto3" json:"args,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` } func (x *Job) Reset() { *x = Job{} if protoimpl.UnsafeEnabled { mi := &file_diskerase_proto_msgTypes[3] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Job) String() string { return protoimpl.X.MessageStringOf(x) } func (*Job) ProtoMessage() {} func (x *Job) ProtoReflect() protoreflect.Message { mi := &file_diskerase_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 Job.ProtoReflect.Descriptor instead. func (*Job) Descriptor() ([]byte, []int) { return file_diskerase_proto_rawDescGZIP(), []int{3} } func (x *Job) GetName() string { if x != nil { return x.Name } return "" } func (x *Job) GetDesc() string { if x != nil { return x.Desc } return "" } func (x *Job) GetArgs() map[string]string { if x != nil { return x.Args } return nil } // ExecReq is used to tell the server to execute a WorkReq // that was previously submitted. type ExecReq struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // This is the unique ID of the WorkReq given back // by WorkResp. Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` } func (x *ExecReq) Reset() { *x = ExecReq{} if protoimpl.UnsafeEnabled { mi := &file_diskerase_proto_msgTypes[4] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *ExecReq) String() string { return protoimpl.X.MessageStringOf(x) } func (*ExecReq) ProtoMessage() {} func (x *ExecReq) ProtoReflect() protoreflect.Message { mi := &file_diskerase_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 ExecReq.ProtoReflect.Descriptor instead. func (*ExecReq) Descriptor() ([]byte, []int) { return file_diskerase_proto_rawDescGZIP(), []int{4} } func (x *ExecReq) GetId() string { if x != nil { return x.Id } return "" } // ExecResp is the response from an ExecReq. type ExecResp struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields } func (x *ExecResp) Reset() { *x = ExecResp{} if protoimpl.UnsafeEnabled { mi := &file_diskerase_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *ExecResp) String() string { return protoimpl.X.MessageStringOf(x) } func (*ExecResp) ProtoMessage() {} func (x *ExecResp) ProtoReflect() protoreflect.Message { mi := &file_diskerase_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 ExecResp.ProtoReflect.Descriptor instead. func (*ExecResp) Descriptor() ([]byte, []int) { return file_diskerase_proto_rawDescGZIP(), []int{5} } // StatusReq requests a status update from the server. type StatusReq struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // The unique ID of the WorkReq. Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` } func (x *StatusReq) Reset() { *x = StatusReq{} if protoimpl.UnsafeEnabled { mi := &file_diskerase_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *StatusReq) String() string { return protoimpl.X.MessageStringOf(x) } func (*StatusReq) ProtoMessage() {} func (x *StatusReq) ProtoReflect() protoreflect.Message { mi := &file_diskerase_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 StatusReq.ProtoReflect.Descriptor instead. func (*StatusReq) Descriptor() ([]byte, []int) { return file_diskerase_proto_rawDescGZIP(), []int{6} } func (x *StatusReq) GetId() string { if x != nil { return x.Id } return "" } // StatusResp is the status of WorkReq. type StatusResp struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // The name of the WorkReq. Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` // The description of the WorkReq. Desc string `protobuf:"bytes,2,opt,name=desc,proto3" json:"desc,omitempty"` // The overall status of the WorkReq. Status Status `protobuf:"varint,3,opt,name=status,proto3,enum=diskerase.Status" json:"status,omitempty"` // The status information on the Blocks. Blocks []*BlockStatus `protobuf:"bytes,4,rep,name=blocks,proto3" json:"blocks,omitempty"` // If we are SatusFailed or StatusCompleted, if // there were any errors when run. HadErrors bool `protobuf:"varint,5,opt,name=had_errors,json=hadErrors,proto3" json:"had_errors,omitempty"` // If the WorkReq was stopped with emergency stop. WasEsStopped bool `protobuf:"varint,6,opt,name=was_es_stopped,json=wasEsStopped,proto3" json:"was_es_stopped,omitempty"` } func (x *StatusResp) Reset() { *x = StatusResp{} if protoimpl.UnsafeEnabled { mi := &file_diskerase_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *StatusResp) String() string { return protoimpl.X.MessageStringOf(x) } func (*StatusResp) ProtoMessage() {} func (x *StatusResp) ProtoReflect() protoreflect.Message { mi := &file_diskerase_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 StatusResp.ProtoReflect.Descriptor instead. func (*StatusResp) Descriptor() ([]byte, []int) { return file_diskerase_proto_rawDescGZIP(), []int{7} } func (x *StatusResp) GetName() string { if x != nil { return x.Name } return "" } func (x *StatusResp) GetDesc() string { if x != nil { return x.Desc } return "" } func (x *StatusResp) GetStatus() Status { if x != nil { return x.Status } return Status_StatusUnknown } func (x *StatusResp) GetBlocks() []*BlockStatus { if x != nil { return x.Blocks } return nil } func (x *StatusResp) GetHadErrors() bool { if x != nil { return x.HadErrors } return false } func (x *StatusResp) GetWasEsStopped() bool { if x != nil { return x.WasEsStopped } return false } // BlockStatus holds the status of block execution. type BlockStatus struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // The description of the block. Desc string `protobuf:"bytes,1,opt,name=desc,proto3" json:"desc,omitempty"` // The status of the block. Status Status `protobuf:"varint,2,opt,name=status,proto3,enum=diskerase.Status" json:"status,omitempty"` // If there any errors in Jobs in the Block. HasError bool `protobuf:"varint,3,opt,name=has_error,json=hasError,proto3" json:"has_error,omitempty"` // The status of Jobs in the Block. Jobs []*JobStatus `protobuf:"bytes,4,rep,name=jobs,proto3" json:"jobs,omitempty"` } func (x *BlockStatus) Reset() { *x = BlockStatus{} if protoimpl.UnsafeEnabled { mi := &file_diskerase_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *BlockStatus) String() string { return protoimpl.X.MessageStringOf(x) } func (*BlockStatus) ProtoMessage() {} func (x *BlockStatus) ProtoReflect() protoreflect.Message { mi := &file_diskerase_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 BlockStatus.ProtoReflect.Descriptor instead. func (*BlockStatus) Descriptor() ([]byte, []int) { return file_diskerase_proto_rawDescGZIP(), []int{8} } func (x *BlockStatus) GetDesc() string { if x != nil { return x.Desc } return "" } func (x *BlockStatus) GetStatus() Status { if x != nil { return x.Status } return Status_StatusUnknown } func (x *BlockStatus) GetHasError() bool { if x != nil { return x.HasError } return false } func (x *BlockStatus) GetJobs() []*JobStatus { if x != nil { return x.Jobs } return nil } // JobStatus holds the status of the Jobs. type JobStatus struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // The name of the Job called. Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` // The description of the Job. Desc string `protobuf:"bytes,2,opt,name=desc,proto3" json:"desc,omitempty"` // The args for the Job. Args map[string]string `protobuf:"bytes,3,rep,name=args,proto3" json:"args,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` // The status of the Job. Status Status `protobuf:"varint,4,opt,name=status,proto3,enum=diskerase.Status" json:"status,omitempty"` // The error, if there was one. Error string `protobuf:"bytes,5,opt,name=error,proto3" json:"error,omitempty"` } func (x *JobStatus) Reset() { *x = JobStatus{} if protoimpl.UnsafeEnabled { mi := &file_diskerase_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *JobStatus) String() string { return protoimpl.X.MessageStringOf(x) } func (*JobStatus) ProtoMessage() {} func (x *JobStatus) ProtoReflect() protoreflect.Message { mi := &file_diskerase_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 JobStatus.ProtoReflect.Descriptor instead. func (*JobStatus) Descriptor() ([]byte, []int) { return file_diskerase_proto_rawDescGZIP(), []int{9} } func (x *JobStatus) GetName() string { if x != nil { return x.Name } return "" } func (x *JobStatus) GetDesc() string { if x != nil { return x.Desc } return "" } func (x *JobStatus) GetArgs() map[string]string { if x != nil { return x.Args } return nil } func (x *JobStatus) GetStatus() Status { if x != nil { return x.Status } return Status_StatusUnknown } func (x *JobStatus) GetError() string { if x != nil { return x.Error } return "" } var File_diskerase_proto protoreflect.FileDescriptor var file_diskerase_proto_rawDesc = []byte{ 0x0a, 0x0f, 0x64, 0x69, 0x73, 0x6b, 0x65, 0x72, 0x61, 0x73, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x09, 0x64, 0x69, 0x73, 0x6b, 0x65, 0x72, 0x61, 0x73, 0x65, 0x22, 0x5b, 0x0a, 0x07, 0x57, 0x6f, 0x72, 0x6b, 0x52, 0x65, 0x71, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x65, 0x73, 0x63, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x64, 0x65, 0x73, 0x63, 0x12, 0x28, 0x0a, 0x06, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x64, 0x69, 0x73, 0x6b, 0x65, 0x72, 0x61, 0x73, 0x65, 0x2e, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x52, 0x06, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x73, 0x22, 0x1a, 0x0a, 0x08, 0x57, 0x6f, 0x72, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x22, 0x5e, 0x0a, 0x05, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x65, 0x73, 0x63, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x64, 0x65, 0x73, 0x63, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x61, 0x74, 0x65, 0x5f, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x72, 0x61, 0x74, 0x65, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x22, 0x0a, 0x04, 0x6a, 0x6f, 0x62, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x64, 0x69, 0x73, 0x6b, 0x65, 0x72, 0x61, 0x73, 0x65, 0x2e, 0x4a, 0x6f, 0x62, 0x52, 0x04, 0x6a, 0x6f, 0x62, 0x73, 0x22, 0x94, 0x01, 0x0a, 0x03, 0x4a, 0x6f, 0x62, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x65, 0x73, 0x63, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x64, 0x65, 0x73, 0x63, 0x12, 0x2c, 0x0a, 0x04, 0x61, 0x72, 0x67, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x64, 0x69, 0x73, 0x6b, 0x65, 0x72, 0x61, 0x73, 0x65, 0x2e, 0x4a, 0x6f, 0x62, 0x2e, 0x41, 0x72, 0x67, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x04, 0x61, 0x72, 0x67, 0x73, 0x1a, 0x37, 0x0a, 0x09, 0x41, 0x72, 0x67, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x19, 0x0a, 0x07, 0x45, 0x78, 0x65, 0x63, 0x52, 0x65, 0x71, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x22, 0x0a, 0x0a, 0x08, 0x45, 0x78, 0x65, 0x63, 0x52, 0x65, 0x73, 0x70, 0x22, 0x1b, 0x0a, 0x09, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x22, 0xd4, 0x01, 0x0a, 0x0a, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x65, 0x73, 0x63, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x64, 0x65, 0x73, 0x63, 0x12, 0x29, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x11, 0x2e, 0x64, 0x69, 0x73, 0x6b, 0x65, 0x72, 0x61, 0x73, 0x65, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x2e, 0x0a, 0x06, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x64, 0x69, 0x73, 0x6b, 0x65, 0x72, 0x61, 0x73, 0x65, 0x2e, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x68, 0x61, 0x64, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x68, 0x61, 0x64, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x12, 0x24, 0x0a, 0x0e, 0x77, 0x61, 0x73, 0x5f, 0x65, 0x73, 0x5f, 0x73, 0x74, 0x6f, 0x70, 0x70, 0x65, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x77, 0x61, 0x73, 0x45, 0x73, 0x53, 0x74, 0x6f, 0x70, 0x70, 0x65, 0x64, 0x22, 0x93, 0x01, 0x0a, 0x0b, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x65, 0x73, 0x63, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x64, 0x65, 0x73, 0x63, 0x12, 0x29, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x11, 0x2e, 0x64, 0x69, 0x73, 0x6b, 0x65, 0x72, 0x61, 0x73, 0x65, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x68, 0x61, 0x73, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x68, 0x61, 0x73, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x28, 0x0a, 0x04, 0x6a, 0x6f, 0x62, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x64, 0x69, 0x73, 0x6b, 0x65, 0x72, 0x61, 0x73, 0x65, 0x2e, 0x4a, 0x6f, 0x62, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x04, 0x6a, 0x6f, 0x62, 0x73, 0x22, 0xe1, 0x01, 0x0a, 0x09, 0x4a, 0x6f, 0x62, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x65, 0x73, 0x63, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x64, 0x65, 0x73, 0x63, 0x12, 0x32, 0x0a, 0x04, 0x61, 0x72, 0x67, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x64, 0x69, 0x73, 0x6b, 0x65, 0x72, 0x61, 0x73, 0x65, 0x2e, 0x4a, 0x6f, 0x62, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x2e, 0x41, 0x72, 0x67, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x04, 0x61, 0x72, 0x67, 0x73, 0x12, 0x29, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x11, 0x2e, 0x64, 0x69, 0x73, 0x6b, 0x65, 0x72, 0x61, 0x73, 0x65, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x1a, 0x37, 0x0a, 0x09, 0x41, 0x72, 0x67, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x2a, 0x6b, 0x0a, 0x06, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x11, 0x0a, 0x0d, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x55, 0x6e, 0x6b, 0x6e, 0x6f, 0x77, 0x6e, 0x10, 0x00, 0x12, 0x14, 0x0a, 0x10, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x4e, 0x6f, 0x74, 0x53, 0x74, 0x61, 0x72, 0x74, 0x65, 0x64, 0x10, 0x01, 0x12, 0x11, 0x0a, 0x0d, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x75, 0x6e, 0x6e, 0x69, 0x6e, 0x67, 0x10, 0x02, 0x12, 0x10, 0x0a, 0x0c, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x10, 0x03, 0x12, 0x13, 0x0a, 0x0f, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x10, 0x04, 0x32, 0xab, 0x01, 0x0a, 0x08, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x12, 0x33, 0x0a, 0x06, 0x53, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x12, 0x12, 0x2e, 0x64, 0x69, 0x73, 0x6b, 0x65, 0x72, 0x61, 0x73, 0x65, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x52, 0x65, 0x71, 0x1a, 0x13, 0x2e, 0x64, 0x69, 0x73, 0x6b, 0x65, 0x72, 0x61, 0x73, 0x65, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x12, 0x31, 0x0a, 0x04, 0x45, 0x78, 0x65, 0x63, 0x12, 0x12, 0x2e, 0x64, 0x69, 0x73, 0x6b, 0x65, 0x72, 0x61, 0x73, 0x65, 0x2e, 0x45, 0x78, 0x65, 0x63, 0x52, 0x65, 0x71, 0x1a, 0x13, 0x2e, 0x64, 0x69, 0x73, 0x6b, 0x65, 0x72, 0x61, 0x73, 0x65, 0x2e, 0x45, 0x78, 0x65, 0x63, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x12, 0x37, 0x0a, 0x06, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x14, 0x2e, 0x64, 0x69, 0x73, 0x6b, 0x65, 0x72, 0x61, 0x73, 0x65, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x1a, 0x15, 0x2e, 0x64, 0x69, 0x73, 0x6b, 0x65, 0x72, 0x61, 0x73, 0x65, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x42, 0x4f, 0x5a, 0x4d, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x50, 0x61, 0x63, 0x6b, 0x74, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x69, 0x6e, 0x67, 0x2f, 0x47, 0x6f, 0x2d, 0x66, 0x6f, 0x72, 0x2d, 0x44, 0x65, 0x76, 0x4f, 0x70, 0x73, 0x2f, 0x63, 0x68, 0x61, 0x70, 0x74, 0x65, 0x72, 0x2f, 0x31, 0x38, 0x2f, 0x64, 0x69, 0x73, 0x6b, 0x65, 0x72, 0x61, 0x73, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x64, 0x69, 0x73, 0x6b, 0x65, 0x72, 0x61, 0x73, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( file_diskerase_proto_rawDescOnce sync.Once file_diskerase_proto_rawDescData = file_diskerase_proto_rawDesc ) func file_diskerase_proto_rawDescGZIP() []byte { file_diskerase_proto_rawDescOnce.Do(func() { file_diskerase_proto_rawDescData = protoimpl.X.CompressGZIP(file_diskerase_proto_rawDescData) }) return file_diskerase_proto_rawDescData } var file_diskerase_proto_enumTypes = make([]protoimpl.EnumInfo, 1) var file_diskerase_proto_msgTypes = make([]protoimpl.MessageInfo, 12) var file_diskerase_proto_goTypes = []interface{}{ (Status)(0), // 0: diskerase.Status (*WorkReq)(nil), // 1: diskerase.WorkReq (*WorkResp)(nil), // 2: diskerase.WorkResp (*Block)(nil), // 3: diskerase.Block (*Job)(nil), // 4: diskerase.Job (*ExecReq)(nil), // 5: diskerase.ExecReq (*ExecResp)(nil), // 6: diskerase.ExecResp (*StatusReq)(nil), // 7: diskerase.StatusReq (*StatusResp)(nil), // 8: diskerase.StatusResp (*BlockStatus)(nil), // 9: diskerase.BlockStatus (*JobStatus)(nil), // 10: diskerase.JobStatus nil, // 11: diskerase.Job.ArgsEntry nil, // 12: diskerase.JobStatus.ArgsEntry } var file_diskerase_proto_depIdxs = []int32{ 3, // 0: diskerase.WorkReq.blocks:type_name -> diskerase.Block 4, // 1: diskerase.Block.jobs:type_name -> diskerase.Job 11, // 2: diskerase.Job.args:type_name -> diskerase.Job.ArgsEntry 0, // 3: diskerase.StatusResp.status:type_name -> diskerase.Status 9, // 4: diskerase.StatusResp.blocks:type_name -> diskerase.BlockStatus 0, // 5: diskerase.BlockStatus.status:type_name -> diskerase.Status 10, // 6: diskerase.BlockStatus.jobs:type_name -> diskerase.JobStatus 12, // 7: diskerase.JobStatus.args:type_name -> diskerase.JobStatus.ArgsEntry 0, // 8: diskerase.JobStatus.status:type_name -> diskerase.Status 1, // 9: diskerase.Workflow.Submit:input_type -> diskerase.WorkReq 5, // 10: diskerase.Workflow.Exec:input_type -> diskerase.ExecReq 7, // 11: diskerase.Workflow.Status:input_type -> diskerase.StatusReq 2, // 12: diskerase.Workflow.Submit:output_type -> diskerase.WorkResp 6, // 13: diskerase.Workflow.Exec:output_type -> diskerase.ExecResp 8, // 14: diskerase.Workflow.Status:output_type -> diskerase.StatusResp 12, // [12:15] is the sub-list for method output_type 9, // [9:12] is the sub-list for method input_type 9, // [9:9] is the sub-list for extension type_name 9, // [9:9] is the sub-list for extension extendee 0, // [0:9] is the sub-list for field type_name } func init() { file_diskerase_proto_init() } func file_diskerase_proto_init() { if File_diskerase_proto != nil { return } if !protoimpl.UnsafeEnabled { file_diskerase_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*WorkReq); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_diskerase_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*WorkResp); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_diskerase_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Block); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_diskerase_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Job); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_diskerase_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*ExecReq); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_diskerase_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*ExecResp); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_diskerase_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*StatusReq); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_diskerase_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*StatusResp); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_diskerase_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*BlockStatus); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_diskerase_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*JobStatus); 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_diskerase_proto_rawDesc, NumEnums: 1, NumMessages: 12, NumExtensions: 0, NumServices: 1, }, GoTypes: file_diskerase_proto_goTypes, DependencyIndexes: file_diskerase_proto_depIdxs, EnumInfos: file_diskerase_proto_enumTypes, MessageInfos: file_diskerase_proto_msgTypes, }.Build() File_diskerase_proto = out.File file_diskerase_proto_rawDesc = nil file_diskerase_proto_goTypes = nil file_diskerase_proto_depIdxs = nil } ================================================ FILE: chapter/16/workflow/proto/diskerase.proto ================================================ syntax = "proto3"; package diskerase; option go_package = "github.com/PacktPublishing/Go-for-DevOps/chapter/18/diskerase/proto/diskerase"; // WorkReq is the definition of some work to be done by the system. message WorkReq { // This is used to describe the work to be done. This name // must be authorized by having a policy with the same name // in the server's policies.json fie. string name = 1; // A description of what this is doing. string desc = 2; // These are groupings of Jobs. Each block is executed one at // a time. repeated Block blocks = 3; } // WorkResp details the ID that will be used to refer to a submitted WorkReq. message WorkResp { // This is the unique ID for this WorkReq. string id = 1; } // Block is a grouping of Jobs that will be executed concurrently // at some rate. message Block { // This describes what the Block is doing. string desc = 1; // The amount of concurrency executions. < 1 will default to 1. int32 rate_limit = 2; // The Jobs to to execute in this Block. repeated Job jobs = 3; } // Job refers to a Job action that is defined on the server. message Job { // This is the name of the Job, which must be registered on // the server. string name = 1; // This is a description of what the job is doing. string desc = 2; // A mapping of key/value arguments. While the value is a string, // it can represent non-string data and will be converted by the // Job on the server. See the Job definition for a list of arguments // that are mandatory and optional. map args = 3; } // ExecReq is used to tell the server to execute a WorkReq // that was previously submitted. message ExecReq { // This is the unique ID of the WorkReq given back // by WorkResp. string id = 1; } // ExecResp is the response from an ExecReq. message ExecResp {} // Status details the status of a Block or Job. enum Status { // Indicates that there is some bug // as the Status for the object was not set. StatusUnknown = 0; // The WorkReq, Block or Job has not started execution. StatusNotStarted = 1; // The WorkReq, Block or Job is currently executing. StatusRunning = 2; // The WorkReq, Block or Job has failed. StatusFailed = 3; // The WorkReq, Block or Job has completed. StatusCompleted = 4; } // StatusReq requests a status update from the server. message StatusReq { // The unique ID of the WorkReq. string id = 1; } // StatusResp is the status of WorkReq. message StatusResp { // The name of the WorkReq. string name = 1; // The description of the WorkReq. string desc = 2; // The overall status of the WorkReq. Status status = 3; // The status information on the Blocks. repeated BlockStatus blocks = 4; // If we are SatusFailed or StatusCompleted, if // there were any errors when run. bool had_errors = 5; // If the WorkReq was stopped with emergency stop. bool was_es_stopped = 6; } // BlockStatus holds the status of block execution. message BlockStatus { // The description of the block. string desc = 1; // The status of the block. Status status = 2; // If there any errors in Jobs in the Block. bool has_error = 3; // The status of Jobs in the Block. repeated JobStatus jobs = 4; } // JobStatus holds the status of the Jobs. message JobStatus { // The name of the Job called. string name = 1; // The description of the Job. string desc = 2; // The args for the Job. map args = 3; // The status of the Job. Status status = 4; // The error, if there was one. string error = 5; } service Workflow { // Submit the work to the server. This will not execute the work, it will // simply verify it against policy and store it for execution. rpc Submit(WorkReq) returns (WorkResp) {}; // Tell the service to execute a WorkReq submitted earlier. rpc Exec(ExecReq) returns (ExecResp) {}; // Get the status of a WorkReq. rpc Status(StatusReq) returns (StatusResp) {}; } ================================================ FILE: chapter/16/workflow/proto/diskerase_grpc.pb.go ================================================ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. package diskerase 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.32.0 or later. const _ = grpc.SupportPackageIsVersion7 // WorkflowClient is the client API for Workflow 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 WorkflowClient interface { // Submit the work to the server. This will not execute the work, it will // simply verify it against policy and store it for execution. Submit(ctx context.Context, in *WorkReq, opts ...grpc.CallOption) (*WorkResp, error) // Tell the service to execute a WorkReq submitted earlier. Exec(ctx context.Context, in *ExecReq, opts ...grpc.CallOption) (*ExecResp, error) // Get the status of a WorkReq. Status(ctx context.Context, in *StatusReq, opts ...grpc.CallOption) (*StatusResp, error) } type workflowClient struct { cc grpc.ClientConnInterface } func NewWorkflowClient(cc grpc.ClientConnInterface) WorkflowClient { return &workflowClient{cc} } func (c *workflowClient) Submit(ctx context.Context, in *WorkReq, opts ...grpc.CallOption) (*WorkResp, error) { out := new(WorkResp) err := c.cc.Invoke(ctx, "/diskerase.Workflow/Submit", in, out, opts...) if err != nil { return nil, err } return out, nil } func (c *workflowClient) Exec(ctx context.Context, in *ExecReq, opts ...grpc.CallOption) (*ExecResp, error) { out := new(ExecResp) err := c.cc.Invoke(ctx, "/diskerase.Workflow/Exec", in, out, opts...) if err != nil { return nil, err } return out, nil } func (c *workflowClient) Status(ctx context.Context, in *StatusReq, opts ...grpc.CallOption) (*StatusResp, error) { out := new(StatusResp) err := c.cc.Invoke(ctx, "/diskerase.Workflow/Status", in, out, opts...) if err != nil { return nil, err } return out, nil } // WorkflowServer is the server API for Workflow service. // All implementations must embed UnimplementedWorkflowServer // for forward compatibility type WorkflowServer interface { // Submit the work to the server. This will not execute the work, it will // simply verify it against policy and store it for execution. Submit(context.Context, *WorkReq) (*WorkResp, error) // Tell the service to execute a WorkReq submitted earlier. Exec(context.Context, *ExecReq) (*ExecResp, error) // Get the status of a WorkReq. Status(context.Context, *StatusReq) (*StatusResp, error) mustEmbedUnimplementedWorkflowServer() } // UnimplementedWorkflowServer must be embedded to have forward compatible implementations. type UnimplementedWorkflowServer struct { } func (UnimplementedWorkflowServer) Submit(context.Context, *WorkReq) (*WorkResp, error) { return nil, status.Errorf(codes.Unimplemented, "method Submit not implemented") } func (UnimplementedWorkflowServer) Exec(context.Context, *ExecReq) (*ExecResp, error) { return nil, status.Errorf(codes.Unimplemented, "method Exec not implemented") } func (UnimplementedWorkflowServer) Status(context.Context, *StatusReq) (*StatusResp, error) { return nil, status.Errorf(codes.Unimplemented, "method Status not implemented") } func (UnimplementedWorkflowServer) mustEmbedUnimplementedWorkflowServer() {} // UnsafeWorkflowServer may be embedded to opt out of forward compatibility for this service. // Use of this interface is not recommended, as added methods to WorkflowServer will // result in compilation errors. type UnsafeWorkflowServer interface { mustEmbedUnimplementedWorkflowServer() } func RegisterWorkflowServer(s grpc.ServiceRegistrar, srv WorkflowServer) { s.RegisterService(&Workflow_ServiceDesc, srv) } func _Workflow_Submit_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(WorkReq) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(WorkflowServer).Submit(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: "/diskerase.Workflow/Submit", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(WorkflowServer).Submit(ctx, req.(*WorkReq)) } return interceptor(ctx, in, info, handler) } func _Workflow_Exec_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(ExecReq) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(WorkflowServer).Exec(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: "/diskerase.Workflow/Exec", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(WorkflowServer).Exec(ctx, req.(*ExecReq)) } return interceptor(ctx, in, info, handler) } func _Workflow_Status_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(StatusReq) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(WorkflowServer).Status(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: "/diskerase.Workflow/Status", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(WorkflowServer).Status(ctx, req.(*StatusReq)) } return interceptor(ctx, in, info, handler) } // Workflow_ServiceDesc is the grpc.ServiceDesc for Workflow service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) var Workflow_ServiceDesc = grpc.ServiceDesc{ ServiceName: "diskerase.Workflow", HandlerType: (*WorkflowServer)(nil), Methods: []grpc.MethodDesc{ { MethodName: "Submit", Handler: _Workflow_Submit_Handler, }, { MethodName: "Exec", Handler: _Workflow_Exec_Handler, }, { MethodName: "Status", Handler: _Workflow_Status_Handler, }, }, Streams: []grpc.StreamDesc{}, Metadata: "diskerase.proto", } ================================================ FILE: chapter/16/workflow/samples/diskerase/LICENSE ================================================ The MIT License (MIT) Copyright © 2021 John Doak Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: chapter/16/workflow/samples/diskerase/cmd/eraseSatellite.go ================================================ /* Copyright © 2021 John Doak Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ package cmd import ( "context" "fmt" "os" "sort" "time" "github.com/PacktPublishing/Go-for-DevOps/chapter/16/workflow/client" "github.com/PacktPublishing/Go-for-DevOps/chapter/16/workflow/data/packages/sites" pb "github.com/PacktPublishing/Go-for-DevOps/chapter/16/workflow/proto" "github.com/spf13/cobra" ) const ( submitLog = "submit.log" ) // eraseSatelliteCmd represents the eraseSatellite command var eraseSatelliteCmd = &cobra.Command{ Use: "eraseSatellite", Short: "Erase all disks at a satellite datacenter", Long: `Erase all disks at a satellite datacenter. The satellite must be in the decom state and usage of using your command. For example: Cobra is a CLI library for Go that empowers applications. This application is a tool to generate the needed files to quickly create a Cobra application.`, Run: func(cmd *cobra.Command, args []string) { if len(args) != 1 { fmt.Printf("must pass a single arg, the name of the satellite datacenter") return } sat := args[0] fmt.Println("generating workflow...") wf, err := generateWork(sat) if err != nil { fmt.Printf("problem generating workflow: %s\n", err) return } // Open our attempt.log file to write our submissions f, err := os.OpenFile(submitLog, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0600) if err != nil { fmt.Printf("could not open our submit.log file: %s", err) return } c, err := client.New(rootCmd.Flag("address").Value.String()) if err != nil { fmt.Printf("could not connect to workflow service: %s\n", err) return } ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() fmt.Println("submitting workflow...") id, err := c.Submit(ctx, wf) if err != nil { fmt.Printf("submission had an issue: %s\n", err) return } if _, err := f.Write([]byte("\n" + id)); err != nil { fmt.Printf("could not write to our submit.log file: %s", err) return } f.Close() fmt.Printf("workflow(%s) accepted, ask server to execute workflow...\n", id) // Now execute our attempt. err = c.Exec(ctx, id) if err != nil { fmt.Printf("executing workflow(%s) on the server had an issue: %s\n", id, err) return } fmt.Printf("server is executing workflow(%s)\n", id) if err := monitor(context.Background(), c, id); err != nil { fmt.Printf("problem monitoring workflow(%s): %s", id, err) return } }, } func init() { rootCmd.AddCommand(eraseSatelliteCmd) } // generateWork takes in the satellite name, validates the satellite can have diskerase // called on it and finally generates the *pb.WorkReq that would be needed to erase that // satellite's machine's disk. func generateWork(sat string) (*pb.WorkReq, error) { wf := &pb.WorkReq{ Name: "SatelliteDiskErase", Desc: "Erasing disks in datacenter satellite " + sat, } site, ok := sites.Data.Sites[sat] if !ok { return nil, fmt.Errorf("there is no datacenter called %q", sat) } if site.Type != "satellite" { return nil, fmt.Errorf("%q is not a satellite datacenter, it is a %s", sat, site.Type) } if site.Status != "decom" { return nil, fmt.Errorf("%q is not in the second state, was in %s", sat, site.Status) } // Get a list of machines for the site, in alphabetical order. machines := make([]sites.Machine, 0, len(site.Machines)) for _, m := range site.Machines { machines = append(machines, m) } sort.SliceStable( machines, func(i, j int) bool { return site.Machines[i].Name < site.Machines[j].Name }, ) // This adds a top level block that validates the site is still in the decom state // and gets a token from the token bucket to do a satellite erasure. preCond := &pb.Block{ Desc: "Check pre-conditions", Jobs: []*pb.Job{ { Name: "validateDecom", Desc: fmt.Sprintf("Validate satellite(%s) is in the decom state", sat), Args: map[string]string{ "site": sat, "type": "satellite", }, }, { Name: "tokenBucket", Desc: "Get disk erase token, which limits our satellite decoms per hour", Args: map[string]string{ "bucket": "diskEraseSatellite", "fatal": "true", }, }, }, } wf.Blocks = append(wf.Blocks, preCond) // For ever set of 5 machines, build a block erasing those 5 machines. // Sleep at the end of the block for 1 minute. // Set the concurrency to 5 so that all disk erasures in a block happen at the same tiem. block := &pb.Block{} for i, m := range machines { // Every 5 machines, commit the block and start a new one. if i%5 == 0 && i != 0 { block.Desc = getBlockDesc(block) block.RateLimit = 5 block.Jobs = append( block.Jobs, &pb.Job{ Name: "sleep", Desc: "Wait 1 minute between disk erasures", Args: map[string]string{ "seconds": "60", }, }, ) wf.Blocks = append(wf.Blocks, block) block = &pb.Block{} } block.Jobs = append( block.Jobs, &pb.Job{ Name: "diskErase", Desc: fmt.Sprintf("Erase satellite(%s) machine(%s) disk", m.Site, m.Name), Args: map[string]string{ "machine": m.Name, "site": m.Site, }, }, ) } // If we have a block not committed, commit it. Dont' put a sleep afterwards. if len(block.Jobs) != 0 { block.Desc = getBlockDesc(block) block.RateLimit = 5 wf.Blocks = append(wf.Blocks, block) } return wf, nil } // getBlockDesc writes a description of the block listing the range of // machines that are currently getting erased. func getBlockDesc(block *pb.Block) string { jobStart := block.Jobs[0].Args["machine"] jobEnd := block.Jobs[len(block.Jobs)-1].Args["machine"] return fmt.Sprintf("disk erase machines %s-%s", jobStart, jobEnd) } ================================================ FILE: chapter/16/workflow/samples/diskerase/cmd/protoStatus.go ================================================ /* Copyright © 2021 John Doak Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ package cmd import ( "context" "fmt" "time" "github.com/PacktPublishing/Go-for-DevOps/chapter/16/workflow/client" "github.com/fatih/color" "github.com/inancgumus/screen" "github.com/spf13/cobra" "google.golang.org/protobuf/encoding/protojson" pb "github.com/PacktPublishing/Go-for-DevOps/chapter/16/workflow/proto" ) // protoStatusCmd represents the protoStatus command var protoStatusCmd = &cobra.Command{ Use: "protoStatus", Short: "Streams the status of a workflow in proto's JSON format until it ends", Long: `If you have a workflow that you want to monitor the status of, this will do that. It can be used for more than just diskerase, though it is primarly meant for that purpose. This differes from "status" in that it outputs in the full proto JSON format with all data. Simply pass the single argument, which is the ID of the workflow.`, Run: func(cmd *cobra.Command, args []string) { if len(args) != 1 { fmt.Printf("must pass a single arg, the ID of the workflow to monitor") return } c, err := client.New(rootCmd.Flag("address").Value.String()) if err != nil { fmt.Printf("could not connect to workflow service: %s\n", err) return } if err := monitorProto(context.Background(), c, args[0]); err != nil { fmt.Printf("had problem talking to server: %s", err) return } }, } func init() { rootCmd.AddCommand(protoStatusCmd) } // monitorProto will contact the server every 10 seconds until the workflow with "id" // has left the running state. func monitorProto(ctx context.Context, c *client.Workflow, id string) error { for { resp, err := c.Status(ctx, id) if err != nil { return fmt.Errorf("problem getting status of ID(%s): %w", id, err) } screen.Clear() screen.MoveTopLeft() color.New(color.FgRed).Println("Updates every 10 seconds") fmt.Println(protojson.Format(resp)) if resp.Status != pb.Status_StatusRunning { fmt.Println("Workflow completed!") return nil } select { case <-ctx.Done(): return ctx.Err() case <-time.After(10 * time.Second): } } } ================================================ FILE: chapter/16/workflow/samples/diskerase/cmd/root.go ================================================ /* Copyright © 2021 John Doak Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ package cmd import ( "fmt" "os" "github.com/spf13/cobra" "github.com/spf13/viper" ) var cfgFile string // rootCmd represents the base command when called without any subcommands var rootCmd = &cobra.Command{ Use: "diskerase", Short: "Execute or monitors a disk erase of a datacenter", Long: `This application will execute or monitor a disk erase for a datacenter. Currently it supports erasures at satellite datacenters only. `, } // Execute adds all child commands to the root command and sets flags appropriately. // This is called by main.main(). It only needs to happen once to the rootCmd. func Execute() { cobra.CheckErr(rootCmd.Execute()) } func init() { cobra.OnInitialize(initConfig) // Here you will define your flags and configuration settings. // Cobra supports persistent flags, which, if defined here, // will be global for your application. rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.diskerase.yaml)") rootCmd.PersistentFlags().String("address", "127.0.0.1:8080", "the address the workflow server is at, host:port") // Cobra also supports local flags, which will only run // when this action is called directly. rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") } // initConfig reads in config file and ENV variables if set. func initConfig() { if cfgFile != "" { // Use config file from the flag. viper.SetConfigFile(cfgFile) } else { // Find home directory. home, err := os.UserHomeDir() cobra.CheckErr(err) // Search config in home directory with name ".diskerase" (without extension). viper.AddConfigPath(home) viper.SetConfigType("yaml") viper.SetConfigName(".diskerase") } viper.AutomaticEnv() // read in environment variables that match // If a config file is found, read it in. if err := viper.ReadInConfig(); err == nil { fmt.Fprintln(os.Stderr, "Using config file:", viper.ConfigFileUsed()) } } ================================================ FILE: chapter/16/workflow/samples/diskerase/cmd/status.go ================================================ /* Copyright © 2021 John Doak Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ package cmd import ( "context" "fmt" "time" "github.com/PacktPublishing/Go-for-DevOps/chapter/16/workflow/client" "github.com/fatih/color" "github.com/inancgumus/screen" "github.com/spf13/cobra" pb "github.com/PacktPublishing/Go-for-DevOps/chapter/16/workflow/proto" ) // statusCmd represents the status command var statusCmd = &cobra.Command{ Use: "status", Short: "Streams the status of a worklow until it ends", Long: `If you have a workflow that you want to monitor the status of, this will do that. It can be used for more than just diskerase, though it is primarly meant for that purpose. Simply pass the single argument, which is the ID of the workflow. `, Run: func(cmd *cobra.Command, args []string) { if len(args) != 1 { fmt.Printf("must pass a single arg, the ID of the workflow to monitor") return } c, err := client.New(rootCmd.Flag("address").Value.String()) if err != nil { fmt.Printf("could not connect to workflow service: %s\n", err) return } if err := monitor(context.Background(), c, args[0]); err != nil { fmt.Printf("had problem talking to server: %s", err) return } }, } func init() { rootCmd.AddCommand(statusCmd) } // monitor will contact the server every 10 seconds until the workflow with "id" // has left the running state. func monitor(ctx context.Context, c *client.Workflow, id string) error { for { resp, err := c.Status(ctx, id) if err != nil { return fmt.Errorf("problem getting status of ID(%s): %w", id, err) } screen.Clear() screen.MoveTopLeft() color.New(color.FgRed).Println("Updates every 10 seconds") fmt.Println(resp.CLISummary(id)) if resp.Status != pb.Status_StatusRunning { fmt.Println("Workflow completed! To retrieve full details, use 'protoStatus' command.") return nil } select { case <-ctx.Done(): return ctx.Err() case <-time.After(10 * time.Second): } } } ================================================ FILE: chapter/16/workflow/samples/diskerase/diskerase.go ================================================ /* Copyright © 2021 John Doak Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ package main import ( "github.com/PacktPublishing/Go-for-DevOps/chapter/16/workflow/data/packages/sites" "github.com/PacktPublishing/Go-for-DevOps/chapter/16/workflow/samples/diskerase/cmd" ) func main() { sites.Init("../../data") cmd.Execute() } ================================================ FILE: chapter/16/workflow/workflow.go ================================================ package main import ( "flag" "fmt" "log" "net" "os" "path/filepath" "github.com/PacktPublishing/Go-for-DevOps/chapter/16/workflow/data/packages/sites" "github.com/PacktPublishing/Go-for-DevOps/chapter/16/workflow/internal/policy/config" "github.com/PacktPublishing/Go-for-DevOps/chapter/16/workflow/internal/service" "google.golang.org/grpc" pb "github.com/PacktPublishing/Go-for-DevOps/chapter/16/workflow/proto" // These register all our job types, as each has an init() that registers the Job with // the service. This is called a side effects import, because we don't actually use it. // The _ before the package indicates it will not be used directly. _ "github.com/PacktPublishing/Go-for-DevOps/chapter/16/workflow/internal/service/jobs/register/diskerase" _ "github.com/PacktPublishing/Go-for-DevOps/chapter/16/workflow/internal/service/jobs/register/sleep" _ "github.com/PacktPublishing/Go-for-DevOps/chapter/16/workflow/internal/service/jobs/register/tokenbucket" _ "github.com/PacktPublishing/Go-for-DevOps/chapter/16/workflow/internal/service/jobs/register/validatedecom" // These register all our policies, exactly like our Jobs work. _ "github.com/PacktPublishing/Go-for-DevOps/chapter/16/workflow/internal/policy/register/restrictjobtypes" _ "github.com/PacktPublishing/Go-for-DevOps/chapter/16/workflow/internal/policy/register/sameargs" _ "github.com/PacktPublishing/Go-for-DevOps/chapter/16/workflow/internal/policy/register/startorend" ) var ( addr = flag.String("addr", "127.0.0.1:8080", "The address to run the server on") ) // dirMode is simply the mode we create our directories with. const dirMode = os.ModeDir | 0700 func main() { flag.Parse() // Read our policy config. config.Init() sites.Init("data") // This makes sure we have a place to store workflows. p := filepath.Join(os.TempDir(), "workflows") stat, err := os.Stat(p) if err == nil { if !stat.IsDir() { panic(p + " is not a direcotry") } if stat.Mode() != os.FileMode(dirMode) { panic(fmt.Sprintf("%s is mode %v, not %v", p, stat.Mode(), dirMode)) } } else { if err := os.Mkdir(p, dirMode); err != nil { panic(fmt.Sprintf("could not create directory(%s): %s", p, err)) } } log.Println("Workflow Storage is at: ", p) // Create our implementation of the gRPC service. serv, err := service.New(p) if err != nil { panic(err) } // Create a new gRPC service and register our implementation. g := grpc.NewServer() pb.RegisterWorkflowServer(g, serv) // Grab our address on the network and begin listening. lis, err := net.Listen("tcp", *addr) if err != nil { panic(err) } // Tell gRPC to use our listener for new connections. log.Println("Server started on: ", *addr) g.Serve(lis) } ================================================ FILE: chapter/5/excel/simple/excel.go ================================================ package main import ( "time" "github.com/360EntSecGroup-Skylar/excelize" ) func main() { xlsx := excelize.NewFile() sheet := "Sheet1" xlsx.SetCellValue(sheet, "A1", "Server Name") xlsx.SetCellValue(sheet, "B1", "Generation") xlsx.SetCellValue(sheet, "C1", "Acquisition Date") xlsx.SetCellValue(sheet, "D1", "CPU Vendor") xlsx.SetCellValue(sheet, "A2", "svlaa01") xlsx.SetCellValue(sheet, "B2", 12) xlsx.SetCellValue(sheet, "C2", mustParse("10/27/2021")) xlsx.SetCellValue(sheet, "D2", "Intel") xlsx.SetCellValue(sheet, "A3", "svlac14") xlsx.SetCellValue(sheet, "B3", 13) xlsx.SetCellValue(sheet, "C3", mustParse("12/13/2021")) xlsx.SetCellValue(sheet, "D3", "AMD") // Save xlsx file by the given path. if err := xlsx.SaveAs("./Book1.xlsx"); err != nil { panic(err) } } func mustParse(s string) time.Time { const layout = "01/02/2006" t, err := time.Parse(layout, s) if err != nil { panic(err) } return t } ================================================ FILE: chapter/5/excel/visualization/excel.go ================================================ package main import ( "encoding/json" "errors" "fmt" "strconv" "sync" "time" "github.com/PacktPublishing/Go-for-DevOps/chapter/5/excel/visualization/internal/chart" "github.com/xuri/excelize/v2" ) type CPUVendor string const ( UnknownCPUVendor CPUVendor = "Unknown" Intel CPUVendor = "Intel" AMD CPUVendor = "AMD" ) var validCPUVendors = map[CPUVendor]bool{ Intel: true, AMD: true, } func main() { sheet, err := newServerSheet() if err != nil { panic(err) } sheet.add("svlaa01", 12, mustParse("10/27/2021"), Intel) sheet.add("svlac14", 13, mustParse("12/13/2021"), AMD) if err := sheet.render(); err != nil { panic(err) } } type summaries struct { cpuVendor cpuVendorSum } type cpuVendorSum struct { unknown, intel, amd int } type serverSheet struct { mu sync.Mutex sheetName string xlsx *excelize.File summaries *summaries nextRow int } func newServerSheet() (*serverSheet, error) { s := &serverSheet{ sheetName: "Sheet1", xlsx: excelize.NewFile(), summaries: &summaries{}, nextRow: 2, } s.xlsx.SetCellValue(s.sheetName, "A1", "Server Name") s.xlsx.SetCellValue(s.sheetName, "B1", "Generation") s.xlsx.SetCellValue(s.sheetName, "C1", "Acquisition Date") s.xlsx.SetCellValue(s.sheetName, "D1", "CPU Vendor") return s, nil } func (s *serverSheet) add(name string, gen int, acquisition time.Time, vendor CPUVendor) error { s.mu.Lock() defer s.mu.Unlock() if name == "" { return errors.New("name cannot be blank") } if gen < 1 || gen > 13 { return errors.New("gen was not between 1 and including 13") } if acquisition.IsZero() { return errors.New("acquisition cannot be the zero time") } if !validCPUVendors[vendor] { return fmt.Errorf("vendor %v is not a valid vendor", vendor) } s.xlsx.SetCellValue(s.sheetName, "A"+strconv.Itoa(s.nextRow), name) s.xlsx.SetCellValue(s.sheetName, "B"+strconv.Itoa(s.nextRow), gen) s.xlsx.SetCellValue(s.sheetName, "C"+strconv.Itoa(s.nextRow), acquisition) s.xlsx.SetColWidth(s.sheetName, "C", "C", 20) s.xlsx.SetCellValue(s.sheetName, "D"+strconv.Itoa(s.nextRow), vendor) switch vendor { case Intel: s.summaries.cpuVendor.intel++ case AMD: s.summaries.cpuVendor.amd++ default: s.summaries.cpuVendor.unknown++ } s.nextRow++ return nil } func (s *serverSheet) render() error { s.writeSummaries() if err := s.createCPUChart(); err != nil { return fmt.Errorf("problem creating CPU chart: %w", err) } return s.xlsx.SaveAs("./Book1.xlsx") } func (s *serverSheet) writeSummaries() { s.xlsx.SetCellValue(s.sheetName, "F1", "Vendor Summary") s.xlsx.SetCellValue(s.sheetName, "F2", "Vendor") s.xlsx.SetCellValue(s.sheetName, "G2", "Total") s.xlsx.SetCellValue(s.sheetName, "F3", Intel) s.xlsx.SetCellValue(s.sheetName, "G3", s.summaries.cpuVendor.intel) s.xlsx.SetCellValue(s.sheetName, "F4", AMD) s.xlsx.SetCellValue(s.sheetName, "G4", s.summaries.cpuVendor.amd) } func (s *serverSheet) createCPUChart() error { c := chart.New() c.Type = "pie3D" c.Dimension = chart.FormatChartDimension{640, 480} c.Title = chart.FormatChartTitle{Name: "Server CPU Vendor Breakdown"} c.Format = chart.FormatPicture{ FPrintsWithSheet: true, NoChangeAspect: false, FLocksWithSheet: false, OffsetX: 15, OffsetY: 10, XScale: 1.0, YScale: 1.0, } c.Legend = chart.FormatChartLegend{ Position: "bottom", ShowLegendKey: true, } c.Plotarea.ShowBubbleSize = true c.Plotarea.ShowCatName = true c.Plotarea.ShowLeaderLines = false c.Plotarea.ShowPercent = true c.Plotarea.ShowSerName = true c.ShowBlanksAs = "zero" c.Series = append( c.Series, chart.FormatChartSeries{ Name: `%s!$F$1`, Categories: fmt.Sprintf(`%s!$F$3:$F$4`, s.sheetName), Values: fmt.Sprintf(`%s!$G$3:$G$4`, s.sheetName), }, ) b, err := json.Marshal(c) if err != nil { return err } if err := s.xlsx.AddChart(s.sheetName, "I1", string(b)); err != nil { return err } return nil } func mustParse(s string) time.Time { const layout = "01/02/2006" t, err := time.Parse(layout, s) if err != nil { panic(err) } return t } ================================================ FILE: chapter/5/excel/visualization/internal/chart/chart.go ================================================ package chart // New creates a FormatChart with default values. func New() *FormatChart { return &FormatChart{ Dimension: FormatChartDimension{ Width: 480, Height: 290, }, Format: FormatPicture{ FPrintsWithSheet: true, FLocksWithSheet: false, NoChangeAspect: false, OffsetX: 0, OffsetY: 0, XScale: 1.0, YScale: 1.0, }, Legend: FormatChartLegend{ Position: "bottom", ShowLegendKey: false, }, Title: FormatChartTitle{ Name: " ", }, ShowBlanksAs: "gap", } } // FormatChartAxis directly maps the Format settings of the chart axis. type FormatChartAxis struct { Crossing string `json:"crossing"` MajorTickMark string `json:"major_tick_mark"` MinorTickMark string `json:"minor_tick_mark"` MinorUnitType string `json:"minor_unit_type"` MajorUnit int `json:"major_unit"` MajorUnitType string `json:"major_unit_type"` DisplayUnits string `json:"display_units"` DisplayUnitsVisible bool `json:"display_units_visible"` DateAxis bool `json:"date_axis"` ReverseOrder bool `json:"reverse_order"` Maximum float64 `json:"maximum"` Minimum float64 `json:"minimum"` NumFormat string `json:"num_Format"` NumFont struct { Color string `json:"color"` Bold bool `json:"bold"` Italic bool `json:"italic"` Underline bool `json:"underline"` } `json:"num_font"` NameLayout FormatLayout `json:"name_layout"` } type FormatChartDimension struct { Width int `json:"width"` Height int `json:"height"` } type FormatChart struct { Type string `json:"type"` Series []FormatChartSeries `json:"series"` Format FormatPicture `json:"Format"` Dimension FormatChartDimension `json:"dimension"` Legend FormatChartLegend `json:"legend"` Title FormatChartTitle `json:"title"` XAxis FormatChartAxis `json:"x_axis"` YAxis FormatChartAxis `json:"y_axis"` Chartarea struct { Border struct { None bool `json:"none"` } `json:"border"` Fill struct { Color string `json:"color"` } `json:"fill"` Pattern struct { Pattern string `json:"pattern"` FgColor string `json:"fg_color"` BgColor string `json:"bg_color"` } `json:"pattern"` } `json:"chartarea"` Plotarea struct { ShowBubbleSize bool `json:"show_bubble_size"` ShowCatName bool `json:"show_cat_name"` ShowLeaderLines bool `json:"show_leader_lines"` ShowPercent bool `json:"show_percent"` ShowSerName bool `json:"show_series_name"` ShowVal bool `json:"show_val"` Gradient struct { Colors []string `json:"colors"` } `json:"gradient"` Border struct { Color string `json:"color"` Width int `json:"width"` DashType string `json:"dash_type"` } `json:"border"` Fill struct { Color string `json:"color"` } `json:"fill"` Layout FormatLayout `json:"layout"` } `json:"plotarea"` ShowBlanksAs string `json:"show_blanks_as"` ShowHiddenData bool `json:"show_hidden_data"` SetRotation int `json:"set_rotation"` SetHoleSize int `json:"set_hole_size"` } // FormatChartLegend directly maps the Format settings of the chart legend. type FormatChartLegend struct { None bool `json:"none"` DeleteSeries []int `json:"delete_series"` Font FormatFont `json:"font"` Layout FormatLayout `json:"layout"` Position string `json:"position"` ShowLegendEntry bool `json:"show_legend_entry"` ShowLegendKey bool `json:"show_legend_key"` } // FormatChartSeries directly maps the Format settings of the chart series. type FormatChartSeries struct { Name string `json:"name"` Categories string `json:"categories"` Values string `json:"values"` Line struct { None bool `json:"none"` Color string `json:"color"` } `json:"line"` Marker struct { Type string `json:"type"` Size int `json:"size,"` Width float64 `json:"width"` Border struct { Color string `json:"color"` None bool `json:"none"` } `json:"border"` Fill struct { Color string `json:"color"` None bool `json:"none"` } `json:"fill"` } `json:"marker"` } // FormatChartTitle directly maps the Format settings of the chart title. type FormatChartTitle struct { None bool `json:"none"` Name string `json:"name"` Overlay bool `json:"overlay"` Layout FormatLayout `json:"layout"` } // FormatLayout directly maps the Format settings of the element layout. type FormatLayout struct { X float64 `json:"x"` Y float64 `json:"y"` Width float64 `json:"width"` Height float64 `json:"height"` } // FormatPicture directly maps the Format settings of the picture. type FormatPicture struct { FPrintsWithSheet bool `json:"print_obj"` FLocksWithSheet bool `json:"locked"` NoChangeAspect bool `json:"lock_aspect_ratio"` OffsetX int `json:"x_offset"` OffsetY int `json:"y_offset"` XScale float64 `json:"x_scale"` YScale float64 `json:"y_scale"` Hyperlink string `json:"hyperlink"` HyperlinkType string `json:"hyperlink_type"` Positioning string `json:"positioning"` } // FormatShape directly maps the Format settings of the shape. type FormatShape struct { Type string `json:"type"` Width int `json:"width"` Height int `json:"height"` Format FormatPicture `json:"Format"` Color FormatShapeColor `json:"color"` Paragraph []FormatShapeParagraph `json:"paragraph"` } // FormatShapeParagraph directly maps the Format settings of the paragraph in // the shape. type FormatShapeParagraph struct { Font FormatFont `json:"font"` Text string `json:"text"` } // FormatShapeColor directly maps the color settings of the shape. type FormatShapeColor struct { Line string `json:"line"` Fill string `json:"fill"` Effect string `json:"effect"` } // FormatFont directly maps the styles settings of the fonts. type FormatFont struct { Bold bool `json:"bold"` Italic bool `json:"italic"` Underline string `json:"underline"` Family string `json:"family"` Size int `json:"size"` Color string `json:"color"` } // FormatStyle directly maps the styles settings of the cells. type FormatStyle struct { Border []struct { Type string `json:"type"` Color string `json:"color"` Style int `json:"style"` } `json:"border"` Fill struct { Type string `json:"type"` Pattern int `json:"pattern"` Color []string `json:"color"` Shading int `json:"shading"` } `json:"fill"` Font *FormatFont `json:"font"` Alignment *struct { Horizontal string `json:"horizontal"` Indent int `json:"indent"` JustifyLastLine bool `json:"justify_last_line"` ReadingOrder uint64 `json:"reading_order"` RelativeIndent int `json:"relative_indent"` ShrinkToFit bool `json:"shrink_to_fit"` TextRotation int `json:"text_rotation"` Vertical string `json:"vertical"` WrapText bool `json:"wrap_text"` } `json:"alignment"` Protection *struct { Hidden bool `json:"hidden"` Locked bool `json:"locked"` } `json:"protection"` NumFmt int `json:"number_Format"` DecimalPlaces int `json:"decimal_places"` CustomNumFmt *string `json:"custom_number_Format"` Lang string `json:"lang"` NegRed bool `json:"negred"` } ================================================ FILE: chapter/6/grpc/buf.work ================================================ version: v1 directories: - proto ================================================ FILE: chapter/6/grpc/client/bin/qotd.go ================================================ package main import ( "context" "flag" "fmt" "github.com/PacktPublishing/Go-for-DevOps/chapter/6/grpc/client" ) var ( addr = flag.String("addr", "127.0.0.1:80", "The address of the server.") author = flag.String("author", "", "The author whose quote to get") ) func main() { flag.Parse() c, err := client.New(*addr) if err != nil { panic(err) } a, q, err := c.QOTD(context.Background(), *author) if err != nil { panic(err) } fmt.Println("Author: ", a) fmt.Printf("Quote of the Day: %q\n", q) } ================================================ FILE: chapter/6/grpc/client/client.go ================================================ package client import ( "context" "time" "google.golang.org/grpc" pb "github.com/PacktPublishing/Go-for-DevOps/chapter/6/grpc/proto" ) // Client is a client to the Quote of the day server. type Client struct { client pb.QOTDClient conn *grpc.ClientConn } // New is the constructor for Client. addr is the server's [host]:[port]. func New(addr string) (*Client, error) { conn, err := grpc.Dial(addr, grpc.WithInsecure()) if err != nil { return nil, err } return &Client{ client: pb.NewQOTDClient(conn), conn: conn, }, nil } // QOTD retrieves a quote of the day. If wantAuthor is not set, will randomly choose the author // of a quote. func (c *Client) QOTD(ctx context.Context, wantAuthor string) (author, quote string, err error) { if _, ok := ctx.Deadline(); !ok { var cancel context.CancelFunc ctx, cancel = context.WithTimeout(ctx, 2*time.Second) defer cancel() } resp, err := c.client.GetQOTD(ctx, &pb.GetReq{Author: wantAuthor}) if err != nil { return "", "", err } return resp.Author, resp.Quote, nil } ================================================ FILE: chapter/6/grpc/proto/buf.gen.yaml ================================================ version: v1 plugins: - name: go out: ./ opt: - paths=source_relative - name: go-grpc out: ./ opt: - paths=source_relative ================================================ FILE: chapter/6/grpc/proto/buf.yaml ================================================ version: v1 lint: use: - DEFAULT breaking: use: - FILE ================================================ FILE: chapter/6/grpc/proto/qotd.pb.go ================================================ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.25.0-devel // protoc v3.18.0 // source: qotd.proto package qotd 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 GetReq struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Author string `protobuf:"bytes,1,opt,name=author,proto3" json:"author,omitempty"` } func (x *GetReq) Reset() { *x = GetReq{} if protoimpl.UnsafeEnabled { mi := &file_qotd_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *GetReq) String() string { return protoimpl.X.MessageStringOf(x) } func (*GetReq) ProtoMessage() {} func (x *GetReq) ProtoReflect() protoreflect.Message { mi := &file_qotd_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 GetReq.ProtoReflect.Descriptor instead. func (*GetReq) Descriptor() ([]byte, []int) { return file_qotd_proto_rawDescGZIP(), []int{0} } func (x *GetReq) GetAuthor() string { if x != nil { return x.Author } return "" } type GetResp struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Author string `protobuf:"bytes,1,opt,name=author,proto3" json:"author,omitempty"` Quote string `protobuf:"bytes,2,opt,name=quote,proto3" json:"quote,omitempty"` } func (x *GetResp) Reset() { *x = GetResp{} if protoimpl.UnsafeEnabled { mi := &file_qotd_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *GetResp) String() string { return protoimpl.X.MessageStringOf(x) } func (*GetResp) ProtoMessage() {} func (x *GetResp) ProtoReflect() protoreflect.Message { mi := &file_qotd_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 GetResp.ProtoReflect.Descriptor instead. func (*GetResp) Descriptor() ([]byte, []int) { return file_qotd_proto_rawDescGZIP(), []int{1} } func (x *GetResp) GetAuthor() string { if x != nil { return x.Author } return "" } func (x *GetResp) GetQuote() string { if x != nil { return x.Quote } return "" } var File_qotd_proto protoreflect.FileDescriptor var file_qotd_proto_rawDesc = []byte{ 0x0a, 0x0a, 0x71, 0x6f, 0x74, 0x64, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x04, 0x71, 0x6f, 0x74, 0x64, 0x22, 0x20, 0x0a, 0x06, 0x47, 0x65, 0x74, 0x52, 0x65, 0x71, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x22, 0x37, 0x0a, 0x07, 0x47, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x12, 0x14, 0x0a, 0x05, 0x71, 0x75, 0x6f, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x71, 0x75, 0x6f, 0x74, 0x65, 0x32, 0x30, 0x0a, 0x04, 0x51, 0x4f, 0x54, 0x44, 0x12, 0x28, 0x0a, 0x07, 0x47, 0x65, 0x74, 0x51, 0x4f, 0x54, 0x44, 0x12, 0x0c, 0x2e, 0x71, 0x6f, 0x74, 0x64, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x65, 0x71, 0x1a, 0x0d, 0x2e, 0x71, 0x6f, 0x74, 0x64, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x42, 0x1e, 0x5a, 0x1c, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x5b, 0x72, 0x65, 0x70, 0x6f, 0x5d, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x71, 0x6f, 0x74, 0x64, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( file_qotd_proto_rawDescOnce sync.Once file_qotd_proto_rawDescData = file_qotd_proto_rawDesc ) func file_qotd_proto_rawDescGZIP() []byte { file_qotd_proto_rawDescOnce.Do(func() { file_qotd_proto_rawDescData = protoimpl.X.CompressGZIP(file_qotd_proto_rawDescData) }) return file_qotd_proto_rawDescData } var file_qotd_proto_msgTypes = make([]protoimpl.MessageInfo, 2) var file_qotd_proto_goTypes = []interface{}{ (*GetReq)(nil), // 0: qotd.GetReq (*GetResp)(nil), // 1: qotd.GetResp } var file_qotd_proto_depIdxs = []int32{ 0, // 0: qotd.QOTD.GetQOTD:input_type -> qotd.GetReq 1, // 1: qotd.QOTD.GetQOTD:output_type -> qotd.GetResp 1, // [1:2] is the sub-list for method output_type 0, // [0:1] is the sub-list for method input_type 0, // [0:0] is the sub-list for extension type_name 0, // [0:0] is the sub-list for extension extendee 0, // [0:0] is the sub-list for field type_name } func init() { file_qotd_proto_init() } func file_qotd_proto_init() { if File_qotd_proto != nil { return } if !protoimpl.UnsafeEnabled { file_qotd_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*GetReq); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_qotd_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*GetResp); 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_qotd_proto_rawDesc, NumEnums: 0, NumMessages: 2, NumExtensions: 0, NumServices: 1, }, GoTypes: file_qotd_proto_goTypes, DependencyIndexes: file_qotd_proto_depIdxs, MessageInfos: file_qotd_proto_msgTypes, }.Build() File_qotd_proto = out.File file_qotd_proto_rawDesc = nil file_qotd_proto_goTypes = nil file_qotd_proto_depIdxs = nil } ================================================ FILE: chapter/6/grpc/proto/qotd.proto ================================================ syntax = "proto3"; package qotd; option go_package = "github.com/[repo]/proto/qotd"; message GetReq { string author = 1; } message GetResp { string author = 1; string quote = 2; } service QOTD { rpc GetQOTD(GetReq) returns (GetResp) {}; } ================================================ FILE: chapter/6/grpc/proto/qotd_grpc.pb.go ================================================ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. package qotd 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.32.0 or later. const _ = grpc.SupportPackageIsVersion7 // QOTDClient is the client API for QOTD 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 QOTDClient interface { GetQOTD(ctx context.Context, in *GetReq, opts ...grpc.CallOption) (*GetResp, error) } type qOTDClient struct { cc grpc.ClientConnInterface } func NewQOTDClient(cc grpc.ClientConnInterface) QOTDClient { return &qOTDClient{cc} } func (c *qOTDClient) GetQOTD(ctx context.Context, in *GetReq, opts ...grpc.CallOption) (*GetResp, error) { out := new(GetResp) err := c.cc.Invoke(ctx, "/qotd.QOTD/GetQOTD", in, out, opts...) if err != nil { return nil, err } return out, nil } // QOTDServer is the server API for QOTD service. // All implementations must embed UnimplementedQOTDServer // for forward compatibility type QOTDServer interface { GetQOTD(context.Context, *GetReq) (*GetResp, error) mustEmbedUnimplementedQOTDServer() } // UnimplementedQOTDServer must be embedded to have forward compatible implementations. type UnimplementedQOTDServer struct { } func (UnimplementedQOTDServer) GetQOTD(context.Context, *GetReq) (*GetResp, error) { return nil, status.Errorf(codes.Unimplemented, "method GetQOTD not implemented") } func (UnimplementedQOTDServer) mustEmbedUnimplementedQOTDServer() {} // UnsafeQOTDServer may be embedded to opt out of forward compatibility for this service. // Use of this interface is not recommended, as added methods to QOTDServer will // result in compilation errors. type UnsafeQOTDServer interface { mustEmbedUnimplementedQOTDServer() } func RegisterQOTDServer(s grpc.ServiceRegistrar, srv QOTDServer) { s.RegisterService(&QOTD_ServiceDesc, srv) } func _QOTD_GetQOTD_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(GetReq) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(QOTDServer).GetQOTD(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: "/qotd.QOTD/GetQOTD", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(QOTDServer).GetQOTD(ctx, req.(*GetReq)) } return interceptor(ctx, in, info, handler) } // QOTD_ServiceDesc is the grpc.ServiceDesc for QOTD service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) var QOTD_ServiceDesc = grpc.ServiceDesc{ ServiceName: "qotd.QOTD", HandlerType: (*QOTDServer)(nil), Methods: []grpc.MethodDesc{ { MethodName: "GetQOTD", Handler: _QOTD_GetQOTD_Handler, }, }, Streams: []grpc.StreamDesc{}, Metadata: "qotd.proto", } ================================================ FILE: chapter/6/grpc/qotd.go ================================================ package main import ( "flag" "log" "github.com/PacktPublishing/Go-for-DevOps/chapter/6/grpc/server" ) var addr = flag.String("addr", "127.0.0.1:80", "The address to run on.") func main() { flag.Parse() s, err := server.New(*addr) if err != nil { panic(err) } done := make(chan error, 1) log.Println("Starting server at: ", *addr) go func() { defer close(done) done <- s.Start() }() err = <-done log.Println("Server exited with error: ", err) } ================================================ FILE: chapter/6/grpc/server/server.go ================================================ package server import ( "context" "fmt" "math/rand" "net" "sync" "google.golang.org/grpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" pb "github.com/PacktPublishing/Go-for-DevOps/chapter/6/grpc/proto" ) type API struct { pb.UnimplementedQOTDServer addr string quotes map[string][]string mu sync.Mutex grpcServer *grpc.Server } func New(addr string) (*API, error) { var opts []grpc.ServerOption a := &API{ addr: addr, quotes: map[string][]string{ "Mark Twain": { "History doesn't repeat itself, but it does rhyme", "Lies, damned lies, and statistics", "Golf is a good walk spoiled", }, "Benjamin Franklin": { "Tell me and I forget. Teach me and I remember. Involve me and I learn", "I didn't fail the test. I just found 100 ways to do it wrong", }, "Eleanor Roosevelt": { "The future belongs to those who believe in the beauty of their dreams", }, }, grpcServer: grpc.NewServer(opts...), } a.grpcServer.RegisterService(&pb.QOTD_ServiceDesc, a) return a, nil } func (a *API) Start() error { a.mu.Lock() defer a.mu.Unlock() lis, err := net.Listen("tcp", a.addr) if err != nil { return err } return a.grpcServer.Serve(lis) } func (a *API) Stop() { a.mu.Lock() defer a.mu.Unlock() a.grpcServer.Stop() } func (a *API) GetQOTD(ctx context.Context, req *pb.GetReq) (*pb.GetResp, error) { var ( author string quotes []string ) if req.Author == "" { for author, quotes = range a.quotes { break } } else { author = req.Author var ok bool quotes, ok = a.quotes[req.Author] if !ok { return nil, status.Error(codes.NotFound, fmt.Sprintf("author %q not found", req.Author)) } } return &pb.GetResp{ Author: author, Quote: quotes[rand.Intn(len(quotes))], }, nil } ================================================ FILE: chapter/7/cobra/app/LICENSE ================================================ The MIT License (MIT) Copyright © 2021 John Doak Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: chapter/7/cobra/app/cmd/get.go ================================================ /* Copyright © 2021 John Doak Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ package cmd import ( "encoding/json" "fmt" "os" "github.com/PacktPublishing/Go-for-DevOps/chapter/6/grpc/client" "github.com/spf13/cobra" "github.com/spf13/pflag" ) // getCmd represents the get command var getCmd = &cobra.Command{ Use: "get", Short: "Fetches a quote of the day from the QOTD server", Long: `This command allows you to fetch a quote of the day from our QOTD server we designed in our chapter on gRPC. This command defaults to a production server (which doesn't exist). This can be changed to the devlopement server (which doesn't exist) using --dev or to a specific address with --addr . Example usage for a random author: qotd get Example usage for a specific author: qotd get --author="mark twain" Example usage using a 127.0.0.1 for the server: qotd get -addr=127.0.0.1:80 -author="mark twain" `, Run: func(cmd *cobra.Command, args []string) { const devAddr = "127.0.0.1:3450" fs := cmd.Flags() addr := mustString(fs, "addr") if mustBool(fs, "dev") { addr = devAddr } c, err := client.New(addr) if err != nil { fmt.Println("error: ", err) os.Exit(1) } a, q, err := c.QOTD(cmd.Context(), mustString(fs, "author")) if err != nil { fmt.Println("error: ", err) os.Exit(1) } switch { case mustBool(fs, "json"): b, err := json.Marshal( struct { Author string Quote string }{a, q}, ) if err != nil { panic(err) } fmt.Printf("%s\n", b) default: fmt.Println("Author: ", a) fmt.Println("Quote: ", q) } }, } func mustString(fs *pflag.FlagSet, name string) string { v, err := fs.GetString(name) if err != nil { panic(err) } return v } func mustBool(fs *pflag.FlagSet, name string) bool { v, err := fs.GetBool(name) if err != nil { panic(err) } return v } func init() { rootCmd.AddCommand(getCmd) getCmd.Flags().BoolP("dev", "d", false, "Uses the dev server instead of prod") getCmd.Flags().String("addr", "127.0.0.1:80", "Set the QOTD server to use, defaults to production") getCmd.Flags().StringP("author", "a", "", "Specify the specific author to get a quote for") getCmd.Flags().Bool("json", false, "Output is in JSON format") } ================================================ FILE: chapter/7/cobra/app/cmd/root.go ================================================ /* Copyright © 2021 John Doak Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ package cmd import ( "context" "fmt" "github.com/spf13/cobra" "os" "github.com/spf13/viper" ) var cfgFile string // rootCmd represents the base command when called without any subcommands var rootCmd = &cobra.Command{ Use: "app", Short: "A brief description of your application", Long: `A longer description that spans multiple lines and likely contains examples and usage of using your application. For example: Cobra is a CLI library for Go that empowers applications. This application is a tool to generate the needed files to quickly create a Cobra application.`, // Uncomment the following line if your bare application // has an action associated with it: // Run: func(cmd *cobra.Command, args []string) { }, } // Execute adds all child commands to the root command and sets flags appropriately. // This is called by main.main(). It only needs to happen once to the rootCmd. func Execute(ctx context.Context) { cobra.CheckErr(rootCmd.ExecuteContext(ctx)) } func init() { cobra.OnInitialize(initConfig) // Here you will define your flags and configuration settings. // Cobra supports persistent flags, which, if defined here, // will be global for your application. rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.app.yaml)") // Cobra also supports local flags, which will only run // when this action is called directly. rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") } // initConfig reads in config file and ENV variables if set. func initConfig() { if cfgFile != "" { // Use config file from the flag. viper.SetConfigFile(cfgFile) } else { // Find home directory. home, err := os.UserHomeDir() cobra.CheckErr(err) // Search config in home directory with name ".app" (without extension). viper.AddConfigPath(home) viper.SetConfigType("yaml") viper.SetConfigName(".app") } viper.AutomaticEnv() // read in environment variables that match // If a config file is found, read it in. if err := viper.ReadInConfig(); err == nil { fmt.Fprintln(os.Stderr, "Using config file:", viper.ConfigFileUsed()) } } ================================================ FILE: chapter/7/cobra/app/main.go ================================================ /* Copyright © 2021 John Doak Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ package main import ( "context" "github.com/PacktPublishing/Go-for-DevOps/chapter/7/cobra/app/cmd" ) func main() { ctx := context.Background() cmd.Execute(ctx) } ================================================ FILE: chapter/7/filter_errors/main.go ================================================ package main import ( "bufio" "fmt" "log" "os" "regexp" ) var errRE = regexp.MustCompile(`(?i)error`) func main() { var s *bufio.Scanner switch len(os.Args) { case 1: log.Println("No file specified, using stdin") s = bufio.NewScanner(os.Stdin) case 2: f, err := os.Open(os.Args[1]) if err != nil { log.Println(err) os.Exit(1) } s = bufio.NewScanner(f) default: log.Println("too many arguments provided") os.Exit(1) } for s.Scan() { line := s.Bytes() if errRE.Match(line) { fmt.Printf("%s\n", line) } } if err := s.Err(); err != nil { log.Println("Error: ", err) os.Exit(1) } } ================================================ FILE: chapter/7/signals/main.go ================================================ package main import ( "context" "fmt" "log" "os" "os/signal" "path/filepath" "strconv" "sync" "syscall" "time" ) func main() { tmpFiles, err := os.MkdirTemp("", "myApp_*") if err != nil { log.Println("error creating temp file directory: ", err) os.Exit(1) } fmt.Println("temp files located at: ", tmpFiles) ctx, cancel := context.WithCancel(context.Background()) wg := &sync.WaitGroup{} wg.Add(1) sigHandler, stopSig := newSignaling() sigHandler.register( func() { cleanup(cancel, wg, tmpFiles) os.Exit(1) }, syscall.SIGINT, syscall.SIGTERM, ) sigHandler.register( func() { cleanup(cancel, wg, tmpFiles) panic("SIGQUIT called") }, syscall.SIGQUIT, ) go func() { defer stopSig() defer wg.Done() createFiles(ctx, tmpFiles) }() sigHandler.handle() fmt.Println("Done") } func createFiles(ctx context.Context, tmpFiles string) { for i := 0; i < 30; i++ { if err := ctx.Err(); err != nil { return } _, err := os.Create(filepath.Join(tmpFiles, strconv.Itoa(i))) if err != nil { panic(err) } fmt.Println("Created file: ", i) time.Sleep(1 * time.Second) } } type signaling struct { ctx context.Context notify chan os.Signal handlers map[os.Signal]func() } func newSignaling() (sig signaling, stop context.CancelFunc) { ctx, cancel := context.WithCancel(context.Background()) return signaling{ ctx: ctx, notify: make(chan os.Signal, 1), handlers: map[os.Signal]func(){}, }, cancel } func (s signaling) register(f func(), sigs ...os.Signal) { signal.Notify( s.notify, sigs..., ) for _, sig := range sigs { s.handlers[sig] = f } } func (s signaling) handle() { for { select { case <-s.ctx.Done(): return case sig := <-s.notify: f, ok := s.handlers[sig] if !ok { log.Printf("unknown signal received: %v", sig) continue } f() } } } func cleanup(cancel context.CancelFunc, wg *sync.WaitGroup, tmpFiles string) { cancel() wg.Wait() if err := os.RemoveAll(tmpFiles); err != nil { fmt.Println("problem doing file cleanup: ", err) return } fmt.Println("cleanup done") } ================================================ FILE: chapter/8/agent/README.md ================================================ # Agent - A System Agent Example ## Introduction This is a an example of a System Agent from the "Go For DevOps" book by John Doak, David Justice and Sarah Murphy. The agent defined here, which can be run by compiling and running the `agent/agent.go`program runs a system agent that exports system stats on port 8081 and runs a gRPC service that allows installing or removing software packages contained in a ".zip" file. These software packages are run in a container and setup on systemd within the user space that the agent runs on (it does not setup system level services). ## Running the agent You can run the agent by compiling and deploying the "agent.go" file on a Linux box and then starting it. This agent is currently only Linux compatible. ## Running a client There is a Cobra client located in `agent/client/cli` that you can compile and run from any device (saying that you compile it for the target platform). The Cobra client leverages a Go client at `agent/client` that can be used to programically access an endpoint (or set of endpoints to deploy on multiple machines at once). We have included a sample application for you to install and run on the remote side, located in `agent/cli/sample/helloweb.zip`. ## BEWARE While this code runs and has been tested on an Ubuntu instance running on Azure, milleage may vary depending on systemd version and Linux distro. In addition, this has not been setup in a production quality way. For example, you will notice a lack of unit tests and integration tests. We have also not vetted all the containerized security parameters with Linux Kernel security experts. Finally, this misses features that should be in a production quality version, such as cgroup integration, passing of capabilities and forced network bindings. This is for an example only! ================================================ FILE: chapter/8/agent/agent.go ================================================ /* The agent application is run on a remote system under a user you will connect to via RPC and issue commands. The applications installed will containerized within that user's space. Even though they are in a container, it is best to use a non-priviledged user. This also exports sytsem stats on port :8081. There is no security on this web export, just an FYI if this system is exposed directly to the internet. */ package main import ( "log" "net/http" "github.com/PacktPublishing/Go-for-DevOps/chapter/8/agent/internal/service" ) func main() { agent, err := service.New() if err != nil { panic(err) } go func() { err := http.ListenAndServe(":8081", nil) panic(err) }() log.Println("Service starting...") if err := agent.Start(); err != nil { panic(err) } } ================================================ FILE: chapter/8/agent/bin/README.md ================================================ Contains agent binaries for platforms/architectures. Running build.sh from this directory will build all current binaries. ================================================ FILE: chapter/8/agent/bin/build.sh ================================================ #!/bin/sh cd .. env GOOS=linux GOARCH=amd64 go build -o bin/linux_amd64/agent ================================================ FILE: chapter/8/agent/bin/linux_amd64/agent ================================================ [File too large to display: 13.8 MB] ================================================ FILE: chapter/8/agent/buf.work ================================================ version: v1 directories: - proto ================================================ FILE: chapter/8/agent/client/cli/LICENSE ================================================ The MIT License (MIT) Copyright © 2021 John Doak Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: chapter/8/agent/client/cli/cmd/auth.go ================================================ package cmd import ( "net" "os" "golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh/agent" ) func agentAuth() (ssh.AuthMethod, error) { conn, err := net.Dial("unix", os.Getenv("SSH_AUTH_SOCK")) if err != nil { return nil, err } client := agent.NewClient(conn) return ssh.PublicKeysCallback(client.Signers), nil } func publicKey(privateKeyFile string) (ssh.AuthMethod, error) { k, err := os.ReadFile(privateKeyFile) if err != nil { return nil, err } signer, err := ssh.ParsePrivateKey(k) if err != nil { return nil, err } return ssh.PublicKeys(signer), nil } func getAuthFromFlags() (ssh.AuthMethod, error) { if keyFile != "" { return publicKey(keyFile) } return agentAuth() } ================================================ FILE: chapter/8/agent/client/cli/cmd/install.go ================================================ /* Copyright © 2021 John Doak Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ package cmd import ( "context" "fmt" "log" "os" "strings" "github.com/PacktPublishing/Go-for-DevOps/chapter/8/agent/client" "github.com/spf13/cobra" "golang.org/x/crypto/ssh" pb "github.com/PacktPublishing/Go-for-DevOps/chapter/8/agent/proto" ) // installCmd represents the install command var installCmd = &cobra.Command{ Use: "install [remote endpoint] [package name] [package local path(.zip file] [binary to start]", Short: "Installs an application in a remote container and starts it", Long: `Install connects to the system agents giving a package name, a package file, and a binary within that package to run. It will connect to the system, unpack the contents into a Linux container and execute the binary using systemd. An usage example: cli install 22.47.60.3:22 helloworld ./apps/packages/helloworld.zip helloworld `, Run: func(cmd *cobra.Command, args []string) { auth, err := getAuthFromFlags() if err != nil { log.Println("Error: failed to get SSH authorizaion: ", err) os.Exit(1) } if !strings.HasSuffix(args[2], ".zip") { log.Println("Error: the package file must end in .zip, got: ", args[2]) os.Exit(1) } b, err := os.ReadFile(args[2]) if err != nil { log.Println("Error: could not read package file: ", err) os.Exit(1) } c, err := client.New( args[0], []ssh.AuthMethod{auth}, ) if err != nil { log.Println("Error: problem connecting to agent: ", err) os.Exit(1) } _, err = c.Install( context.Background(), &pb.InstallReq{ Name: args[1], Package: b, Binary: args[3], }, ) if err != nil { log.Println("Error: ", err) os.Exit(1) } fmt.Println("Done") }, } func init() { rootCmd.AddCommand(installCmd) // Here you will define your flags and configuration settings. // Cobra supports Persistent Flags which will work for this command // and all subcommands, e.g.: // installCmd.PersistentFlags().String("foo", "", "A help for foo") // Cobra supports local flags which will only run when this command // is called directly, e.g.: // installCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") } ================================================ FILE: chapter/8/agent/client/cli/cmd/remove.go ================================================ /* Copyright © 2021 John Doak Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ package cmd import ( "context" "log" "os" "github.com/PacktPublishing/Go-for-DevOps/chapter/8/agent/client" "github.com/spf13/cobra" "golang.org/x/crypto/ssh" pb "github.com/PacktPublishing/Go-for-DevOps/chapter/8/agent/proto" ) // removeCmd represents the remove command var removeCmd = &cobra.Command{ Use: "remove [remote endpoint] [package name]", Short: "Remove an application installed by the system agent.", Long: `Remove an application installed by the system agent." An usage example: cli remove 22.47.60.3:22 helloworld `, Run: func(cmd *cobra.Command, args []string) { auth, err := getAuthFromFlags() if err != nil { log.Println("Error: failed to get SSH authorizaion: ", err) os.Exit(1) } c, err := client.New( args[0], []ssh.AuthMethod{auth}, ) _, err = c.Remove( context.Background(), &pb.RemoveReq{ Name: args[1], }, ) if err != nil { log.Println("Error: ", err) os.Exit(1) } }, } func init() { rootCmd.AddCommand(removeCmd) // Here you will define your flags and configuration settings. // Cobra supports Persistent Flags which will work for this command // and all subcommands, e.g.: // removeCmd.PersistentFlags().String("foo", "", "A help for foo") // Cobra supports local flags which will only run when this command // is called directly, e.g.: // removeCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") } ================================================ FILE: chapter/8/agent/client/cli/cmd/root.go ================================================ /* Copyright © 2021 John Doak Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ package cmd import ( "fmt" "os" "github.com/spf13/cobra" "github.com/spf13/viper" ) var ( cfgFile string endpoint string keyFile string ) // rootCmd represents the base command when called without any subcommands var rootCmd = &cobra.Command{ Use: "cli", Short: "This application is used to interact with a remote system agent", Long: `A longer description that spans multiple lines and likely contains examples and usage of using your application. For example: Cobra is a CLI library for Go that empowers applications. This application is a tool to generate the needed files to quickly create a Cobra application.`, // Uncomment the following line if your bare application // has an action associated with it: // Run: func(cmd *cobra.Command, args []string) { }, } // Execute adds all child commands to the root command and sets flags appropriately. // This is called by main.main(). It only needs to happen once to the rootCmd. func Execute() { cobra.CheckErr(rootCmd.Execute()) } func init() { cobra.OnInitialize(initConfig) // Here you will define your flags and configuration settings. // Cobra supports persistent flags, which, if defined here, // will be global for your application. rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.cli.yaml)") rootCmd.PersistentFlags().StringVar(&endpoint, "endpoint", "", "the host:port of the remote agent") rootCmd.PersistentFlags().StringVar(&keyFile, "key", "", "a private key file(pem) path that stores the SSH private key to use") // Cobra also supports local flags, which will only run // when this action is called directly. rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") } // initConfig reads in config file and ENV variables if set. func initConfig() { if cfgFile != "" { // Use config file from the flag. viper.SetConfigFile(cfgFile) } else { // Find home directory. home, err := os.UserHomeDir() cobra.CheckErr(err) // Search config in home directory with name ".cli" (without extension). viper.AddConfigPath(home) viper.SetConfigType("yaml") viper.SetConfigName(".cli") } viper.AutomaticEnv() // read in environment variables that match // If a config file is found, read it in. if err := viper.ReadInConfig(); err == nil { fmt.Fprintln(os.Stderr, "Using config file:", viper.ConfigFileUsed()) } } ================================================ FILE: chapter/8/agent/client/cli/main.go ================================================ /* Copyright © 2021 John Doak Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ package main import "github.com/PacktPublishing/Go-for-DevOps/chapter/8/agent/client/cli/cmd" func main() { cmd.Execute() } ================================================ FILE: chapter/8/agent/client/cli/sample/helloweb/helloweb.go ================================================ package main import ( "flag" "fmt" "log" "net/http" ) var port = flag.Int("port", 8080, "The port to run on") func main() { flag.Parse() http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Hello web") }) log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", *port), nil)) } ================================================ FILE: chapter/8/agent/client/client.go ================================================ /* Package client provides a client to the system agent that uses SSH and unix sockets to make the connection. The SSH forwarding is based on code from: https://stackoverflow.com/questions/21417223/simple-ssh-port-forward-in-golang */ package client import ( "context" "net" "os" "path/filepath" "time" "github.com/johnsiilver/serveonssh" "golang.org/x/crypto/ssh" "google.golang.org/grpc" pb "github.com/PacktPublishing/Go-for-DevOps/chapter/8/agent/proto" ) type Client struct { user string endpoint string conn *grpc.ClientConn client pb.AgentClient p serveonssh.Proxy } // New creates a new Client that connects to a remote endpoint via SSH and then // uses that connection to dial into a domain socket the agent is using. The // gRPC client actually uses a domain socket on this side which is then forwarded // over SSH. endpoint is the host:port of the remote endpoint. func New(endpoint string, auth []ssh.AuthMethod) (*Client, error) { config := &ssh.ClientConfig{ User: os.Getenv("USER"), Auth: auth, Timeout: 5 * time.Second, HostKeyCallback: ssh.InsecureIgnoreHostKey(), } remoteSocket := filepath.Join("/home", config.User, "/sa/socket/sa.sock") p, err := serveonssh.New(endpoint, remoteSocket, config) if err != nil { return nil, err } opts := []grpc.DialOption{ grpc.WithInsecure(), grpc.WithDialer(func(addr string, timeout time.Duration) (net.Conn, error) { return p.Dialer()() }), } conn, err := grpc.Dial("not needed", opts...) if err != nil { return nil, err } return &Client{ endpoint: endpoint, conn: conn, client: pb.NewAgentClient(conn), p: p, }, nil } func (c *Client) Close() error { c.conn.Close() c.p.Close() return nil } func (c *Client) Install(ctx context.Context, req *pb.InstallReq) (*pb.InstallResp, error) { return c.client.Install(ctx, req) } func (c *Client) Remove(ctx context.Context, req *pb.RemoveReq) (*pb.RemoveResp, error) { return c.client.Remove(ctx, req) } ================================================ FILE: chapter/8/agent/internal/service/service.go ================================================ // Package service contains the Agent that provides control access to the system // and system stats. package service import ( "archive/zip" "bytes" "context" "expvar" "fmt" "io" "log" "net" "os" "os/user" "path/filepath" "sync" "sync/atomic" "time" "github.com/coreos/go-systemd/v22/dbus" "google.golang.org/grpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" linuxproc "github.com/c9s/goprocinfo/linux" pb "github.com/PacktPublishing/Go-for-DevOps/chapter/8/agent/proto" ) const ( // pkgDir is the directory in the Agent user's home where we are installing and // running packages. A more secure version would be to have the agent do this // in individual user directories that match some user on all machines. However // this is for illustration purposes only. pkgDir = "sa/packages/" serviceExt = ".service" ) // Agent provides a system agent service that runs a gRPC service for doing // application installs and an HTTP service for relaying stats. type Agent struct { pb.UnimplementedAgentServer dbus *dbus.Conn user string // mu protects locks. mu sync.Mutex locks map[string]*sync.Mutex cpuData, memData atomic.Value } // New creates a new Agent instance. func New() (*Agent, error) { conn, err := dbus.NewUserConnection() if err != nil { return nil, fmt.Errorf("problem connecting to systemd: %w", err) } u, err := user.Current() if err != nil { return nil, err } return &Agent{ dbus: conn, user: u.Username, locks: map[string]*sync.Mutex{}, }, nil } // Start starts the agent. As the agent is not intended to ever stop, this has // no Stop(). This blocks unless there is a problem. func (a *Agent) Start() error { var sockAddr = filepath.Join("/home", a.user, "/sa/socket/sa.sock") if err := os.MkdirAll(filepath.Dir(sockAddr), 0700); err != nil { return fmt.Errorf("could not create socket dir path: %w", err) } // Remove old socket file if it exists. os.Remove(sockAddr) if err := a.perfLoop(); err != nil { return err } l, err := net.Listen("unix", sockAddr) if err != nil { return fmt.Errorf("could not connect to socket: %w", err) } var opts []grpc.ServerOption grpcServer := grpc.NewServer(opts...) pb.RegisterAgentServer(grpcServer, a) return grpcServer.Serve(l) } // Install implements our gRPC Install RPC. func (a *Agent) Install(ctx context.Context, req *pb.InstallReq) (*pb.InstallResp, error) { if err := req.Validate(); err != nil { return nil, status.Error(codes.InvalidArgument, err.Error()) } a.lock(req.Name) defer a.unlock(req.Name, false) loc, err := a.unpack(req.Name, req.Package) if err != nil { return nil, err } if err := a.migrate(req, loc); err != nil { return nil, err } if err := a.startProgram(ctx, req.Name); err != nil { return nil, err } return &pb.InstallResp{}, nil } func (a *Agent) Remove(ctx context.Context, req *pb.RemoveReq) (*pb.RemoveResp, error) { a.lock(req.Name) defer a.unlock(req.Name, true) if err := a.stopProgram(ctx, req.Name); err != nil { return nil, err } if err := rmUnitFile(a.dbus, a.user, req); err != nil { return nil, err } return &pb.RemoveResp{}, nil } // lock locks a named mutex. func (a *Agent) lock(name string) { a.mu.Lock() v, ok := a.locks[name] if !ok { v = &sync.Mutex{} a.locks[name] = v } a.mu.Unlock() v.Lock() } // unlock unlocks a named mutex. func (a *Agent) unlock(name string, del bool) { a.mu.Lock() v, ok := a.locks[name] if !ok { return } if del { delete(a.locks, name) } a.mu.Unlock() v.Unlock() } // unpack unpacks a zipfile and stores in in a temporary directory that is returned. func (a *Agent) unpack(name string, zipFile []byte) (string, error) { dir, err := os.MkdirTemp("", fmt.Sprintf("sa_install_%s_*", name)) if err != nil { return "", err } r, err := zip.NewReader(bytes.NewReader(zipFile), int64(len(zipFile))) if err != nil { return "", err } // Iterate through the files in the archive, // printing some of their contents. for _, f := range r.File { if err := a.writeFile(f, dir); err != nil { return "", err } } return dir, nil } // writeFile writes a zip file under the root directory dir. func (a *Agent) writeFile(z *zip.File, dir string) error { if z.FileInfo().IsDir() { err := os.Mkdir( filepath.Join(dir, filepath.FromSlash(z.Name)), z.Mode(), ) return err } rc, err := z.Open() if err != nil { return fmt.Errorf("could not open file %q: %w", z.Name, err) } defer rc.Close() nf, err := os.OpenFile( filepath.Join(dir, filepath.FromSlash(z.Name)), os.O_CREATE|os.O_WRONLY, z.Mode(), ) if err != nil { return fmt.Errorf("could not open file in temp diretory: %w", err) } defer nf.Close() _, err = io.Copy(nf, rc) if err != nil { return fmt.Errorf("file copy error: %w", err) } return nil } // migrate shuts down any existing job that is running and migrates our files // from the temp location to the final location. func (a *Agent) migrate(req *pb.InstallReq, loc string) error { units, err := a.dbus.ListUnitsByNames([]string{req.Name + serviceExt}) if err == nil && units[0].JobId != 0 { result := make(chan string, 1) _, err := a.dbus.StopUnit(req.Name+serviceExt, "replace", result) if err != nil { return fmt.Errorf("migate could not stop the service: %w", err) } switch v := <-result; v { case "done": default: return fmt.Errorf("systemd StopUnit() returned %q", v) } //a.conn.KillUnit(name, 15) } if err := writeUnitFile(a.dbus, a.user, req); err != nil { return fmt.Errorf("could not write the unit file: %w", err) } p := filepath.Join("/home", a.user, pkgDir) if _, err := os.Stat(p); err == nil { os.RemoveAll(p) } if err := os.Rename(loc, p); err != nil { return err } return nil } // startProgram starts our program under systemd. func (a *Agent) startProgram(ctx context.Context, name string) error { // EnableUnitFiles(files []string, runtime bool, force bool) (bool, []EnableUnitFileChange, error) result := make(chan string, 1) id, err := a.dbus.StartUnit(name+serviceExt, "replace", result) if err != nil { return fmt.Errorf("could not start the unit: %w", err) } switch v := <-result; v { case "done": log.Printf("new service(%s) is done: %v", name+serviceExt, id) default: return fmt.Errorf("systemd StartUnit() returned %q", v) } time.Sleep(30 * time.Second) statuses, err := a.dbus.ListUnitsByNames([]string{name + serviceExt}) if err != nil { return fmt.Errorf("could not find unit after start: %s", err) } if len(statuses) != 1 { return fmt.Errorf("could not find unit after start") } status := statuses[0] switch { case status.ActiveState != "active": return fmt.Errorf("program is not in active state") case status.SubState != "running": return fmt.Errorf("program is not in running state") case status.LoadState != "loaded": return fmt.Errorf("program is not in loaded state") } return nil } // stopProgram stops a program under systemd. func (a *Agent) stopProgram(ctx context.Context, name string) error { result := make(chan string, 1) _, err := a.dbus.StopUnit(name+serviceExt, "replace", result) if err != nil { return fmt.Errorf("could not stop the service: %w", err) } switch v := <-result; v { case "done": default: return fmt.Errorf("systemd StopUnit() returned %q", v) } return nil } // collectCPU collects our CPU stats and stores them in .cpuData. func (a *Agent) collectCPU(resolution int32) error { stat, err := linuxproc.ReadStat("/proc/stat") if err != nil { return err } v := &pb.CPUPerfs{ ResolutionSecs: resolution, UnixTimeNano: time.Now().UnixNano(), } for _, p := range stat.CPUStats { c := &pb.CPUPerf{ Id: p.Id, User: int32(p.User), System: int32(p.System), Idle: int32(p.Idle), IoWait: int32(p.IOWait), Irq: int32(p.IRQ), } v.Cpu = append(v.Cpu, c) } a.cpuData.Store(v) return nil } // collectMem collects our memory stats and stores them in .memData. func (a *Agent) collectMem(resolution int32) error { mem, err := linuxproc.ReadMemInfo("/proc/meminfo") if err != nil { return err } a.memData.Store( &pb.MemPerf{ ResolutionSecs: resolution, UnixTimeNano: time.Now().UnixNano(), Total: int32(mem.MemTotal), Free: int32(mem.MemFree), Avail: int32(mem.MemAvailable), }, ) return nil } // perfLoop grabs data every 10 seconds + gather time and stores it. // It also does all registration of these variables with expvar. // This should only be called once on systemAgent start. func (a *Agent) perfLoop() error { const resolutionSecs = 10 if err := a.collectCPU(resolutionSecs); err != nil { return err } if err := a.collectMem(resolutionSecs); err != nil { return err } expvar.Publish( "system-cpu", expvar.Func( func() interface{} { return a.cpuData.Load().(*pb.CPUPerfs) }, ), ) expvar.Publish( "system-mem", expvar.Func( func() interface{} { return a.memData.Load().(*pb.MemPerf) }, ), ) go func() { for { time.Sleep(resolutionSecs * time.Second) if err := a.collectCPU(resolutionSecs); err != nil { log.Println(err) } if err := a.collectMem(resolutionSecs); err != nil { log.Println(err) } } }() return nil } ================================================ FILE: chapter/8/agent/internal/service/unit_file.go ================================================ package service import ( "errors" "fmt" "io/fs" "os" "os/user" "path/filepath" "strings" "sync" "text/template" "github.com/coreos/go-systemd/v22/dbus" pb "github.com/PacktPublishing/Go-for-DevOps/chapter/8/agent/proto" ) var systemdUnits = "" func init() { u, err := user.Current() if err != nil { panic(err) } systemdUnits = filepath.Join("/home", u.Username, ".config/systemd/user") if err := os.MkdirAll(systemdUnits, 0700); err != nil { panic(err) } } var unitTmpl = template.Must( template.New("unit").Parse( ` [Unit] Description={{.Desc}} StartLimitIntervalSec=500 StartLimitBurst=5 [Service] PrivateUsers=true PrivateDevices=true ReadOnlyPaths=/ RootDirectory={{.RootPath}} ReadWritePaths={{.RootPath}} SecureBits=noroot Restart=on-failure RestartSec=5s ExecStart={{.BinaryPath}} {{.Args}} [Install] WantedBy=multi-user.target `)) type unitArgs struct { Desc string BinaryPath string RootPath string Args string } var wufMu sync.Mutex func writeUnitFile(dbusConn *dbus.Conn, user string, req *pb.InstallReq) error { a := unitArgs{ Desc: req.Name, BinaryPath: filepath.Join("/", req.Binary), //BinaryPath: filepath.Join("/home", user, pkgDir, req.Name, req.Binary), RootPath: filepath.Join("/home", user, pkgDir, req.Name), Args: strings.Join(req.Args, " "), } unit := req.Name + ".service" p := filepath.Join(systemdUnits, unit) f, err := os.OpenFile(p, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0600) if err != nil { return fmt.Errorf("could not create systemd unit file: %w", err) } if err := unitTmpl.Execute(f, a); err != nil { f.Close() return err } f.Close() // Let's only try to reload the daemon one at a time. wufMu.Lock() defer wufMu.Unlock() return dbusConn.Reload() } func rmUnitFile(dbusConn *dbus.Conn, user string, req *pb.RemoveReq) error { unit := req.Name + ".service" p := filepath.Join(systemdUnits, unit) if err := os.Remove(p); err != nil { if errors.Is(err.(*os.PathError), fs.ErrNotExist) { return nil } return err } // Let's only try to reload the daemon one at a time. wufMu.Lock() defer wufMu.Unlock() return dbusConn.Reload() } ================================================ FILE: chapter/8/agent/proto/agent.pb.go ================================================ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.25.0-devel // protoc v3.18.0 // source: agent.proto package agent 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 InstallReq struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` Package []byte `protobuf:"bytes,2,opt,name=package,proto3" json:"package,omitempty"` Binary string `protobuf:"bytes,3,opt,name=binary,proto3" json:"binary,omitempty"` Args []string `protobuf:"bytes,4,rep,name=args,proto3" json:"args,omitempty"` } func (x *InstallReq) Reset() { *x = InstallReq{} if protoimpl.UnsafeEnabled { mi := &file_agent_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *InstallReq) String() string { return protoimpl.X.MessageStringOf(x) } func (*InstallReq) ProtoMessage() {} func (x *InstallReq) ProtoReflect() protoreflect.Message { mi := &file_agent_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 InstallReq.ProtoReflect.Descriptor instead. func (*InstallReq) Descriptor() ([]byte, []int) { return file_agent_proto_rawDescGZIP(), []int{0} } func (x *InstallReq) GetName() string { if x != nil { return x.Name } return "" } func (x *InstallReq) GetPackage() []byte { if x != nil { return x.Package } return nil } func (x *InstallReq) GetBinary() string { if x != nil { return x.Binary } return "" } func (x *InstallReq) GetArgs() []string { if x != nil { return x.Args } return nil } type InstallResp struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields } func (x *InstallResp) Reset() { *x = InstallResp{} if protoimpl.UnsafeEnabled { mi := &file_agent_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *InstallResp) String() string { return protoimpl.X.MessageStringOf(x) } func (*InstallResp) ProtoMessage() {} func (x *InstallResp) ProtoReflect() protoreflect.Message { mi := &file_agent_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 InstallResp.ProtoReflect.Descriptor instead. func (*InstallResp) Descriptor() ([]byte, []int) { return file_agent_proto_rawDescGZIP(), []int{1} } type RemoveReq struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` } func (x *RemoveReq) Reset() { *x = RemoveReq{} if protoimpl.UnsafeEnabled { mi := &file_agent_proto_msgTypes[2] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *RemoveReq) String() string { return protoimpl.X.MessageStringOf(x) } func (*RemoveReq) ProtoMessage() {} func (x *RemoveReq) ProtoReflect() protoreflect.Message { mi := &file_agent_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 RemoveReq.ProtoReflect.Descriptor instead. func (*RemoveReq) Descriptor() ([]byte, []int) { return file_agent_proto_rawDescGZIP(), []int{2} } func (x *RemoveReq) GetName() string { if x != nil { return x.Name } return "" } type RemoveResp struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields } func (x *RemoveResp) Reset() { *x = RemoveResp{} if protoimpl.UnsafeEnabled { mi := &file_agent_proto_msgTypes[3] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *RemoveResp) String() string { return protoimpl.X.MessageStringOf(x) } func (*RemoveResp) ProtoMessage() {} func (x *RemoveResp) ProtoReflect() protoreflect.Message { mi := &file_agent_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 RemoveResp.ProtoReflect.Descriptor instead. func (*RemoveResp) Descriptor() ([]byte, []int) { return file_agent_proto_rawDescGZIP(), []int{3} } type CPUPerfs struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields ResolutionSecs int32 `protobuf:"varint,1,opt,name=resolutionSecs,proto3" json:"resolutionSecs,omitempty"` UnixTimeNano int64 `protobuf:"varint,2,opt,name=unix_time_nano,json=unixTimeNano,proto3" json:"unix_time_nano,omitempty"` Cpu []*CPUPerf `protobuf:"bytes,3,rep,name=cpu,proto3" json:"cpu,omitempty"` } func (x *CPUPerfs) Reset() { *x = CPUPerfs{} if protoimpl.UnsafeEnabled { mi := &file_agent_proto_msgTypes[4] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *CPUPerfs) String() string { return protoimpl.X.MessageStringOf(x) } func (*CPUPerfs) ProtoMessage() {} func (x *CPUPerfs) ProtoReflect() protoreflect.Message { mi := &file_agent_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 CPUPerfs.ProtoReflect.Descriptor instead. func (*CPUPerfs) Descriptor() ([]byte, []int) { return file_agent_proto_rawDescGZIP(), []int{4} } func (x *CPUPerfs) GetResolutionSecs() int32 { if x != nil { return x.ResolutionSecs } return 0 } func (x *CPUPerfs) GetUnixTimeNano() int64 { if x != nil { return x.UnixTimeNano } return 0 } func (x *CPUPerfs) GetCpu() []*CPUPerf { if x != nil { return x.Cpu } return nil } type CPUPerf struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` User int32 `protobuf:"varint,2,opt,name=user,proto3" json:"user,omitempty"` System int32 `protobuf:"varint,3,opt,name=system,proto3" json:"system,omitempty"` Idle int32 `protobuf:"varint,4,opt,name=idle,proto3" json:"idle,omitempty"` IoWait int32 `protobuf:"varint,5,opt,name=io_wait,json=ioWait,proto3" json:"io_wait,omitempty"` Irq int32 `protobuf:"varint,6,opt,name=irq,proto3" json:"irq,omitempty"` } func (x *CPUPerf) Reset() { *x = CPUPerf{} if protoimpl.UnsafeEnabled { mi := &file_agent_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *CPUPerf) String() string { return protoimpl.X.MessageStringOf(x) } func (*CPUPerf) ProtoMessage() {} func (x *CPUPerf) ProtoReflect() protoreflect.Message { mi := &file_agent_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 CPUPerf.ProtoReflect.Descriptor instead. func (*CPUPerf) Descriptor() ([]byte, []int) { return file_agent_proto_rawDescGZIP(), []int{5} } func (x *CPUPerf) GetId() string { if x != nil { return x.Id } return "" } func (x *CPUPerf) GetUser() int32 { if x != nil { return x.User } return 0 } func (x *CPUPerf) GetSystem() int32 { if x != nil { return x.System } return 0 } func (x *CPUPerf) GetIdle() int32 { if x != nil { return x.Idle } return 0 } func (x *CPUPerf) GetIoWait() int32 { if x != nil { return x.IoWait } return 0 } func (x *CPUPerf) GetIrq() int32 { if x != nil { return x.Irq } return 0 } type MemPerf struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields ResolutionSecs int32 `protobuf:"varint,1,opt,name=resolutionSecs,proto3" json:"resolutionSecs,omitempty"` UnixTimeNano int64 `protobuf:"varint,2,opt,name=unix_time_nano,json=unixTimeNano,proto3" json:"unix_time_nano,omitempty"` Total int32 `protobuf:"varint,3,opt,name=total,proto3" json:"total,omitempty"` Free int32 `protobuf:"varint,4,opt,name=free,proto3" json:"free,omitempty"` Avail int32 `protobuf:"varint,5,opt,name=avail,proto3" json:"avail,omitempty"` } func (x *MemPerf) Reset() { *x = MemPerf{} if protoimpl.UnsafeEnabled { mi := &file_agent_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *MemPerf) String() string { return protoimpl.X.MessageStringOf(x) } func (*MemPerf) ProtoMessage() {} func (x *MemPerf) ProtoReflect() protoreflect.Message { mi := &file_agent_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 MemPerf.ProtoReflect.Descriptor instead. func (*MemPerf) Descriptor() ([]byte, []int) { return file_agent_proto_rawDescGZIP(), []int{6} } func (x *MemPerf) GetResolutionSecs() int32 { if x != nil { return x.ResolutionSecs } return 0 } func (x *MemPerf) GetUnixTimeNano() int64 { if x != nil { return x.UnixTimeNano } return 0 } func (x *MemPerf) GetTotal() int32 { if x != nil { return x.Total } return 0 } func (x *MemPerf) GetFree() int32 { if x != nil { return x.Free } return 0 } func (x *MemPerf) GetAvail() int32 { if x != nil { return x.Avail } return 0 } var File_agent_proto protoreflect.FileDescriptor var file_agent_proto_rawDesc = []byte{ 0x0a, 0x0b, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0c, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x22, 0x66, 0x0a, 0x0a, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c, 0x52, 0x65, 0x71, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x70, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x62, 0x69, 0x6e, 0x61, 0x72, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x62, 0x69, 0x6e, 0x61, 0x72, 0x79, 0x12, 0x12, 0x0a, 0x04, 0x61, 0x72, 0x67, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x04, 0x61, 0x72, 0x67, 0x73, 0x22, 0x0d, 0x0a, 0x0b, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x22, 0x1f, 0x0a, 0x09, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x52, 0x65, 0x71, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x0c, 0x0a, 0x0a, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x52, 0x65, 0x73, 0x70, 0x22, 0x81, 0x01, 0x0a, 0x08, 0x43, 0x50, 0x55, 0x50, 0x65, 0x72, 0x66, 0x73, 0x12, 0x26, 0x0a, 0x0e, 0x72, 0x65, 0x73, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x63, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0e, 0x72, 0x65, 0x73, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x63, 0x73, 0x12, 0x24, 0x0a, 0x0e, 0x75, 0x6e, 0x69, 0x78, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6e, 0x61, 0x6e, 0x6f, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x75, 0x6e, 0x69, 0x78, 0x54, 0x69, 0x6d, 0x65, 0x4e, 0x61, 0x6e, 0x6f, 0x12, 0x27, 0x0a, 0x03, 0x63, 0x70, 0x75, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x43, 0x50, 0x55, 0x50, 0x65, 0x72, 0x66, 0x52, 0x03, 0x63, 0x70, 0x75, 0x22, 0x84, 0x01, 0x0a, 0x07, 0x43, 0x50, 0x55, 0x50, 0x65, 0x72, 0x66, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x75, 0x73, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x75, 0x73, 0x65, 0x72, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x12, 0x12, 0x0a, 0x04, 0x69, 0x64, 0x6c, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x69, 0x64, 0x6c, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x69, 0x6f, 0x5f, 0x77, 0x61, 0x69, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x69, 0x6f, 0x57, 0x61, 0x69, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x69, 0x72, 0x71, 0x18, 0x06, 0x20, 0x01, 0x28, 0x05, 0x52, 0x03, 0x69, 0x72, 0x71, 0x22, 0x97, 0x01, 0x0a, 0x07, 0x4d, 0x65, 0x6d, 0x50, 0x65, 0x72, 0x66, 0x12, 0x26, 0x0a, 0x0e, 0x72, 0x65, 0x73, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x63, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0e, 0x72, 0x65, 0x73, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x63, 0x73, 0x12, 0x24, 0x0a, 0x0e, 0x75, 0x6e, 0x69, 0x78, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6e, 0x61, 0x6e, 0x6f, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x75, 0x6e, 0x69, 0x78, 0x54, 0x69, 0x6d, 0x65, 0x4e, 0x61, 0x6e, 0x6f, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x72, 0x65, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x66, 0x72, 0x65, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x61, 0x76, 0x61, 0x69, 0x6c, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x61, 0x76, 0x61, 0x69, 0x6c, 0x32, 0x88, 0x01, 0x0a, 0x05, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x12, 0x40, 0x0a, 0x07, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c, 0x12, 0x18, 0x2e, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c, 0x52, 0x65, 0x71, 0x1a, 0x19, 0x2e, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x12, 0x3d, 0x0a, 0x06, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x12, 0x17, 0x2e, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x52, 0x65, 0x71, 0x1a, 0x18, 0x2e, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x42, 0x50, 0x5a, 0x4e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x50, 0x61, 0x63, 0x6b, 0x74, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x69, 0x6e, 0x67, 0x2f, 0x47, 0x6f, 0x2d, 0x66, 0x6f, 0x72, 0x2d, 0x44, 0x65, 0x76, 0x4f, 0x70, 0x73, 0x2f, 0x63, 0x68, 0x61, 0x70, 0x74, 0x65, 0x72, 0x2f, 0x31, 0x2f, 0x73, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x36, 0x2f, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( file_agent_proto_rawDescOnce sync.Once file_agent_proto_rawDescData = file_agent_proto_rawDesc ) func file_agent_proto_rawDescGZIP() []byte { file_agent_proto_rawDescOnce.Do(func() { file_agent_proto_rawDescData = protoimpl.X.CompressGZIP(file_agent_proto_rawDescData) }) return file_agent_proto_rawDescData } var file_agent_proto_msgTypes = make([]protoimpl.MessageInfo, 7) var file_agent_proto_goTypes = []interface{}{ (*InstallReq)(nil), // 0: system.agent.InstallReq (*InstallResp)(nil), // 1: system.agent.InstallResp (*RemoveReq)(nil), // 2: system.agent.RemoveReq (*RemoveResp)(nil), // 3: system.agent.RemoveResp (*CPUPerfs)(nil), // 4: system.agent.CPUPerfs (*CPUPerf)(nil), // 5: system.agent.CPUPerf (*MemPerf)(nil), // 6: system.agent.MemPerf } var file_agent_proto_depIdxs = []int32{ 5, // 0: system.agent.CPUPerfs.cpu:type_name -> system.agent.CPUPerf 0, // 1: system.agent.Agent.Install:input_type -> system.agent.InstallReq 2, // 2: system.agent.Agent.Remove:input_type -> system.agent.RemoveReq 1, // 3: system.agent.Agent.Install:output_type -> system.agent.InstallResp 3, // 4: system.agent.Agent.Remove:output_type -> system.agent.RemoveResp 3, // [3:5] is the sub-list for method output_type 1, // [1:3] is the sub-list for method input_type 1, // [1:1] is the sub-list for extension type_name 1, // [1:1] is the sub-list for extension extendee 0, // [0:1] is the sub-list for field type_name } func init() { file_agent_proto_init() } func file_agent_proto_init() { if File_agent_proto != nil { return } if !protoimpl.UnsafeEnabled { file_agent_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*InstallReq); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_agent_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*InstallResp); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_agent_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*RemoveReq); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_agent_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*RemoveResp); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_agent_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*CPUPerfs); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_agent_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*CPUPerf); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_agent_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*MemPerf); 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_agent_proto_rawDesc, NumEnums: 0, NumMessages: 7, NumExtensions: 0, NumServices: 1, }, GoTypes: file_agent_proto_goTypes, DependencyIndexes: file_agent_proto_depIdxs, MessageInfos: file_agent_proto_msgTypes, }.Build() File_agent_proto = out.File file_agent_proto_rawDesc = nil file_agent_proto_goTypes = nil file_agent_proto_depIdxs = nil } ================================================ FILE: chapter/8/agent/proto/agent.proto ================================================ syntax = "proto3"; package system.agent; option go_package = "github.com/PacktPublishing/Go-for-DevOps/chapter/6/agent/proto/agent"; message InstallReq { string name = 1; bytes package = 2; string binary = 3; repeated string args = 4; } message InstallResp {} message RemoveReq { string name = 1; } message RemoveResp {} message CPUPerfs { int32 resolutionSecs = 1; int64 unix_time_nano = 2; repeated CPUPerf cpu = 3; } message CPUPerf { string id = 1; int32 user = 2; int32 system = 3; int32 idle = 4; int32 io_wait = 5; int32 irq = 6; } message MemPerf { int32 resolutionSecs = 1; int64 unix_time_nano = 2; int32 total = 3; int32 free = 4; int32 avail = 5; } service Agent { rpc Install(InstallReq) returns (InstallResp) {}; rpc Remove(RemoveReq) returns (RemoveResp) {}; } ================================================ FILE: chapter/8/agent/proto/agent_grpc.pb.go ================================================ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. package agent 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.32.0 or later. const _ = grpc.SupportPackageIsVersion7 // AgentClient is the client API for Agent 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 AgentClient interface { Install(ctx context.Context, in *InstallReq, opts ...grpc.CallOption) (*InstallResp, error) Remove(ctx context.Context, in *RemoveReq, opts ...grpc.CallOption) (*RemoveResp, error) } type agentClient struct { cc grpc.ClientConnInterface } func NewAgentClient(cc grpc.ClientConnInterface) AgentClient { return &agentClient{cc} } func (c *agentClient) Install(ctx context.Context, in *InstallReq, opts ...grpc.CallOption) (*InstallResp, error) { out := new(InstallResp) err := c.cc.Invoke(ctx, "/system.agent.Agent/Install", in, out, opts...) if err != nil { return nil, err } return out, nil } func (c *agentClient) Remove(ctx context.Context, in *RemoveReq, opts ...grpc.CallOption) (*RemoveResp, error) { out := new(RemoveResp) err := c.cc.Invoke(ctx, "/system.agent.Agent/Remove", in, out, opts...) if err != nil { return nil, err } return out, nil } // AgentServer is the server API for Agent service. // All implementations must embed UnimplementedAgentServer // for forward compatibility type AgentServer interface { Install(context.Context, *InstallReq) (*InstallResp, error) Remove(context.Context, *RemoveReq) (*RemoveResp, error) mustEmbedUnimplementedAgentServer() } // UnimplementedAgentServer must be embedded to have forward compatible implementations. type UnimplementedAgentServer struct { } func (UnimplementedAgentServer) Install(context.Context, *InstallReq) (*InstallResp, error) { return nil, status.Errorf(codes.Unimplemented, "method Install not implemented") } func (UnimplementedAgentServer) Remove(context.Context, *RemoveReq) (*RemoveResp, error) { return nil, status.Errorf(codes.Unimplemented, "method Remove not implemented") } func (UnimplementedAgentServer) mustEmbedUnimplementedAgentServer() {} // UnsafeAgentServer may be embedded to opt out of forward compatibility for this service. // Use of this interface is not recommended, as added methods to AgentServer will // result in compilation errors. type UnsafeAgentServer interface { mustEmbedUnimplementedAgentServer() } func RegisterAgentServer(s grpc.ServiceRegistrar, srv AgentServer) { s.RegisterService(&Agent_ServiceDesc, srv) } func _Agent_Install_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(InstallReq) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(AgentServer).Install(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: "/system.agent.Agent/Install", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(AgentServer).Install(ctx, req.(*InstallReq)) } return interceptor(ctx, in, info, handler) } func _Agent_Remove_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(RemoveReq) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(AgentServer).Remove(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: "/system.agent.Agent/Remove", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(AgentServer).Remove(ctx, req.(*RemoveReq)) } return interceptor(ctx, in, info, handler) } // Agent_ServiceDesc is the grpc.ServiceDesc for Agent service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) var Agent_ServiceDesc = grpc.ServiceDesc{ ServiceName: "system.agent.Agent", HandlerType: (*AgentServer)(nil), Methods: []grpc.MethodDesc{ { MethodName: "Install", Handler: _Agent_Install_Handler, }, { MethodName: "Remove", Handler: _Agent_Remove_Handler, }, }, Streams: []grpc.StreamDesc{}, Metadata: "agent.proto", } ================================================ FILE: chapter/8/agent/proto/buf.gen.yaml ================================================ version: v1 plugins: - name: go out: ./ opt: - paths=source_relative - name: go-grpc out: ./ opt: - paths=source_relative ================================================ FILE: chapter/8/agent/proto/buf.yaml ================================================ version: v1 lint: use: - DEFAULT breaking: use: - FILE ================================================ FILE: chapter/8/agent/proto/extra.go ================================================ /* This file contains manually added methods for our proto that are useful to the server. */ package agent import ( "fmt" "strings" "google.golang.org/protobuf/encoding/protojson" ) // Validate is used to validate an InstallReq. func (i *InstallReq) Validate() error { i.Name = strings.TrimSpace(i.Name) i.Binary = strings.TrimSpace(i.Binary) switch "" { case i.Name: return fmt.Errorf("Name must be set") case i.Binary: return fmt.Errorf("Binary must be set") } if len(i.Package) == 0 { return fmt.Errorf("Package must be set") } switch { case !validName(i.Name): return fmt.Errorf("Name(%s) must only contain 0-9, A-Z, a-z", i.Name) case !validName(i.Binary): return fmt.Errorf("Binary(%s) must only contain 0-9, A-Z, a-z", i.Binary) } return nil } func validName(s string) bool { for i := 0; i < len(s); i++ { switch { // 0-9 case s[i] >= 48 && s[i] <= 57: // A-Z case s[i] >= 65 && s[i] <= 90: // a-z case s[i] >= 97 && s[i] <= 122: default: return false } } return true } // MarshalJSON implement json.Marshaller for CPUPerfs so that we use the // protojson.Marshal() instead of the standard marshaller. func (x *CPUPerfs) MarshalJSON() ([]byte, error) { return protojson.Marshal(x) } // UnmarshalJSON implement json.Unmarshaller for CPUPerfs so that we use the // protojson.Unmarshal() instead of the standard unmarshaller. func (x *CPUPerfs) UnmarshalJSON(b []byte) error { return protojson.Unmarshal(b, x) } // MarshalJSON implement json.Marshaller for MemPerf so that we use the // protojson.Marshal() instead of the standard marshaller. func (x *MemPerf) MarshalJSON() ([]byte, error) { return protojson.Marshal(x) } // UnmarshalJSON implement json.Unmarshaller for MemPerf so that we use the // protojson.Unmarshal() instead of the standard unmarshaller. func (x *MemPerf) UnmarshalJSON(b []byte) error { return protojson.Unmarshal(b, x) } ================================================ FILE: chapter/8/rollout/README.md ================================================ ## Rollout Demo ### Setup For this demo, you are going to need setup in which you have 1 system acting as a load balancer and other systems that act as backends. We will be pushing out an update that: - Removes a system form the load balancer - Upgrades the binary on the remote system - Tests the binary health - Adds the system back to the load balancer If no load balancer pool exists, it will add one. If one exists, the pool must be healthy and have the same backends as what we are upgrading according to our configuration file. How you setup the test systems is an exercise for the reader. For testing we used Azure VM scale sets for the backends and a single VM for the load balancer. You could do this with local VMs on your workstation, physical machines or whatever means you want. Here are the prerequisites: - The system where the rollout is executed must have access to: - The system with the load balancer - All backends - Must have an SSH key file that can log into each system - All systems must be able to communicate to each other on SSH - Backends must be able to open port 8082 - LB must be able to open port 8080 and 8081 - Write a services.json file that represents our upgrade. Re-write the sample to your settings. **Note**: Like most example code, this isn't production quality. This is simply meant as an example to teach lessons and show off capabilites you can use for your own nees. ### Running the LB You will need to compile and push the rollout/lb binary to the system that will act as your load balancer. Running it is a simple as: ```bash ./lb load balancer started(8080)... grpc server started(8081)... ``` **Note**: The load balancer listens on all ports and has no security for gRPC. So don't expose this on an addressable IP. ### Prepare the rollout You will need to compile the rollout binary and put it on whatever system is going to do the rollout. In the same directory you will need to copy the webserver you want to deploy. You can use the one in `rollout/lb/sample` if you want. But whatever binary it is, it must: - Answer with something on / - Answer with "ok" on /healthz - Run on port 8082 Finally, you need to have a `services.json` file that represents the rollout. Modify the sample one to your needs. If you have questions on the values, have a look at config.go where all the fields are detailed. ### Doing a rollout With these files located in the same directory: - rollout - services.json - webserver You can simply do: ```bash ./rollout --keyFile=/home/[user]/.ssh/[key].pem services.json ``` This should start the process and do a rollout. The output will look like: ``` Setup LB with pool `/` Starting Workflow Running canary on: 10.0.0.5 Sleeping after canary for 1 minutes Upgrading endpoint: 10.0.0.7 Upgrading endpoint: 10.0.0.6 Upgrading endpoint: 10.0.0.8 Upgrading endpoint: 10.0.0.9 Workflow Completed with no failures ``` ### Common errors ```bash Workflow Failed: esPreconditionFailure Failed State Error Precondition checkLBState precondition fail: expected backends(5) != found backends(1) ``` This usually occurs because you did a failed rollout. When a rollout happens, it expects either there to be an empty load balancer pool or all expected backends to be in and healthy. You can just restart the "lb" binary and run again. ================================================ FILE: chapter/8/rollout/config.go ================================================ package main import ( "fmt" "net" "strconv" "strings" "golang.org/x/crypto/ssh" ) // config represents the configuration file that details the work to be done. type config struct { // Concurrency is the number of servers that can be upgraded at a time. Concurrency int32 // CanaryNum is the number of canaries to do before proceeding with a general rollout. // Any canary failure fails the workflow. Canaries execute one at a time. CanaryNum int32 // MaxFailures is the maximum number of failures to tolerate before stopping. // You can have more failures than MaxFailures due to concurrency settings. MaxFailures int32 // Src is the path on disk to the binary to push. Src string // Dst is the path on the remote disk to copy the binary to. Dst string // LB is the host:port of the load balancer. LB string // Pattern is the load balancer's Pool pattern. Pattern string // Backends are the backends that need to be updated, simply the host in IP form. Backends []string // BackendUser is the user to use when logging in to the backends. BackendUser string // BinaryPort is the port the binary will start on. This is used to configure the // load balancer. BinaryPort int // ssh is the SSH client configuration for all host connections. This is not set // in the config file, it is added in during main(). ssh *ssh.ClientConfig } // validate does basic validation of the config. func (s config) validate() error { if _, _, err := checkIPPort(s.LB); err != nil { return fmt.Errorf("LB(%s) is not correct: %w", s.LB, err) } if len(s.Backends) < 1 { return fmt.Errorf("must specify some Backends") } for _, b := range s.Backends { _, err := checkIP(b) if err != nil { return fmt.Errorf("Backend(%s) is not correct: %w", b, err) } } if s.BinaryPort < 1 { return fmt.Errorf("BinaryPort(%d) is invalid", s.BinaryPort) } if strings.TrimSpace(s.Pattern) == "" { return fmt.Errorf("Pattern(%s) is invalid", s.Pattern) } if s.Concurrency < 1 { return fmt.Errorf("Concurrency(%d) is invalid", s.Concurrency) } return nil } func checkIP(s string) (net.IP, error) { ip := net.ParseIP(s) if ip == nil { return nil, fmt.Errorf("%q is not a valid IP", s) } return ip, nil } // checkIPPort takes a host:port string and splits it out and verifies it. func checkIPPort(b string) (net.IP, int32, error) { h, ps, err := net.SplitHostPort(b) if err != nil { return nil, 0, err } p, _ := strconv.Atoi(ps) ip := net.ParseIP(h) if ip == nil { return nil, 0, err } if p < 1 || p > 65534 { return nil, 0, fmt.Errorf("invalid port: %d", p) } return ip, int32(p), nil } ================================================ FILE: chapter/8/rollout/endstate_string.go ================================================ // Code generated by "stringer -type=endState"; DO NOT EDIT. package main import "strconv" func _() { // An "invalid array index" compiler error signifies that the constant values have changed. // Re-run the stringer command to generate them again. var x [1]struct{} _ = x[esUnknown-0] _ = x[esSuccess-1] _ = x[esPreconditionFailure-2] _ = x[esCanaryFailure-3] _ = x[esMaxFailures-4] } const _endState_name = "esUnknownesSuccessesPreconditionFailureesCanaryFailureesMaxFailures" var _endState_index = [...]uint8{0, 9, 18, 39, 54, 67} func (i endState) String() string { if i < 0 || i >= endState(len(_endState_index)-1) { return "endState(" + strconv.FormatInt(int64(i), 10) + ")" } return _endState_name[_endState_index[i]:_endState_index[i+1]] } ================================================ FILE: chapter/8/rollout/lb/README.md ================================================ ## Load Balancer Example We have added a simple layer 7 Load Balancer here to use in the rollout example from the book. This code is not covered in that chapter, instead it simply covers that you are interacting with a load balancer to add and remove web servers. To make the full example work, we have built a simple load balancer using Go with client libraries and sample applications. There is also a CLI application for interacting with the load balancer outside the example application. ### Using the Load Balancer The load balancer is setup to run on port 8080. Your OS will need to allow this port and nothing else can be running on it. To run the load balancer, in this directory simply run: ```bash go run lb.go ``` ### Interacting with the CLI We included a CLI app, which is not required for the example. It allowed us to do basic testing with the load balancer. The CLI app will allow you to: - Add a pool, which is based on the URL pattern to match against - Remove a pool by pattern - Add a backend to a pool - Remove a backend from a pool - Get a pools health Pattern matching is based on the `http` package pattern matching. See the GoDoc for more information. Adding a pool with two backends looks like: ```go $ go run cli.go --lb=127.0.0.1:8081 --pattern=/ addPool $ go run cli.go --lb=127.0.0.1:8081 --pattern=/ --ip=127.0.0.1 --port=8082 --url_path=/ addBackend $ go run cli.go --lb=127.0.0.1:8081 --pattern=/ --ip=127.0.0.1 --port=8083 --url_path=/ addBackend ``` This first contacts our load balancer (127.0.0.1:8081) and adds a pool that matches pattern /. Then it adds two backends running on local ports 8082 and 8083 with a URL path of /. Note that the CLI sets up a health check that queries the backend's `/healthz` page looking for `ok` in the body of the response. If it doesn't respond, you can't add that backend. This is also checked at intervals and it will remove unhealthy nodes until they pass a health check. There is an example web server you can run in the `sample/web` directory to provide the load balancer with valid backends. Simply go into that directory and run: ```bash $ go run main.go --port=8082 ``` This would run a webserver on port 8082. You can do this multiple times on different ports. In the example above we ran them on 8082 and 8083. You can see the health of your pool with: ```bash go run cli.go --lb=127.0.0.1:8081 --pattern=/ poolHealth Pool Status / PS_FULL Backend Status 127.0.0.1:8082 BS_HEALTHY 127.0.0.1:8083 BS_HEALTHY ``` ### NOTES - This is not a production level load balancer. It lacks a lot of bells and whistles, monitoring, metrics and most importantly tests. - There is no security on the gRPC service. ================================================ FILE: chapter/8/rollout/lb/buf.work ================================================ version: v1 directories: - proto ================================================ FILE: chapter/8/rollout/lb/client/cli/cli.go ================================================ package main import ( "context" "flag" "fmt" "net" "time" "github.com/PacktPublishing/Go-for-DevOps/chapter/8/rollout/lb/client" "github.com/fatih/color" "github.com/rodaine/table" pb "github.com/PacktPublishing/Go-for-DevOps/chapter/8/rollout/lb/proto" ) var ( server = flag.String("lb", "", "The load balancer address to connect to, host:port") ip = flag.String("ip", "", "An IP setting") port = flag.Int("port", 0, "A port setting") urlPath = flag.String("url_path", "", "The url path to use") pattern = flag.String("pattern", "", "A pattern setting") ) var hcs = client.HealthChecks{ Interval: 10 * time.Second, HealthChecks: []client.HealthCheck{ client.StatusCheck{ URLPath: "/healthz", HealthyValues: []string{"ok", "OK"}, }, }, } func main() { flag.Parse() if len(flag.Args()) != 1 { panic("bad args") } c, err := client.New(*server) if err != nil { panic(err) } switch flag.Args()[0] { case "addPool": ctx, _ := context.WithTimeout(context.Background(), 30*time.Second) if err := c.AddPool(ctx, *pattern, pb.PoolType_PT_P2C, hcs); err != nil { panic(err) } case "removePool": ctx, _ := context.WithTimeout(context.Background(), 30*time.Second) if err := c.RemovePool(ctx, *pattern); err != nil { panic(err) } case "addBackend": ctx, _ := context.WithTimeout(context.Background(), 30*time.Second) b := client.IPBackend{ IP: net.ParseIP(*ip), Port: int32(*port), URLPath: *urlPath, } if err := c.AddBackend(ctx, *pattern, b); err != nil { panic(err) } case "removeBackend": ctx, _ := context.WithTimeout(context.Background(), 30*time.Second) b := client.IPBackend{ IP: net.ParseIP(*ip), Port: int32(*port), URLPath: *urlPath, } if err := c.AddBackend(ctx, *pattern, b); err != nil { panic(err) } case "poolHealth": ctx, _ := context.WithTimeout(context.Background(), 2*time.Second) ph, err := c.PoolHealth(ctx, *pattern, true, true) if err != nil { panic(err) } headerFmt := color.New(color.FgGreen, color.Underline).SprintfFunc() columnFmt := color.New(color.FgYellow).SprintfFunc() tbl := table.New("Pool", "Status") tbl.WithHeaderFormatter(headerFmt).WithFirstColumnFormatter(columnFmt) tbl.AddRow(*pattern, ph.Status) tbl.Print() tbl = table.New("Backend", "Status") tbl.WithHeaderFormatter(headerFmt).WithFirstColumnFormatter(columnFmt) for _, b := range ph.Backends { switch { case b.Backend.GetIpBackend() != nil: v := b.Backend.GetIpBackend() tbl.AddRow( fmt.Sprintf("%s:%d%s", v.Ip, v.Port, v.UrlPath), b.Status.String(), ) } } tbl.Print() default: panic("non-recognized command") } } ================================================ FILE: chapter/8/rollout/lb/client/client.go ================================================ package client import ( "context" "fmt" "net" "time" "google.golang.org/grpc" pb "github.com/PacktPublishing/Go-for-DevOps/chapter/8/rollout/lb/proto" ) // HealthChecks are a set of backend health checks that a backend must pass // in order to be considered healthy. type HealthChecks struct { // HealthChecks are the checks to run against the backends. HealthChecks []HealthCheck // Interval is the between checks. Interval time.Duration } func (h HealthChecks) toPB() *pb.HealthChecks { hcs := &pb.HealthChecks{ IntervalSecs: int32(h.Interval / time.Second), } for _, hc := range h.HealthChecks { p := hc.toPB() hcs.HealthChecks = append(hcs.HealthChecks, p) } return hcs } // HealthCheck defines a health check that must pass for a backend in a Pool // to be considered healthy. type HealthCheck interface { toPB() *pb.HealthCheck isHealthCheck() } // StatusCheck implements HealthCheck to check a node at "URLPath" for // any string in "HealthyValues". If found, the node is said to be healthy. type StatusCheck struct { // URLPath is the path to the health status page, like "/health". URLPath string // HealthValues are values that the URLPath can return and the service // is considered healthy. HealthyValues []string } func (s StatusCheck) toPB() *pb.HealthCheck { return &pb.HealthCheck{ HealthCheck: &pb.HealthCheck_StatusCheck{ StatusCheck: &pb.StatusCheck{ UrlPath: s.URLPath, HealthyValues: s.HealthyValues, }, }, } } func (s StatusCheck) isHealthCheck() {} // Client is a client to the Quote of the day server. type Client struct { client pb.LoadBalancerClient conn *grpc.ClientConn } // New is the constructor for Client. addr is the server's [host]:[port]. func New(addr string) (*Client, error) { conn, err := grpc.Dial(addr, grpc.WithInsecure()) if err != nil { return nil, err } return &Client{ client: pb.NewLoadBalancerClient(conn), conn: conn, }, nil } // AddPool adds a pool that serves "pattern" using a PoolType that controls how // the pool load balances traffic and a HealthCheck to determine if a node is healthy. func (c *Client) AddPool(ctx context.Context, pattern string, pt pb.PoolType, hcs HealthChecks) error { if _, ok := ctx.Deadline(); !ok { var cancel context.CancelFunc ctx, cancel = context.WithTimeout(ctx, 30*time.Second) defer cancel() } _, err := c.client.AddPool( ctx, &pb.AddPoolReq{ Pattern: pattern, PoolType: pt, HealthChecks: hcs.toPB(), }, ) if err != nil { return err } return nil } // RemovePool removes the pool that is serving "pattern". func (c *Client) RemovePool(ctx context.Context, pattern string) error { if _, ok := ctx.Deadline(); !ok { var cancel context.CancelFunc ctx, cancel = context.WithTimeout(ctx, 30*time.Second) defer cancel() } _, err := c.client.RemovePool(ctx, &pb.RemovePoolReq{Pattern: pattern}) if err != nil { return err } return nil } // Backend represents a load balancer backend. type Backend interface { isBackend() } // IPBackend implements Backend. type IPBackend struct { IP net.IP Port int32 URLPath string } func (i IPBackend) isBackend() {} // AddBackend adds backend "b" from the pool serving "pattern". func (c *Client) AddBackend(ctx context.Context, pattern string, b Backend) error { switch v := b.(type) { case IPBackend: return c.addIPBackend(ctx, pattern, v) } return fmt.Errorf("Backend is not a recognized type(%T)", b) } func (c *Client) addIPBackend(ctx context.Context, pattern string, b IPBackend) error { _, err := c.client.AddBackend( ctx, &pb.AddBackendReq{ Pattern: pattern, Backend: &pb.Backend{ Backend: &pb.Backend_IpBackend{ IpBackend: &pb.IPBackend{ Ip: b.IP.String(), Port: b.Port, UrlPath: b.URLPath, }, }, }, }, ) if err != nil { return err } return nil } // RemoveBackend removes backend "b" from the pool serving "pattern". func (c *Client) RemoveBackend(ctx context.Context, pattern string, b Backend) error { switch v := b.(type) { case IPBackend: return c.removeIPBackend(ctx, pattern, v) } return fmt.Errorf("Backend is not a recognized type(%T)", b) } func (c *Client) removeIPBackend(ctx context.Context, pattern string, b IPBackend) error { _, err := c.client.RemoveBackend( ctx, &pb.RemoveBackendReq{ Pattern: pattern, Backend: &pb.Backend{ Backend: &pb.Backend_IpBackend{ IpBackend: &pb.IPBackend{ Ip: b.IP.String(), Port: b.Port, UrlPath: b.URLPath, }, }, }, }, ) if err != nil { return err } return nil } // PoolHealth queries the server for the health of the pool that serves "pattern". // healthy and sick determine what node information is included. func (c *Client) PoolHealth(ctx context.Context, pattern string, healthy, sick bool) (*pb.PoolHealth, error) { resp, err := c.client.PoolHealth( ctx, &pb.PoolHealthReq{ Pattern: pattern, Healthy: healthy, Sick: sick, }, ) if err != nil { return nil, err } return resp.Health, nil } ================================================ FILE: chapter/8/rollout/lb/lb.go ================================================ package main import ( "log" "net" "github.com/PacktPublishing/Go-for-DevOps/chapter/8/rollout/lb/server/grpc" "github.com/PacktPublishing/Go-for-DevOps/chapter/8/rollout/lb/server/http" ) func main() { ln, err := net.Listen("tcp", ":8080") if err != nil { panic(err) } lb, err := http.New() if err != nil { panic(err) } log.Println("load balancer started(8080)...") go func() { if err := lb.Serve(ln); err != nil { panic(err) } }() serv, err := grpc.New(":8081", lb) if err != nil { panic(err) } log.Println("grpc server started(8081)...") if err := serv.Start(); err != nil { panic(err) } } ================================================ FILE: chapter/8/rollout/lb/proto/buf.gen.yaml ================================================ version: v1 plugins: - name: go out: ./ opt: - paths=source_relative - name: go-grpc out: ./ opt: - paths=source_relative ================================================ FILE: chapter/8/rollout/lb/proto/buf.yaml ================================================ version: v1 lint: use: - DEFAULT breaking: use: - FILE ================================================ FILE: chapter/8/rollout/lb/proto/lb.pb.go ================================================ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.25.0-devel // protoc v3.18.0 // source: lb.proto package lb 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) ) // PoolType indicates what type of pool to use. This influences // how the pool distributes its workload. type PoolType int32 const ( // This indicates an error by the user. PoolType_PT_UNKNOWN PoolType = 0 // The power of 2 choices selection pool. PoolType_PT_P2C PoolType = 1 ) // Enum value maps for PoolType. var ( PoolType_name = map[int32]string{ 0: "PT_UNKNOWN", 1: "PT_P2C", } PoolType_value = map[string]int32{ "PT_UNKNOWN": 0, "PT_P2C": 1, } ) func (x PoolType) Enum() *PoolType { p := new(PoolType) *p = x return p } func (x PoolType) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } func (PoolType) Descriptor() protoreflect.EnumDescriptor { return file_lb_proto_enumTypes[0].Descriptor() } func (PoolType) Type() protoreflect.EnumType { return &file_lb_proto_enumTypes[0] } func (x PoolType) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } // Deprecated: Use PoolType.Descriptor instead. func (PoolType) EnumDescriptor() ([]byte, []int) { return file_lb_proto_rawDescGZIP(), []int{0} } type PoolStatus int32 const ( // This indicates an error by the developers. PoolStatus_PS_UNKNOWN PoolStatus = 0 // The pool has all its configured backends working. PoolStatus_PS_FULL PoolStatus = 1 // The pool has no configured backends. PoolStatus_PS_EMPTY PoolStatus = 2 // The pool has one or more configured backends not working. PoolStatus_PS_DEGRADED PoolStatus = 3 ) // Enum value maps for PoolStatus. var ( PoolStatus_name = map[int32]string{ 0: "PS_UNKNOWN", 1: "PS_FULL", 2: "PS_EMPTY", 3: "PS_DEGRADED", } PoolStatus_value = map[string]int32{ "PS_UNKNOWN": 0, "PS_FULL": 1, "PS_EMPTY": 2, "PS_DEGRADED": 3, } ) func (x PoolStatus) Enum() *PoolStatus { p := new(PoolStatus) *p = x return p } func (x PoolStatus) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } func (PoolStatus) Descriptor() protoreflect.EnumDescriptor { return file_lb_proto_enumTypes[1].Descriptor() } func (PoolStatus) Type() protoreflect.EnumType { return &file_lb_proto_enumTypes[1] } func (x PoolStatus) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } // Deprecated: Use PoolStatus.Descriptor instead. func (PoolStatus) EnumDescriptor() ([]byte, []int) { return file_lb_proto_rawDescGZIP(), []int{1} } // BackendStatus details the status of a backend. type BackendStatus int32 const ( // This indicates an error by the developers. BackendStatus_BS_UNKNOWN BackendStatus = 0 // The node is healthy according to its health checks. BackendStatus_BS_HEALTHY BackendStatus = 1 // The node is sick according to its health checks. BackendStatus_BS_SICK BackendStatus = 2 ) // Enum value maps for BackendStatus. var ( BackendStatus_name = map[int32]string{ 0: "BS_UNKNOWN", 1: "BS_HEALTHY", 2: "BS_SICK", } BackendStatus_value = map[string]int32{ "BS_UNKNOWN": 0, "BS_HEALTHY": 1, "BS_SICK": 2, } ) func (x BackendStatus) Enum() *BackendStatus { p := new(BackendStatus) *p = x return p } func (x BackendStatus) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } func (BackendStatus) Descriptor() protoreflect.EnumDescriptor { return file_lb_proto_enumTypes[2].Descriptor() } func (BackendStatus) Type() protoreflect.EnumType { return &file_lb_proto_enumTypes[2] } func (x BackendStatus) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } // Deprecated: Use BackendStatus.Descriptor instead. func (BackendStatus) EnumDescriptor() ([]byte, []int) { return file_lb_proto_rawDescGZIP(), []int{2} } type HealthChecks struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields HealthChecks []*HealthCheck `protobuf:"bytes,1,rep,name=health_checks,json=healthChecks,proto3" json:"health_checks,omitempty"` IntervalSecs int32 `protobuf:"varint,2,opt,name=interval_secs,json=intervalSecs,proto3" json:"interval_secs,omitempty"` } func (x *HealthChecks) Reset() { *x = HealthChecks{} if protoimpl.UnsafeEnabled { mi := &file_lb_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *HealthChecks) String() string { return protoimpl.X.MessageStringOf(x) } func (*HealthChecks) ProtoMessage() {} func (x *HealthChecks) ProtoReflect() protoreflect.Message { mi := &file_lb_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 HealthChecks.ProtoReflect.Descriptor instead. func (*HealthChecks) Descriptor() ([]byte, []int) { return file_lb_proto_rawDescGZIP(), []int{0} } func (x *HealthChecks) GetHealthChecks() []*HealthCheck { if x != nil { return x.HealthChecks } return nil } func (x *HealthChecks) GetIntervalSecs() int32 { if x != nil { return x.IntervalSecs } return 0 } type HealthCheck struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // Types that are assignable to HealthCheck: // *HealthCheck_StatusCheck HealthCheck isHealthCheck_HealthCheck `protobuf_oneof:"health_check"` } func (x *HealthCheck) Reset() { *x = HealthCheck{} if protoimpl.UnsafeEnabled { mi := &file_lb_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *HealthCheck) String() string { return protoimpl.X.MessageStringOf(x) } func (*HealthCheck) ProtoMessage() {} func (x *HealthCheck) ProtoReflect() protoreflect.Message { mi := &file_lb_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 HealthCheck.ProtoReflect.Descriptor instead. func (*HealthCheck) Descriptor() ([]byte, []int) { return file_lb_proto_rawDescGZIP(), []int{1} } func (m *HealthCheck) GetHealthCheck() isHealthCheck_HealthCheck { if m != nil { return m.HealthCheck } return nil } func (x *HealthCheck) GetStatusCheck() *StatusCheck { if x, ok := x.GetHealthCheck().(*HealthCheck_StatusCheck); ok { return x.StatusCheck } return nil } type isHealthCheck_HealthCheck interface { isHealthCheck_HealthCheck() } type HealthCheck_StatusCheck struct { StatusCheck *StatusCheck `protobuf:"bytes,1,opt,name=status_check,json=statusCheck,proto3,oneof"` } func (*HealthCheck_StatusCheck) isHealthCheck_HealthCheck() {} // StatusCheck is a check against a URL path. That path must // emit in its body one of the healthy_values or it fails. type StatusCheck struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields UrlPath string `protobuf:"bytes,1,opt,name=url_path,json=urlPath,proto3" json:"url_path,omitempty"` HealthyValues []string `protobuf:"bytes,2,rep,name=healthy_values,json=healthyValues,proto3" json:"healthy_values,omitempty"` } func (x *StatusCheck) Reset() { *x = StatusCheck{} if protoimpl.UnsafeEnabled { mi := &file_lb_proto_msgTypes[2] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *StatusCheck) String() string { return protoimpl.X.MessageStringOf(x) } func (*StatusCheck) ProtoMessage() {} func (x *StatusCheck) ProtoReflect() protoreflect.Message { mi := &file_lb_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 StatusCheck.ProtoReflect.Descriptor instead. func (*StatusCheck) Descriptor() ([]byte, []int) { return file_lb_proto_rawDescGZIP(), []int{2} } func (x *StatusCheck) GetUrlPath() string { if x != nil { return x.UrlPath } return "" } func (x *StatusCheck) GetHealthyValues() []string { if x != nil { return x.HealthyValues } return nil } type Backend struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // Types that are assignable to Backend: // *Backend_IpBackend Backend isBackend_Backend `protobuf_oneof:"backend"` } func (x *Backend) Reset() { *x = Backend{} if protoimpl.UnsafeEnabled { mi := &file_lb_proto_msgTypes[3] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Backend) String() string { return protoimpl.X.MessageStringOf(x) } func (*Backend) ProtoMessage() {} func (x *Backend) ProtoReflect() protoreflect.Message { mi := &file_lb_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 Backend.ProtoReflect.Descriptor instead. func (*Backend) Descriptor() ([]byte, []int) { return file_lb_proto_rawDescGZIP(), []int{3} } func (m *Backend) GetBackend() isBackend_Backend { if m != nil { return m.Backend } return nil } func (x *Backend) GetIpBackend() *IPBackend { if x, ok := x.GetBackend().(*Backend_IpBackend); ok { return x.IpBackend } return nil } type isBackend_Backend interface { isBackend_Backend() } type Backend_IpBackend struct { IpBackend *IPBackend `protobuf:"bytes,1,opt,name=ip_backend,json=ipBackend,proto3,oneof"` } func (*Backend_IpBackend) isBackend_Backend() {} // IPBackend provides a Backend that has its endpoint as an ip:port. type IPBackend struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // The IP(v4 or v6) to use as the host. Ip string `protobuf:"bytes,1,opt,name=ip,proto3" json:"ip,omitempty"` // The port number to connect on. Port int32 `protobuf:"varint,2,opt,name=port,proto3" json:"port,omitempty"` // The url_path to forward to. Generally this is empty. UrlPath string `protobuf:"bytes,3,opt,name=url_path,json=urlPath,proto3" json:"url_path,omitempty"` } func (x *IPBackend) Reset() { *x = IPBackend{} if protoimpl.UnsafeEnabled { mi := &file_lb_proto_msgTypes[4] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *IPBackend) String() string { return protoimpl.X.MessageStringOf(x) } func (*IPBackend) ProtoMessage() {} func (x *IPBackend) ProtoReflect() protoreflect.Message { mi := &file_lb_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 IPBackend.ProtoReflect.Descriptor instead. func (*IPBackend) Descriptor() ([]byte, []int) { return file_lb_proto_rawDescGZIP(), []int{4} } func (x *IPBackend) GetIp() string { if x != nil { return x.Ip } return "" } func (x *IPBackend) GetPort() int32 { if x != nil { return x.Port } return 0 } func (x *IPBackend) GetUrlPath() string { if x != nil { return x.UrlPath } return "" } // PoolHealth details the health status of a pool. type PoolHealth struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // The status of the pool. Status PoolStatus `protobuf:"varint,1,opt,name=status,proto3,enum=rollout.lb.PoolStatus" json:"status,omitempty"` // The pool's backend health status. This can contain all backends // or only sick/healthy backends, depending on the request. Backends []*BackendHealth `protobuf:"bytes,2,rep,name=backends,proto3" json:"backends,omitempty"` } func (x *PoolHealth) Reset() { *x = PoolHealth{} if protoimpl.UnsafeEnabled { mi := &file_lb_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *PoolHealth) String() string { return protoimpl.X.MessageStringOf(x) } func (*PoolHealth) ProtoMessage() {} func (x *PoolHealth) ProtoReflect() protoreflect.Message { mi := &file_lb_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 PoolHealth.ProtoReflect.Descriptor instead. func (*PoolHealth) Descriptor() ([]byte, []int) { return file_lb_proto_rawDescGZIP(), []int{5} } func (x *PoolHealth) GetStatus() PoolStatus { if x != nil { return x.Status } return PoolStatus_PS_UNKNOWN } func (x *PoolHealth) GetBackends() []*BackendHealth { if x != nil { return x.Backends } return nil } // BackendHealth details the health status of a backend. type BackendHealth struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Backend *Backend `protobuf:"bytes,1,opt,name=backend,proto3" json:"backend,omitempty"` Status BackendStatus `protobuf:"varint,2,opt,name=status,proto3,enum=rollout.lb.BackendStatus" json:"status,omitempty"` } func (x *BackendHealth) Reset() { *x = BackendHealth{} if protoimpl.UnsafeEnabled { mi := &file_lb_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *BackendHealth) String() string { return protoimpl.X.MessageStringOf(x) } func (*BackendHealth) ProtoMessage() {} func (x *BackendHealth) ProtoReflect() protoreflect.Message { mi := &file_lb_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 BackendHealth.ProtoReflect.Descriptor instead. func (*BackendHealth) Descriptor() ([]byte, []int) { return file_lb_proto_rawDescGZIP(), []int{6} } func (x *BackendHealth) GetBackend() *Backend { if x != nil { return x.Backend } return nil } func (x *BackendHealth) GetStatus() BackendStatus { if x != nil { return x.Status } return BackendStatus_BS_UNKNOWN } // AddPoolReq requests to create a pool for handling requests. type AddPoolReq struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // The URL pattern to direct traffic too. Pattern string `protobuf:"bytes,1,opt,name=pattern,proto3" json:"pattern,omitempty"` // The type of traffic distribution pool to use. PoolType PoolType `protobuf:"varint,2,opt,name=pool_type,json=poolType,proto3,enum=rollout.lb.PoolType" json:"pool_type,omitempty"` // Health checks to against backends. HealthChecks *HealthChecks `protobuf:"bytes,4,opt,name=health_checks,json=healthChecks,proto3" json:"health_checks,omitempty"` } func (x *AddPoolReq) Reset() { *x = AddPoolReq{} if protoimpl.UnsafeEnabled { mi := &file_lb_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *AddPoolReq) String() string { return protoimpl.X.MessageStringOf(x) } func (*AddPoolReq) ProtoMessage() {} func (x *AddPoolReq) ProtoReflect() protoreflect.Message { mi := &file_lb_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 AddPoolReq.ProtoReflect.Descriptor instead. func (*AddPoolReq) Descriptor() ([]byte, []int) { return file_lb_proto_rawDescGZIP(), []int{7} } func (x *AddPoolReq) GetPattern() string { if x != nil { return x.Pattern } return "" } func (x *AddPoolReq) GetPoolType() PoolType { if x != nil { return x.PoolType } return PoolType_PT_UNKNOWN } func (x *AddPoolReq) GetHealthChecks() *HealthChecks { if x != nil { return x.HealthChecks } return nil } // AddPoolResp is the response to adding a pool. type AddPoolResp struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields } func (x *AddPoolResp) Reset() { *x = AddPoolResp{} if protoimpl.UnsafeEnabled { mi := &file_lb_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *AddPoolResp) String() string { return protoimpl.X.MessageStringOf(x) } func (*AddPoolResp) ProtoMessage() {} func (x *AddPoolResp) ProtoReflect() protoreflect.Message { mi := &file_lb_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 AddPoolResp.ProtoReflect.Descriptor instead. func (*AddPoolResp) Descriptor() ([]byte, []int) { return file_lb_proto_rawDescGZIP(), []int{8} } // RemovePoolReq is used to remove a pool by its pattern. type RemovePoolReq struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // Pattern is the pattern to remove. Pattern string `protobuf:"bytes,1,opt,name=pattern,proto3" json:"pattern,omitempty"` } func (x *RemovePoolReq) Reset() { *x = RemovePoolReq{} if protoimpl.UnsafeEnabled { mi := &file_lb_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *RemovePoolReq) String() string { return protoimpl.X.MessageStringOf(x) } func (*RemovePoolReq) ProtoMessage() {} func (x *RemovePoolReq) ProtoReflect() protoreflect.Message { mi := &file_lb_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 RemovePoolReq.ProtoReflect.Descriptor instead. func (*RemovePoolReq) Descriptor() ([]byte, []int) { return file_lb_proto_rawDescGZIP(), []int{9} } func (x *RemovePoolReq) GetPattern() string { if x != nil { return x.Pattern } return "" } // RemovePoolResp is the response to removing a pool. type RemovePoolResp struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields } func (x *RemovePoolResp) Reset() { *x = RemovePoolResp{} if protoimpl.UnsafeEnabled { mi := &file_lb_proto_msgTypes[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *RemovePoolResp) String() string { return protoimpl.X.MessageStringOf(x) } func (*RemovePoolResp) ProtoMessage() {} func (x *RemovePoolResp) ProtoReflect() protoreflect.Message { mi := &file_lb_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 RemovePoolResp.ProtoReflect.Descriptor instead. func (*RemovePoolResp) Descriptor() ([]byte, []int) { return file_lb_proto_rawDescGZIP(), []int{10} } // AddBackendReq adds a backend to a pool. type AddBackendReq struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // The pattern to add a backend to. Pattern string `protobuf:"bytes,1,opt,name=pattern,proto3" json:"pattern,omitempty"` // The backend to add to the pool. Backend *Backend `protobuf:"bytes,2,opt,name=backend,proto3" json:"backend,omitempty"` } func (x *AddBackendReq) Reset() { *x = AddBackendReq{} if protoimpl.UnsafeEnabled { mi := &file_lb_proto_msgTypes[11] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *AddBackendReq) String() string { return protoimpl.X.MessageStringOf(x) } func (*AddBackendReq) ProtoMessage() {} func (x *AddBackendReq) ProtoReflect() protoreflect.Message { mi := &file_lb_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 AddBackendReq.ProtoReflect.Descriptor instead. func (*AddBackendReq) Descriptor() ([]byte, []int) { return file_lb_proto_rawDescGZIP(), []int{11} } func (x *AddBackendReq) GetPattern() string { if x != nil { return x.Pattern } return "" } func (x *AddBackendReq) GetBackend() *Backend { if x != nil { return x.Backend } return nil } type AddBackendResp struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields } func (x *AddBackendResp) Reset() { *x = AddBackendResp{} if protoimpl.UnsafeEnabled { mi := &file_lb_proto_msgTypes[12] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *AddBackendResp) String() string { return protoimpl.X.MessageStringOf(x) } func (*AddBackendResp) ProtoMessage() {} func (x *AddBackendResp) ProtoReflect() protoreflect.Message { mi := &file_lb_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 AddBackendResp.ProtoReflect.Descriptor instead. func (*AddBackendResp) Descriptor() ([]byte, []int) { return file_lb_proto_rawDescGZIP(), []int{12} } // RemoveBackendReq is used to remove a Backend from a Pool. type RemoveBackendReq struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // The pool pattern to remove the backend from. Pattern string `protobuf:"bytes,1,opt,name=pattern,proto3" json:"pattern,omitempty"` // The backend to remove from the pool. Backend *Backend `protobuf:"bytes,2,opt,name=backend,proto3" json:"backend,omitempty"` } func (x *RemoveBackendReq) Reset() { *x = RemoveBackendReq{} if protoimpl.UnsafeEnabled { mi := &file_lb_proto_msgTypes[13] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *RemoveBackendReq) String() string { return protoimpl.X.MessageStringOf(x) } func (*RemoveBackendReq) ProtoMessage() {} func (x *RemoveBackendReq) ProtoReflect() protoreflect.Message { mi := &file_lb_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 RemoveBackendReq.ProtoReflect.Descriptor instead. func (*RemoveBackendReq) Descriptor() ([]byte, []int) { return file_lb_proto_rawDescGZIP(), []int{13} } func (x *RemoveBackendReq) GetPattern() string { if x != nil { return x.Pattern } return "" } func (x *RemoveBackendReq) GetBackend() *Backend { if x != nil { return x.Backend } return nil } // RemoveBackendResp is the response to removing a Backend. type RemoveBackendResp struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields } func (x *RemoveBackendResp) Reset() { *x = RemoveBackendResp{} if protoimpl.UnsafeEnabled { mi := &file_lb_proto_msgTypes[14] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *RemoveBackendResp) String() string { return protoimpl.X.MessageStringOf(x) } func (*RemoveBackendResp) ProtoMessage() {} func (x *RemoveBackendResp) ProtoReflect() protoreflect.Message { mi := &file_lb_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 RemoveBackendResp.ProtoReflect.Descriptor instead. func (*RemoveBackendResp) Descriptor() ([]byte, []int) { return file_lb_proto_rawDescGZIP(), []int{14} } // PoolHealthReq is a request to get the health of a pool. type PoolHealthReq struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // Pattern is the pool pattern you are getting health for. Pattern string `protobuf:"bytes,1,opt,name=pattern,proto3" json:"pattern,omitempty"` // If set to true, will return the backends that are healthy. Healthy bool `protobuf:"varint,3,opt,name=healthy,proto3" json:"healthy,omitempty"` // If set to true, will return backends that are sick. Sick bool `protobuf:"varint,4,opt,name=sick,proto3" json:"sick,omitempty"` } func (x *PoolHealthReq) Reset() { *x = PoolHealthReq{} if protoimpl.UnsafeEnabled { mi := &file_lb_proto_msgTypes[15] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *PoolHealthReq) String() string { return protoimpl.X.MessageStringOf(x) } func (*PoolHealthReq) ProtoMessage() {} func (x *PoolHealthReq) ProtoReflect() protoreflect.Message { mi := &file_lb_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 PoolHealthReq.ProtoReflect.Descriptor instead. func (*PoolHealthReq) Descriptor() ([]byte, []int) { return file_lb_proto_rawDescGZIP(), []int{15} } func (x *PoolHealthReq) GetPattern() string { if x != nil { return x.Pattern } return "" } func (x *PoolHealthReq) GetHealthy() bool { if x != nil { return x.Healthy } return false } func (x *PoolHealthReq) GetSick() bool { if x != nil { return x.Sick } return false } type PoolHealthResp struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Health *PoolHealth `protobuf:"bytes,1,opt,name=health,proto3" json:"health,omitempty"` } func (x *PoolHealthResp) Reset() { *x = PoolHealthResp{} if protoimpl.UnsafeEnabled { mi := &file_lb_proto_msgTypes[16] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *PoolHealthResp) String() string { return protoimpl.X.MessageStringOf(x) } func (*PoolHealthResp) ProtoMessage() {} func (x *PoolHealthResp) ProtoReflect() protoreflect.Message { mi := &file_lb_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 PoolHealthResp.ProtoReflect.Descriptor instead. func (*PoolHealthResp) Descriptor() ([]byte, []int) { return file_lb_proto_rawDescGZIP(), []int{16} } func (x *PoolHealthResp) GetHealth() *PoolHealth { if x != nil { return x.Health } return nil } var File_lb_proto protoreflect.FileDescriptor var file_lb_proto_rawDesc = []byte{ 0x0a, 0x08, 0x6c, 0x62, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0a, 0x72, 0x6f, 0x6c, 0x6c, 0x6f, 0x75, 0x74, 0x2e, 0x6c, 0x62, 0x22, 0x71, 0x0a, 0x0c, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x73, 0x12, 0x3c, 0x0a, 0x0d, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x5f, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x72, 0x6f, 0x6c, 0x6c, 0x6f, 0x75, 0x74, 0x2e, 0x6c, 0x62, 0x2e, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x0c, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x5f, 0x73, 0x65, 0x63, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0c, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x53, 0x65, 0x63, 0x73, 0x22, 0x5b, 0x0a, 0x0b, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x12, 0x3c, 0x0a, 0x0c, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x5f, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x72, 0x6f, 0x6c, 0x6c, 0x6f, 0x75, 0x74, 0x2e, 0x6c, 0x62, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x48, 0x00, 0x52, 0x0b, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x42, 0x0e, 0x0a, 0x0c, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x5f, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x22, 0x4f, 0x0a, 0x0b, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x12, 0x19, 0x0a, 0x08, 0x75, 0x72, 0x6c, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x75, 0x72, 0x6c, 0x50, 0x61, 0x74, 0x68, 0x12, 0x25, 0x0a, 0x0e, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x79, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0d, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x79, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x22, 0x4c, 0x0a, 0x07, 0x42, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x12, 0x36, 0x0a, 0x0a, 0x69, 0x70, 0x5f, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x72, 0x6f, 0x6c, 0x6c, 0x6f, 0x75, 0x74, 0x2e, 0x6c, 0x62, 0x2e, 0x49, 0x50, 0x42, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x48, 0x00, 0x52, 0x09, 0x69, 0x70, 0x42, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x42, 0x09, 0x0a, 0x07, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x22, 0x4a, 0x0a, 0x09, 0x49, 0x50, 0x42, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x70, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x75, 0x72, 0x6c, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x75, 0x72, 0x6c, 0x50, 0x61, 0x74, 0x68, 0x22, 0x73, 0x0a, 0x0a, 0x50, 0x6f, 0x6f, 0x6c, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x12, 0x2e, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x16, 0x2e, 0x72, 0x6f, 0x6c, 0x6c, 0x6f, 0x75, 0x74, 0x2e, 0x6c, 0x62, 0x2e, 0x50, 0x6f, 0x6f, 0x6c, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x35, 0x0a, 0x08, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x72, 0x6f, 0x6c, 0x6c, 0x6f, 0x75, 0x74, 0x2e, 0x6c, 0x62, 0x2e, 0x42, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x52, 0x08, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x73, 0x22, 0x71, 0x0a, 0x0d, 0x42, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x12, 0x2d, 0x0a, 0x07, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x72, 0x6f, 0x6c, 0x6c, 0x6f, 0x75, 0x74, 0x2e, 0x6c, 0x62, 0x2e, 0x42, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x52, 0x07, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x12, 0x31, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x19, 0x2e, 0x72, 0x6f, 0x6c, 0x6c, 0x6f, 0x75, 0x74, 0x2e, 0x6c, 0x62, 0x2e, 0x42, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x98, 0x01, 0x0a, 0x0a, 0x41, 0x64, 0x64, 0x50, 0x6f, 0x6f, 0x6c, 0x52, 0x65, 0x71, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x61, 0x74, 0x74, 0x65, 0x72, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x70, 0x61, 0x74, 0x74, 0x65, 0x72, 0x6e, 0x12, 0x31, 0x0a, 0x09, 0x70, 0x6f, 0x6f, 0x6c, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x14, 0x2e, 0x72, 0x6f, 0x6c, 0x6c, 0x6f, 0x75, 0x74, 0x2e, 0x6c, 0x62, 0x2e, 0x50, 0x6f, 0x6f, 0x6c, 0x54, 0x79, 0x70, 0x65, 0x52, 0x08, 0x70, 0x6f, 0x6f, 0x6c, 0x54, 0x79, 0x70, 0x65, 0x12, 0x3d, 0x0a, 0x0d, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x5f, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x72, 0x6f, 0x6c, 0x6c, 0x6f, 0x75, 0x74, 0x2e, 0x6c, 0x62, 0x2e, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x73, 0x52, 0x0c, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x73, 0x22, 0x0d, 0x0a, 0x0b, 0x41, 0x64, 0x64, 0x50, 0x6f, 0x6f, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x22, 0x29, 0x0a, 0x0d, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x50, 0x6f, 0x6f, 0x6c, 0x52, 0x65, 0x71, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x61, 0x74, 0x74, 0x65, 0x72, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x70, 0x61, 0x74, 0x74, 0x65, 0x72, 0x6e, 0x22, 0x10, 0x0a, 0x0e, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x50, 0x6f, 0x6f, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x22, 0x58, 0x0a, 0x0d, 0x41, 0x64, 0x64, 0x42, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x52, 0x65, 0x71, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x61, 0x74, 0x74, 0x65, 0x72, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x70, 0x61, 0x74, 0x74, 0x65, 0x72, 0x6e, 0x12, 0x2d, 0x0a, 0x07, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x72, 0x6f, 0x6c, 0x6c, 0x6f, 0x75, 0x74, 0x2e, 0x6c, 0x62, 0x2e, 0x42, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x52, 0x07, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x22, 0x10, 0x0a, 0x0e, 0x41, 0x64, 0x64, 0x42, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, 0x22, 0x5b, 0x0a, 0x10, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x42, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x52, 0x65, 0x71, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x61, 0x74, 0x74, 0x65, 0x72, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x70, 0x61, 0x74, 0x74, 0x65, 0x72, 0x6e, 0x12, 0x2d, 0x0a, 0x07, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x72, 0x6f, 0x6c, 0x6c, 0x6f, 0x75, 0x74, 0x2e, 0x6c, 0x62, 0x2e, 0x42, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x52, 0x07, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x22, 0x13, 0x0a, 0x11, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x42, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, 0x22, 0x57, 0x0a, 0x0d, 0x50, 0x6f, 0x6f, 0x6c, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x52, 0x65, 0x71, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x61, 0x74, 0x74, 0x65, 0x72, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x70, 0x61, 0x74, 0x74, 0x65, 0x72, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x79, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x69, 0x63, 0x6b, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x04, 0x73, 0x69, 0x63, 0x6b, 0x22, 0x40, 0x0a, 0x0e, 0x50, 0x6f, 0x6f, 0x6c, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x52, 0x65, 0x73, 0x70, 0x12, 0x2e, 0x0a, 0x06, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x72, 0x6f, 0x6c, 0x6c, 0x6f, 0x75, 0x74, 0x2e, 0x6c, 0x62, 0x2e, 0x50, 0x6f, 0x6f, 0x6c, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x52, 0x06, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x2a, 0x26, 0x0a, 0x08, 0x50, 0x6f, 0x6f, 0x6c, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0e, 0x0a, 0x0a, 0x50, 0x54, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x50, 0x54, 0x5f, 0x50, 0x32, 0x43, 0x10, 0x01, 0x2a, 0x48, 0x0a, 0x0a, 0x50, 0x6f, 0x6f, 0x6c, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x0e, 0x0a, 0x0a, 0x50, 0x53, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x50, 0x53, 0x5f, 0x46, 0x55, 0x4c, 0x4c, 0x10, 0x01, 0x12, 0x0c, 0x0a, 0x08, 0x50, 0x53, 0x5f, 0x45, 0x4d, 0x50, 0x54, 0x59, 0x10, 0x02, 0x12, 0x0f, 0x0a, 0x0b, 0x50, 0x53, 0x5f, 0x44, 0x45, 0x47, 0x52, 0x41, 0x44, 0x45, 0x44, 0x10, 0x03, 0x2a, 0x3c, 0x0a, 0x0d, 0x42, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x0e, 0x0a, 0x0a, 0x42, 0x53, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x0e, 0x0a, 0x0a, 0x42, 0x53, 0x5f, 0x48, 0x45, 0x41, 0x4c, 0x54, 0x48, 0x59, 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, 0x42, 0x53, 0x5f, 0x53, 0x49, 0x43, 0x4b, 0x10, 0x02, 0x32, 0xf1, 0x02, 0x0a, 0x0c, 0x4c, 0x6f, 0x61, 0x64, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x72, 0x12, 0x3c, 0x0a, 0x07, 0x41, 0x64, 0x64, 0x50, 0x6f, 0x6f, 0x6c, 0x12, 0x16, 0x2e, 0x72, 0x6f, 0x6c, 0x6c, 0x6f, 0x75, 0x74, 0x2e, 0x6c, 0x62, 0x2e, 0x41, 0x64, 0x64, 0x50, 0x6f, 0x6f, 0x6c, 0x52, 0x65, 0x71, 0x1a, 0x17, 0x2e, 0x72, 0x6f, 0x6c, 0x6c, 0x6f, 0x75, 0x74, 0x2e, 0x6c, 0x62, 0x2e, 0x41, 0x64, 0x64, 0x50, 0x6f, 0x6f, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x12, 0x45, 0x0a, 0x0a, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x50, 0x6f, 0x6f, 0x6c, 0x12, 0x19, 0x2e, 0x72, 0x6f, 0x6c, 0x6c, 0x6f, 0x75, 0x74, 0x2e, 0x6c, 0x62, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x50, 0x6f, 0x6f, 0x6c, 0x52, 0x65, 0x71, 0x1a, 0x1a, 0x2e, 0x72, 0x6f, 0x6c, 0x6c, 0x6f, 0x75, 0x74, 0x2e, 0x6c, 0x62, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x50, 0x6f, 0x6f, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x12, 0x45, 0x0a, 0x0a, 0x41, 0x64, 0x64, 0x42, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x12, 0x19, 0x2e, 0x72, 0x6f, 0x6c, 0x6c, 0x6f, 0x75, 0x74, 0x2e, 0x6c, 0x62, 0x2e, 0x41, 0x64, 0x64, 0x42, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x52, 0x65, 0x71, 0x1a, 0x1a, 0x2e, 0x72, 0x6f, 0x6c, 0x6c, 0x6f, 0x75, 0x74, 0x2e, 0x6c, 0x62, 0x2e, 0x41, 0x64, 0x64, 0x42, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x12, 0x4e, 0x0a, 0x0d, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x42, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x12, 0x1c, 0x2e, 0x72, 0x6f, 0x6c, 0x6c, 0x6f, 0x75, 0x74, 0x2e, 0x6c, 0x62, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x42, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x52, 0x65, 0x71, 0x1a, 0x1d, 0x2e, 0x72, 0x6f, 0x6c, 0x6c, 0x6f, 0x75, 0x74, 0x2e, 0x6c, 0x62, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x42, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x12, 0x45, 0x0a, 0x0a, 0x50, 0x6f, 0x6f, 0x6c, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x12, 0x19, 0x2e, 0x72, 0x6f, 0x6c, 0x6c, 0x6f, 0x75, 0x74, 0x2e, 0x6c, 0x62, 0x2e, 0x50, 0x6f, 0x6f, 0x6c, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x52, 0x65, 0x71, 0x1a, 0x1a, 0x2e, 0x72, 0x6f, 0x6c, 0x6c, 0x6f, 0x75, 0x74, 0x2e, 0x6c, 0x62, 0x2e, 0x50, 0x6f, 0x6f, 0x6c, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x42, 0x52, 0x5a, 0x50, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x50, 0x61, 0x63, 0x6b, 0x74, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x69, 0x6e, 0x67, 0x2f, 0x47, 0x6f, 0x2d, 0x66, 0x6f, 0x72, 0x2d, 0x44, 0x65, 0x76, 0x4f, 0x70, 0x73, 0x2f, 0x63, 0x68, 0x61, 0x70, 0x74, 0x65, 0x72, 0x2f, 0x31, 0x2f, 0x73, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x36, 0x2f, 0x72, 0x6f, 0x6c, 0x6c, 0x6f, 0x75, 0x74, 0x2f, 0x6c, 0x62, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x6c, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( file_lb_proto_rawDescOnce sync.Once file_lb_proto_rawDescData = file_lb_proto_rawDesc ) func file_lb_proto_rawDescGZIP() []byte { file_lb_proto_rawDescOnce.Do(func() { file_lb_proto_rawDescData = protoimpl.X.CompressGZIP(file_lb_proto_rawDescData) }) return file_lb_proto_rawDescData } var file_lb_proto_enumTypes = make([]protoimpl.EnumInfo, 3) var file_lb_proto_msgTypes = make([]protoimpl.MessageInfo, 17) var file_lb_proto_goTypes = []interface{}{ (PoolType)(0), // 0: rollout.lb.PoolType (PoolStatus)(0), // 1: rollout.lb.PoolStatus (BackendStatus)(0), // 2: rollout.lb.BackendStatus (*HealthChecks)(nil), // 3: rollout.lb.HealthChecks (*HealthCheck)(nil), // 4: rollout.lb.HealthCheck (*StatusCheck)(nil), // 5: rollout.lb.StatusCheck (*Backend)(nil), // 6: rollout.lb.Backend (*IPBackend)(nil), // 7: rollout.lb.IPBackend (*PoolHealth)(nil), // 8: rollout.lb.PoolHealth (*BackendHealth)(nil), // 9: rollout.lb.BackendHealth (*AddPoolReq)(nil), // 10: rollout.lb.AddPoolReq (*AddPoolResp)(nil), // 11: rollout.lb.AddPoolResp (*RemovePoolReq)(nil), // 12: rollout.lb.RemovePoolReq (*RemovePoolResp)(nil), // 13: rollout.lb.RemovePoolResp (*AddBackendReq)(nil), // 14: rollout.lb.AddBackendReq (*AddBackendResp)(nil), // 15: rollout.lb.AddBackendResp (*RemoveBackendReq)(nil), // 16: rollout.lb.RemoveBackendReq (*RemoveBackendResp)(nil), // 17: rollout.lb.RemoveBackendResp (*PoolHealthReq)(nil), // 18: rollout.lb.PoolHealthReq (*PoolHealthResp)(nil), // 19: rollout.lb.PoolHealthResp } var file_lb_proto_depIdxs = []int32{ 4, // 0: rollout.lb.HealthChecks.health_checks:type_name -> rollout.lb.HealthCheck 5, // 1: rollout.lb.HealthCheck.status_check:type_name -> rollout.lb.StatusCheck 7, // 2: rollout.lb.Backend.ip_backend:type_name -> rollout.lb.IPBackend 1, // 3: rollout.lb.PoolHealth.status:type_name -> rollout.lb.PoolStatus 9, // 4: rollout.lb.PoolHealth.backends:type_name -> rollout.lb.BackendHealth 6, // 5: rollout.lb.BackendHealth.backend:type_name -> rollout.lb.Backend 2, // 6: rollout.lb.BackendHealth.status:type_name -> rollout.lb.BackendStatus 0, // 7: rollout.lb.AddPoolReq.pool_type:type_name -> rollout.lb.PoolType 3, // 8: rollout.lb.AddPoolReq.health_checks:type_name -> rollout.lb.HealthChecks 6, // 9: rollout.lb.AddBackendReq.backend:type_name -> rollout.lb.Backend 6, // 10: rollout.lb.RemoveBackendReq.backend:type_name -> rollout.lb.Backend 8, // 11: rollout.lb.PoolHealthResp.health:type_name -> rollout.lb.PoolHealth 10, // 12: rollout.lb.LoadBalancer.AddPool:input_type -> rollout.lb.AddPoolReq 12, // 13: rollout.lb.LoadBalancer.RemovePool:input_type -> rollout.lb.RemovePoolReq 14, // 14: rollout.lb.LoadBalancer.AddBackend:input_type -> rollout.lb.AddBackendReq 16, // 15: rollout.lb.LoadBalancer.RemoveBackend:input_type -> rollout.lb.RemoveBackendReq 18, // 16: rollout.lb.LoadBalancer.PoolHealth:input_type -> rollout.lb.PoolHealthReq 11, // 17: rollout.lb.LoadBalancer.AddPool:output_type -> rollout.lb.AddPoolResp 13, // 18: rollout.lb.LoadBalancer.RemovePool:output_type -> rollout.lb.RemovePoolResp 15, // 19: rollout.lb.LoadBalancer.AddBackend:output_type -> rollout.lb.AddBackendResp 17, // 20: rollout.lb.LoadBalancer.RemoveBackend:output_type -> rollout.lb.RemoveBackendResp 19, // 21: rollout.lb.LoadBalancer.PoolHealth:output_type -> rollout.lb.PoolHealthResp 17, // [17:22] is the sub-list for method output_type 12, // [12:17] is the sub-list for method input_type 12, // [12:12] is the sub-list for extension type_name 12, // [12:12] is the sub-list for extension extendee 0, // [0:12] is the sub-list for field type_name } func init() { file_lb_proto_init() } func file_lb_proto_init() { if File_lb_proto != nil { return } if !protoimpl.UnsafeEnabled { file_lb_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*HealthChecks); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_lb_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*HealthCheck); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_lb_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*StatusCheck); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_lb_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Backend); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_lb_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*IPBackend); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_lb_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*PoolHealth); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_lb_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*BackendHealth); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_lb_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*AddPoolReq); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_lb_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*AddPoolResp); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_lb_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*RemovePoolReq); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_lb_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*RemovePoolResp); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_lb_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*AddBackendReq); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_lb_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*AddBackendResp); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_lb_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*RemoveBackendReq); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_lb_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*RemoveBackendResp); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_lb_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*PoolHealthReq); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_lb_proto_msgTypes[16].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*PoolHealthResp); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } } file_lb_proto_msgTypes[1].OneofWrappers = []interface{}{ (*HealthCheck_StatusCheck)(nil), } file_lb_proto_msgTypes[3].OneofWrappers = []interface{}{ (*Backend_IpBackend)(nil), } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_lb_proto_rawDesc, NumEnums: 3, NumMessages: 17, NumExtensions: 0, NumServices: 1, }, GoTypes: file_lb_proto_goTypes, DependencyIndexes: file_lb_proto_depIdxs, EnumInfos: file_lb_proto_enumTypes, MessageInfos: file_lb_proto_msgTypes, }.Build() File_lb_proto = out.File file_lb_proto_rawDesc = nil file_lb_proto_goTypes = nil file_lb_proto_depIdxs = nil } ================================================ FILE: chapter/8/rollout/lb/proto/lb.proto ================================================ syntax = "proto3"; package rollout.lb; option go_package = "github.com/PacktPublishing/Go-for-DevOps/chapter/6/rollout/lb/proto/lb"; // PoolType indicates what type of pool to use. This influences // how the pool distributes its workload. enum PoolType { // This indicates an error by the user. PT_UNKNOWN = 0; // The power of 2 choices selection pool. PT_P2C = 1; } enum PoolStatus { // This indicates an error by the developers. PS_UNKNOWN = 0; // The pool has all its configured backends working. PS_FULL = 1; // The pool has no configured backends. PS_EMPTY = 2; // The pool has one or more configured backends not working. PS_DEGRADED = 3; } // BackendStatus details the status of a backend. enum BackendStatus { // This indicates an error by the developers. BS_UNKNOWN = 0; // The node is healthy according to its health checks. BS_HEALTHY = 1; // The node is sick according to its health checks. BS_SICK = 2; } message HealthChecks { repeated HealthCheck health_checks = 1; int32 interval_secs = 2; } message HealthCheck { oneof health_check { StatusCheck status_check = 1; } } // StatusCheck is a check against a URL path. That path must // emit in its body one of the healthy_values or it fails. message StatusCheck { string url_path = 1; repeated string healthy_values = 2; } message Backend { oneof backend { IPBackend ip_backend = 1; } } // IPBackend provides a Backend that has its endpoint as an ip:port. message IPBackend { // The IP(v4 or v6) to use as the host. string ip = 1; // The port number to connect on. int32 port = 2; // The url_path to forward to. Generally this is empty. string url_path = 3; } // PoolHealth details the health status of a pool. message PoolHealth { // The status of the pool. PoolStatus status = 1; // The pool's backend health status. This can contain all backends // or only sick/healthy backends, depending on the request. repeated BackendHealth backends = 2; } // BackendHealth details the health status of a backend. message BackendHealth { Backend backend = 1; BackendStatus status = 2; } // AddPoolReq requests to create a pool for handling requests. message AddPoolReq { // The URL pattern to direct traffic too. string pattern = 1; // The type of traffic distribution pool to use. PoolType pool_type = 2; // Health checks to against backends. HealthChecks health_checks = 4; } // AddPoolResp is the response to adding a pool. message AddPoolResp {} // RemovePoolReq is used to remove a pool by its pattern. message RemovePoolReq { // Pattern is the pattern to remove. string pattern = 1; } // RemovePoolResp is the response to removing a pool. message RemovePoolResp {} // AddBackendReq adds a backend to a pool. message AddBackendReq { // The pattern to add a backend to. string pattern = 1; // The backend to add to the pool. Backend backend = 2; } message AddBackendResp {} // RemoveBackendReq is used to remove a Backend from a Pool. message RemoveBackendReq { // The pool pattern to remove the backend from. string pattern = 1; // The backend to remove from the pool. Backend backend = 2; } // RemoveBackendResp is the response to removing a Backend. message RemoveBackendResp {} // PoolHealthReq is a request to get the health of a pool. message PoolHealthReq { // Pattern is the pool pattern you are getting health for. string pattern = 1; // If set to true, will return the backends that are healthy. bool healthy = 3; // If set to true, will return backends that are sick. bool sick = 4; } message PoolHealthResp { PoolHealth health = 1; } service LoadBalancer { rpc AddPool(AddPoolReq) returns (AddPoolResp) {}; rpc RemovePool(RemovePoolReq) returns (RemovePoolResp) {}; rpc AddBackend(AddBackendReq) returns (AddBackendResp) {}; rpc RemoveBackend(RemoveBackendReq) returns (RemoveBackendResp) {}; rpc PoolHealth(PoolHealthReq) returns (PoolHealthResp) {}; } ================================================ FILE: chapter/8/rollout/lb/proto/lb_grpc.pb.go ================================================ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. package lb 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.32.0 or later. const _ = grpc.SupportPackageIsVersion7 // LoadBalancerClient is the client API for LoadBalancer 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 LoadBalancerClient interface { AddPool(ctx context.Context, in *AddPoolReq, opts ...grpc.CallOption) (*AddPoolResp, error) RemovePool(ctx context.Context, in *RemovePoolReq, opts ...grpc.CallOption) (*RemovePoolResp, error) AddBackend(ctx context.Context, in *AddBackendReq, opts ...grpc.CallOption) (*AddBackendResp, error) RemoveBackend(ctx context.Context, in *RemoveBackendReq, opts ...grpc.CallOption) (*RemoveBackendResp, error) PoolHealth(ctx context.Context, in *PoolHealthReq, opts ...grpc.CallOption) (*PoolHealthResp, error) } type loadBalancerClient struct { cc grpc.ClientConnInterface } func NewLoadBalancerClient(cc grpc.ClientConnInterface) LoadBalancerClient { return &loadBalancerClient{cc} } func (c *loadBalancerClient) AddPool(ctx context.Context, in *AddPoolReq, opts ...grpc.CallOption) (*AddPoolResp, error) { out := new(AddPoolResp) err := c.cc.Invoke(ctx, "/rollout.lb.LoadBalancer/AddPool", in, out, opts...) if err != nil { return nil, err } return out, nil } func (c *loadBalancerClient) RemovePool(ctx context.Context, in *RemovePoolReq, opts ...grpc.CallOption) (*RemovePoolResp, error) { out := new(RemovePoolResp) err := c.cc.Invoke(ctx, "/rollout.lb.LoadBalancer/RemovePool", in, out, opts...) if err != nil { return nil, err } return out, nil } func (c *loadBalancerClient) AddBackend(ctx context.Context, in *AddBackendReq, opts ...grpc.CallOption) (*AddBackendResp, error) { out := new(AddBackendResp) err := c.cc.Invoke(ctx, "/rollout.lb.LoadBalancer/AddBackend", in, out, opts...) if err != nil { return nil, err } return out, nil } func (c *loadBalancerClient) RemoveBackend(ctx context.Context, in *RemoveBackendReq, opts ...grpc.CallOption) (*RemoveBackendResp, error) { out := new(RemoveBackendResp) err := c.cc.Invoke(ctx, "/rollout.lb.LoadBalancer/RemoveBackend", in, out, opts...) if err != nil { return nil, err } return out, nil } func (c *loadBalancerClient) PoolHealth(ctx context.Context, in *PoolHealthReq, opts ...grpc.CallOption) (*PoolHealthResp, error) { out := new(PoolHealthResp) err := c.cc.Invoke(ctx, "/rollout.lb.LoadBalancer/PoolHealth", in, out, opts...) if err != nil { return nil, err } return out, nil } // LoadBalancerServer is the server API for LoadBalancer service. // All implementations must embed UnimplementedLoadBalancerServer // for forward compatibility type LoadBalancerServer interface { AddPool(context.Context, *AddPoolReq) (*AddPoolResp, error) RemovePool(context.Context, *RemovePoolReq) (*RemovePoolResp, error) AddBackend(context.Context, *AddBackendReq) (*AddBackendResp, error) RemoveBackend(context.Context, *RemoveBackendReq) (*RemoveBackendResp, error) PoolHealth(context.Context, *PoolHealthReq) (*PoolHealthResp, error) mustEmbedUnimplementedLoadBalancerServer() } // UnimplementedLoadBalancerServer must be embedded to have forward compatible implementations. type UnimplementedLoadBalancerServer struct { } func (UnimplementedLoadBalancerServer) AddPool(context.Context, *AddPoolReq) (*AddPoolResp, error) { return nil, status.Errorf(codes.Unimplemented, "method AddPool not implemented") } func (UnimplementedLoadBalancerServer) RemovePool(context.Context, *RemovePoolReq) (*RemovePoolResp, error) { return nil, status.Errorf(codes.Unimplemented, "method RemovePool not implemented") } func (UnimplementedLoadBalancerServer) AddBackend(context.Context, *AddBackendReq) (*AddBackendResp, error) { return nil, status.Errorf(codes.Unimplemented, "method AddBackend not implemented") } func (UnimplementedLoadBalancerServer) RemoveBackend(context.Context, *RemoveBackendReq) (*RemoveBackendResp, error) { return nil, status.Errorf(codes.Unimplemented, "method RemoveBackend not implemented") } func (UnimplementedLoadBalancerServer) PoolHealth(context.Context, *PoolHealthReq) (*PoolHealthResp, error) { return nil, status.Errorf(codes.Unimplemented, "method PoolHealth not implemented") } func (UnimplementedLoadBalancerServer) mustEmbedUnimplementedLoadBalancerServer() {} // UnsafeLoadBalancerServer may be embedded to opt out of forward compatibility for this service. // Use of this interface is not recommended, as added methods to LoadBalancerServer will // result in compilation errors. type UnsafeLoadBalancerServer interface { mustEmbedUnimplementedLoadBalancerServer() } func RegisterLoadBalancerServer(s grpc.ServiceRegistrar, srv LoadBalancerServer) { s.RegisterService(&LoadBalancer_ServiceDesc, srv) } func _LoadBalancer_AddPool_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(AddPoolReq) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(LoadBalancerServer).AddPool(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: "/rollout.lb.LoadBalancer/AddPool", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(LoadBalancerServer).AddPool(ctx, req.(*AddPoolReq)) } return interceptor(ctx, in, info, handler) } func _LoadBalancer_RemovePool_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(RemovePoolReq) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(LoadBalancerServer).RemovePool(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: "/rollout.lb.LoadBalancer/RemovePool", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(LoadBalancerServer).RemovePool(ctx, req.(*RemovePoolReq)) } return interceptor(ctx, in, info, handler) } func _LoadBalancer_AddBackend_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(AddBackendReq) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(LoadBalancerServer).AddBackend(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: "/rollout.lb.LoadBalancer/AddBackend", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(LoadBalancerServer).AddBackend(ctx, req.(*AddBackendReq)) } return interceptor(ctx, in, info, handler) } func _LoadBalancer_RemoveBackend_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(RemoveBackendReq) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(LoadBalancerServer).RemoveBackend(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: "/rollout.lb.LoadBalancer/RemoveBackend", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(LoadBalancerServer).RemoveBackend(ctx, req.(*RemoveBackendReq)) } return interceptor(ctx, in, info, handler) } func _LoadBalancer_PoolHealth_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(PoolHealthReq) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(LoadBalancerServer).PoolHealth(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: "/rollout.lb.LoadBalancer/PoolHealth", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(LoadBalancerServer).PoolHealth(ctx, req.(*PoolHealthReq)) } return interceptor(ctx, in, info, handler) } // LoadBalancer_ServiceDesc is the grpc.ServiceDesc for LoadBalancer service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) var LoadBalancer_ServiceDesc = grpc.ServiceDesc{ ServiceName: "rollout.lb.LoadBalancer", HandlerType: (*LoadBalancerServer)(nil), Methods: []grpc.MethodDesc{ { MethodName: "AddPool", Handler: _LoadBalancer_AddPool_Handler, }, { MethodName: "RemovePool", Handler: _LoadBalancer_RemovePool_Handler, }, { MethodName: "AddBackend", Handler: _LoadBalancer_AddBackend_Handler, }, { MethodName: "RemoveBackend", Handler: _LoadBalancer_RemoveBackend_Handler, }, { MethodName: "PoolHealth", Handler: _LoadBalancer_PoolHealth_Handler, }, }, Streams: []grpc.StreamDesc{}, Metadata: "lb.proto", } ================================================ FILE: chapter/8/rollout/lb/sample/web/main.go ================================================ package main import ( "flag" "fmt" "log" "net/http" "github.com/google/uuid" ) var ( node = flag.String("node", uuid.New().String(), "The node name") port = flag.Int("port", 8082, "The port to run on") ) func main() { flag.Parse() http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Hello web from node "+*node) }) http.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "ok") }) log.Println("running on port: ", *port) log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", *port), nil)) } ================================================ FILE: chapter/8/rollout/lb/server/grpc/server.go ================================================ // Package grpc implements a gRPC server for controlling our HTTP load balancer. package grpc import ( "context" "fmt" "log" "net" "strings" "sync" "time" "github.com/PacktPublishing/Go-for-DevOps/chapter/8/rollout/lb/server/http" "google.golang.org/grpc" pb "github.com/PacktPublishing/Go-for-DevOps/chapter/8/rollout/lb/proto" ) // Server is a gRPC server for interacting with the load balancer. type Server struct { pb.UnimplementedLoadBalancerServer addr string lb *http.LoadBalancer grpcServer *grpc.Server mu sync.Mutex } // New creates a new instance of Server. func New(addr string, lb *http.LoadBalancer) (*Server, error) { var opts []grpc.ServerOption s := &Server{ addr: addr, lb: lb, grpcServer: grpc.NewServer(opts...), } s.grpcServer.RegisterService(&pb.LoadBalancer_ServiceDesc, s) return s, nil } // Start starts the server and blocks. func (s *Server) Start() error { s.mu.Lock() defer s.mu.Unlock() lis, err := net.Listen("tcp", s.addr) if err != nil { return err } return s.grpcServer.Serve(lis) } // Stop stops the server. func (s *Server) Stop() { s.mu.Lock() defer s.mu.Unlock() s.grpcServer.Stop() } // AddPool adds a pool as defined in req. func (s *Server) AddPool(ctx context.Context, req *pb.AddPoolReq) (*pb.AddPoolResp, error) { log.Println("adding pool") if strings.TrimSpace(req.Pattern) == "" { return nil, fmt.Errorf("pattern must not be empty") } if req.PoolType == pb.PoolType_PT_UNKNOWN { return nil, fmt.Errorf("must set a pool_type") } if len(req.HealthChecks.HealthChecks) == 0 { return nil, fmt.Errorf("must have at least 1 health_check") } var hcs []http.HealthCheck for _, hc := range req.HealthChecks.HealthChecks { switch { case hc.GetStatusCheck() != nil: scr := hc.GetStatusCheck() sc, err := http.StatusCheck(scr.UrlPath, scr.HealthyValues) if err != nil { return nil, err } hcs = append(hcs, sc) default: return nil, fmt.Errorf("a health_check is missing its concrete type") } } interval := time.Duration(req.HealthChecks.IntervalSecs) * time.Second var pool http.Pool switch req.PoolType { case pb.PoolType_PT_P2C: var err error pool, err = http.NewP2C( http.HealthMultiplexer(hcs...), interval, ) if err != nil { return nil, err } default: return nil, fmt.Errorf("unknown pool_type(%v", req.PoolType) } if err := s.lb.AddPool(req.Pattern, pool); err != nil { return nil, err } return &pb.AddPoolResp{}, nil } // RemovePool removes a pool as defined in req. func (s *Server) RemovePool(ctx context.Context, req *pb.RemovePoolReq) (*pb.RemovePoolResp, error) { if strings.TrimSpace(req.Pattern) == "" { return nil, fmt.Errorf("pattern must not be empty") } if err := s.lb.RemovePool(req.Pattern); err != nil { return nil, err } return &pb.RemovePoolResp{}, nil } // AddBackend adds a backend as defined in req. func (s *Server) AddBackend(ctx context.Context, req *pb.AddBackendReq) (*pb.AddBackendResp, error) { log.Println("adding backend") if strings.TrimSpace(req.Pattern) == "" { return nil, fmt.Errorf("pattern must not be empty") } pool, err := s.lb.GetPool(req.Pattern) if err != nil { return nil, err } var back http.Backend switch { case req.Backend.GetIpBackend() != nil: v := req.Backend.GetIpBackend() ip := net.ParseIP(v.Ip) if ip == nil { return nil, fmt.Errorf("backend ip is invalid") } if v.Port < 1 || v.Port > 65534 { return nil, fmt.Errorf("port is invalid") } b, err := http.NewIPBackend(ip, v.Port, v.UrlPath) if err != nil { return nil, err } back = b default: return nil, fmt.Errorf("a backend is missing its concrete type") } if err := pool.Add(ctx, back); err != nil { return nil, err } return &pb.AddBackendResp{}, nil } // RemoveBackend remoes a backend as defined in req. func (s *Server) RemoveBackend(ctx context.Context, req *pb.RemoveBackendReq) (*pb.RemoveBackendResp, error) { if strings.TrimSpace(req.Pattern) == "" { return nil, fmt.Errorf("pattern must not be empty") } var back http.Backend switch { case req.Backend.GetIpBackend() != nil: v := req.Backend.GetIpBackend() ip := net.ParseIP(v.Ip) if ip == nil { return nil, fmt.Errorf("backend ip is invalid") } if v.Port < 1 || v.Port > 65534 { return nil, fmt.Errorf("port is invalid") } b, err := http.NewIPBackend(ip, v.Port, v.UrlPath) if err != nil { return nil, err } back = b default: return nil, fmt.Errorf("a backend is missing its concrete type") } pool, err := s.lb.GetPool(req.Pattern) if err != nil { return nil, err } if err := pool.Remove(ctx, back); err != nil { return nil, err } return &pb.RemoveBackendResp{}, nil } // PoolHealth returns the health of a pool defined in req. func (s *Server) PoolHealth(ctx context.Context, req *pb.PoolHealthReq) (*pb.PoolHealthResp, error) { ph, err := s.lb.PoolHealth(ctx, req) if err != nil { return nil, err } return &pb.PoolHealthResp{Health: ph}, nil } ================================================ FILE: chapter/8/rollout/lb/server/http/p2c.go ================================================ /* This is an implementation of the power of 2 choices selction method, made popular in this paper: https://www.eecs.harvard.edu/~michaelm/postscripts/mythesis.pdf This paper is based on the work of: https://homes.cs.washington.edu/~karlin/papers/AzarBKU99.pdf */ package http import ( "context" "fmt" "log" "math/rand" "net/http" "sync" "sync/atomic" "time" pb "github.com/PacktPublishing/Go-for-DevOps/chapter/8/rollout/lb/proto" ) // weightedBackend implements Backend with a wrapper around another Backend. This // implementation adds a weight that can be used to help determine which Backend to // choose when doing a P2C. type weightedBackend struct { Backend weight int32 } func (w *weightedBackend) get() int32 { return atomic.LoadInt32(&w.weight) } func (w *weightedBackend) call() { w.Backend.call() atomic.AddInt32(&w.weight, 1) } func (w *weightedBackend) done() { w.Backend.done() i := atomic.AddInt32(&w.weight, -1) if i < 0 { panic("weightedBackend cannot be < 0") } } func (w *weightedBackend) handler() http.Handler { return http.HandlerFunc( func(wr http.ResponseWriter, r *http.Request) { w.call() defer w.done() w.Backend.handler().ServeHTTP(wr, r) }, ) } // P2C implements Pool using the Power of 2 choice selection method. type P2C struct { hc HealthCheck interval time.Duration mu sync.Mutex healthy, sick *atomic.Value // []*weightedBackend rand *rand.Rand done chan struct{} } // NewP2C creates a new P2C instance. hc is the health check // to perform on the backend to make sure its healthy and interval is how often to do // the health check. func NewP2C(hc HealthCheck, interval time.Duration) (*P2C, error) { sp := &P2C{ hc: hc, interval: interval, healthy: &atomic.Value{}, sick: &atomic.Value{}, rand: rand.New(rand.NewSource(time.Now().UnixNano())), done: make(chan struct{}), } sp.healthy.Store([]*weightedBackend{}) sp.sick.Store([]*weightedBackend{}) go sp.healthLoop() return sp, nil } // Close implements Pool.Close(). func (s *P2C) Close() error { close(s.done) return nil } // Add implements Pool.Add(). func (s *P2C) Add(ctx context.Context, b Backend) error { if ctx.Err() != nil { return ctx.Err() } if err := s.hc(ctx, b.url().String()); err != nil { b.setHealth(sick) return fmt.Errorf("backend is sick: %w", err) } b.setHealth(healthy) s.mu.Lock() defer s.mu.Unlock() if err := s.addToValue(&weightedBackend{Backend: b}, s.healthy); err != nil { return err } return nil } // Remove implements Pool.Remove(). func (s *P2C) Remove(ctx context.Context, b Backend) error { s.mu.Lock() defer s.mu.Unlock() s.removeFromValue(b, s.healthy) s.removeFromValue(b, s.sick) return nil } // Health implements Pool.Health(). func (s *P2C) Health(ctx context.Context, req *pb.PoolHealthReq) (*pb.PoolHealth, error) { status := pb.PoolStatus_PS_FULL healthy := s.healthy.Load().([]*weightedBackend) sick := s.sick.Load().([]*weightedBackend) healthyNodes := len(healthy) sickNodes := len(sick) if sickNodes == 0 && healthyNodes == 0 { return &pb.PoolHealth{ Status: pb.PoolStatus_PS_EMPTY, }, nil } if sickNodes > 0 { status = pb.PoolStatus_PS_DEGRADED } ph := &pb.PoolHealth{ Status: status, } if req.Healthy { for _, wb := range healthy { switch v := wb.Backend.(type) { case *IPBackend: h := &pb.BackendHealth{ Status: pb.BackendStatus_BS_HEALTHY, Backend: &pb.Backend{ Backend: &pb.Backend_IpBackend{ IpBackend: &pb.IPBackend{ Ip: v.ip.String(), Port: v.port, UrlPath: v.urlPath, }, }, }, } ph.Backends = append(ph.Backends, h) default: return nil, fmt.Errorf("an unknown healthy backend type found(%T)", wb.Backend) } } } if req.Sick { for _, wb := range sick { switch v := wb.Backend.(type) { case *IPBackend: h := &pb.BackendHealth{ Status: pb.BackendStatus_BS_SICK, Backend: &pb.Backend{ Backend: &pb.Backend_IpBackend{ IpBackend: &pb.IPBackend{ Ip: v.ip.String(), Port: v.port, UrlPath: v.urlPath, }, }, }, } ph.Backends = append(ph.Backends, h) default: return nil, fmt.Errorf("an unknown sick backend type found(%T)", wb.Backend) } } } return ph, nil } func (s *P2C) addToValue(b *weightedBackend, v *atomic.Value) error { backs := (*v).Load().([]*weightedBackend) n := make([]*weightedBackend, 0, len(backs)+1) for _, back := range backs { // This is quite slow, but... we should not be adding backends often // to a single instance. If this somehow becomes a bottleneck, we can // always calculate some hash on Add() to do checks on. if b.url().String() == back.url().String() { return fmt.Errorf("backend already exists") } n = append(n, back) } n = append(n, b) v.Store(n) return nil } func (s *P2C) removeFromValue(b Backend, v *atomic.Value) error { backs := v.Load().([]*weightedBackend) newCap := len(backs) - 1 if newCap < 0 { return nil // No way it exists } n := make([]*weightedBackend, 0, newCap) for _, back := range backs { if b.url().String() == back.url().String() { continue } n = append(n, back) } if len(backs) == len(n) { return fmt.Errorf("could not find backend(%s)", b.url().String()) } v.Store(n) return nil } // ServeHTTP implements Pool.ServeHTTP(). func (s *P2C) ServeHTTP(w http.ResponseWriter, r *http.Request) { backs := s.healthy.Load().([]*weightedBackend) if len(backs) == 0 { http.Error(w, "no backends available", http.StatusInternalServerError) return } x := s.rand.Int31n(int32(len(backs))) y := s.rand.Int31n(int32(len(backs))) if backs[x].weight < backs[y].weight { backs[x].handler().ServeHTTP(w, r) return } backs[y].handler().ServeHTTP(w, r) } func (s *P2C) healthLoop() { for { select { case <-s.done: return case <-time.After(s.interval): ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) s.healthChecks(ctx) cancel() } } } func (s *P2C) healthChecks(ctx context.Context) { s.mu.Lock() defer s.mu.Unlock() wg := sync.WaitGroup{} for _, b := range s.healthy.Load().([]*weightedBackend) { b := b wg.Add(1) go func() { defer wg.Done() if err := s.hc(ctx, b.url().String()); err != nil { s.healthyToSick(b) } }() } for _, b := range s.sick.Load().([]*weightedBackend) { b := b wg.Add(1) go func() { defer wg.Done() if err := s.hc(ctx, b.url().String()); err == nil { s.sickToHealthy(b) } }() } wg.Wait() } func (s *P2C) healthyToSick(b *weightedBackend) { log.Printf("backend %s became sick", b.url()) b.setHealth(sick) if err := s.removeFromValue(b, s.healthy); err != nil { log.Println(err) return } if err := s.addToValue(b, s.sick); err != nil { panic(err) } } func (s *P2C) sickToHealthy(b *weightedBackend) { log.Printf("backend %s became healthy", b.url()) b.setHealth(healthy) if err := s.removeFromValue(b, s.sick); err != nil { log.Println(err) return } if err := s.addToValue(b, s.healthy); err != nil { panic(err) } } ================================================ FILE: chapter/8/rollout/lb/server/http/server.go ================================================ package http import ( "bytes" "context" "fmt" "io" "net" "net/http" "net/http/httputil" "net/url" "sync" "sync/atomic" "time" pb "github.com/PacktPublishing/Go-for-DevOps/chapter/8/rollout/lb/proto" ) type routeHandler struct { muxStore atomic.Value // *http.ServeMux } func newRouteHandler(mux *http.ServeMux) *routeHandler { if mux == nil { panic("mux cannot be nil") } r := &routeHandler{} r.muxStore.Store(mux) return r } func (r *routeHandler) mux() *http.ServeMux { return r.muxStore.Load().(*http.ServeMux) } func (r *routeHandler) replace(mux *http.ServeMux) { if mux == nil { return } r.muxStore.Store(mux) } func (r *routeHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { r.muxStore.Load().(http.Handler).ServeHTTP(w, req) } func newServ(handler *routeHandler) *http.Server { return &http.Server{ Handler: handler, IdleTimeout: 5 * time.Second, MaxHeaderBytes: 1024 * 1000, } } // LoadBalancer is an HTTP reverse proxy load balancer. type LoadBalancer struct { mu sync.Mutex pools map[string]Pool handler *routeHandler serv *http.Server } // New creates a new LoadBalancer instance. func New() (*LoadBalancer, error) { handler := newRouteHandler(http.NewServeMux()) return &LoadBalancer{ pools: map[string]Pool{}, handler: handler, serv: newServ(handler), }, nil } // AddPool adds a pool of backends that serve the serveURL listed here. If a pattern // is added more than once, this will panic. func (l *LoadBalancer) AddPool(pattern string, pool Pool) error { l.mu.Lock() defer l.mu.Unlock() if _, ok := l.pools[pattern]; ok { return fmt.Errorf("pattern(%s) is already registered", pattern) } l.pools[pattern] = pool l.handler.mux().Handle(pattern, pool) return nil } // GetPool returns a pool by its pattern. func (l *LoadBalancer) GetPool(pattern string) (Pool, error) { l.mu.Lock() defer l.mu.Unlock() p, ok := l.pools[pattern] if ok { return p, nil } return nil, fmt.Errorf("pool(%s) not found", pattern) } // RemovePool removes a pool of backends that serve a pattern. If the pattern does not // exist, the error is still nil. func (l *LoadBalancer) RemovePool(pattern string) error { l.mu.Lock() defer l.mu.Unlock() p, ok := l.pools[pattern] if !ok { return nil } p.Close() delete(l.pools, pattern) mux := http.NewServeMux() for k, v := range l.pools { mux.Handle(k, v) } l.handler.replace(mux) return nil } // PoolHealth returns the health of a pool as defined in the req. func (l *LoadBalancer) PoolHealth(ctx context.Context, req *pb.PoolHealthReq) (*pb.PoolHealth, error) { l.mu.Lock() p, ok := l.pools[req.Pattern] l.mu.Unlock() if !ok { return nil, fmt.Errorf("pool not found") } return p.Health(ctx, req) } // Serve will serve HTTP traffic(non-TLS) on lis. func (l *LoadBalancer) Serve(lis net.Listener) error { return l.serv.Serve(lis) } // ServeTLS will serve HTTPS traffic on lis. See http.Server.ServeTLS for more documentation. func (l *LoadBalancer) ServeTLS(lis net.Listener, certFile, keyFile string) error { return l.serv.ServeTLS(lis, certFile, keyFile) } // Pool represents a set of backends that serve a URL. type Pool interface { // Add adds a new Backend to the pool. The Backend must be healthy. Add(ctx context.Context, b Backend) error // Remove removes a backend from the loadbalancer. Remove(ctx context.Context, b Backend) error // Health returns the health of a pool. Health(ctx context.Context, req *pb.PoolHealthReq) (*pb.PoolHealth, error) // Close closes the pool. It should not be used after this. Close() error // ServeHTTP implements http.Handler. ServeHTTP(w http.ResponseWriter, r *http.Request) } // HealthCheck provides a health check for a backend. The state passed is the // current state of the backend. If state is Healthy but fails the check, this // will cause the backend to be removed from service . If state is Sick and // passes the check, the backend will be returned to service. type HealthCheck func(ctx context.Context, endpoint string) error // HealthMultiplexer allows combining multiple HealthCheck(s) together. func HealthMultiplexer(healthChecks ...HealthCheck) HealthCheck { return func(ctx context.Context, endpoint string) error { ctx, cancel := context.WithCancel(ctx) defer cancel() results := make(chan error, len(healthChecks)) for _, hc := range healthChecks { hc := hc go func() { results <- hc(ctx, endpoint) }() } for i := 0; i < len(healthChecks); i++ { result := <-results if result != nil { return result } } return nil } } // StatusCheck returns a HealthCheck that checks the status func StatusCheck(urlPath string, healthyValues []string) (HealthCheck, error) { if len(healthyValues) == 0 { return nil, fmt.Errorf("must provide at least one healthy value") } return func(ctx context.Context, endpoint string) error { u, err := url.Parse(endpoint) if err != nil { return err } u.Scheme = "http" u.Path = urlPath ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) defer cancel() c := &http.Client{} req, err := http.NewRequestWithContext(ctx, "GET", u.String(), nil) if err != nil { return err } resp, err := c.Do(req) if err != nil { return err } defer resp.Body.Close() b, err := io.ReadAll(resp.Body) if err != nil { return err } b = bytes.TrimSpace(b) for _, v := range healthyValues { if len(v) != len(b) { continue } if string(b) == v { return nil } } return fmt.Errorf("not healthy, got status(%s)", b) }, nil } // healthState is the health state of a backend. type healthState int8 const ( unknownHS healthState = 0 healthy healthState = 1 sick healthState = 2 ) type Backend interface { // url is the URL of the backend. url() *url.URL // setSick marks the node as sick. setHealth(healthState) // health returns the current healthState health() healthState // call is called before a backend is used. call() // done is called after a backend is used. done() // handler provides the backends http.Handler. handler() http.Handler } // IPBackend provides a backend to our proxy that will use ip:port as the backend. // This gives us static backends that isolate us from DNS changes or failures. type IPBackend struct { ip net.IP port int32 urlPath string healthState atomic.Value // HealthState endpoint string u *url.URL handle *httputil.ReverseProxy } // NewIPBackend is the constructor for IPBackend. func NewIPBackend(ip net.IP, port int32, urlPath string) (*IPBackend, error) { i := &IPBackend{ ip: ip, port: port, endpoint: fmt.Sprintf("%s:%d", ip, port), } i.healthState.Store(unknownHS) i.resolveURL() i.handle = httputil.NewSingleHostReverseProxy(i.u) if err := i.validate(); err != nil { return nil, err } return i, nil } func (i *IPBackend) validate() error { if i.ip.To4() == nil && i.ip.To16() == nil { return fmt.Errorf("ip %q was not valid", i.ip) } if i.port < 1 || i.port > 65534 { return fmt.Errorf("port %d was not valid", i.port) } return nil } func (i *IPBackend) url() *url.URL { return i.u } func (i *IPBackend) setHealth(hs healthState) { i.healthState.Store(hs) } func (i *IPBackend) health() healthState { return i.healthState.Load().(healthState) } func (i *IPBackend) call() {} // not needed func (i *IPBackend) done() {} // not needed func (i *IPBackend) handler() http.Handler { return i.handle } func (i *IPBackend) resolveURL() error { u, err := url.Parse(i.urlPath) if err != nil { return err } base, err := url.Parse("http://" + i.endpoint) if err != nil { return err } i.u = base.ResolveReference(u) return nil } ================================================ FILE: chapter/8/rollout/rollout.go ================================================ package main import ( "context" "encoding/json" "flag" "fmt" "log" "net" "os" "time" "github.com/PacktPublishing/Go-for-DevOps/chapter/8/rollout/lb/client" "github.com/fatih/color" "github.com/rodaine/table" "golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh/agent" pb "github.com/PacktPublishing/Go-for-DevOps/chapter/8/rollout/lb/proto" ) var ( keyFile = flag.String("keyFile", "", "The key file to use for SSH connections. If not set, uses the SSH agent.") ) var ( headerFmt = color.New(color.FgGreen, color.Underline).SprintfFunc() columnFmt = color.New(color.FgYellow).SprintfFunc() ) func main() { flag.Parse() ctx := context.Background() config, wf, err := setup() if err != nil { color.Red("Setup Error: %s", err) os.Exit(1) } err = getSSHConfig(config) if err != nil { color.Red("SSH setup error: %s", err) os.Exit(1) } // If the load balancer doesn't have pool "/", set one up. if _, err := wf.lb.PoolHealth(ctx, "/", false, false); err != nil { err := wf.lb.AddPool( ctx, "/", pb.PoolType_PT_P2C, client.HealthChecks{ HealthChecks: []client.HealthCheck{ client.StatusCheck{ URLPath: "/healthz", HealthyValues: []string{"ok", "OK"}, }, }, Interval: 5 * time.Second, }, ) if err != nil { color.Red("LB did not have pool `/` and couldn't create it: %s", err) os.Exit(1) } color.Blue("Setup LB with pool `/`") } color.Red("Starting Workflow") if err := wf.run(ctx); err != nil { status := wf.status() color.Red("Workflow Failed: %s", status.endState) var tbl table.Table if status.endState == esPreconditionFailure { tbl = table.New("Failed State", "Error") tbl.WithHeaderFormatter(headerFmt).WithFirstColumnFormatter(columnFmt) tbl.AddRow("Precondition", err) } else { tbl = table.New("Endpoint", "Failed State", "Error") tbl.WithHeaderFormatter(headerFmt).WithFirstColumnFormatter(columnFmt) for _, action := range status.failures { tbl.AddRow(action.endpoint, action.failure(), action.err) } } tbl.Print() os.Exit(1) } status := wf.status() if len(status.failures) == 0 { color.Blue("Workflow Completed with no failures") os.Exit(0) } color.Blue("Workflow Completed, but had %d failed actions", len(status.failures)) for i := 0; i < 3; i++ { color.Green("Retrying failed actions in 5 minutes...") time.Sleep(5 * time.Minute) fmt.Println("Executing failed actions...") wf.retryFailed(ctx) status = wf.status() if len(status.failures) == 0 { break } color.Blue("Workflow Failures retry, but had %d failed actions", len(status.failures)) } status = wf.status() if len(status.failures) == 0 { color.Blue("Workflow Completed with no failures") os.Exit(0) } color.Blue("Workflow Completed but with %d failures after retries exhausted") tbl := table.New("Endpoint", "Failed State", "Error") tbl.WithHeaderFormatter(headerFmt).WithFirstColumnFormatter(columnFmt) for _, action := range status.failures { failure := action.failure() if failure == "" { failure = "during setup" } tbl.AddRow(action.endpoint, failure, action.err) } tbl.Print() os.Exit(0) } func setup() (*config, *workflow, error) { if len(flag.Args()) != 1 { return nil, nil, fmt.Errorf("must have argument to service file") } b, err := os.ReadFile(flag.Args()[0]) if err != nil { return nil, nil, fmt.Errorf("can't open workflow configuration file: %w", err) } config := &config{} if err := json.Unmarshal(b, config); err != nil { return nil, nil, fmt.Errorf("%q is misconfigured: %w", flag.Args()[0], err) } if err := config.validate(); err != nil { log.Println(string(b)) return nil, nil, fmt.Errorf("config file didn't validate: %w", err) } lb, err := client.New(config.LB) if err != nil { return nil, nil, fmt.Errorf("can't connected to LB(%s): %s\n", config.LB, err) } wf, err := newWorkflow(config, lb) if err != nil { return nil, nil, fmt.Errorf("could not create workflow: %w", err) } return config, wf, nil } func getSSHConfig(config *config) error { auth, err := getAuthFromFlags() if err != nil { return err } if config.BackendUser == "" { config.BackendUser = os.Getenv("USER") } config.ssh = &ssh.ClientConfig{ User: config.BackendUser, Auth: []ssh.AuthMethod{auth}, Timeout: 5 * time.Second, HostKeyCallback: ssh.InsecureIgnoreHostKey(), } return nil } func getAuthFromFlags() (ssh.AuthMethod, error) { if *keyFile != "" { return publicKey(*keyFile) } return agentAuth() } func agentAuth() (ssh.AuthMethod, error) { conn, err := net.Dial("unix", os.Getenv("SSH_AUTH_SOCK")) if err != nil { return nil, fmt.Errorf("problem dialing SSH agent when --key was not provided: %w", err) } client := agent.NewClient(conn) return ssh.PublicKeysCallback(client.Signers), nil } func publicKey(privateKeyFile string) (ssh.AuthMethod, error) { k, err := os.ReadFile(privateKeyFile) if err != nil { return nil, err } signer, err := ssh.ParsePrivateKey(k) if err != nil { return nil, err } return ssh.PublicKeys(signer), nil } ================================================ FILE: chapter/8/rollout/service.json ================================================ { "Concurrency": 2, "CanaryNum": 1, "MaxFailures": 2, "Src": "/home/[user]/rollout/webserver", "Dst": "/home/[user]/webserver", "LB": "10.0.0.4:8081", "Pattern": "/", "Backends": [ "10.0.0.5", "10.0.0.6", "10.0.0.7", "10.0.0.8", "10.0.0.9" ], "BackendUser": "azureuser", "BinaryPort": 8082 } ================================================ FILE: chapter/8/rollout/workflow.go ================================================ package main import ( "context" "errors" "fmt" "io" "net" "net/http" "net/url" "os" "path" "reflect" "runtime" "strconv" "strings" "sync" "sync/atomic" "syscall" "time" "github.com/PacktPublishing/Go-for-DevOps/chapter/8/rollout/lb/client" "github.com/fatih/color" "github.com/pkg/sftp" "golang.org/x/crypto/ssh" pb "github.com/PacktPublishing/Go-for-DevOps/chapter/8/rollout/lb/proto" ) //go:generate stringer -type=endState // endStates are the final states after a run of a workflow. type endState int8 const ( // esUnknown indicates we haven't reached and end state. esUnknown endState = 0 // esSuccess means that the workflow has completed successfully. This // does not mean there haven't been failurea. esSuccess endState = 1 // esPreconditionFailure means no work was done as we failed on a precondition. esPreconditionFailure endState = 2 // esCanaryFailure indicates one of the canaries failed, stopping the workflow. esCanaryFailure endState = 3 // esMaxFailures indicates that the workflow passed the canary phase, but failed // at a later phase. esMaxFailures endState = 4 ) // workflow represents our rollout workflow. type workflow struct { config *config lb *client.Client failures int32 endState endState actions []*actions } // newWorkflow creates a new workflow. func newWorkflow(config *config, lb *client.Client) (*workflow, error) { wf := &workflow{ config: config, lb: lb, } if err := wf.buildActions(); err != nil { return nil, err } return wf, nil } // run runs our workflow on the supplied "actions" doing "canaryNum" canaries, // then running "concurrency" number of actions that will stop at "maxFailures" number of // failurea. func (w *workflow) run(ctx context.Context) error { // Run a local precondition to make sure our load balancer is in a healthy state. preCtx, cancel := context.WithTimeout(ctx, 30*time.Second) if err := w.checkLBState(preCtx); err != nil { w.endState = esPreconditionFailure return fmt.Errorf("checkLBState precondition fail: %s", err) } cancel() // Run our canaries one at a time. Any problem stops the workflow. for i := 0; i < len(w.actions) && int32(i) < w.config.CanaryNum; i++ { color.Green("Running canary on: %s", w.actions[i].endpoint) ctx, cancel := context.WithTimeout(ctx, 10*time.Minute) err := w.actions[i].run(ctx) cancel() if err != nil { w.endState = esCanaryFailure return fmt.Errorf("canary failure on endpoint(%s): %w\n", w.actions[i].endpoint, err) } color.Yellow("Sleeping after canary for 1 minutes") time.Sleep(1 * time.Minute) } limit := make(chan struct{}, w.config.Concurrency) wg := sync.WaitGroup{} // Run the rest of the actions, with a limit to our concurrency. for i := w.config.CanaryNum; int(i) < len(w.actions); i++ { i := i limit <- struct{}{} if atomic.LoadInt32(&w.failures) > w.config.MaxFailures { break } wg.Add(1) go func() { defer func() { <-limit }() defer wg.Done() ctx, cancel := context.WithTimeout(ctx, 10*time.Minute) color.Green("Upgrading endpoint: %s", w.actions[i].endpoint) err := w.actions[i].run(ctx) cancel() if err != nil { color.Red("Endpoint(%s) had upgrade error: %s", w.actions[i].endpoint, err) atomic.AddInt32(&w.failures, 1) } }() } wg.Wait() if atomic.LoadInt32(&w.failures) > w.config.MaxFailures { w.endState = esMaxFailures return errors.New("exceeded max failures") } w.endState = esSuccess return nil } // retryFailed retries all failed actiona. This is only used if func (w *workflow) retryFailed(ctx context.Context) { if w.endState != esSuccess { panic("retrlyFailed cannot be called unless the workflow was a success") } ws := w.status() wg := sync.WaitGroup{} for i := 0; i < len(ws.failures); i++ { wg.Add(1) go func() { defer wg.Done() ctx, cancel := context.WithTimeout(ctx, 10*time.Minute) err := ws.failures[i].run(ctx) cancel() if err == nil { atomic.AddInt32(&w.failures, -1) } }() } wg.Wait() } // checkLBState checks the load balancer pool for "pattern" contains all "endpoints" // in a healthy state. func (w *workflow) checkLBState(ctx context.Context) error { ph, err := w.lb.PoolHealth(ctx, w.config.Pattern, true, true) if err != nil { return fmt.Errorf("PoolHealth(%s) error: %w", w.config.Pattern, err) } switch ph.Status { case pb.PoolStatus_PS_EMPTY: case pb.PoolStatus_PS_FULL: if len(w.config.Backends) != len(ph.Backends) { return fmt.Errorf("expected backends(%d) != found backends(%d)", len(w.config.Backends), len(ph.Backends)) } m := map[string]bool{} for _, e := range w.config.Backends { m[e] = true } for _, hb := range ph.Backends { switch { case hb.Backend.GetIpBackend() != nil: b := hb.Backend.GetIpBackend() if !m[b.Ip] { return fmt.Errorf("configured backend %q not in config file", b.Ip) } default: return fmt.Errorf("we only support IPBackend, got %T", hb.Backend) } } default: return fmt.Errorf("pool was not at full health, was %s", ph.Status) } return nil } // buildActions builds actions from our configuration file. func (w *workflow) buildActions() error { for _, b := range w.config.Backends { a, err := newServerActions(b, w.config, w.lb) if err != nil { return err } w.actions = append(w.actions, a) } return nil } type workflowStatus struct { // endState is the endState of the workflow. endState endState // failures is a list of failed actiona. failures []*actions } // status will return the workflow's status after run() has complete.d func (w *workflow) status() workflowStatus { ws := workflowStatus{endState: w.endState} for _, action := range w.actions { if action.err != nil { ws.failures = append(ws.failures, action) } } return ws } type stateFn func(ctx context.Context) (stateFn, error) type actions struct { endpoint string backend client.IPBackend config *config srcf *os.File dst string lb *client.Client sshClient *ssh.Client started bool failedState stateFn err error } func newServerActions(endpoint string, config *config, lb *client.Client) (*actions, error) { ip, err := checkIP(endpoint) if err != nil { return nil, err } return &actions{ endpoint: endpoint, backend: client.IPBackend{IP: ip, Port: int32(config.BinaryPort)}, config: config, lb: lb, }, nil } func (a *actions) run(ctx context.Context) (err error) { a.srcf, err = os.Open(a.config.Src) if err != nil { a.err = fmt.Errorf("cannot open binary to copy(%s): %w", a.config.Src, err) return a.err } back := a.endpoint + ":22" a.sshClient, err = ssh.Dial("tcp", back, a.config.ssh) if err != nil { a.err = fmt.Errorf("problem dialing the endpoint(%s): %w", back, err) return a.err } defer a.sshClient.Close() fn := a.rmBackend if a.failedState != nil { fn = a.failedState } a.started = true for { if ctx.Err() != nil { a.err = ctx.Err() return ctx.Err() } fn, err = fn(ctx) if err != nil { a.failedState = fn a.err = err return err } if fn == nil { return nil } } } func (a *actions) rmBackend(ctx context.Context) (stateFn, error) { err := a.lb.RemoveBackend(ctx, a.config.Pattern, a.backend) if err != nil { return nil, fmt.Errorf("problem removing backend from pool: %w", err) } return a.jobKill, nil } func (a *actions) jobKill(ctx context.Context) (stateFn, error) { pids, err := a.findPIDs(ctx) if err != nil { return nil, fmt.Errorf("problem finding existing PIDs: %w", err) } if len(pids) == 0 { return a.cp, nil } if err := a.killPIDs(ctx, pids, 15); err != nil { return nil, fmt.Errorf("failed to kill existing PIDs: %w", err) } if err := a.waitForDeath(ctx, pids, 30*time.Second); err != nil { if err := a.killPIDs(ctx, pids, 9); err != nil { return nil, fmt.Errorf("failed to kill existing PIDs: %w", err) } if err := a.waitForDeath(ctx, pids, 10*time.Second); err != nil { return nil, fmt.Errorf("failed to kill existing PIDs after -9: %w", err) } return a.cp, nil } return a.cp, nil } func (a *actions) cp(ctx context.Context) (stateFn, error) { if err := a.sftp(); err != nil { return nil, fmt.Errorf("failed to cp binary to remote end: %w", err) } return a.jobStart, nil } func (a *actions) jobStart(ctx context.Context) (stateFn, error) { if err := a.runBinary(ctx); err != nil { return nil, fmt.Errorf("failed to start binary after copy: %w", err) } return a.reachable(ctx) } func (a *actions) reachable(ctx context.Context) (stateFn, error) { ctx, cancel := context.WithTimeout(ctx, 1*time.Minute) defer cancel() c := &http.Client{} u := &url.URL{ Host: net.JoinHostPort(a.endpoint, strconv.Itoa(a.config.BinaryPort)), Path: "/healthz", Scheme: "http", } req, err := http.NewRequestWithContext( ctx, "GET", u.String(), nil, ) if err != nil { return nil, fmt.Errorf("problem creating HTTP request: %w", err) } for { if ctx.Err() != nil { return nil, errors.New("reachable() timed out") } resp, err := c.Do(req) if err != nil { time.Sleep(1 * time.Second) continue } b, err := io.ReadAll(resp.Body) if err != nil { continue } if strings.TrimSpace(string(b)) == "ok" { return a.addBackend, nil } } } func (a *actions) addBackend(ctx context.Context) (stateFn, error) { err := a.lb.AddBackend(ctx, a.config.Pattern, a.backend) if err != nil { return nil, err } return nil, nil } func (a *actions) findPIDs(ctx context.Context) ([]string, error) { serviceName := path.Base(a.config.Src) result, err := a.combinedOutput( ctx, a.sshClient, fmt.Sprintf("pidof %s", serviceName), ) if err != nil { if err.(*ssh.ExitError).ExitStatus() == 127 { return nil, err } return nil, nil } return strings.Split(strings.TrimSpace(result), " "), nil } func (a *actions) killPIDs(ctx context.Context, pids []string, signal syscall.Signal) error { for _, pid := range pids { _, err := a.combinedOutput( ctx, a.sshClient, fmt.Sprintf("kill -s %d %s", signal, pid), ) if err != nil { return err } } return nil } func (a *actions) waitForDeath(ctx context.Context, pids []string, timeout time.Duration) error { t := time.NewTimer(timeout) defer t.Stop() for { select { case <-t.C: return errors.New("timeout waiting for pids death") default: } results, err := a.findPIDs(ctx) if err != nil { return fmt.Errorf("findPIDs giving errors: %w", err) } if len(results) == 0 { return nil } time.Sleep(1 * time.Second) } } func (a *actions) runBinary(ctx context.Context) error { err := a.startOnly( ctx, a.sshClient, fmt.Sprintf("/usr/bin/nohup %s &", a.config.Dst), ) if err != nil { return fmt.Errorf("problem running the binary on the remove side: %w", err) } return nil } func (a *actions) sftp() error { c, err := sftp.NewClient(a.sshClient) if err != nil { return fmt.Errorf("could not create SFTP client: %w", err) } defer c.Close() dstf, err := c.OpenFile(a.config.Dst, os.O_WRONLY|os.O_CREATE|os.O_TRUNC) if err != nil { return fmt.Errorf("SFTP could not open file on remote destination(%s): %w", a.config.Dst, err) } defer dstf.Close() if err := dstf.Chmod(0770); err != nil { return fmt.Errorf("SFTP could not set the file mode to 0770: %w", err) } _, err = io.Copy(dstf, a.srcf) if err != nil { return fmt.Errorf("SFTP failed to do a complete copy: %w", err) } return nil } // combinedOutput runs a command on an SSH client. The context can be cancelled, however // SSH does not always honor the kill signals we send, so this might not break. So closing // the session does nothing. So depending on what the server is doing, cancelling the context // may do nothing and it may still block. func (*actions) combinedOutput(ctx context.Context, conn *ssh.Client, cmd string) (string, error) { sess, err := conn.NewSession() if err != nil { return "", err } defer sess.Close() if v, ok := ctx.Deadline(); ok { t := time.NewTimer(v.Sub(time.Now())) defer t.Stop() go func() { x := <-t.C if !x.IsZero() { sess.Signal(ssh.SIGKILL) } }() } b, err := sess.Output(cmd) if err != nil { return "", err } return string(b), nil } func (*actions) startOnly(ctx context.Context, conn *ssh.Client, cmd string) error { sess, err := conn.NewSession() if err != nil { return fmt.Errorf("could not start new SSH session: %w", err) } // Note: don't close the session, it will prevent the program from starting. return sess.Start(cmd) } func (a *actions) failure() string { if a.failedState == nil { return "" } return runtime.FuncForPC(reflect.ValueOf(a.failedState).Pointer()).Name() } ================================================ FILE: chapter/8/scanner/scanner.go ================================================ package main import ( "context" "encoding/json" "fmt" "log" "net" "os" "os/exec" "os/user" "sync" "time" "inet.af/netaddr" ) const ( ping = "ping" ssh = "ssh" uname = "uname" ) func main() { _, err := exec.LookPath(ping) if err != nil { log.Fatal("cannot find ping in our PATH") } _, err = exec.LookPath(ssh) if err != nil { log.Fatal("cannot find ssh in our PATH") } if len(os.Args) != 2 { log.Fatal("error: only one argument allowed, the network CIDR to scan") } ipCh, err := hosts(os.Args[1]) if err != nil { log.Fatalf("error: CIDR address did not parse: %s", err) } u, err := user.Current() if err != nil { log.Fatal(err) } scanResults := scanPrefixes(ipCh) unameResults := unamePrefixes(u.Username, scanResults) for rec := range unameResults { b, _ := json.Marshal(rec) fmt.Printf("%s\n", b) } } // record holds information about a scan of a host. type record struct { // Host is the IP address of the host. Host net.IP // Reachable indicates if this host was pingable. Reachable bool // LoginSSH indicates if we were able to authenticate with SSH. LoginSSH bool // Uname is the output of the "uname -a" command. If this is an empty string // but LoginSSH is true, this means uname was not supported by the host. Uname string } // host takes a CIDR string (192.168.0.0/24) and returns all host IPs for that network. // This will not send back the broadcast or network addresses. Does not support /31 addresses. func hosts(cidr string) (chan net.IP, error) { ch := make(chan net.IP, 1) prefix, err := netaddr.ParseIPPrefix(cidr) if err != nil { return nil, err } go func() { defer close(ch) var last net.IP for ip := prefix.IP().Next(); prefix.Contains(ip); ip = ip.Next() { // Prevents sending the broadcast address. if len(last) != 0 { //log.Printf("sending: %s, contained: %v", last, prefix.Contains(ip)) ch <- last } last = ip.IPAddr().IP } }() return ch, nil } // scanPrefixes takes a channel of net.IP and pings them. If an IP responds to ping it is put // on the returned success channel, otherwise it is put on the fail channel. func scanPrefixes(ipCh chan net.IP) chan record { ch := make(chan record, 1) go func() { defer close(ch) limit := make(chan struct{}, 100) wg := sync.WaitGroup{} for ip := range ipCh { limit <- struct{}{} wg.Add(1) go func(ip net.IP) { defer func() { <-limit }() defer wg.Done() ctx, cancel := context.WithTimeout( context.Background(), 3*time.Second, ) defer cancel() rec := record{Host: ip} if hostAlive(ctx, ip) { rec.Reachable = true } ch <- rec }(ip) } wg.Wait() }() return ch } // unamePrefixes takes a channel of net.IP and runs "uname -a" on them via the ssh binary. func unamePrefixes(user string, recs chan record) chan record { ch := make(chan record, 1) go func() { defer close(ch) limit := make(chan struct{}, 100) wg := sync.WaitGroup{} for rec := range recs { if !rec.Reachable { ch <- rec continue } limit <- struct{}{} wg.Add(1) go func(rec record) { defer func() { <-limit }() defer wg.Done() text, err := runUname(context.Background(), rec.Host, user) if err != nil { ch <- rec return } rec.LoginSSH = true rec.Uname = text ch <- rec }(rec) } wg.Wait() }() return ch } // hostAlive uses the "ping" binary on the host to test an IP. Has a 2 second timeout. func hostAlive(ctx context.Context, host net.IP) bool { cmd := exec.CommandContext(ctx, ping, "-c", "1", "-t", "2", host.String()) if err := cmd.Run(); err != nil { return false } return true } // runUname will attempt to use the "ssh" binary to log into a host and run "uname -a". // This will return the output of that command. func runUname(ctx context.Context, host net.IP, user string) (string, error) { if _, ok := ctx.Deadline(); !ok { var cancel context.CancelFunc ctx, cancel = context.WithTimeout(ctx, 5*time.Second) defer cancel() } login := fmt.Sprintf("%s@%s", user, host) cmd := exec.CommandContext( ctx, ssh, "-o StrictHostKeyChecking=no", "-o BatchMode=yes", login, "uname -a", ) out, err := cmd.CombinedOutput() if err != nil { return "", err } return string(out), nil } ================================================ FILE: chapter/8/ssh/client/expect/expect.go ================================================ package main import ( "bytes" "flag" "fmt" "io" "log" "net" "os" "os/user" "regexp" "strings" "time" "github.com/google/goexpect" "golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh/terminal" ) var ( private = flag.String("private", "", "The path to the SSH private key for this connection") ) func main() { flag.Parse() if len(os.Args) != 2 { fmt.Println("Error: command must be 1 arg, [host]") os.Exit(1) } _, _, err := net.SplitHostPort(os.Args[1]) if err != nil { os.Args[1] = os.Args[1] + ":22" _, _, err = net.SplitHostPort(os.Args[1]) if err != nil { fmt.Println("Error: problem with host passed: ", err) os.Exit(1) } } var auth ssh.AuthMethod if *private == "" { fi, _ := os.Stdin.Stat() if (fi.Mode() & os.ModeCharDevice) == 0 { fmt.Println("-private not set, cannot use password when STDIN is a pipe") os.Exit(1) } auth, err = passwordFromTerm() if err != nil { fmt.Println(err) os.Exit(1) } } else { auth, err = publicKey(*private) if err != nil { fmt.Println(err) os.Exit(1) } } u, err := user.Current() if err != nil { fmt.Println("Error: problem getting current user: ", err) os.Exit(1) } config := &ssh.ClientConfig{ User: u.Username, Auth: []ssh.AuthMethod{auth}, HostKeyCallback: ssh.InsecureIgnoreHostKey(), Timeout: 5 * time.Second, } conn, err := ssh.Dial("tcp", os.Args[1], config) if err != nil { fmt.Println("Error: could not dial host: ", err) os.Exit(1) } defer conn.Close() fmt.Println("Installing Expect on remote system") if err := installExpect(conn); err != nil { fmt.Println("Error: ", err) os.Exit(1) } fmt.Println("Done") } func passwordFromTerm() (ssh.AuthMethod, error) { fmt.Printf("SSH Passsword: ") p, err := terminal.ReadPassword(int(os.Stdin.Fd())) if err != nil { return nil, err } fmt.Println("") // Show the return if len(bytes.TrimSpace(p)) == 0 { return nil, fmt.Errorf("password was an empty string") } return ssh.Password(string(p)), nil } func publicKey(privateKeyFile string) (ssh.AuthMethod, error) { k, err := os.ReadFile(privateKeyFile) if err != nil { return nil, err } signer, err := ssh.ParsePrivateKey(k) if err != nil { return nil, err } return ssh.PublicKeys(signer), nil } func installExpect(conn *ssh.Client) (err error) { // Here we are setting up an io.Writer that will write // to our debug strings.Builder{}. If we run into an // error, the output of the command will dumpt to STDERR. r, w := io.Pipe() debug := strings.Builder{} debugDone := make(chan struct{}) go func() { io.Copy(&debug, r) close(debugDone) }() defer func() { // Wait for our io.Copy() to be done. <-debugDone // Only log this if we had an error. if err != nil { log.Printf("expect debug:\n%s", debug.String()) } }() e, _, err := expect.SpawnSSH(conn, 5*time.Second, expect.Tee(w)) if err != nil { return err } defer e.Close() var promptRE = regexp.MustCompile(`\$ `) _, _, err = e.Expect(promptRE, 10*time.Second) if err != nil { return fmt.Errorf("did not get shell prompt") } if err := e.Send("sudo apt-get install expect\n"); err != nil { return fmt.Errorf("error on send command: %s", err) } _, _, ecase, err := e.ExpectSwitchCase( []expect.Caser{ &expect.Case{ R: regexp.MustCompile(`Do you want to continue\? \[Y/n\] `), T: expect.OK(), }, &expect.Case{ R: regexp.MustCompile(`is already the newest`), T: expect.OK(), }, }, 10*time.Second, ) if err != nil { return fmt.Errorf("apt-get install did not send what we expected") } switch ecase { case 0: if err := e.Send("Y\n"); err != nil { return err } } _, _, err = e.Expect(promptRE, 10*time.Second) if err != nil { return fmt.Errorf("did not get shell prompt") } return nil } ================================================ FILE: chapter/8/ssh/client/remotecmd/remotecmd.go ================================================ package main import ( "bytes" "context" "flag" "fmt" "net" "os" "os/user" "time" "golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh/terminal" ) var ( private = flag.String("private", "", "The path to the SSH private key for this connection") ) func main() { flag.Parse() if len(os.Args) != 3 { fmt.Println("Error: command must be 2 args, [host] [command]") os.Exit(1) } _, _, err := net.SplitHostPort(os.Args[1]) if err != nil { os.Args[1] = os.Args[1] + ":22" _, _, err = net.SplitHostPort(os.Args[1]) if err != nil { fmt.Println("Error: problem with host passed: ", err) os.Exit(1) } } var auth ssh.AuthMethod if *private == "" { fi, _ := os.Stdin.Stat() if (fi.Mode() & os.ModeCharDevice) == 0 { fmt.Println("-private not set, cannot use password when STDIN is a pipe") os.Exit(1) } auth, err = passwordFromTerm() if err != nil { fmt.Println(err) os.Exit(1) } } else { auth, err = publicKey(*private) if err != nil { fmt.Println(err) os.Exit(1) } } u, err := user.Current() if err != nil { fmt.Println("Error: problem getting current user: ", err) os.Exit(1) } config := &ssh.ClientConfig{ User: u.Username, Auth: []ssh.AuthMethod{auth}, HostKeyCallback: ssh.InsecureIgnoreHostKey(), Timeout: 5 * time.Second, } conn, err := ssh.Dial("tcp", os.Args[1], config) if err != nil { fmt.Println("Error: could not dial host: ", err) os.Exit(1) } defer conn.Close() ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) defer cancel() out, err := combinedOutput(ctx, conn, os.Args[2]) if err != nil { fmt.Println("command error: ", err) os.Exit(1) } fmt.Println(out) } func passwordFromTerm() (ssh.AuthMethod, error) { fmt.Printf("SSH Passsword: ") p, err := terminal.ReadPassword(int(os.Stdin.Fd())) if err != nil { return nil, err } fmt.Println("") // Show the return if len(bytes.TrimSpace(p)) == 0 { return nil, fmt.Errorf("password was an empty string") } return ssh.Password(string(p)), nil } func publicKey(privateKeyFile string) (ssh.AuthMethod, error) { k, err := os.ReadFile(privateKeyFile) if err != nil { return nil, err } signer, err := ssh.ParsePrivateKey(k) if err != nil { return nil, err } return ssh.PublicKeys(signer), nil } // combinedOutput runs a command on an SSH client. The context can be cancelled, however // SSH does not always honor the kill signals we send, so this might not break. So closing // the session does nothing. So depending on what the server is doing, cancelling the context // may do nothing and it may still block. func combinedOutput(ctx context.Context, conn *ssh.Client, cmd string) (string, error) { sess, err := conn.NewSession() if err != nil { return "", err } defer sess.Close() if v, ok := ctx.Deadline(); ok { t := time.NewTimer(v.Sub(time.Now())) defer t.Stop() go func() { x := <-t.C if !x.IsZero() { sess.Signal(ssh.SIGKILL) } }() } b, err := sess.Output(cmd) if err != nil { return "", err } return string(b), nil } ================================================ FILE: chapter/9/alerting/alertmanager.yml ================================================ route: receiver: default group_by: [ alertname ] routes: - match: exported_job: demo-server receiver: demo-server receivers: - name: default pagerduty_configs: - service_key: "**Primary-Integration-Key**" - name: demo-server pagerduty_configs: - service_key: "**Server-Team-Integration-Key**" ================================================ FILE: chapter/9/alerting/client/Dockerfile ================================================ FROM golang:1.17 COPY . /usr/src/client/ WORKDIR /usr/src/client/ RUN go env -w GOPROXY=direct RUN go install ./main.go CMD ["/go/bin/main"] ================================================ FILE: chapter/9/alerting/client/go.mod ================================================ module github.com/PacktPublishing/Go-for-DevOps/chapter/9/logging/demo/client go 1.17 require ( go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.28.0 go.opentelemetry.io/otel v1.3.0 go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.26.0 go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v0.26.0 go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.3.0 go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.3.0 go.opentelemetry.io/otel/metric v0.26.0 go.opentelemetry.io/otel/sdk v1.3.0 go.opentelemetry.io/otel/sdk/metric v0.26.0 google.golang.org/grpc v1.43.0 ) require ( github.com/cenkalti/backoff/v4 v4.1.2 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/felixge/httpsnoop v1.0.2 // indirect github.com/go-logr/logr v1.2.1 // indirect github.com/go-logr/stdr v1.2.0 // indirect github.com/golang/protobuf v1.5.2 // indirect github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.3.0 // indirect go.opentelemetry.io/otel/internal/metric v0.26.0 // indirect go.opentelemetry.io/otel/sdk/export/metric v0.26.0 // indirect go.opentelemetry.io/otel/trace v1.3.0 // indirect go.opentelemetry.io/proto/otlp v0.11.0 // indirect golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 // indirect golang.org/x/sys v0.0.0-20210510120138-977fb7262007 // indirect golang.org/x/text v0.3.3 // indirect google.golang.org/genproto v0.0.0-20200527145253-8367513e4ece // indirect google.golang.org/protobuf v1.27.1 // indirect ) ================================================ FILE: chapter/9/alerting/client/go.sum ================================================ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A= github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/cenkalti/backoff/v4 v4.1.2 h1:6Yo7N8UP2K6LWZnW94DLVSSrbobcWdVzAYOisuDPIFo= github.com/cenkalti/backoff/v4 v4.1.2/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.1.1/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/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= 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.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.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/felixge/httpsnoop v1.0.2 h1:+nS9g82KMXccJ/wp0zyRW9ZBHFETmMGtkk+2CTTrW4o= github.com/felixge/httpsnoop v1.0.2/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.1 h1:DX7uPQ4WgAWfoh+NGGlbJQswnYIVvz0SRlLS3rPZQDA= github.com/go-logr/logr v1.2.1/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/stdr v1.2.0 h1:j4LrlVXgrbIWO83mmQUnK0Hi+YnbD+vzrE1z/EphbFE= github.com/go-logr/stdr v1.2.0/go.mod h1:YkVgnZu1ZjjL7xTxrfm/LLZBfkhTqSR1ydtm6jTKKwI= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 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.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 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.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 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.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 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/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.28.0 h1:hpEoMBvKLC6CqFZogJypr9IHwwSNF3ayEkNzD502QAM= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.28.0/go.mod h1:Ihno+mNBfZlT0Qot3XyRTdZ/9U/Cg2Pfgj75DTdIfq4= go.opentelemetry.io/otel v1.3.0 h1:APxLf0eiBwLl+SOXiJJCVYzA1OOJNyAoV8C5RNRyy7Y= go.opentelemetry.io/otel v1.3.0/go.mod h1:PWIKzi6JCp7sM0k9yZ43VX+T345uNbAkDKwHVjb2PTs= go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.3.0 h1:R/OBkMoGgfy2fLhs2QhkCI1w4HLEQX92GCcJB6SSdNk= go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.3.0/go.mod h1:VpP4/RMn8bv8gNo9uK7/IMY4mtWLELsS+JIP0inH0h4= go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.26.0 h1:dIE9swzwOnkGaJ6OF1QQQdBk2EdrJnD9Ilao2G9DeLU= go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.26.0/go.mod h1:1E0NE+3ywwedkOEl3d7nFjyI/bqRECMhI3xTGh13pxY= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v0.26.0 h1:uBujg02iT0vOsjBF85BgcEaMGT6RaViwA9Sz/nh4bxQ= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v0.26.0/go.mod h1:pK3MWIu31OABQez2HFn3IRglTfIzXZtqRtgqE8fDt9U= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.3.0 h1:giGm8w67Ja7amYNfYMdme7xSp2pIxThWopw8+QP51Yk= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.3.0/go.mod h1:hO1KLR7jcKaDDKDkvI9dP/FIhpmna5lkqPUQdEjFAM8= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.3.0 h1:VQbUHoJqytHHSJ1OZodPH9tvZZSVzUHjPHpkO85sT6k= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.3.0/go.mod h1:keUU7UfnwWTWpJ+FWnyqmogPa82nuU5VUANFq49hlMY= go.opentelemetry.io/otel/internal/metric v0.26.0 h1:dlrvawyd/A+X8Jp0EBT4wWEe4k5avYaXsXrBr4dbfnY= go.opentelemetry.io/otel/internal/metric v0.26.0/go.mod h1:CbBP6AxKynRs3QCbhklyLUtpfzbqCLiafV9oY2Zj1Jk= go.opentelemetry.io/otel/metric v0.26.0 h1:VaPYBTvA13h/FsiWfxa3yZnZEm15BhStD8JZQSA773M= go.opentelemetry.io/otel/metric v0.26.0/go.mod h1:c6YL0fhRo4YVoNs6GoByzUgBp36hBL523rECoZA5UWg= go.opentelemetry.io/otel/sdk v1.3.0 h1:3278edCoH89MEJ0Ky8WQXVmDQv3FX4ZJ3Pp+9fJreAI= go.opentelemetry.io/otel/sdk v1.3.0/go.mod h1:rIo4suHNhQwBIPg9axF8V9CA72Wz2mKF1teNrup8yzs= go.opentelemetry.io/otel/sdk/export/metric v0.26.0 h1:eNseg5yyZqaAAY+Att3owR3Bl0Is5rCZywqO1OrGx18= go.opentelemetry.io/otel/sdk/export/metric v0.26.0/go.mod h1:UpqzSnUOjFeSIVQLPp3pYIXfB/MiMFyXXzYT/bercxQ= go.opentelemetry.io/otel/sdk/metric v0.26.0 h1:7IKp3gc/ObieCtshBeYYVFp3ZP7xIH1OzODi1Wao90Y= go.opentelemetry.io/otel/sdk/metric v0.26.0/go.mod h1:2VIeK0kS1YvRLFg3J58ptZTXYpiWlkq2n5RQt6w7He8= go.opentelemetry.io/otel/trace v1.3.0 h1:doy8Hzb1RJ+I3yFhtDmwNc7tIyw1tNMOIsyPzp1NOGY= go.opentelemetry.io/otel/trace v1.3.0/go.mod h1:c/VDhno8888bvQYmbYLqe41/Ldmr/KKunbvWM4/fEjk= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.opentelemetry.io/proto/otlp v0.11.0 h1:cLDgIBTf4lLOlztkhzAEdQsJ4Lj+i5Wc9k6Nn0K1VyU= go.opentelemetry.io/proto/otlp v0.11.0/go.mod h1:QpEjXPrNQzrFDZgoTo49dgHR9RYRSrg3NAKnUGl9YpQ= go.uber.org/goleak v1.1.12 h1:gZAh5/EyT/HQwlpkCy6wTpqfH9H8Lz8zbm3dZh+OyzA= go.uber.org/goleak v1.1.12/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 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/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 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-20190108225652-1e06a53dbb7e/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-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 h1:4nGaVu0QrbjT/AK2PRLuQfQuh6DJve+pELhqTdAj3x0= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 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-20181221193216-37e7f081c4d4/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.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 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-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007 h1:gG67DSER+11cZvqIMb8S8bt0vZtiN6xWYARwirrOSfE= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 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/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 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-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20200527145253-8367513e4ece h1:1YM0uhfumvoDu9sx8+RyWwTI63zoCQvI23IYFRlvte0= google.golang.org/genproto v0.0.0-20200527145253-8367513e4ece/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= 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.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= google.golang.org/grpc v1.43.0 h1:Eeu7bZtDZ2DpRCsLhUlcrLnvYaMK1Gz86a+hMVvELmM= google.golang.org/grpc v1.43.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= 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.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= ================================================ FILE: chapter/9/alerting/client/main.go ================================================ package main import ( "context" "fmt" "log" "math/rand" "net/http" "os" "time" "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/exporters/otlp/otlpmetric" "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc" "go.opentelemetry.io/otel/exporters/otlp/otlptrace" "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc" "go.opentelemetry.io/otel/metric" "go.opentelemetry.io/otel/metric/global" "go.opentelemetry.io/otel/propagation" controller "go.opentelemetry.io/otel/sdk/metric/controller/basic" processor "go.opentelemetry.io/otel/sdk/metric/processor/basic" "go.opentelemetry.io/otel/sdk/metric/selector/simple" "go.opentelemetry.io/otel/sdk/resource" sdktrace "go.opentelemetry.io/otel/sdk/trace" semconv "go.opentelemetry.io/otel/semconv/v1.4.0" "google.golang.org/grpc" ) // main sets up the trace and metrics providers and starts a loop to continuously call the server func main() { shutdown := initTraceAndMetricsProvider() defer shutdown() continuouslySendRequests() } // initTraceAndMetricsProvider initializes an OTLP exporter, and configures the corresponding trace and // metric providers. func initTraceAndMetricsProvider() func() { ctx := context.Background() otelAgentAddr, ok := os.LookupEnv("OTEL_EXPORTER_OTLP_ENDPOINT") if !ok { otelAgentAddr = "0.0.0.0:4317" } closeMetrics := initMetrics(ctx, otelAgentAddr) closeTraces := initTracer(ctx, otelAgentAddr) return func() { doneCtx, cancel := context.WithTimeout(ctx, time.Second) defer cancel() // pushes any last exports to the receiver closeTraces(doneCtx) closeMetrics(doneCtx) } } // initTracer initializes an OTLP trace exporter and registers the trace provider with the global context func initTracer(ctx context.Context, otelAgentAddr string) func(context.Context) { traceClient := otlptracegrpc.NewClient( otlptracegrpc.WithInsecure(), otlptracegrpc.WithEndpoint(otelAgentAddr), otlptracegrpc.WithDialOption(grpc.WithBlock())) traceExp, err := otlptrace.New(ctx, traceClient) handleErr(err, "Failed to create the collector trace exporter") res, err := resource.New(ctx, resource.WithFromEnv(), resource.WithProcess(), resource.WithTelemetrySDK(), resource.WithHost(), resource.WithAttributes( // the service name used to display traces in backends semconv.ServiceNameKey.String("demo-client"), ), ) handleErr(err, "failed to create resource") bsp := sdktrace.NewBatchSpanProcessor(traceExp) tracerProvider := sdktrace.NewTracerProvider( sdktrace.WithSampler(sdktrace.AlwaysSample()), sdktrace.WithResource(res), sdktrace.WithSpanProcessor(bsp), ) // set global propagator to tracecontext (the default is no-op). otel.SetTextMapPropagator(propagation.TraceContext{}) otel.SetTracerProvider(tracerProvider) return func(doneCtx context.Context) { if err := traceExp.Shutdown(doneCtx); err != nil { otel.Handle(err) } } } // initMetrics initializes a metrics pusher and registers the metrics provider with the global context func initMetrics(ctx context.Context, otelAgentAddr string) func(context.Context) { metricClient := otlpmetricgrpc.NewClient( otlpmetricgrpc.WithInsecure(), otlpmetricgrpc.WithEndpoint(otelAgentAddr)) metricExp, err := otlpmetric.New(ctx, metricClient) handleErr(err, "Failed to create the collector metric exporter") res, err := resource.New(ctx, resource.WithFromEnv(), resource.WithProcess(), resource.WithTelemetrySDK(), resource.WithAttributes(attribute.String("service", "demo-client")), resource.WithHost(), resource.WithAttributes( // the service name used to display traces in backends semconv.ServiceNameKey.String("demo-client"), ), ) pusher := controller.New( processor.NewFactory( simple.NewWithHistogramDistribution(), metricExp, ), controller.WithExporter(metricExp), controller.WithCollectPeriod(2*time.Second), controller.WithResource(res), ) global.SetMeterProvider(pusher) err = pusher.Start(ctx) handleErr(err, "Failed to start metric pusher") return func(doneCtx context.Context) { // pushes any last exports to the receiver if err := pusher.Stop(doneCtx); err != nil { otel.Handle(err) } } } // handleErr provides a simple way to handle errors and messages func handleErr(err error, message string) { if err != nil { log.Fatalf("%s: %v", message, err) } } // continuouslySendRequests continuously sends requests to the server and generates random lines of text to be measured func continuouslySendRequests() { var ( tracer = otel.Tracer("demo-client-tracer") meter = global.Meter("demo-client-meter") instruments = NewClientInstruments(meter) commonLabels = []attribute.KeyValue{ attribute.String("method", "repl"), attribute.String("client", "cli"), } rng = rand.New(rand.NewSource(time.Now().UnixNano())) ) for { startTime := time.Now() ctx, span := tracer.Start(context.Background(), "ExecuteRequest") makeRequest(ctx) span.End() latencyMs := float64(time.Since(startTime)) / 1e6 nr := int(rng.Int31n(7)) for i := 0; i < nr; i++ { randLineLength := rng.Int63n(999) meter.RecordBatch( ctx, commonLabels, instruments.LineCounts.Measurement(1), instruments.LineLengths.Measurement(randLineLength), ) fmt.Printf("#%d: LineLength: %dBy\n", i, randLineLength) } meter.RecordBatch( ctx, commonLabels, instruments.RequestLatency.Measurement(latencyMs), instruments.RequestCount.Measurement(1), ) fmt.Printf("Latency: %.3fms\n", latencyMs) time.Sleep(time.Duration(1) * time.Second) } } // makeRequest sends requests to the server using an OTEL HTTP transport which will instrument the requests with traces. func makeRequest(ctx context.Context) { demoServerAddr, ok := os.LookupEnv("DEMO_SERVER_ENDPOINT") if !ok { demoServerAddr = "http://0.0.0.0:7080/hello" } // Trace an HTTP client by wrapping the transport client := http.Client{ Transport: otelhttp.NewTransport(http.DefaultTransport), } // Make sure we pass the context to the request to avoid broken traces. req, err := http.NewRequestWithContext(ctx, "GET", demoServerAddr, nil) if err != nil { handleErr(err, "failed to http request") } // All requests made with this client will create spans. res, err := client.Do(req) if err != nil { panic(err) } res.Body.Close() } // ClientInstruments is a collection of instruments used to measure client requests to the server type ClientInstruments struct { RequestLatency metric.Float64Histogram RequestCount metric.Int64Counter LineLengths metric.Int64Histogram LineCounts metric.Int64Counter } // NewClientInstruments takes a meter and builds a set of instruments to be used to measure client requests to the server. func NewClientInstruments(meter metric.Meter) ClientInstruments { return ClientInstruments{ RequestLatency: metric.Must(meter). NewFloat64Histogram( "demo_client/request_latency", metric.WithDescription("The latency of requests processed"), ), RequestCount: metric.Must(meter). NewInt64Counter( "demo_client/request_counts", metric.WithDescription("The number of requests processed"), ), LineLengths: metric.Must(meter). NewInt64Histogram( "demo_client/line_lengths", metric.WithDescription("The lengths of the various lines in"), ), LineCounts: metric.Must(meter). NewInt64Counter( "demo_client/line_counts", metric.WithDescription("The counts of the lines in"), ), } } ================================================ FILE: chapter/9/alerting/docker-compose.yaml ================================================ version: "2" services: # Jaeger jaeger-all-in-one: image: jaegertracing/all-in-one:latest ports: - "16686:16686" - "14268" - "14250" # Collector otel-collector: image: ${OTELCOL_IMG} command: ["--config=/etc/otel-collector-config.yaml", "${OTELCOL_ARGS}"] volumes: - ./otel-collector-config.yaml:/etc/otel-collector-config.yaml ports: - "1888:1888" # pprof extension - "8888:8888" # Prometheus metrics exposed by the collector - "8889:8889" # Prometheus exporter metrics - "13133:13133" # health_check extension - "4317" # OTLP gRPC receiver - "55670:55679" # zpages extension depends_on: - jaeger-all-in-one demo-client: build: dockerfile: Dockerfile context: ./client environment: - OTEL_EXPORTER_OTLP_ENDPOINT=otel-collector:4317 - DEMO_SERVER_ENDPOINT=http://demo-server:7080/hello depends_on: - demo-server demo-server: build: dockerfile: Dockerfile context: ./server environment: - OTEL_EXPORTER_OTLP_ENDPOINT=otel-collector:4317 ports: - "7080" depends_on: - otel-collector prometheus: container_name: prometheus image: prom/prometheus:latest volumes: - ./prometheus.yaml:/etc/prometheus/prometheus.yml - ./rules:/etc/prometheus/rules ports: - "9090:9090" alertmanager: container_name: alertmanager image: prom/alertmanager:latest restart: unless-stopped ports: - "9093:9093" volumes: - ./alertmanager.yml:/config/alertmanager.yaml - alertmanager-data:/data command: --config.file=/config/alertmanager.yaml --log.level=debug volumes: alertmanager-data: ================================================ FILE: chapter/9/alerting/otel-collector-config.yaml ================================================ receivers: otlp: protocols: grpc: exporters: prometheus: endpoint: "0.0.0.0:8889" const_labels: label1: value1 logging: jaeger: endpoint: jaeger-all-in-one:14250 tls: insecure: true processors: batch: service: pipelines: traces: receivers: [otlp] processors: [batch] exporters: [logging, jaeger] metrics: receivers: [otlp] processors: [batch] exporters: [logging, prometheus] ================================================ FILE: chapter/9/alerting/prometheus.yaml ================================================ scrape_configs: - job_name: 'otel-collector' scrape_interval: 10s static_configs: - targets: ['otel-collector:8889'] - targets: ['otel-collector:8888'] alerting: alertmanagers: - scheme: http static_configs: - targets: [ 'alertmanager:9093' ] rule_files: - /etc/prometheus/rules/* ================================================ FILE: chapter/9/alerting/readme.md ================================================ # Metrics with OpenTelemetry and Prometheus TODO: fill in the walk through ## Running this example - `docker-compose up -d` - Once started the client application will periodically send requests to the server. Metrics will be collected for the requests and responses, then exported for analysis in prometheus. To view the metrics in Prometheus, open http://localhost:9090/. - To see the request rate for the server see: http://localhost:9090/graph?g0.expr=rate(demo_server_request_counts%5B2m%5D)&g0.tab=0&g0.stacked=0&g0.show_exemplars=0&g0.range_input=1h If you see something like: ```bash docker-compose up -d Traceback (most recent call last): File "urllib3/connectionpool.py", line 670, in urlopen File "urllib3/connectionpool.py", line 392, in _make_request ``` This indicates that you aren't running docker. Make sure you have docker installed and it is running. ## Tearing down this example - `docker-compose down` ## Influences / Credit The code in this demo was heavily influenced from the example application in https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/examples/demo which carries the following license. ``` // Copyright The OpenTelemetry 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. ``` See also: [OTEL License](https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/10cfdaac1387b4df7a525c3050ce18ec8f8068be/LICENSE ================================================ FILE: chapter/9/alerting/rules/demo-server.yml ================================================ groups: - name: demo-server rules: - alert: HighRequestLatency expr: | histogram_quantile(0.5, rate(http_server_duration_bucket{exported_job="demo-server"}[5m])) > 200000 labels: severity: page annotations: summary: High request latency ================================================ FILE: chapter/9/alerting/server/Dockerfile ================================================ FROM golang:1.17 COPY . /usr/src/server/ WORKDIR /usr/src/server/ RUN go env -w GOPROXY=direct RUN go install ./main.go CMD ["/go/bin/main"] ================================================ FILE: chapter/9/alerting/server/go.mod ================================================ module github.com/PacktPublishing/Go-for-DevOps/chapter/9/logging/demo/server go 1.17 require ( go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.28.0 go.opentelemetry.io/otel v1.3.0 go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.26.0 go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v0.26.0 go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.3.0 go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.3.0 go.opentelemetry.io/otel/metric v0.26.0 go.opentelemetry.io/otel/sdk v1.3.0 go.opentelemetry.io/otel/sdk/metric v0.26.0 go.opentelemetry.io/otel/trace v1.3.0 google.golang.org/grpc v1.43.0 ) require ( github.com/cenkalti/backoff/v4 v4.1.2 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/felixge/httpsnoop v1.0.2 // indirect github.com/go-logr/logr v1.2.1 // indirect github.com/go-logr/stdr v1.2.0 // indirect github.com/golang/protobuf v1.5.2 // indirect github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.3.0 // indirect go.opentelemetry.io/otel/internal/metric v0.26.0 // indirect go.opentelemetry.io/otel/sdk/export/metric v0.26.0 // indirect go.opentelemetry.io/proto/otlp v0.11.0 // indirect golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 // indirect golang.org/x/sys v0.0.0-20210510120138-977fb7262007 // indirect golang.org/x/text v0.3.3 // indirect google.golang.org/genproto v0.0.0-20200527145253-8367513e4ece // indirect google.golang.org/protobuf v1.27.1 // indirect ) ================================================ FILE: chapter/9/alerting/server/go.sum ================================================ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A= github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/cenkalti/backoff/v4 v4.1.2 h1:6Yo7N8UP2K6LWZnW94DLVSSrbobcWdVzAYOisuDPIFo= github.com/cenkalti/backoff/v4 v4.1.2/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.1.1/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/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= 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.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.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/felixge/httpsnoop v1.0.2 h1:+nS9g82KMXccJ/wp0zyRW9ZBHFETmMGtkk+2CTTrW4o= github.com/felixge/httpsnoop v1.0.2/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.1 h1:DX7uPQ4WgAWfoh+NGGlbJQswnYIVvz0SRlLS3rPZQDA= github.com/go-logr/logr v1.2.1/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/stdr v1.2.0 h1:j4LrlVXgrbIWO83mmQUnK0Hi+YnbD+vzrE1z/EphbFE= github.com/go-logr/stdr v1.2.0/go.mod h1:YkVgnZu1ZjjL7xTxrfm/LLZBfkhTqSR1ydtm6jTKKwI= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 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.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 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.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 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.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 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/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.28.0 h1:hpEoMBvKLC6CqFZogJypr9IHwwSNF3ayEkNzD502QAM= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.28.0/go.mod h1:Ihno+mNBfZlT0Qot3XyRTdZ/9U/Cg2Pfgj75DTdIfq4= go.opentelemetry.io/otel v1.3.0 h1:APxLf0eiBwLl+SOXiJJCVYzA1OOJNyAoV8C5RNRyy7Y= go.opentelemetry.io/otel v1.3.0/go.mod h1:PWIKzi6JCp7sM0k9yZ43VX+T345uNbAkDKwHVjb2PTs= go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.3.0 h1:R/OBkMoGgfy2fLhs2QhkCI1w4HLEQX92GCcJB6SSdNk= go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.3.0/go.mod h1:VpP4/RMn8bv8gNo9uK7/IMY4mtWLELsS+JIP0inH0h4= go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.26.0 h1:dIE9swzwOnkGaJ6OF1QQQdBk2EdrJnD9Ilao2G9DeLU= go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.26.0/go.mod h1:1E0NE+3ywwedkOEl3d7nFjyI/bqRECMhI3xTGh13pxY= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v0.26.0 h1:uBujg02iT0vOsjBF85BgcEaMGT6RaViwA9Sz/nh4bxQ= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v0.26.0/go.mod h1:pK3MWIu31OABQez2HFn3IRglTfIzXZtqRtgqE8fDt9U= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.3.0 h1:giGm8w67Ja7amYNfYMdme7xSp2pIxThWopw8+QP51Yk= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.3.0/go.mod h1:hO1KLR7jcKaDDKDkvI9dP/FIhpmna5lkqPUQdEjFAM8= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.3.0 h1:VQbUHoJqytHHSJ1OZodPH9tvZZSVzUHjPHpkO85sT6k= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.3.0/go.mod h1:keUU7UfnwWTWpJ+FWnyqmogPa82nuU5VUANFq49hlMY= go.opentelemetry.io/otel/internal/metric v0.26.0 h1:dlrvawyd/A+X8Jp0EBT4wWEe4k5avYaXsXrBr4dbfnY= go.opentelemetry.io/otel/internal/metric v0.26.0/go.mod h1:CbBP6AxKynRs3QCbhklyLUtpfzbqCLiafV9oY2Zj1Jk= go.opentelemetry.io/otel/metric v0.26.0 h1:VaPYBTvA13h/FsiWfxa3yZnZEm15BhStD8JZQSA773M= go.opentelemetry.io/otel/metric v0.26.0/go.mod h1:c6YL0fhRo4YVoNs6GoByzUgBp36hBL523rECoZA5UWg= go.opentelemetry.io/otel/sdk v1.3.0 h1:3278edCoH89MEJ0Ky8WQXVmDQv3FX4ZJ3Pp+9fJreAI= go.opentelemetry.io/otel/sdk v1.3.0/go.mod h1:rIo4suHNhQwBIPg9axF8V9CA72Wz2mKF1teNrup8yzs= go.opentelemetry.io/otel/sdk/export/metric v0.26.0 h1:eNseg5yyZqaAAY+Att3owR3Bl0Is5rCZywqO1OrGx18= go.opentelemetry.io/otel/sdk/export/metric v0.26.0/go.mod h1:UpqzSnUOjFeSIVQLPp3pYIXfB/MiMFyXXzYT/bercxQ= go.opentelemetry.io/otel/sdk/metric v0.26.0 h1:7IKp3gc/ObieCtshBeYYVFp3ZP7xIH1OzODi1Wao90Y= go.opentelemetry.io/otel/sdk/metric v0.26.0/go.mod h1:2VIeK0kS1YvRLFg3J58ptZTXYpiWlkq2n5RQt6w7He8= go.opentelemetry.io/otel/trace v1.3.0 h1:doy8Hzb1RJ+I3yFhtDmwNc7tIyw1tNMOIsyPzp1NOGY= go.opentelemetry.io/otel/trace v1.3.0/go.mod h1:c/VDhno8888bvQYmbYLqe41/Ldmr/KKunbvWM4/fEjk= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.opentelemetry.io/proto/otlp v0.11.0 h1:cLDgIBTf4lLOlztkhzAEdQsJ4Lj+i5Wc9k6Nn0K1VyU= go.opentelemetry.io/proto/otlp v0.11.0/go.mod h1:QpEjXPrNQzrFDZgoTo49dgHR9RYRSrg3NAKnUGl9YpQ= go.uber.org/goleak v1.1.12 h1:gZAh5/EyT/HQwlpkCy6wTpqfH9H8Lz8zbm3dZh+OyzA= go.uber.org/goleak v1.1.12/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 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/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 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-20190108225652-1e06a53dbb7e/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-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 h1:4nGaVu0QrbjT/AK2PRLuQfQuh6DJve+pELhqTdAj3x0= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 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-20181221193216-37e7f081c4d4/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.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 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-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007 h1:gG67DSER+11cZvqIMb8S8bt0vZtiN6xWYARwirrOSfE= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 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/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 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-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20200527145253-8367513e4ece h1:1YM0uhfumvoDu9sx8+RyWwTI63zoCQvI23IYFRlvte0= google.golang.org/genproto v0.0.0-20200527145253-8367513e4ece/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= 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.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= google.golang.org/grpc v1.43.0 h1:Eeu7bZtDZ2DpRCsLhUlcrLnvYaMK1Gz86a+hMVvELmM= google.golang.org/grpc v1.43.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= 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.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= ================================================ FILE: chapter/9/alerting/server/main.go ================================================ package main import ( "context" "log" "math/rand" "net/http" "os" "time" "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/exporters/otlp/otlpmetric" "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc" "go.opentelemetry.io/otel/exporters/otlp/otlptrace" "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc" "go.opentelemetry.io/otel/metric" "go.opentelemetry.io/otel/metric/global" "go.opentelemetry.io/otel/propagation" controller "go.opentelemetry.io/otel/sdk/metric/controller/basic" processor "go.opentelemetry.io/otel/sdk/metric/processor/basic" "go.opentelemetry.io/otel/sdk/metric/selector/simple" "go.opentelemetry.io/otel/sdk/resource" sdktrace "go.opentelemetry.io/otel/sdk/trace" semconv "go.opentelemetry.io/otel/semconv/v1.4.0" "go.opentelemetry.io/otel/trace" "google.golang.org/grpc" ) var rng = rand.New(rand.NewSource(time.Now().UnixNano())) // main initializes metrics and tracing providers and listens to requests at /hello returning "Hello World!" with // randomized latency. func main() { shutdown := initProvider() defer shutdown() // create a handler wrapped in OpenTelemetry instrumentation handler := handleRequestWithRandomSleep() wrappedHandler := otelhttp.NewHandler(handler, "/hello") // serve up the wrapped handler http.Handle("/hello", wrappedHandler) http.ListenAndServe(":7080", nil) } // handleRequestWithRandomSleep registers a request handler that will record request counts and randomly sleep to induce // artificial request latency. func handleRequestWithRandomSleep() http.HandlerFunc { var ( meter = global.Meter("demo-server-meter") instruments = NewServerInstruments(meter) commonLabels = []attribute.KeyValue{ attribute.String("server-attribute", "foo"), } ) return func(w http.ResponseWriter, req *http.Request) { // random sleep to simulate latency var sleep int64 switch modulus := time.Now().Unix() % 5; modulus { case 0: sleep = rng.Int63n(2000) case 1: sleep = rng.Int63n(15) case 2: sleep = rng.Int63n(917) case 3: sleep = rng.Int63n(87) case 4: sleep = rng.Int63n(1173) } time.Sleep(time.Duration(sleep) * time.Millisecond) ctx := req.Context() meter.RecordBatch( ctx, commonLabels, instruments.RequestCount.Measurement(1), ) span := trace.SpanFromContext(ctx) span.SetAttributes(commonLabels...) w.Write([]byte("Hello World")) } } // initTraceAndMetricsProvider initializes an OTLP exporter, and configures the corresponding trace and // metric providers. func initProvider() func() { ctx := context.Background() otelAgentAddr, ok := os.LookupEnv("OTEL_EXPORTER_OTLP_ENDPOINT") if !ok { otelAgentAddr = "0.0.0.0:4317" } closeMetrics := initMetrics(ctx, otelAgentAddr) closeTraces := initTracer(ctx, otelAgentAddr) return func() { doneCtx, cancel := context.WithTimeout(ctx, time.Second) defer cancel() // pushes any last exports to the receiver closeTraces(doneCtx) closeMetrics(doneCtx) } } // initTracer initializes an OTLP trace exporter and registers the trace provider with the global context func initTracer(ctx context.Context, otelAgentAddr string) func(context.Context) { traceClient := otlptracegrpc.NewClient( otlptracegrpc.WithInsecure(), otlptracegrpc.WithEndpoint(otelAgentAddr), otlptracegrpc.WithDialOption(grpc.WithBlock())) traceExp, err := otlptrace.New(ctx, traceClient) handleErr(err, "Failed to create the collector trace exporter") res, err := resource.New(ctx, resource.WithFromEnv(), resource.WithProcess(), resource.WithTelemetrySDK(), resource.WithHost(), resource.WithAttributes( // the service name used to display traces in backends semconv.ServiceNameKey.String("demo-server"), ), ) handleErr(err, "failed to create resource") bsp := sdktrace.NewBatchSpanProcessor(traceExp) tracerProvider := sdktrace.NewTracerProvider( sdktrace.WithSampler(sdktrace.AlwaysSample()), sdktrace.WithResource(res), sdktrace.WithSpanProcessor(bsp), ) // set global propagator to tracecontext (the default is no-op). otel.SetTextMapPropagator(propagation.TraceContext{}) otel.SetTracerProvider(tracerProvider) return func(doneCtx context.Context) { if err := traceExp.Shutdown(doneCtx); err != nil { otel.Handle(err) } } } // initMetrics initializes a metrics pusher and registers the metrics provider with the global context func initMetrics(ctx context.Context, otelAgentAddr string) func(context.Context) { metricClient := otlpmetricgrpc.NewClient( otlpmetricgrpc.WithInsecure(), otlpmetricgrpc.WithEndpoint(otelAgentAddr)) metricExp, err := otlpmetric.New(ctx, metricClient) handleErr(err, "Failed to create the collector metric exporter") res, err := resource.New(ctx, resource.WithFromEnv(), resource.WithProcess(), resource.WithTelemetrySDK(), resource.WithHost(), resource.WithAttributes( // the service name used to display traces in backends semconv.ServiceNameKey.String("demo-server"), ), ) pusher := controller.New( processor.NewFactory( simple.NewWithHistogramDistribution(), metricExp, ), controller.WithExporter(metricExp), controller.WithCollectPeriod(2*time.Second), controller.WithResource(res), ) global.SetMeterProvider(pusher) err = pusher.Start(ctx) handleErr(err, "Failed to start metric pusher") return func(doneCtx context.Context) { // pushes any last exports to the receiver if err := pusher.Stop(doneCtx); err != nil { otel.Handle(err) } } } func handleErr(err error, message string) { if err != nil { log.Fatalf("%s: %v", message, err) } } // ServerInstruments contains the metric instruments used by the server type ServerInstruments struct { RequestCount metric.Int64Counter } // NewServerInstruments takes a meter and builds a request count instrument to be used to measure server received requests. func NewServerInstruments(meter metric.Meter) ServerInstruments { return ServerInstruments{ RequestCount: metric.Must(meter).NewInt64Counter( "demo_server/request_counts", metric.WithDescription("The number of requests received"), ), } } ================================================ FILE: chapter/9/logging/docker-compose.yml ================================================ version: "3" services: opentelemetry-collector-contrib: image: otel/opentelemetry-collector-contrib-dev:latest command: ["--config=/etc/otel-collector-config.yml"] volumes: - ./otel-collector-config.yml:/etc/otel-collector-config.yml - ./varlogpods:/var/log/pods ================================================ FILE: chapter/9/logging/otel-collector-config.yml ================================================ receivers: filelog: include: - /var/log/pods/*/*/*.log exclude: # Exclude logs from all containers named otel-collector - /var/log/pods/*/otel-collector/*.log start_at: beginning include_file_path: true include_file_name: false operators: # Find out which format is used by kubernetes - type: router id: get-format routes: - output: parser-docker expr: '$$body matches "^\\{"' - output: parser-crio expr: '$$body matches "^[^ Z]+ "' - output: parser-containerd expr: '$$body matches "^[^ Z]+Z"' # Parse CRI-O format - type: regex_parser id: parser-crio regex: '^(?P