From 26c7b77f3ef56984371b6f8af8446e3e1fab1296 Mon Sep 17 00:00:00 2001 From: Ming Zhao Date: Sat, 25 Apr 2026 14:47:58 +0000 Subject: [PATCH] Add production-grade Go gRPC scaffold service MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reference implementation of a production-ready Go gRPC service on K8s, demonstrating current best practices: structured logging (slog), OTel metrics with Prometheus, gRPC channelz/reflection/health, rate limiting, auth interceptor, structured errors (google.rpc.Status + errdetails), feature flags (OpenFeature), and optional Postgres via pgx+sqlc. Also upgrades rules_go 0.56.1→0.60.0, gazelle 0.44.0→0.50.0, and Go SDK 1.24.6→1.24.13 to fix Bazel 8.5.0 compatibility. --- MODULE.bazel | 25 +- MODULE.bazel.lock | 1401 +++++------------ README.md | 10 + WORKSPACE | 4 + deps.bzl | 561 +++++++ go.work | 1 + grpc_example/go/BUILD.bazel | 1 - grpc_example/proto/BUILD.bazel | 15 +- grpc_example/rust/BUILD.bazel | 8 +- scaffold/BUILD.bazel | 0 scaffold/cmd/greeter/BUILD.bazel | 39 + scaffold/cmd/greeter/main.go | 179 +++ scaffold/deploy/Dockerfile | 16 + scaffold/deploy/deployment.yaml | 72 + scaffold/deploy/hpa.yaml | 18 + scaffold/deploy/service.yaml | 23 + scaffold/go.mod | 44 + scaffold/go.sum | 93 ++ scaffold/greeterservice/BUILD.bazel | 28 + scaffold/greeterservice/service.go | 79 + scaffold/greeterservice/service_test.go | 46 + scaffold/internal/admin/BUILD.bazel | 13 + scaffold/internal/admin/admin.go | 45 + scaffold/internal/auth/BUILD.bazel | 14 + scaffold/internal/auth/auth.go | 81 + scaffold/internal/database/BUILD.bazel | 9 + scaffold/internal/database/database.go | 52 + scaffold/internal/database/db/BUILD.bazel | 16 + scaffold/internal/database/db/db.go | 26 + scaffold/internal/database/db/models.go | 10 + scaffold/internal/database/db/query.sql.go | 53 + scaffold/internal/database/query.sql | 8 + scaffold/internal/database/schema.sql | 6 + scaffold/internal/database/sqlc.yaml | 10 + scaffold/internal/errors/BUILD.bazel | 17 + scaffold/internal/errors/errors.go | 107 ++ scaffold/internal/featureflags/BUILD.bazel | 9 + scaffold/internal/featureflags/flags.go | 12 + scaffold/internal/logging/BUILD.bazel | 14 + scaffold/internal/logging/logging.go | 95 ++ scaffold/internal/observability/BUILD.bazel | 18 + .../internal/observability/observability.go | 70 + scaffold/internal/ratelimit/BUILD.bazel | 16 + scaffold/internal/ratelimit/ratelimit.go | 44 + scaffold/internal/version/BUILD.bazel | 8 + scaffold/internal/version/version.go | 7 + 46 files changed, 2378 insertions(+), 1045 deletions(-) create mode 100644 deps.bzl create mode 100644 scaffold/BUILD.bazel create mode 100644 scaffold/cmd/greeter/BUILD.bazel create mode 100644 scaffold/cmd/greeter/main.go create mode 100644 scaffold/deploy/Dockerfile create mode 100644 scaffold/deploy/deployment.yaml create mode 100644 scaffold/deploy/hpa.yaml create mode 100644 scaffold/deploy/service.yaml create mode 100644 scaffold/go.mod create mode 100644 scaffold/go.sum create mode 100644 scaffold/greeterservice/BUILD.bazel create mode 100644 scaffold/greeterservice/service.go create mode 100644 scaffold/greeterservice/service_test.go create mode 100644 scaffold/internal/admin/BUILD.bazel create mode 100644 scaffold/internal/admin/admin.go create mode 100644 scaffold/internal/auth/BUILD.bazel create mode 100644 scaffold/internal/auth/auth.go create mode 100644 scaffold/internal/database/BUILD.bazel create mode 100644 scaffold/internal/database/database.go create mode 100644 scaffold/internal/database/db/BUILD.bazel create mode 100644 scaffold/internal/database/db/db.go create mode 100644 scaffold/internal/database/db/models.go create mode 100644 scaffold/internal/database/db/query.sql.go create mode 100644 scaffold/internal/database/query.sql create mode 100644 scaffold/internal/database/schema.sql create mode 100644 scaffold/internal/database/sqlc.yaml create mode 100644 scaffold/internal/errors/BUILD.bazel create mode 100644 scaffold/internal/errors/errors.go create mode 100644 scaffold/internal/featureflags/BUILD.bazel create mode 100644 scaffold/internal/featureflags/flags.go create mode 100644 scaffold/internal/logging/BUILD.bazel create mode 100644 scaffold/internal/logging/logging.go create mode 100644 scaffold/internal/observability/BUILD.bazel create mode 100644 scaffold/internal/observability/observability.go create mode 100644 scaffold/internal/ratelimit/BUILD.bazel create mode 100644 scaffold/internal/ratelimit/ratelimit.go create mode 100644 scaffold/internal/version/BUILD.bazel create mode 100644 scaffold/internal/version/version.go diff --git a/MODULE.bazel b/MODULE.bazel index a5e799a..e2ddd3a 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -5,10 +5,10 @@ module( ) # Go rules for building Go code -bazel_dep(name = "rules_go", version = "0.56.1") +bazel_dep(name = "rules_go", version = "0.60.0") # Gazelle for generating BUILD files automatically -bazel_dep(name = "gazelle", version = "0.44.0") +bazel_dep(name = "gazelle", version = "0.50.0") # Rust rules for building Rust code bazel_dep(name = "rules_rust", version = "0.67.0") @@ -22,7 +22,7 @@ bazel_dep(name = "protobuf", version = "29.0") # Configure Go SDK go_sdk = use_extension("@rules_go//go:extensions.bzl", "go_sdk") -go_sdk.download(version = "1.24.6") +go_sdk.download(version = "1.24.13") # Configure Go dependencies from go.work go_deps = use_extension("@gazelle//:extensions.bzl", "go_deps") @@ -34,7 +34,24 @@ go_deps.config( "GOPROXY": "https://proxy.golang.org,direct", }, ) -use_repo(go_deps, "org_golang_google_grpc") +use_repo( + go_deps, + "com_github_google_uuid", + "com_github_grpc_ecosystem_go_grpc_middleware_v2", + "com_github_jackc_pgx_v5", + "com_github_open_feature_go_sdk", + "com_github_prometheus_client_golang", + "io_opentelemetry_go_contrib_instrumentation_google_golang_org_grpc_otelgrpc", + "io_opentelemetry_go_otel", + "io_opentelemetry_go_otel_exporters_prometheus", + "io_opentelemetry_go_otel_sdk", + "io_opentelemetry_go_otel_sdk_metric", + "io_opentelemetry_go_otel_trace", + "org_golang_google_genproto_googleapis_rpc", + "org_golang_google_grpc", + "org_golang_google_protobuf", + "org_golang_x_time", +) # Configure Rust toolchain rust = use_extension("@rules_rust//rust:extensions.bzl", "rust") diff --git a/MODULE.bazel.lock b/MODULE.bazel.lock index b55de90..bea545b 100644 --- a/MODULE.bazel.lock +++ b/MODULE.bazel.lock @@ -1,5 +1,5 @@ { - "lockFileVersion": 13, + "lockFileVersion": 24, "registryFileHashes": { "https://bcr.bazel.build/bazel_registry.json": "8a28e4aff06ee60aed2a8c281907fb8bcbf3b753c91fb5a5c57da3215d5b3497", "https://bcr.bazel.build/modules/abseil-cpp/20210324.2/MODULE.bazel": "7cd0312e064fde87c8d1cd79ba06c876bd23630c83466e9500321be55c96ace2", @@ -11,9 +11,9 @@ "https://bcr.bazel.build/modules/abseil-cpp/20240116.1/MODULE.bazel": "37bcdb4440fbb61df6a1c296ae01b327f19e9bb521f9b8e26ec854b6f97309ed", "https://bcr.bazel.build/modules/abseil-cpp/20240116.1/source.json": "9be551b8d4e3ef76875c0d744b5d6a504a27e3ae67bc6b28f46415fd2d2957da", "https://bcr.bazel.build/modules/apple_support/1.13.0/MODULE.bazel": "7c8cdea7e031b7f9f67f0b497adf6d2c6a2675e9304ca93a9af6ed84eef5a524", + "https://bcr.bazel.build/modules/apple_support/1.23.1/MODULE.bazel": "53763fed456a968cf919b3240427cf3a9d5481ec5466abc9d5dc51bc70087442", "https://bcr.bazel.build/modules/apple_support/1.24.1/MODULE.bazel": "f46e8ddad60aef170ee92b2f3d00ef66c147ceafea68b6877cb45bd91737f5f8", "https://bcr.bazel.build/modules/apple_support/1.24.1/source.json": "cf725267cbacc5f028ef13bb77e7f2c2e0066923a4dab1025e4a0511b1ed258a", - "https://bcr.bazel.build/modules/apple_support/1.5.0/MODULE.bazel": "50341a62efbc483e8a2a6aec30994a58749bd7b885e18dd96aa8c33031e558ef", "https://bcr.bazel.build/modules/aspect_bazel_lib/1.31.2/MODULE.bazel": "7bee702b4862612f29333590f4b658a5832d433d6f8e4395f090e8f4e85d442f", "https://bcr.bazel.build/modules/aspect_bazel_lib/1.38.0/MODULE.bazel": "6307fec451ba9962c1c969eb516ebfe1e46528f7fa92e1c9ac8646bef4cdaa3f", "https://bcr.bazel.build/modules/aspect_bazel_lib/1.40.3/MODULE.bazel": "668e6bcb4d957fc0e284316dba546b705c8d43c857f87119619ee83c4555b859", @@ -30,8 +30,10 @@ "https://bcr.bazel.build/modules/bazel_features/1.19.0/MODULE.bazel": "59adcdf28230d220f0067b1f435b8537dd033bfff8db21335ef9217919c7fb58", "https://bcr.bazel.build/modules/bazel_features/1.27.0/MODULE.bazel": "621eeee06c4458a9121d1f104efb80f39d34deff4984e778359c60eaf1a8cb65", "https://bcr.bazel.build/modules/bazel_features/1.28.0/MODULE.bazel": "4b4200e6cbf8fa335b2c3f43e1d6ef3e240319c33d43d60cc0fbd4b87ece299d", + "https://bcr.bazel.build/modules/bazel_features/1.30.0/MODULE.bazel": "a14b62d05969a293b80257e72e597c2da7f717e1e69fa8b339703ed6731bec87", "https://bcr.bazel.build/modules/bazel_features/1.32.0/MODULE.bazel": "095d67022a58cb20f7e20e1aefecfa65257a222c18a938e2914fd257b5f1ccdc", - "https://bcr.bazel.build/modules/bazel_features/1.32.0/source.json": "2546c766986a6541f0bacd3e8542a1f621e2b14a80ea9e88c6f89f7eedf64ae1", + "https://bcr.bazel.build/modules/bazel_features/1.36.0/MODULE.bazel": "596cb62090b039caf1cad1d52a8bc35cf188ca9a4e279a828005e7ee49a1bec3", + "https://bcr.bazel.build/modules/bazel_features/1.36.0/source.json": "279625cafa5b63cc0a8ee8448d93bc5ac1431f6000c50414051173fd22a6df3c", "https://bcr.bazel.build/modules/bazel_features/1.4.1/MODULE.bazel": "e45b6bb2350aff3e442ae1111c555e27eac1d915e77775f6fdc4b351b758b5d7", "https://bcr.bazel.build/modules/bazel_features/1.9.1/MODULE.bazel": "8f679097876a9b609ad1f60249c49d68bfab783dd9be012faf9d82547b14815a", "https://bcr.bazel.build/modules/bazel_skylib/1.0.3/MODULE.bazel": "bcb0fd896384802d1ad283b4e4eb4d718eebd8cb820b0a2c3a347fb971afd9d8", @@ -55,8 +57,8 @@ "https://bcr.bazel.build/modules/gazelle/0.33.0/MODULE.bazel": "a13a0f279b462b784fb8dd52a4074526c4a2afe70e114c7d09066097a46b3350", "https://bcr.bazel.build/modules/gazelle/0.34.0/MODULE.bazel": "abdd8ce4d70978933209db92e436deb3a8b737859e9354fb5fd11fb5c2004c8a", "https://bcr.bazel.build/modules/gazelle/0.36.0/MODULE.bazel": "e375d5d6e9a6ca59b0cb38b0540bc9a05b6aa926d322f2de268ad267a2ee74c0", - "https://bcr.bazel.build/modules/gazelle/0.44.0/MODULE.bazel": "fd3177ca0938da57a1e416cad3f39b9c4334defbc717e89aba9d9ddbbb0341da", - "https://bcr.bazel.build/modules/gazelle/0.44.0/source.json": "7fb65ef9c1ce470d099ca27fd478673d9d64c844af28d0d472b0874c7d590cb6", + "https://bcr.bazel.build/modules/gazelle/0.50.0/MODULE.bazel": "5dce69aa1d7b5bc85d1e1cfe61f0a9a27426c46425ec1d064685f941a5977329", + "https://bcr.bazel.build/modules/gazelle/0.50.0/source.json": "3cadcd284172c4274dc44c71b571f08ec4f22b28d5e9ad2708347c6e6c3a41b9", "https://bcr.bazel.build/modules/google_benchmark/1.8.2/MODULE.bazel": "a70cf1bba851000ba93b58ae2f6d76490a9feb74192e57ab8e8ff13c34ec50cb", "https://bcr.bazel.build/modules/googletest/1.11.0/MODULE.bazel": "3a83f095183f66345ca86aa13c58b59f9f94a2f81999c093d4eeaa2d262d12f4", "https://bcr.bazel.build/modules/googletest/1.14.0.bcr.1/MODULE.bazel": "22c31a561553727960057361aa33bf20fb2e98584bc4fec007906e27053f80c6", @@ -65,9 +67,10 @@ "https://bcr.bazel.build/modules/jsoncpp/1.9.5/MODULE.bazel": "31271aedc59e815656f5736f282bb7509a97c7ecb43e927ac1a37966e0578075", "https://bcr.bazel.build/modules/jsoncpp/1.9.5/source.json": "4108ee5085dd2885a341c7fab149429db457b3169b86eb081fa245eadf69169d", "https://bcr.bazel.build/modules/libpfm/4.11.0/MODULE.bazel": "45061ff025b301940f1e30d2c16bea596c25b176c8b6b3087e92615adbd52902", - "https://bcr.bazel.build/modules/package_metadata/0.0.2/MODULE.bazel": "fb8d25550742674d63d7b250063d4580ca530499f045d70748b1b142081ebb92", - "https://bcr.bazel.build/modules/package_metadata/0.0.2/source.json": "e53a759a72488d2c0576f57491ef2da0cf4aab05ac0997314012495935531b73", + "https://bcr.bazel.build/modules/package_metadata/0.0.5/MODULE.bazel": "ef4f9439e3270fdd6b9fd4dbc3d2f29d13888e44c529a1b243f7a31dfbc2e8e4", + "https://bcr.bazel.build/modules/package_metadata/0.0.5/source.json": "2326db2f6592578177751c3e1f74786b79382cd6008834c9d01ec865b9126a85", "https://bcr.bazel.build/modules/platforms/0.0.10/MODULE.bazel": "8cb8efaf200bdeb2150d93e162c40f388529a25852b332cec879373771e48ed5", + "https://bcr.bazel.build/modules/platforms/0.0.11/MODULE.bazel": "0daefc49732e227caa8bfa834d65dc52e8cc18a2faf80df25e8caea151a9413f", "https://bcr.bazel.build/modules/platforms/0.0.4/MODULE.bazel": "9b328e31ee156f53f3c416a64f8491f7eb731742655a47c9eec4703a71644aee", "https://bcr.bazel.build/modules/platforms/0.0.5/MODULE.bazel": "5733b54ea419d5eaf7997054bb55f6a1d0b5ff8aedf0176fef9eea44f3acda37", "https://bcr.bazel.build/modules/platforms/0.0.6/MODULE.bazel": "ad6eeef431dc52aefd2d77ed20a4b353f8ebf0f4ecdd26a807d2da5aa8cd0615", @@ -81,6 +84,7 @@ "https://bcr.bazel.build/modules/protobuf/27.1/MODULE.bazel": "703a7b614728bb06647f965264967a8ef1c39e09e8f167b3ca0bb1fd80449c0d", "https://bcr.bazel.build/modules/protobuf/28.3/MODULE.bazel": "2b3764bbab2e46703412bd3b859efcf0322638ed015e88432df3bb740507a1e9", "https://bcr.bazel.build/modules/protobuf/29.0-rc2.bcr.1/MODULE.bazel": "52f4126f63a2f0bbf36b99c2a87648f08467a4eaf92ba726bc7d6a500bbf770c", + "https://bcr.bazel.build/modules/protobuf/29.0-rc2/MODULE.bazel": "6241d35983510143049943fc0d57937937122baf1b287862f9dc8590fc4c37df", "https://bcr.bazel.build/modules/protobuf/29.0/MODULE.bazel": "319dc8bf4c679ff87e71b1ccfb5a6e90a6dbc4693501d471f48662ac46d04e4e", "https://bcr.bazel.build/modules/protobuf/29.1/MODULE.bazel": "557c3457560ff49e122ed76c0bc3397a64af9574691cb8201b4e46d4ab2ecb95", "https://bcr.bazel.build/modules/protobuf/29.1/source.json": "04cca85dce26b895ed037d98336d860367fe09919208f2ad383f0df1aff63199", @@ -97,12 +101,16 @@ "https://bcr.bazel.build/modules/rules_cc/0.0.1/MODULE.bazel": "cb2aa0747f84c6c3a78dad4e2049c154f08ab9d166b1273835a8174940365647", "https://bcr.bazel.build/modules/rules_cc/0.0.10/MODULE.bazel": "ec1705118f7eaedd6e118508d3d26deba2a4e76476ada7e0e3965211be012002", "https://bcr.bazel.build/modules/rules_cc/0.0.13/MODULE.bazel": "0e8529ed7b323dad0775ff924d2ae5af7640b23553dfcd4d34344c7e7a867191", + "https://bcr.bazel.build/modules/rules_cc/0.0.14/MODULE.bazel": "5e343a3aac88b8d7af3b1b6d2093b55c347b8eefc2e7d1442f7a02dc8fea48ac", "https://bcr.bazel.build/modules/rules_cc/0.0.15/MODULE.bazel": "6704c35f7b4a72502ee81f61bf88706b54f06b3cbe5558ac17e2e14666cd5dcc", "https://bcr.bazel.build/modules/rules_cc/0.0.16/MODULE.bazel": "7661303b8fc1b4d7f532e54e9d6565771fea666fbdf839e0a86affcd02defe87", + "https://bcr.bazel.build/modules/rules_cc/0.0.17/MODULE.bazel": "2ae1d8f4238ec67d7185d8861cb0a2cdf4bc608697c331b95bf990e69b62e64a", "https://bcr.bazel.build/modules/rules_cc/0.0.2/MODULE.bazel": "6915987c90970493ab97393024c156ea8fb9f3bea953b2f3ec05c34f19b5695c", "https://bcr.bazel.build/modules/rules_cc/0.0.6/MODULE.bazel": "abf360251023dfe3efcef65ab9d56beefa8394d4176dd29529750e1c57eaa33f", "https://bcr.bazel.build/modules/rules_cc/0.0.8/MODULE.bazel": "964c85c82cfeb6f3855e6a07054fdb159aced38e99a5eecf7bce9d53990afa3e", "https://bcr.bazel.build/modules/rules_cc/0.0.9/MODULE.bazel": "836e76439f354b89afe6a911a7adf59a6b2518fafb174483ad78a2a2fde7b1c5", + "https://bcr.bazel.build/modules/rules_cc/0.1.1/MODULE.bazel": "2f0222a6f229f0bf44cd711dc13c858dad98c62d52bd51d8fc3a764a83125513", + "https://bcr.bazel.build/modules/rules_cc/0.1.5/MODULE.bazel": "88dfc9361e8b5ae1008ac38f7cdfd45ad738e4fa676a3ad67d19204f045a1fd8", "https://bcr.bazel.build/modules/rules_cc/0.2.4/MODULE.bazel": "1ff1223dfd24f3ecf8f028446d4a27608aa43c3f41e346d22838a4223980b8cc", "https://bcr.bazel.build/modules/rules_cc/0.2.8/MODULE.bazel": "f1df20f0bf22c28192a794f29b501ee2018fa37a3862a1a2132ae2940a23a642", "https://bcr.bazel.build/modules/rules_cc/0.2.8/source.json": "85087982aca15f31307bd52698316b28faa31bd2c3095a41f456afec0131344c", @@ -115,23 +123,29 @@ "https://bcr.bazel.build/modules/rules_go/0.41.0/MODULE.bazel": "55861d8e8bb0e62cbd2896f60ff303f62ffcb0eddb74ecb0e5c0cbe36fc292c8", "https://bcr.bazel.build/modules/rules_go/0.42.0/MODULE.bazel": "8cfa875b9aa8c6fce2b2e5925e73c1388173ea3c32a0db4d2b4804b453c14270", "https://bcr.bazel.build/modules/rules_go/0.46.0/MODULE.bazel": "3477df8bdcc49e698b9d25f734c4f3a9f5931ff34ee48a2c662be168f5f2d3fd", - "https://bcr.bazel.build/modules/rules_go/0.51.0/MODULE.bazel": "b6920f505935bfd69381651c942496d99b16e2a12f3dd5263b90ded16f3b4d0f", - "https://bcr.bazel.build/modules/rules_go/0.56.1/MODULE.bazel": "d5b835c548ac917345f1780cd2da52edc1130a908fe091c92096895303ae78a0", - "https://bcr.bazel.build/modules/rules_go/0.56.1/source.json": "0c902f7272e8d4e47e459af97be472bc19dadbbe6023a0719d1adce8483ac75a", + "https://bcr.bazel.build/modules/rules_go/0.59.0/MODULE.bazel": "b7e43e7414a3139a7547d1b4909b29085fbe5182b6c58cbe1ed4c6272815aeae", + "https://bcr.bazel.build/modules/rules_go/0.60.0/MODULE.bazel": "4a57ff2ffc2a3570e3c5646575c5a4b07287e91bcdac5d1f72383d51502b48cb", + "https://bcr.bazel.build/modules/rules_go/0.60.0/source.json": "1e21368c5e0c3013a110bd79a8fcff8ca46b5bcb2b561713a7273cbfcff7c464", "https://bcr.bazel.build/modules/rules_java/4.0.0/MODULE.bazel": "5a78a7ae82cd1a33cef56dc578c7d2a46ed0dca12643ee45edbb8417899e6f74", "https://bcr.bazel.build/modules/rules_java/5.3.5/MODULE.bazel": "a4ec4f2db570171e3e5eb753276ee4b389bae16b96207e9d3230895c99644b86", + "https://bcr.bazel.build/modules/rules_java/6.0.0/MODULE.bazel": "8a43b7df601a7ec1af61d79345c17b31ea1fedc6711fd4abfd013ea612978e39", + "https://bcr.bazel.build/modules/rules_java/6.4.0/MODULE.bazel": "e986a9fe25aeaa84ac17ca093ef13a4637f6107375f64667a15999f77db6c8f6", "https://bcr.bazel.build/modules/rules_java/6.5.2/MODULE.bazel": "1d440d262d0e08453fa0c4d8f699ba81609ed0e9a9a0f02cd10b3e7942e61e31", "https://bcr.bazel.build/modules/rules_java/7.10.0/MODULE.bazel": "530c3beb3067e870561739f1144329a21c851ff771cd752a49e06e3dc9c2e71a", "https://bcr.bazel.build/modules/rules_java/7.12.2/MODULE.bazel": "579c505165ee757a4280ef83cda0150eea193eed3bef50b1004ba88b99da6de6", - "https://bcr.bazel.build/modules/rules_java/7.12.2/source.json": "b0890f9cda8ff1b8e691a3ac6037b5c14b7fd4134765a3946b89f31ea02e5884", "https://bcr.bazel.build/modules/rules_java/7.2.0/MODULE.bazel": "06c0334c9be61e6cef2c8c84a7800cef502063269a5af25ceb100b192453d4ab", + "https://bcr.bazel.build/modules/rules_java/7.3.2/MODULE.bazel": "50dece891cfdf1741ea230d001aa9c14398062f2b7c066470accace78e412bc2", "https://bcr.bazel.build/modules/rules_java/7.6.1/MODULE.bazel": "2f14b7e8a1aa2f67ae92bc69d1ec0fa8d9f827c4e17ff5e5f02e91caa3b2d0fe", - "https://bcr.bazel.build/modules/rules_java/7.6.5/MODULE.bazel": "481164be5e02e4cab6e77a36927683263be56b7e36fef918b458d7a8a1ebadb1", + "https://bcr.bazel.build/modules/rules_java/8.14.0/MODULE.bazel": "717717ed40cc69994596a45aec6ea78135ea434b8402fb91b009b9151dd65615", + "https://bcr.bazel.build/modules/rules_java/8.14.0/source.json": "8a88c4ca9e8759da53cddc88123880565c520503321e2566b4e33d0287a3d4bc", "https://bcr.bazel.build/modules/rules_jvm_external/4.4.2/MODULE.bazel": "a56b85e418c83eb1839819f0b515c431010160383306d13ec21959ac412d2fe7", "https://bcr.bazel.build/modules/rules_jvm_external/5.1/MODULE.bazel": "33f6f999e03183f7d088c9be518a63467dfd0be94a11d0055fe2d210f89aa909", "https://bcr.bazel.build/modules/rules_jvm_external/5.2/MODULE.bazel": "d9351ba35217ad0de03816ef3ed63f89d411349353077348a45348b096615036", + "https://bcr.bazel.build/modules/rules_jvm_external/5.3/MODULE.bazel": "bf93870767689637164657731849fb887ad086739bd5d360d90007a581d5527d", + "https://bcr.bazel.build/modules/rules_jvm_external/6.1/MODULE.bazel": "75b5fec090dbd46cf9b7d8ea08cf84a0472d92ba3585b476f44c326eda8059c4", "https://bcr.bazel.build/modules/rules_jvm_external/6.3/MODULE.bazel": "c998e060b85f71e00de5ec552019347c8bca255062c990ac02d051bb80a38df0", "https://bcr.bazel.build/modules/rules_jvm_external/6.3/source.json": "6f5f5a5a4419ae4e37c35a5bb0a6ae657ed40b7abc5a5189111b47fcebe43197", + "https://bcr.bazel.build/modules/rules_kotlin/1.9.0/MODULE.bazel": "ef85697305025e5a61f395d4eaede272a5393cee479ace6686dba707de804d59", "https://bcr.bazel.build/modules/rules_kotlin/1.9.6/MODULE.bazel": "d269a01a18ee74d0335450b10f62c9ed81f2321d7958a2934e44272fe82dcef3", "https://bcr.bazel.build/modules/rules_kotlin/1.9.6/source.json": "2faa4794364282db7c06600b7e5e34867a564ae91bda7cae7c29c64e9466b7d5", "https://bcr.bazel.build/modules/rules_license/0.0.3/MODULE.bazel": "627e9ab0247f7d1e05736b59dbb1b6871373de5ad31c3011880b4133cafd4bd0", @@ -145,19 +159,18 @@ "https://bcr.bazel.build/modules/rules_pkg/1.0.1/source.json": "bd82e5d7b9ce2d31e380dd9f50c111d678c3bdaca190cb76b0e1c71b05e1ba8a", "https://bcr.bazel.build/modules/rules_proto/4.0.0/MODULE.bazel": "a7a7b6ce9bee418c1a760b3d84f83a299ad6952f9903c67f19e4edd964894e06", "https://bcr.bazel.build/modules/rules_proto/5.3.0-21.7/MODULE.bazel": "e8dff86b0971688790ae75528fe1813f71809b5afd57facb44dad9e8eca631b7", - "https://bcr.bazel.build/modules/rules_proto/6.0.0/MODULE.bazel": "b531d7f09f58dce456cd61b4579ce8c86b38544da75184eadaf0a7cb7966453f", "https://bcr.bazel.build/modules/rules_proto/6.0.2/MODULE.bazel": "ce916b775a62b90b61888052a416ccdda405212b6aaeb39522f7dc53431a5e73", "https://bcr.bazel.build/modules/rules_proto/7.0.2/MODULE.bazel": "bf81793bd6d2ad89a37a40693e56c61b0ee30f7a7fdbaf3eabbf5f39de47dea2", "https://bcr.bazel.build/modules/rules_proto/7.1.0/MODULE.bazel": "002d62d9108f75bb807cd56245d45648f38275cb3a99dcd45dfb864c5d74cb96", "https://bcr.bazel.build/modules/rules_proto/7.1.0/source.json": "39f89066c12c24097854e8f57ab8558929f9c8d474d34b2c00ac04630ad8940e", "https://bcr.bazel.build/modules/rules_python/0.10.2/MODULE.bazel": "cc82bc96f2997baa545ab3ce73f196d040ffb8756fd2d66125a530031cd90e5f", - "https://bcr.bazel.build/modules/rules_python/0.22.1/MODULE.bazel": "26114f0c0b5e93018c0c066d6673f1a2c3737c7e90af95eff30cfee38d0bbac7", "https://bcr.bazel.build/modules/rules_python/0.23.1/MODULE.bazel": "49ffccf0511cb8414de28321f5fcf2a31312b47c40cc21577144b7447f2bf300", "https://bcr.bazel.build/modules/rules_python/0.25.0/MODULE.bazel": "72f1506841c920a1afec76975b35312410eea3aa7b63267436bfb1dd91d2d382", "https://bcr.bazel.build/modules/rules_python/0.28.0/MODULE.bazel": "cba2573d870babc976664a912539b320cbaa7114cd3e8f053c720171cde331ed", "https://bcr.bazel.build/modules/rules_python/0.31.0/MODULE.bazel": "93a43dc47ee570e6ec9f5779b2e64c1476a6ce921c48cc9a1678a91dd5f8fd58", - "https://bcr.bazel.build/modules/rules_python/0.31.0/source.json": "a41c836d4065888eef4377f2f27b6eea0fedb9b5adb1bab1970437373fe90dc7", "https://bcr.bazel.build/modules/rules_python/0.4.0/MODULE.bazel": "9208ee05fd48bf09ac60ed269791cf17fb343db56c8226a720fbb1cdf467166c", + "https://bcr.bazel.build/modules/rules_python/0.40.0/MODULE.bazel": "9d1a3cd88ed7d8e39583d9ffe56ae8a244f67783ae89b60caafc9f5cf318ada7", + "https://bcr.bazel.build/modules/rules_python/0.40.0/source.json": "939d4bd2e3110f27bfb360292986bb79fd8dcefb874358ccd6cdaa7bda029320", "https://bcr.bazel.build/modules/rules_rust/0.45.1/MODULE.bazel": "a69d0db3a958fab2c6520961e1b2287afcc8b36690fd31bbc4f6f7391397150d", "https://bcr.bazel.build/modules/rules_rust/0.67.0/MODULE.bazel": "87c3816c4321352dcfd9e9e26b58e84efc5b21351ae3ef8fb5d0d57bde7237f5", "https://bcr.bazel.build/modules/rules_rust/0.67.0/source.json": "a8ef4d3be30eb98e060cad9e5875a55b603195487f76e01b619b51a1df4641cc", @@ -171,27 +184,29 @@ "https://bcr.bazel.build/modules/stardoc/0.5.1/MODULE.bazel": "1a05d92974d0c122f5ccf09291442580317cdd859f07a8655f1db9a60374f9f8", "https://bcr.bazel.build/modules/stardoc/0.5.3/MODULE.bazel": "c7f6948dae6999bf0db32c1858ae345f112cacf98f174c7a8bb707e41b974f1c", "https://bcr.bazel.build/modules/stardoc/0.5.4/MODULE.bazel": "6569966df04610b8520957cb8e97cf2e9faac2c0309657c537ab51c16c18a2a4", + "https://bcr.bazel.build/modules/stardoc/0.5.6/MODULE.bazel": "c43dabc564990eeab55e25ed61c07a1aadafe9ece96a4efabb3f8bf9063b71ef", "https://bcr.bazel.build/modules/stardoc/0.7.0/MODULE.bazel": "05e3d6d30c099b6770e97da986c53bd31844d7f13d41412480ea265ac9e8079c", + "https://bcr.bazel.build/modules/stardoc/0.7.1/MODULE.bazel": "3548faea4ee5dda5580f9af150e79d0f6aea934fc60c1cc50f4efdd9420759e7", + "https://bcr.bazel.build/modules/stardoc/0.7.1/source.json": "b6500ffcd7b48cd72c29bb67bcac781e12701cc0d6d55d266a652583cfcdab01", "https://bcr.bazel.build/modules/upb/0.0.0-20220923-a547704/MODULE.bazel": "7298990c00040a0e2f121f6c32544bab27d4452f80d9ce51349b1a28f3005c43", "https://bcr.bazel.build/modules/zlib/1.2.11/MODULE.bazel": "07b389abc85fdbca459b69e2ec656ae5622873af3f845e1c9d80fe179f3effa0", "https://bcr.bazel.build/modules/zlib/1.2.12/MODULE.bazel": "3b1a8834ada2a883674be8cbd36ede1b6ec481477ada359cd2d3ddc562340b27", - "https://bcr.bazel.build/modules/zlib/1.3.1.bcr.3/MODULE.bazel": "af322bc08976524477c79d1e45e241b6efbeb918c497e8840b8ab116802dda79", - "https://bcr.bazel.build/modules/zlib/1.3.1.bcr.3/source.json": "2be409ac3c7601245958cd4fcdff4288be79ed23bd690b4b951f500d54ee6e7d", + "https://bcr.bazel.build/modules/zlib/1.3.1.bcr.5/MODULE.bazel": "eec517b5bbe5492629466e11dae908d043364302283de25581e3eb944326c4ca", + "https://bcr.bazel.build/modules/zlib/1.3.1.bcr.5/source.json": "22bc55c47af97246cfc093d0acf683a7869377de362b5d1c552c2c2e16b7a806", "https://bcr.bazel.build/modules/zlib/1.3.1/MODULE.bazel": "751c9940dcfe869f5f7274e1295422a34623555916eb98c174c1e945594bf198" }, "selectedYankedVersions": {}, "moduleExtensions": { - "@@rules_kotlin~//src/main/starlark/core/repositories:bzlmod_setup.bzl%rules_kotlin_extensions": { + "@@rules_kotlin+//src/main/starlark/core/repositories:bzlmod_setup.bzl%rules_kotlin_extensions": { "general": { - "bzlTransitiveDigest": "fus14IFJ/1LGWWGKPH/U18VnJCoMjfDt1ckahqCnM0A=", - "usagesDigest": "aJF6fLy82rR95Ff5CZPAqxNoFgOMLMN5ImfBS0nhnkg=", + "bzlTransitiveDigest": "rL/34P1aFDq2GqVC2zCFgQ8nTuOC6ziogocpvG50Qz8=", + "usagesDigest": "QI2z8ZUR+mqtbwsf2fLqYdJAkPOHdOV+tF2yVAUgRzw=", "recordedFileInputs": {}, "recordedDirentsInputs": {}, "envVariables": {}, "generatedRepoSpecs": { "com_github_jetbrains_kotlin_git": { - "bzlFile": "@@rules_kotlin~//src/main/starlark/core/repositories:compiler.bzl", - "ruleClassName": "kotlin_compiler_git_repository", + "repoRuleId": "@@rules_kotlin+//src/main/starlark/core/repositories:compiler.bzl%kotlin_compiler_git_repository", "attributes": { "urls": [ "https://github.com/JetBrains/kotlin/releases/download/v1.9.23/kotlin-compiler-1.9.23.zip" @@ -200,16 +215,14 @@ } }, "com_github_jetbrains_kotlin": { - "bzlFile": "@@rules_kotlin~//src/main/starlark/core/repositories:compiler.bzl", - "ruleClassName": "kotlin_capabilities_repository", + "repoRuleId": "@@rules_kotlin+//src/main/starlark/core/repositories:compiler.bzl%kotlin_capabilities_repository", "attributes": { "git_repository_name": "com_github_jetbrains_kotlin_git", "compiler_version": "1.9.23" } }, "com_github_google_ksp": { - "bzlFile": "@@rules_kotlin~//src/main/starlark/core/repositories:ksp.bzl", - "ruleClassName": "ksp_compiler_plugin_repository", + "repoRuleId": "@@rules_kotlin+//src/main/starlark/core/repositories:ksp.bzl%ksp_compiler_plugin_repository", "attributes": { "urls": [ "https://github.com/google/ksp/releases/download/1.9.23-1.0.20/artifacts.zip" @@ -219,8 +232,7 @@ } }, "com_github_pinterest_ktlint": { - "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", - "ruleClassName": "http_file", + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_file", "attributes": { "sha256": "01b2e0ef893383a50dbeb13970fe7fa3be36ca3e83259e01649945b09d736985", "urls": [ @@ -230,8 +242,7 @@ } }, "rules_android": { - "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", - "ruleClassName": "http_archive", + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", "attributes": { "sha256": "cd06d15dd8bb59926e4d65f9003bfc20f9da4b2519985c27e190cddc8b7a7806", "strip_prefix": "rules_android-0.1.1", @@ -243,1016 +254,338 @@ }, "recordedRepoMappingEntries": [ [ - "rules_kotlin~", + "rules_kotlin+", "bazel_tools", "bazel_tools" ] ] } - }, - "@@rules_python~//python/extensions:python.bzl%python": { - "general": { - "bzlTransitiveDigest": "8vDKUdCc6qEk2/YsFiPsFO1Jqgl+XPFRklapOxMAbE8=", - "usagesDigest": "ATOseWU5EE5A0RV+r4ACrYJ9g+Rp7OdaFygles1jsNk=", - "recordedFileInputs": {}, - "recordedDirentsInputs": {}, - "envVariables": { - "RULES_PYTHON_BZLMOD_DEBUG": null - }, - "generatedRepoSpecs": { - "python_3_8_aarch64-apple-darwin": { - "bzlFile": "@@rules_python~//python:repositories.bzl", - "ruleClassName": "python_repository", - "attributes": { - "sha256": "1825b1f7220bc93ff143f2e70b5c6a79c6469e0eeb40824e07a7277f59aabfda", - "patches": [], - "platform": "aarch64-apple-darwin", - "python_version": "3.8.18", - "release_filename": "20231002/cpython-3.8.18+20231002-aarch64-apple-darwin-install_only.tar.gz", - "urls": [ - "https://github.com/indygreg/python-build-standalone/releases/download/20231002/cpython-3.8.18+20231002-aarch64-apple-darwin-install_only.tar.gz" - ], - "distutils_content": "", - "strip_prefix": "python", - "coverage_tool": "", - "ignore_root_user_error": false - } - }, - "python_3_8_aarch64-unknown-linux-gnu": { - "bzlFile": "@@rules_python~//python:repositories.bzl", - "ruleClassName": "python_repository", - "attributes": { - "sha256": "236a300f386ead02ca98dbddbc026ff4ef4de6701a394106e291ff8b75445ee1", - "patches": [], - "platform": "aarch64-unknown-linux-gnu", - "python_version": "3.8.18", - "release_filename": "20231002/cpython-3.8.18+20231002-aarch64-unknown-linux-gnu-install_only.tar.gz", - "urls": [ - "https://github.com/indygreg/python-build-standalone/releases/download/20231002/cpython-3.8.18+20231002-aarch64-unknown-linux-gnu-install_only.tar.gz" - ], - "distutils_content": "", - "strip_prefix": "python", - "coverage_tool": "", - "ignore_root_user_error": false - } - }, - "python_3_8_x86_64-apple-darwin": { - "bzlFile": "@@rules_python~//python:repositories.bzl", - "ruleClassName": "python_repository", - "attributes": { - "sha256": "fcf04532e644644213977242cd724fe5e84c0a5ac92ae038e07f1b01b474fca3", - "patches": [], - "platform": "x86_64-apple-darwin", - "python_version": "3.8.18", - "release_filename": "20231002/cpython-3.8.18+20231002-x86_64-apple-darwin-install_only.tar.gz", - "urls": [ - "https://github.com/indygreg/python-build-standalone/releases/download/20231002/cpython-3.8.18+20231002-x86_64-apple-darwin-install_only.tar.gz" - ], - "distutils_content": "", - "strip_prefix": "python", - "coverage_tool": "", - "ignore_root_user_error": false - } - }, - "python_3_8_x86_64-pc-windows-msvc": { - "bzlFile": "@@rules_python~//python:repositories.bzl", - "ruleClassName": "python_repository", - "attributes": { - "sha256": "a9d203e78caed94de368d154e841610cef6f6b484738573f4ae9059d37e898a5", - "patches": [], - "platform": "x86_64-pc-windows-msvc", - "python_version": "3.8.18", - "release_filename": "20231002/cpython-3.8.18+20231002-x86_64-pc-windows-msvc-shared-install_only.tar.gz", - "urls": [ - "https://github.com/indygreg/python-build-standalone/releases/download/20231002/cpython-3.8.18+20231002-x86_64-pc-windows-msvc-shared-install_only.tar.gz" - ], - "distutils_content": "", - "strip_prefix": "python", - "coverage_tool": "", - "ignore_root_user_error": false - } - }, - "python_3_8_x86_64-unknown-linux-gnu": { - "bzlFile": "@@rules_python~//python:repositories.bzl", - "ruleClassName": "python_repository", - "attributes": { - "sha256": "1e8a3babd1500111359b0f5675d770984bcbcb2cc8890b117394f0ed342fb9ec", - "patches": [], - "platform": "x86_64-unknown-linux-gnu", - "python_version": "3.8.18", - "release_filename": "20231002/cpython-3.8.18+20231002-x86_64-unknown-linux-gnu-install_only.tar.gz", - "urls": [ - "https://github.com/indygreg/python-build-standalone/releases/download/20231002/cpython-3.8.18+20231002-x86_64-unknown-linux-gnu-install_only.tar.gz" - ], - "distutils_content": "", - "strip_prefix": "python", - "coverage_tool": "", - "ignore_root_user_error": false - } - }, - "python_3_8_host": { - "bzlFile": "@@rules_python~//python/private:toolchains_repo.bzl", - "ruleClassName": "host_toolchain", - "attributes": { - "python_version": "3.8.18", - "user_repository_name": "python_3_8", - "platforms": [ - "aarch64-apple-darwin", - "aarch64-unknown-linux-gnu", - "x86_64-apple-darwin", - "x86_64-pc-windows-msvc", - "x86_64-unknown-linux-gnu" - ] - } - }, - "python_3_8": { - "bzlFile": "@@rules_python~//python/private:toolchains_repo.bzl", - "ruleClassName": "toolchain_aliases", - "attributes": { - "python_version": "3.8.18", - "user_repository_name": "python_3_8", - "platforms": [ - "aarch64-apple-darwin", - "aarch64-unknown-linux-gnu", - "x86_64-apple-darwin", - "x86_64-pc-windows-msvc", - "x86_64-unknown-linux-gnu" - ] - } - }, - "python_3_9_aarch64-apple-darwin": { - "bzlFile": "@@rules_python~//python:repositories.bzl", - "ruleClassName": "python_repository", - "attributes": { - "sha256": "fdc4054837e37b69798c2ef796222a480bc1f80e8ad3a01a95d0168d8282a007", - "patches": [], - "platform": "aarch64-apple-darwin", - "python_version": "3.9.18", - "release_filename": "20231002/cpython-3.9.18+20231002-aarch64-apple-darwin-install_only.tar.gz", - "urls": [ - "https://github.com/indygreg/python-build-standalone/releases/download/20231002/cpython-3.9.18+20231002-aarch64-apple-darwin-install_only.tar.gz" - ], - "distutils_content": "", - "strip_prefix": "python", - "coverage_tool": "", - "ignore_root_user_error": false - } - }, - "python_3_9_aarch64-unknown-linux-gnu": { - "bzlFile": "@@rules_python~//python:repositories.bzl", - "ruleClassName": "python_repository", - "attributes": { - "sha256": "1e0a3e8ce8e58901a259748c0ab640d2b8294713782d14229e882c6898b2fb36", - "patches": [], - "platform": "aarch64-unknown-linux-gnu", - "python_version": "3.9.18", - "release_filename": "20231002/cpython-3.9.18+20231002-aarch64-unknown-linux-gnu-install_only.tar.gz", - "urls": [ - "https://github.com/indygreg/python-build-standalone/releases/download/20231002/cpython-3.9.18+20231002-aarch64-unknown-linux-gnu-install_only.tar.gz" - ], - "distutils_content": "", - "strip_prefix": "python", - "coverage_tool": "", - "ignore_root_user_error": false - } - }, - "python_3_9_ppc64le-unknown-linux-gnu": { - "bzlFile": "@@rules_python~//python:repositories.bzl", - "ruleClassName": "python_repository", - "attributes": { - "sha256": "101c38b22fb2f5a0945156da4259c8e9efa0c08de9d7f59afa51e7ce6e22a1cc", - "patches": [], - "platform": "ppc64le-unknown-linux-gnu", - "python_version": "3.9.18", - "release_filename": "20231002/cpython-3.9.18+20231002-ppc64le-unknown-linux-gnu-install_only.tar.gz", - "urls": [ - "https://github.com/indygreg/python-build-standalone/releases/download/20231002/cpython-3.9.18+20231002-ppc64le-unknown-linux-gnu-install_only.tar.gz" - ], - "distutils_content": "", - "strip_prefix": "python", - "coverage_tool": "", - "ignore_root_user_error": false - } - }, - "python_3_9_s390x-unknown-linux-gnu": { - "bzlFile": "@@rules_python~//python:repositories.bzl", - "ruleClassName": "python_repository", - "attributes": { - "sha256": "eee31e55ffbc1f460d7b17f05dd89e45a2636f374a6f8dc29ea13d0497f7f586", - "patches": [], - "platform": "s390x-unknown-linux-gnu", - "python_version": "3.9.18", - "release_filename": "20231002/cpython-3.9.18+20231002-s390x-unknown-linux-gnu-install_only.tar.gz", - "urls": [ - "https://github.com/indygreg/python-build-standalone/releases/download/20231002/cpython-3.9.18+20231002-s390x-unknown-linux-gnu-install_only.tar.gz" - ], - "distutils_content": "", - "strip_prefix": "python", - "coverage_tool": "", - "ignore_root_user_error": false - } - }, - "python_3_9_x86_64-apple-darwin": { - "bzlFile": "@@rules_python~//python:repositories.bzl", - "ruleClassName": "python_repository", - "attributes": { - "sha256": "82231cb77d4a5c8081a1a1d5b8ae440abe6993514eb77a926c826e9a69a94fb1", - "patches": [], - "platform": "x86_64-apple-darwin", - "python_version": "3.9.18", - "release_filename": "20231002/cpython-3.9.18+20231002-x86_64-apple-darwin-install_only.tar.gz", - "urls": [ - "https://github.com/indygreg/python-build-standalone/releases/download/20231002/cpython-3.9.18+20231002-x86_64-apple-darwin-install_only.tar.gz" - ], - "distutils_content": "", - "strip_prefix": "python", - "coverage_tool": "", - "ignore_root_user_error": false - } - }, - "python_3_9_x86_64-pc-windows-msvc": { - "bzlFile": "@@rules_python~//python:repositories.bzl", - "ruleClassName": "python_repository", - "attributes": { - "sha256": "02ea7bb64524886bd2b05d6b6be4401035e4ba4319146f274f0bcd992822cd75", - "patches": [], - "platform": "x86_64-pc-windows-msvc", - "python_version": "3.9.18", - "release_filename": "20231002/cpython-3.9.18+20231002-x86_64-pc-windows-msvc-shared-install_only.tar.gz", - "urls": [ - "https://github.com/indygreg/python-build-standalone/releases/download/20231002/cpython-3.9.18+20231002-x86_64-pc-windows-msvc-shared-install_only.tar.gz" - ], - "distutils_content": "", - "strip_prefix": "python", - "coverage_tool": "", - "ignore_root_user_error": false - } - }, - "python_3_9_x86_64-unknown-linux-gnu": { - "bzlFile": "@@rules_python~//python:repositories.bzl", - "ruleClassName": "python_repository", - "attributes": { - "sha256": "f3ff38b1ccae7dcebd8bbf2e533c9a984fac881de0ffd1636fbb61842bd924de", - "patches": [], - "platform": "x86_64-unknown-linux-gnu", - "python_version": "3.9.18", - "release_filename": "20231002/cpython-3.9.18+20231002-x86_64-unknown-linux-gnu-install_only.tar.gz", - "urls": [ - "https://github.com/indygreg/python-build-standalone/releases/download/20231002/cpython-3.9.18+20231002-x86_64-unknown-linux-gnu-install_only.tar.gz" - ], - "distutils_content": "", - "strip_prefix": "python", - "coverage_tool": "", - "ignore_root_user_error": false - } - }, - "python_3_9_host": { - "bzlFile": "@@rules_python~//python/private:toolchains_repo.bzl", - "ruleClassName": "host_toolchain", - "attributes": { - "python_version": "3.9.18", - "user_repository_name": "python_3_9", - "platforms": [ - "aarch64-apple-darwin", - "aarch64-unknown-linux-gnu", - "ppc64le-unknown-linux-gnu", - "s390x-unknown-linux-gnu", - "x86_64-apple-darwin", - "x86_64-pc-windows-msvc", - "x86_64-unknown-linux-gnu" - ] - } - }, - "python_3_9": { - "bzlFile": "@@rules_python~//python/private:toolchains_repo.bzl", - "ruleClassName": "toolchain_aliases", - "attributes": { - "python_version": "3.9.18", - "user_repository_name": "python_3_9", - "platforms": [ - "aarch64-apple-darwin", - "aarch64-unknown-linux-gnu", - "ppc64le-unknown-linux-gnu", - "s390x-unknown-linux-gnu", - "x86_64-apple-darwin", - "x86_64-pc-windows-msvc", - "x86_64-unknown-linux-gnu" - ] - } - }, - "python_3_10_aarch64-apple-darwin": { - "bzlFile": "@@rules_python~//python:repositories.bzl", - "ruleClassName": "python_repository", - "attributes": { - "sha256": "fd027b1dedf1ea034cdaa272e91771bdf75ddef4c8653b05d224a0645aa2ca3c", - "patches": [], - "platform": "aarch64-apple-darwin", - "python_version": "3.10.13", - "release_filename": "20231002/cpython-3.10.13+20231002-aarch64-apple-darwin-install_only.tar.gz", - "urls": [ - "https://github.com/indygreg/python-build-standalone/releases/download/20231002/cpython-3.10.13+20231002-aarch64-apple-darwin-install_only.tar.gz" - ], - "distutils_content": "", - "strip_prefix": "python", - "coverage_tool": "", - "ignore_root_user_error": false - } - }, - "python_3_10_aarch64-unknown-linux-gnu": { - "bzlFile": "@@rules_python~//python:repositories.bzl", - "ruleClassName": "python_repository", - "attributes": { - "sha256": "8675915ff454ed2f1597e27794bc7df44f5933c26b94aa06af510fe91b58bb97", - "patches": [], - "platform": "aarch64-unknown-linux-gnu", - "python_version": "3.10.13", - "release_filename": "20231002/cpython-3.10.13+20231002-aarch64-unknown-linux-gnu-install_only.tar.gz", - "urls": [ - "https://github.com/indygreg/python-build-standalone/releases/download/20231002/cpython-3.10.13+20231002-aarch64-unknown-linux-gnu-install_only.tar.gz" - ], - "distutils_content": "", - "strip_prefix": "python", - "coverage_tool": "", - "ignore_root_user_error": false - } - }, - "python_3_10_ppc64le-unknown-linux-gnu": { - "bzlFile": "@@rules_python~//python:repositories.bzl", - "ruleClassName": "python_repository", - "attributes": { - "sha256": "f3f9c43eec1a0c3f72845d0b705da17a336d3906b7df212d2640b8f47e8ff375", - "patches": [], - "platform": "ppc64le-unknown-linux-gnu", - "python_version": "3.10.13", - "release_filename": "20231002/cpython-3.10.13+20231002-ppc64le-unknown-linux-gnu-install_only.tar.gz", - "urls": [ - "https://github.com/indygreg/python-build-standalone/releases/download/20231002/cpython-3.10.13+20231002-ppc64le-unknown-linux-gnu-install_only.tar.gz" - ], - "distutils_content": "", - "strip_prefix": "python", - "coverage_tool": "", - "ignore_root_user_error": false - } - }, - "python_3_10_s390x-unknown-linux-gnu": { - "bzlFile": "@@rules_python~//python:repositories.bzl", - "ruleClassName": "python_repository", - "attributes": { - "sha256": "859f6cfe9aedb6e8858892fdc124037e83ab05f28d42a7acd314c6a16d6bd66c", - "patches": [], - "platform": "s390x-unknown-linux-gnu", - "python_version": "3.10.13", - "release_filename": "20231002/cpython-3.10.13+20231002-s390x-unknown-linux-gnu-install_only.tar.gz", - "urls": [ - "https://github.com/indygreg/python-build-standalone/releases/download/20231002/cpython-3.10.13+20231002-s390x-unknown-linux-gnu-install_only.tar.gz" - ], - "distutils_content": "", - "strip_prefix": "python", - "coverage_tool": "", - "ignore_root_user_error": false - } - }, - "python_3_10_x86_64-apple-darwin": { - "bzlFile": "@@rules_python~//python:repositories.bzl", - "ruleClassName": "python_repository", - "attributes": { - "sha256": "be0b19b6af1f7d8c667e5abef5505ad06cf72e5a11bb5844970c395a7e5b1275", - "patches": [], - "platform": "x86_64-apple-darwin", - "python_version": "3.10.13", - "release_filename": "20231002/cpython-3.10.13+20231002-x86_64-apple-darwin-install_only.tar.gz", - "urls": [ - "https://github.com/indygreg/python-build-standalone/releases/download/20231002/cpython-3.10.13+20231002-x86_64-apple-darwin-install_only.tar.gz" - ], - "distutils_content": "", - "strip_prefix": "python", - "coverage_tool": "", - "ignore_root_user_error": false - } - }, - "python_3_10_x86_64-pc-windows-msvc": { - "bzlFile": "@@rules_python~//python:repositories.bzl", - "ruleClassName": "python_repository", - "attributes": { - "sha256": "b8d930ce0d04bda83037ad3653d7450f8907c88e24bb8255a29b8dab8930d6f1", - "patches": [], - "platform": "x86_64-pc-windows-msvc", - "python_version": "3.10.13", - "release_filename": "20231002/cpython-3.10.13+20231002-x86_64-pc-windows-msvc-shared-install_only.tar.gz", - "urls": [ - "https://github.com/indygreg/python-build-standalone/releases/download/20231002/cpython-3.10.13+20231002-x86_64-pc-windows-msvc-shared-install_only.tar.gz" - ], - "distutils_content": "", - "strip_prefix": "python", - "coverage_tool": "", - "ignore_root_user_error": false - } - }, - "python_3_10_x86_64-unknown-linux-gnu": { - "bzlFile": "@@rules_python~//python:repositories.bzl", - "ruleClassName": "python_repository", - "attributes": { - "sha256": "5d0429c67c992da19ba3eb58b3acd0b35ec5e915b8cae9a4aa8ca565c423847a", - "patches": [], - "platform": "x86_64-unknown-linux-gnu", - "python_version": "3.10.13", - "release_filename": "20231002/cpython-3.10.13+20231002-x86_64-unknown-linux-gnu-install_only.tar.gz", - "urls": [ - "https://github.com/indygreg/python-build-standalone/releases/download/20231002/cpython-3.10.13+20231002-x86_64-unknown-linux-gnu-install_only.tar.gz" - ], - "distutils_content": "", - "strip_prefix": "python", - "coverage_tool": "", - "ignore_root_user_error": false - } - }, - "python_3_10_host": { - "bzlFile": "@@rules_python~//python/private:toolchains_repo.bzl", - "ruleClassName": "host_toolchain", - "attributes": { - "python_version": "3.10.13", - "user_repository_name": "python_3_10", - "platforms": [ - "aarch64-apple-darwin", - "aarch64-unknown-linux-gnu", - "ppc64le-unknown-linux-gnu", - "s390x-unknown-linux-gnu", - "x86_64-apple-darwin", - "x86_64-pc-windows-msvc", - "x86_64-unknown-linux-gnu" - ] - } - }, - "python_3_10": { - "bzlFile": "@@rules_python~//python/private:toolchains_repo.bzl", - "ruleClassName": "toolchain_aliases", - "attributes": { - "python_version": "3.10.13", - "user_repository_name": "python_3_10", - "platforms": [ - "aarch64-apple-darwin", - "aarch64-unknown-linux-gnu", - "ppc64le-unknown-linux-gnu", - "s390x-unknown-linux-gnu", - "x86_64-apple-darwin", - "x86_64-pc-windows-msvc", - "x86_64-unknown-linux-gnu" - ] - } - }, - "python_3_11_aarch64-apple-darwin": { - "bzlFile": "@@rules_python~//python:repositories.bzl", - "ruleClassName": "python_repository", - "attributes": { - "sha256": "b042c966920cf8465385ca3522986b12d745151a72c060991088977ca36d3883", - "patches": [], - "platform": "aarch64-apple-darwin", - "python_version": "3.11.7", - "release_filename": "20240107/cpython-3.11.7+20240107-aarch64-apple-darwin-install_only.tar.gz", - "urls": [ - "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.11.7+20240107-aarch64-apple-darwin-install_only.tar.gz" - ], - "distutils_content": "", - "strip_prefix": "python", - "coverage_tool": "", - "ignore_root_user_error": false - } - }, - "python_3_11_aarch64-unknown-linux-gnu": { - "bzlFile": "@@rules_python~//python:repositories.bzl", - "ruleClassName": "python_repository", - "attributes": { - "sha256": "b102eaf865eb715aa98a8a2ef19037b6cc3ae7dfd4a632802650f29de635aa13", - "patches": [], - "platform": "aarch64-unknown-linux-gnu", - "python_version": "3.11.7", - "release_filename": "20240107/cpython-3.11.7+20240107-aarch64-unknown-linux-gnu-install_only.tar.gz", - "urls": [ - "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.11.7+20240107-aarch64-unknown-linux-gnu-install_only.tar.gz" - ], - "distutils_content": "", - "strip_prefix": "python", - "coverage_tool": "", - "ignore_root_user_error": false - } - }, - "python_3_11_ppc64le-unknown-linux-gnu": { - "bzlFile": "@@rules_python~//python:repositories.bzl", - "ruleClassName": "python_repository", - "attributes": { - "sha256": "b44e1b74afe75c7b19143413632c4386708ae229117f8f950c2094e9681d34c7", - "patches": [], - "platform": "ppc64le-unknown-linux-gnu", - "python_version": "3.11.7", - "release_filename": "20240107/cpython-3.11.7+20240107-ppc64le-unknown-linux-gnu-install_only.tar.gz", - "urls": [ - "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.11.7+20240107-ppc64le-unknown-linux-gnu-install_only.tar.gz" - ], - "distutils_content": "", - "strip_prefix": "python", - "coverage_tool": "", - "ignore_root_user_error": false - } - }, - "python_3_11_s390x-unknown-linux-gnu": { - "bzlFile": "@@rules_python~//python:repositories.bzl", - "ruleClassName": "python_repository", - "attributes": { - "sha256": "49520e3ff494708020f306e30b0964f079170be83e956be4504f850557378a22", - "patches": [], - "platform": "s390x-unknown-linux-gnu", - "python_version": "3.11.7", - "release_filename": "20240107/cpython-3.11.7+20240107-s390x-unknown-linux-gnu-install_only.tar.gz", - "urls": [ - "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.11.7+20240107-s390x-unknown-linux-gnu-install_only.tar.gz" - ], - "distutils_content": "", - "strip_prefix": "python", - "coverage_tool": "", - "ignore_root_user_error": false - } - }, - "python_3_11_x86_64-apple-darwin": { - "bzlFile": "@@rules_python~//python:repositories.bzl", - "ruleClassName": "python_repository", - "attributes": { - "sha256": "a0e615eef1fafdc742da0008425a9030b7ea68a4ae4e73ac557ef27b112836d4", - "patches": [], - "platform": "x86_64-apple-darwin", - "python_version": "3.11.7", - "release_filename": "20240107/cpython-3.11.7+20240107-x86_64-apple-darwin-install_only.tar.gz", - "urls": [ - "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.11.7+20240107-x86_64-apple-darwin-install_only.tar.gz" - ], - "distutils_content": "", - "strip_prefix": "python", - "coverage_tool": "", - "ignore_root_user_error": false - } - }, - "python_3_11_x86_64-pc-windows-msvc": { - "bzlFile": "@@rules_python~//python:repositories.bzl", - "ruleClassName": "python_repository", - "attributes": { - "sha256": "67077e6fa918e4f4fd60ba169820b00be7c390c497bf9bc9cab2c255ea8e6f3e", - "patches": [], - "platform": "x86_64-pc-windows-msvc", - "python_version": "3.11.7", - "release_filename": "20240107/cpython-3.11.7+20240107-x86_64-pc-windows-msvc-shared-install_only.tar.gz", - "urls": [ - "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.11.7+20240107-x86_64-pc-windows-msvc-shared-install_only.tar.gz" - ], - "distutils_content": "", - "strip_prefix": "python", - "coverage_tool": "", - "ignore_root_user_error": false - } - }, - "python_3_11_x86_64-unknown-linux-gnu": { - "bzlFile": "@@rules_python~//python:repositories.bzl", - "ruleClassName": "python_repository", - "attributes": { - "sha256": "4a51ce60007a6facf64e5495f4cf322e311ba9f39a8cd3f3e4c026eae488e140", - "patches": [], - "platform": "x86_64-unknown-linux-gnu", - "python_version": "3.11.7", - "release_filename": "20240107/cpython-3.11.7+20240107-x86_64-unknown-linux-gnu-install_only.tar.gz", - "urls": [ - "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.11.7+20240107-x86_64-unknown-linux-gnu-install_only.tar.gz" - ], - "distutils_content": "", - "strip_prefix": "python", - "coverage_tool": "", - "ignore_root_user_error": false - } - }, - "python_3_11_host": { - "bzlFile": "@@rules_python~//python/private:toolchains_repo.bzl", - "ruleClassName": "host_toolchain", - "attributes": { - "python_version": "3.11.7", - "user_repository_name": "python_3_11", - "platforms": [ - "aarch64-apple-darwin", - "aarch64-unknown-linux-gnu", - "ppc64le-unknown-linux-gnu", - "s390x-unknown-linux-gnu", - "x86_64-apple-darwin", - "x86_64-pc-windows-msvc", - "x86_64-unknown-linux-gnu" - ] - } - }, - "python_3_11": { - "bzlFile": "@@rules_python~//python/private:toolchains_repo.bzl", - "ruleClassName": "toolchain_aliases", - "attributes": { - "python_version": "3.11.7", - "user_repository_name": "python_3_11", - "platforms": [ - "aarch64-apple-darwin", - "aarch64-unknown-linux-gnu", - "ppc64le-unknown-linux-gnu", - "s390x-unknown-linux-gnu", - "x86_64-apple-darwin", - "x86_64-pc-windows-msvc", - "x86_64-unknown-linux-gnu" - ] - } - }, - "python_3_12_aarch64-apple-darwin": { - "bzlFile": "@@rules_python~//python:repositories.bzl", - "ruleClassName": "python_repository", - "attributes": { - "sha256": "f93f8375ca6ac0a35d58ff007043cbd3a88d9609113f1cb59cf7c8d215f064af", - "patches": [], - "platform": "aarch64-apple-darwin", - "python_version": "3.12.1", - "release_filename": "20240107/cpython-3.12.1+20240107-aarch64-apple-darwin-install_only.tar.gz", - "urls": [ - "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.12.1+20240107-aarch64-apple-darwin-install_only.tar.gz" - ], - "distutils_content": "", - "strip_prefix": "python", - "coverage_tool": "", - "ignore_root_user_error": false - } - }, - "python_3_12_aarch64-unknown-linux-gnu": { - "bzlFile": "@@rules_python~//python:repositories.bzl", - "ruleClassName": "python_repository", - "attributes": { - "sha256": "236533ef20e665007a111c2f36efb59c87ae195ad7dca223b6dc03fb07064f0b", - "patches": [], - "platform": "aarch64-unknown-linux-gnu", - "python_version": "3.12.1", - "release_filename": "20240107/cpython-3.12.1+20240107-aarch64-unknown-linux-gnu-install_only.tar.gz", - "urls": [ - "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.12.1+20240107-aarch64-unknown-linux-gnu-install_only.tar.gz" - ], - "distutils_content": "", - "strip_prefix": "python", - "coverage_tool": "", - "ignore_root_user_error": false - } - }, - "python_3_12_ppc64le-unknown-linux-gnu": { - "bzlFile": "@@rules_python~//python:repositories.bzl", - "ruleClassName": "python_repository", - "attributes": { - "sha256": "78051f0d1411ee62bc2af5edfccf6e8400ac4ef82887a2affc19a7ace6a05267", - "patches": [], - "platform": "ppc64le-unknown-linux-gnu", - "python_version": "3.12.1", - "release_filename": "20240107/cpython-3.12.1+20240107-ppc64le-unknown-linux-gnu-install_only.tar.gz", - "urls": [ - "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.12.1+20240107-ppc64le-unknown-linux-gnu-install_only.tar.gz" - ], - "distutils_content": "", - "strip_prefix": "python", - "coverage_tool": "", - "ignore_root_user_error": false - } - }, - "python_3_12_s390x-unknown-linux-gnu": { - "bzlFile": "@@rules_python~//python:repositories.bzl", - "ruleClassName": "python_repository", - "attributes": { - "sha256": "60631211c701f8d2c56e5dd7b154e68868128a019b9db1d53a264f56c0d4aee2", - "patches": [], - "platform": "s390x-unknown-linux-gnu", - "python_version": "3.12.1", - "release_filename": "20240107/cpython-3.12.1+20240107-s390x-unknown-linux-gnu-install_only.tar.gz", - "urls": [ - "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.12.1+20240107-s390x-unknown-linux-gnu-install_only.tar.gz" - ], - "distutils_content": "", - "strip_prefix": "python", - "coverage_tool": "", - "ignore_root_user_error": false - } - }, - "python_3_12_x86_64-apple-darwin": { - "bzlFile": "@@rules_python~//python:repositories.bzl", - "ruleClassName": "python_repository", - "attributes": { - "sha256": "eca96158c1568dedd9a0b3425375637a83764d1fa74446438293089a8bfac1f8", - "patches": [], - "platform": "x86_64-apple-darwin", - "python_version": "3.12.1", - "release_filename": "20240107/cpython-3.12.1+20240107-x86_64-apple-darwin-install_only.tar.gz", - "urls": [ - "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.12.1+20240107-x86_64-apple-darwin-install_only.tar.gz" - ], - "distutils_content": "", - "strip_prefix": "python", - "coverage_tool": "", - "ignore_root_user_error": false - } - }, - "python_3_12_x86_64-pc-windows-msvc": { - "bzlFile": "@@rules_python~//python:repositories.bzl", - "ruleClassName": "python_repository", - "attributes": { - "sha256": "fd5a9e0f41959d0341246d3643f2b8794f638adc0cec8dd5e1b6465198eae08a", - "patches": [], - "platform": "x86_64-pc-windows-msvc", - "python_version": "3.12.1", - "release_filename": "20240107/cpython-3.12.1+20240107-x86_64-pc-windows-msvc-shared-install_only.tar.gz", - "urls": [ - "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.12.1+20240107-x86_64-pc-windows-msvc-shared-install_only.tar.gz" - ], - "distutils_content": "", - "strip_prefix": "python", - "coverage_tool": "", - "ignore_root_user_error": false - } - }, - "python_3_12_x86_64-unknown-linux-gnu": { - "bzlFile": "@@rules_python~//python:repositories.bzl", - "ruleClassName": "python_repository", - "attributes": { - "sha256": "74e330b8212ca22fd4d9a2003b9eec14892155566738febc8e5e572f267b9472", - "patches": [], - "platform": "x86_64-unknown-linux-gnu", - "python_version": "3.12.1", - "release_filename": "20240107/cpython-3.12.1+20240107-x86_64-unknown-linux-gnu-install_only.tar.gz", - "urls": [ - "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.12.1+20240107-x86_64-unknown-linux-gnu-install_only.tar.gz" - ], - "distutils_content": "", - "strip_prefix": "python", - "coverage_tool": "", - "ignore_root_user_error": false - } - }, - "python_3_12_host": { - "bzlFile": "@@rules_python~//python/private:toolchains_repo.bzl", - "ruleClassName": "host_toolchain", - "attributes": { - "python_version": "3.12.1", - "user_repository_name": "python_3_12", - "platforms": [ - "aarch64-apple-darwin", - "aarch64-unknown-linux-gnu", - "ppc64le-unknown-linux-gnu", - "s390x-unknown-linux-gnu", - "x86_64-apple-darwin", - "x86_64-pc-windows-msvc", - "x86_64-unknown-linux-gnu" - ] - } - }, - "python_3_12": { - "bzlFile": "@@rules_python~//python/private:toolchains_repo.bzl", - "ruleClassName": "toolchain_aliases", - "attributes": { - "python_version": "3.12.1", - "user_repository_name": "python_3_12", - "platforms": [ - "aarch64-apple-darwin", - "aarch64-unknown-linux-gnu", - "ppc64le-unknown-linux-gnu", - "s390x-unknown-linux-gnu", - "x86_64-apple-darwin", - "x86_64-pc-windows-msvc", - "x86_64-unknown-linux-gnu" - ] - } - }, - "pythons_hub": { - "bzlFile": "@@rules_python~//python/private/bzlmod:pythons_hub.bzl", - "ruleClassName": "hub_repo", - "attributes": { - "default_python_version": "3.11", - "toolchain_prefixes": [ - "_0000_python_3_8_", - "_0001_python_3_9_", - "_0002_python_3_10_", - "_0003_python_3_12_", - "_0004_python_3_11_" - ], - "toolchain_python_versions": [ - "3.8", - "3.9", - "3.10", - "3.12", - "3.11" - ], - "toolchain_set_python_version_constraints": [ - "True", - "True", - "True", - "True", - "False" - ], - "toolchain_user_repository_names": [ - "python_3_8", - "python_3_9", - "python_3_10", - "python_3_12", - "python_3_11" - ] - } - }, - "python_versions": { - "bzlFile": "@@rules_python~//python/private:toolchains_repo.bzl", - "ruleClassName": "multi_toolchain_aliases", - "attributes": { - "python_versions": { - "3.8": "python_3_8", - "3.9": "python_3_9", - "3.10": "python_3_10", - "3.11": "python_3_11", - "3.12": "python_3_12" - } - } - } - }, - "recordedRepoMappingEntries": [ - [ - "rules_python~", - "bazel_skylib", - "bazel_skylib~" - ], - [ - "rules_python~", - "bazel_tools", - "bazel_tools" - ] + } + }, + "facts": { + "@@rules_go+//go:extensions.bzl%go_sdk": { + "1.24.13": { + "aix_ppc64": [ + "go1.24.13.aix-ppc64.tar.gz", + "1b35e5eef87a8125838fdd0a12790f07139aabbc40e81b66c596a0ca51569eda" + ], + "darwin_amd64": [ + "go1.24.13.darwin-amd64.tar.gz", + "6cc6549b06725220b342b740497ffd24e0ebdcef75781a77931ca199f46ad781" + ], + "darwin_arm64": [ + "go1.24.13.darwin-arm64.tar.gz", + "f282d882c3353485e2fc6c634606d85caf36e855167d59b996dbeae19fa7629a" + ], + "dragonfly_amd64": [ + "go1.24.13.dragonfly-amd64.tar.gz", + "558eeb813da2933546c3e46aefd40911261e15d2dfe01b4432744fc2661e59d3" + ], + "freebsd_386": [ + "go1.24.13.freebsd-386.tar.gz", + "1e3bd90c1d138a3d4bbc39f3990f59faab5e9d83006b51c5dec17538716d2651" + ], + "freebsd_amd64": [ + "go1.24.13.freebsd-amd64.tar.gz", + "96e3c439befbb365ecde3ae475f9319ef7693d5d66a05992e8f8d29c60a63761" + ], + "freebsd_arm": [ + "go1.24.13.freebsd-arm.tar.gz", + "3d8005886f2dff23357d5d36e7759fdadc642d5207c01bd9e761d5d9e27f3257" + ], + "freebsd_arm64": [ + "go1.24.13.freebsd-arm64.tar.gz", + "67efe294235fd85fc0fb810275dfd402a459c8522f7bf075f63c7047db474f1b" + ], + "freebsd_riscv64": [ + "go1.24.13.freebsd-riscv64.tar.gz", + "4973d786b9bc4291063ecc69546e662f18d5fa564745673c7ea39c3febc11cc3" + ], + "illumos_amd64": [ + "go1.24.13.illumos-amd64.tar.gz", + "140c87078e2570c179327cb11d515c8396f969ecc202def008b179a1a8ce8919" + ], + "linux_386": [ + "go1.24.13.linux-386.tar.gz", + "a55cb4587b1face90dc9334d8ad44ccd41fade77dcff645a74927eb0adc52272" + ], + "linux_amd64": [ + "go1.24.13.linux-amd64.tar.gz", + "1fc94b57134d51669c72173ad5d49fd62afb0f1db9bf3f798fd98ee423f8d730" + ], + "linux_arm64": [ + "go1.24.13.linux-arm64.tar.gz", + "74d97be1cc3a474129590c67ebf748a96e72d9f3a2b6fef3ed3275de591d49b3" + ], + "linux_armv6l": [ + "go1.24.13.linux-armv6l.tar.gz", + "a26b1e54c0fe7b0babc79716a89b830f1cde54f6c6f914a9995d3f0d0bdd0242" + ], + "linux_loong64": [ + "go1.24.13.linux-loong64.tar.gz", + "8fd090f77b88b9e6f3807a24fce5187163f0036a30d47abab97a1861321f62ca" + ], + "linux_mips": [ + "go1.24.13.linux-mips.tar.gz", + "b879aea34facab984856575ddc3416cfeaa4e3b87a5b23e23ad7acb850bed605" + ], + "linux_mips64": [ + "go1.24.13.linux-mips64.tar.gz", + "b652f8f1199fab0e139bd1e41596470cb104b404e829e658d571ad7566148ada" + ], + "linux_mips64le": [ + "go1.24.13.linux-mips64le.tar.gz", + "d1c233a227fd4c5be04d4e8929b4b75e7237556916b8295c0436a1301af8ea12" + ], + "linux_mipsle": [ + "go1.24.13.linux-mipsle.tar.gz", + "4685999157eafa2b731d58e93b50693c34b6efdb2a63755591e785886ca8aaf0" + ], + "linux_ppc64": [ + "go1.24.13.linux-ppc64.tar.gz", + "0ff9907f079e4f2c4d1c4a30a0acc4bb8b627a7043f49b19dc97eb4491b78fdc" + ], + "linux_ppc64le": [ + "go1.24.13.linux-ppc64le.tar.gz", + "5f0dfab58ce15a84d824363c041246c76847a69d14f9ffac16bd5342299ecc14" + ], + "linux_riscv64": [ + "go1.24.13.linux-riscv64.tar.gz", + "9a8166261489d3f38c7a568785b7012c123e3561779d282d568a72d58506754f" + ], + "linux_s390x": [ + "go1.24.13.linux-s390x.tar.gz", + "a3e3e2012f9b4d392fab85fd4596bbd798ea8e0ceba259f47023b8cb5ebfffc1" + ], + "netbsd_386": [ + "go1.24.13.netbsd-386.tar.gz", + "882bc70303155c0062076b195e18425b7b6b98299cd514c654d08d0770ef3a6a" + ], + "netbsd_amd64": [ + "go1.24.13.netbsd-amd64.tar.gz", + "e4daab492889cc14bbe24b89f586b07b64454a707dd3cc12683d2c2fbee479e6" + ], + "netbsd_arm": [ + "go1.24.13.netbsd-arm.tar.gz", + "248f29ff86c26379a03cc065185417aff7c25db15f81034535c8d49727455802" + ], + "netbsd_arm64": [ + "go1.24.13.netbsd-arm64.tar.gz", + "f402058ddc22df723025725ae5048bb707d492936e9da7c6f01cc20662f1fe33" + ], + "openbsd_386": [ + "go1.24.13.openbsd-386.tar.gz", + "64d9fa7d4c3ba47dfb89f32316c92428a4aea0be06c6e8d8a3dee31fc6d5a560" + ], + "openbsd_amd64": [ + "go1.24.13.openbsd-amd64.tar.gz", + "ed9c2c7eebd576c31599e682917b19fe6fed7afbbef57a8d86aa2f75f5b757ff" + ], + "openbsd_arm": [ + "go1.24.13.openbsd-arm.tar.gz", + "e0caefacd6f82b3b823255f6b4c9614c9cf899e6f15812f87db4fb264533b64b" + ], + "openbsd_arm64": [ + "go1.24.13.openbsd-arm64.tar.gz", + "25ae8746328c37b92fcca8f89d9c28473fb7a7f72c177cdbc35b28f25aea89e4" + ], + "openbsd_ppc64": [ + "go1.24.13.openbsd-ppc64.tar.gz", + "57086bd3469c5c18d3ab1076a31217f7c429428eef78644118d44e0bc9cc13ef" + ], + "openbsd_riscv64": [ + "go1.24.13.openbsd-riscv64.tar.gz", + "a84c8acf158778a49b71e62750a49ca0f3d2082c9952aeb115c0dd9f6eb20469" + ], + "plan9_386": [ + "go1.24.13.plan9-386.tar.gz", + "d196f31881538073fd599739e46911ffde517c7aedc0f053597de8d955aa1689" + ], + "plan9_amd64": [ + "go1.24.13.plan9-amd64.tar.gz", + "5309e57ebd44b6ab7d506e9d0aad4272ec77e694e7ea30588c2acf950d57c5d3" + ], + "plan9_arm": [ + "go1.24.13.plan9-arm.tar.gz", + "e9ea1ab4e67108b0479aab664acdb1ff0bec4e0f09949a7fab859080467664e8" + ], + "solaris_amd64": [ + "go1.24.13.solaris-amd64.tar.gz", + "e2cae0e7c1e33b772ad2b97be28098bd8b97e9b3d5777f5c6caf2793ccee9e3a" + ], + "windows_386": [ + "go1.24.13.windows-386.zip", + "3f52b77db92e1d7d03561db9ee2ef6c5f691228ed301fb2ee07390952c4cbed4" + ], + "windows_amd64": [ + "go1.24.13.windows-amd64.zip", + "40b16bc8f00540a2cb02dff4de72b73e966fdd8d65f95e33d8e4080b48a2459a" + ], + "windows_arm64": [ + "go1.24.13.windows-arm64.zip", + "e2c4e1b0c083965adecb893b97145f89bb8e39b3d745c6499fa1f8aa44bc065f" ] - } - }, - "@@rules_python~//python/private/bzlmod:internal_deps.bzl%internal_deps": { - "general": { - "bzlTransitiveDigest": "7yogJIhmw7i9Wq/n9sQB8N0F84220dJbw64SjOwrmQk=", - "usagesDigest": "r7vtlnQfWxEwrL+QFXux06yzeWEkq/hrcwAssoCoSLY=", - "recordedFileInputs": {}, - "recordedDirentsInputs": {}, - "envVariables": {}, - "generatedRepoSpecs": { - "rules_python_internal": { - "bzlFile": "@@rules_python~//python/private:internal_config_repo.bzl", - "ruleClassName": "internal_config_repo", - "attributes": {} - }, - "pypi__build": { - "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", - "ruleClassName": "http_archive", - "attributes": { - "url": "https://files.pythonhosted.org/packages/58/91/17b00d5fac63d3dca605f1b8269ba3c65e98059e1fd99d00283e42a454f0/build-0.10.0-py3-none-any.whl", - "sha256": "af266720050a66c893a6096a2f410989eeac74ff9a68ba194b3f6473e8e26171", - "type": "zip", - "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:defs.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude in /python/pip_install/tools/bazel.py\n # to avoid non-determinism following pip install's behavior.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/* *\",\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" - } - }, - "pypi__click": { - "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", - "ruleClassName": "http_archive", - "attributes": { - "url": "https://files.pythonhosted.org/packages/00/2e/d53fa4befbf2cfa713304affc7ca780ce4fc1fd8710527771b58311a3229/click-8.1.7-py3-none-any.whl", - "sha256": "ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28", - "type": "zip", - "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:defs.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude in /python/pip_install/tools/bazel.py\n # to avoid non-determinism following pip install's behavior.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/* *\",\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" - } - }, - "pypi__colorama": { - "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", - "ruleClassName": "http_archive", - "attributes": { - "url": "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", - "sha256": "4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", - "type": "zip", - "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:defs.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude in /python/pip_install/tools/bazel.py\n # to avoid non-determinism following pip install's behavior.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/* *\",\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" - } - }, - "pypi__importlib_metadata": { - "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", - "ruleClassName": "http_archive", - "attributes": { - "url": "https://files.pythonhosted.org/packages/cc/37/db7ba97e676af155f5fcb1a35466f446eadc9104e25b83366e8088c9c926/importlib_metadata-6.8.0-py3-none-any.whl", - "sha256": "3ebb78df84a805d7698245025b975d9d67053cd94c79245ba4b3eb694abe68bb", - "type": "zip", - "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:defs.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude in /python/pip_install/tools/bazel.py\n # to avoid non-determinism following pip install's behavior.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/* *\",\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" - } - }, - "pypi__installer": { - "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", - "ruleClassName": "http_archive", - "attributes": { - "url": "https://files.pythonhosted.org/packages/e5/ca/1172b6638d52f2d6caa2dd262ec4c811ba59eee96d54a7701930726bce18/installer-0.7.0-py3-none-any.whl", - "sha256": "05d1933f0a5ba7d8d6296bb6d5018e7c94fa473ceb10cf198a92ccea19c27b53", - "type": "zip", - "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:defs.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude in /python/pip_install/tools/bazel.py\n # to avoid non-determinism following pip install's behavior.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/* *\",\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" - } - }, - "pypi__more_itertools": { - "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", - "ruleClassName": "http_archive", - "attributes": { - "url": "https://files.pythonhosted.org/packages/5a/cb/6dce742ea14e47d6f565589e859ad225f2a5de576d7696e0623b784e226b/more_itertools-10.1.0-py3-none-any.whl", - "sha256": "64e0735fcfdc6f3464ea133afe8ea4483b1c5fe3a3d69852e6503b43a0b222e6", - "type": "zip", - "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:defs.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude in /python/pip_install/tools/bazel.py\n # to avoid non-determinism following pip install's behavior.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/* *\",\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" - } - }, - "pypi__packaging": { - "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", - "ruleClassName": "http_archive", - "attributes": { - "url": "https://files.pythonhosted.org/packages/ab/c3/57f0601a2d4fe15de7a553c00adbc901425661bf048f2a22dfc500caf121/packaging-23.1-py3-none-any.whl", - "sha256": "994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61", - "type": "zip", - "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:defs.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude in /python/pip_install/tools/bazel.py\n # to avoid non-determinism following pip install's behavior.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/* *\",\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" - } - }, - "pypi__pep517": { - "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", - "ruleClassName": "http_archive", - "attributes": { - "url": "https://files.pythonhosted.org/packages/ee/2f/ef63e64e9429111e73d3d6cbee80591672d16f2725e648ebc52096f3d323/pep517-0.13.0-py3-none-any.whl", - "sha256": "4ba4446d80aed5b5eac6509ade100bff3e7943a8489de249654a5ae9b33ee35b", - "type": "zip", - "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:defs.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude in /python/pip_install/tools/bazel.py\n # to avoid non-determinism following pip install's behavior.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/* *\",\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" - } - }, - "pypi__pip": { - "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", - "ruleClassName": "http_archive", - "attributes": { - "url": "https://files.pythonhosted.org/packages/50/c2/e06851e8cc28dcad7c155f4753da8833ac06a5c704c109313b8d5a62968a/pip-23.2.1-py3-none-any.whl", - "sha256": "7ccf472345f20d35bdc9d1841ff5f313260c2c33fe417f48c30ac46cccabf5be", - "type": "zip", - "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:defs.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude in /python/pip_install/tools/bazel.py\n # to avoid non-determinism following pip install's behavior.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/* *\",\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" - } - }, - "pypi__pip_tools": { - "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", - "ruleClassName": "http_archive", - "attributes": { - "url": "https://files.pythonhosted.org/packages/e8/df/47e6267c6b5cdae867adbdd84b437393e6202ce4322de0a5e0b92960e1d6/pip_tools-7.3.0-py3-none-any.whl", - "sha256": "8717693288720a8c6ebd07149c93ab0be1fced0b5191df9e9decd3263e20d85e", - "type": "zip", - "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:defs.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude in /python/pip_install/tools/bazel.py\n # to avoid non-determinism following pip install's behavior.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/* *\",\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" - } - }, - "pypi__pyproject_hooks": { - "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", - "ruleClassName": "http_archive", - "attributes": { - "url": "https://files.pythonhosted.org/packages/d5/ea/9ae603de7fbb3df820b23a70f6aff92bf8c7770043254ad8d2dc9d6bcba4/pyproject_hooks-1.0.0-py3-none-any.whl", - "sha256": "283c11acd6b928d2f6a7c73fa0d01cb2bdc5f07c57a2eeb6e83d5e56b97976f8", - "type": "zip", - "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:defs.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude in /python/pip_install/tools/bazel.py\n # to avoid non-determinism following pip install's behavior.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/* *\",\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" - } - }, - "pypi__setuptools": { - "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", - "ruleClassName": "http_archive", - "attributes": { - "url": "https://files.pythonhosted.org/packages/4f/ab/0bcfebdfc3bfa8554b2b2c97a555569c4c1ebc74ea288741ea8326c51906/setuptools-68.1.2-py3-none-any.whl", - "sha256": "3d8083eed2d13afc9426f227b24fd1659489ec107c0e86cec2ffdde5c92e790b", - "type": "zip", - "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:defs.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude in /python/pip_install/tools/bazel.py\n # to avoid non-determinism following pip install's behavior.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/* *\",\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" - } - }, - "pypi__tomli": { - "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", - "ruleClassName": "http_archive", - "attributes": { - "url": "https://files.pythonhosted.org/packages/97/75/10a9ebee3fd790d20926a90a2547f0bf78f371b2f13aa822c759680ca7b9/tomli-2.0.1-py3-none-any.whl", - "sha256": "939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc", - "type": "zip", - "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:defs.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude in /python/pip_install/tools/bazel.py\n # to avoid non-determinism following pip install's behavior.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/* *\",\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" - } - }, - "pypi__wheel": { - "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", - "ruleClassName": "http_archive", - "attributes": { - "url": "https://files.pythonhosted.org/packages/b8/8b/31273bf66016be6ad22bb7345c37ff350276cfd46e389a0c2ac5da9d9073/wheel-0.41.2-py3-none-any.whl", - "sha256": "75909db2664838d015e3d9139004ee16711748a52c8f336b52882266540215d8", - "type": "zip", - "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:defs.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude in /python/pip_install/tools/bazel.py\n # to avoid non-determinism following pip install's behavior.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/* *\",\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" - } - }, - "pypi__zipp": { - "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", - "ruleClassName": "http_archive", - "attributes": { - "url": "https://files.pythonhosted.org/packages/8c/08/d3006317aefe25ea79d3b76c9650afabaf6d63d1c8443b236e7405447503/zipp-3.16.2-py3-none-any.whl", - "sha256": "679e51dd4403591b2d6838a48de3d283f3d188412a9782faadf845f298736ba0", - "type": "zip", - "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:defs.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude in /python/pip_install/tools/bazel.py\n # to avoid non-determinism following pip install's behavior.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/* *\",\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" - } - } - }, - "recordedRepoMappingEntries": [ - [ - "rules_python~", - "bazel_tools", - "bazel_tools" - ] + }, + "1.25.0": { + "aix_ppc64": [ + "go1.25.0.aix-ppc64.tar.gz", + "e5234a7dac67bc86c528fe9752fc9d63557918627707a733ab4cac1a6faed2d4" + ], + "darwin_amd64": [ + "go1.25.0.darwin-amd64.tar.gz", + "5bd60e823037062c2307c71e8111809865116714d6f6b410597cf5075dfd80ef" + ], + "darwin_arm64": [ + "go1.25.0.darwin-arm64.tar.gz", + "544932844156d8172f7a28f77f2ac9c15a23046698b6243f633b0a0b00c0749c" + ], + "dragonfly_amd64": [ + "go1.25.0.dragonfly-amd64.tar.gz", + "5ed3cf9a810a1483822538674f1336c06b51aa1b94d6d545a1a0319a48177120" + ], + "freebsd_386": [ + "go1.25.0.freebsd-386.tar.gz", + "abea5d5c6697e6b5c224731f2158fe87c602996a2a233ac0c4730cd57bf8374e" + ], + "freebsd_amd64": [ + "go1.25.0.freebsd-amd64.tar.gz", + "86e6fe0a29698d7601c4442052dac48bd58d532c51cccb8f1917df648138730b" + ], + "freebsd_arm": [ + "go1.25.0.freebsd-arm.tar.gz", + "d90b78e41921f72f30e8bbc81d9dec2cff7ff384a33d8d8debb24053e4336bfe" + ], + "freebsd_arm64": [ + "go1.25.0.freebsd-arm64.tar.gz", + "451d0da1affd886bfb291b7c63a6018527b269505db21ce6e14724f22ab0662e" + ], + "freebsd_riscv64": [ + "go1.25.0.freebsd-riscv64.tar.gz", + "7b565f76bd8bda46549eeaaefe0e53b251e644c230577290c0f66b1ecdb3cdbe" + ], + "illumos_amd64": [ + "go1.25.0.illumos-amd64.tar.gz", + "b1e1fdaab1ad25aa1c08d7a36c97d45d74b98b89c3f78c6d2145f77face54a2c" + ], + "linux_386": [ + "go1.25.0.linux-386.tar.gz", + "8c602dd9d99bc9453b3995d20ce4baf382cc50855900a0ece5de9929df4a993a" + ], + "linux_amd64": [ + "go1.25.0.linux-amd64.tar.gz", + "2852af0cb20a13139b3448992e69b868e50ed0f8a1e5940ee1de9e19a123b613" + ], + "linux_arm64": [ + "go1.25.0.linux-arm64.tar.gz", + "05de75d6994a2783699815ee553bd5a9327d8b79991de36e38b66862782f54ae" + ], + "linux_armv6l": [ + "go1.25.0.linux-armv6l.tar.gz", + "a5a8f8198fcf00e1e485b8ecef9ee020778bf32a408a4e8873371bfce458cd09" + ], + "linux_loong64": [ + "go1.25.0.linux-loong64.tar.gz", + "cab86b1cf761b1cb3bac86a8877cfc92e7b036fc0d3084123d77013d61432afc" + ], + "linux_mips": [ + "go1.25.0.linux-mips.tar.gz", + "d66b6fb74c3d91b9829dc95ec10ca1f047ef5e89332152f92e136cf0e2da5be1" + ], + "linux_mips64": [ + "go1.25.0.linux-mips64.tar.gz", + "4082e4381a8661bc2a839ff94ba3daf4f6cde20f8fb771b5b3d4762dc84198a2" + ], + "linux_mips64le": [ + "go1.25.0.linux-mips64le.tar.gz", + "70002c299ec7f7175ac2ef673b1b347eecfa54ae11f34416a6053c17f855afcc" + ], + "linux_mipsle": [ + "go1.25.0.linux-mipsle.tar.gz", + "b00a3a39eff099f6df9f1c7355bf28e4589d0586f42d7d4a394efb763d145a73" + ], + "linux_ppc64": [ + "go1.25.0.linux-ppc64.tar.gz", + "df166f33bd98160662560a72ff0b4ba731f969a80f088922bddcf566a88c1ec1" + ], + "linux_ppc64le": [ + "go1.25.0.linux-ppc64le.tar.gz", + "0f18a89e7576cf2c5fa0b487a1635d9bcbf843df5f110e9982c64df52a983ad0" + ], + "linux_riscv64": [ + "go1.25.0.linux-riscv64.tar.gz", + "c018ff74a2c48d55c8ca9b07c8e24163558ffec8bea08b326d6336905d956b67" + ], + "linux_s390x": [ + "go1.25.0.linux-s390x.tar.gz", + "34e5a2e19f2292fbaf8783e3a241e6e49689276aef6510a8060ea5ef54eee408" + ], + "netbsd_386": [ + "go1.25.0.netbsd-386.tar.gz", + "f8586cdb7aa855657609a5c5f6dbf523efa00c2bbd7c76d3936bec80aa6c0aba" + ], + "netbsd_amd64": [ + "go1.25.0.netbsd-amd64.tar.gz", + "ae8dc1469385b86a157a423bb56304ba45730de8a897615874f57dd096db2c2a" + ], + "netbsd_arm": [ + "go1.25.0.netbsd-arm.tar.gz", + "1ff7e4cc764425fc9dd6825eaee79d02b3c7cafffbb3691687c8d672ade76cb7" + ], + "netbsd_arm64": [ + "go1.25.0.netbsd-arm64.tar.gz", + "e1b310739f26724216aa6d7d7208c4031f9ff54c9b5b9a796ddc8bebcb4a5f16" + ], + "openbsd_386": [ + "go1.25.0.openbsd-386.tar.gz", + "4802a9b20e533da91adb84aab42e94aa56cfe3e5475d0550bed3385b182e69d8" + ], + "openbsd_amd64": [ + "go1.25.0.openbsd-amd64.tar.gz", + "c016cd984bebe317b19a4f297c4f50def120dc9788490540c89f28e42f1dabe1" + ], + "openbsd_arm": [ + "go1.25.0.openbsd-arm.tar.gz", + "a1e31d0bf22172ddde42edf5ec811ef81be43433df0948ece52fecb247ccfd8d" + ], + "openbsd_arm64": [ + "go1.25.0.openbsd-arm64.tar.gz", + "343ea8edd8c218196e15a859c6072d0dd3246fbbb168481ab665eb4c4140458d" + ], + "openbsd_ppc64": [ + "go1.25.0.openbsd-ppc64.tar.gz", + "694c14da1bcaeb5e3332d49bdc2b6d155067648f8fe1540c5de8f3cf8e157154" + ], + "openbsd_riscv64": [ + "go1.25.0.openbsd-riscv64.tar.gz", + "aa510ad25cf54c06cd9c70b6d80ded69cb20188ac6e1735655eef29ff7e7885f" + ], + "plan9_386": [ + "go1.25.0.plan9-386.tar.gz", + "46f8cef02086cf04bf186c5912776b56535178d4cb319cd19c9fdbdd29231986" + ], + "plan9_amd64": [ + "go1.25.0.plan9-amd64.tar.gz", + "29b34391d84095e44608a228f63f2f88113a37b74a79781353ec043dfbcb427b" + ], + "plan9_arm": [ + "go1.25.0.plan9-arm.tar.gz", + "0a047107d13ebe7943aaa6d54b1d7bbd2e45e68ce449b52915a818da715799c2" + ], + "solaris_amd64": [ + "go1.25.0.solaris-amd64.tar.gz", + "9977f9e4351984364a3b2b78f8b88bfd1d339812356d5237678514594b7d3611" + ], + "windows_386": [ + "go1.25.0.windows-386.zip", + "df9f39db82a803af0db639e3613a36681ab7a42866b1384b3f3a1045663961a7" + ], + "windows_amd64": [ + "go1.25.0.windows-amd64.zip", + "89efb4f9b30812eee083cc1770fdd2913c14d301064f6454851428f9707d190b" + ], + "windows_arm64": [ + "go1.25.0.windows-arm64.zip", + "27bab004c72b3d7bd05a69b6ec0fc54a309b4b78cc569dd963d8b3ec28bfdb8c" ] } } diff --git a/README.md b/README.md index deb7408..d77952c 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,15 @@ ## What's Included +- **scaffold/**: Production-grade Go gRPC service reference implementation + - Structured logging (slog + JSON), request ID propagation + - OpenTelemetry metrics + traces with Prometheus exposition + - gRPC health checking, reflection, and channelz debugging + - Rate limiting, auth interceptor, structured error handling (google.rpc.Status) + - Feature flags via OpenFeature SDK + - PostgreSQL database access (pgx + sqlc), optional at runtime + - Build metadata injection via Bazel x_defs + - Kubernetes deployment manifests (health probes, HPA, graceful shutdown) - **grpc_example/**: Working gRPC service implementations - Go client and server - Rust client and server @@ -19,6 +28,7 @@ ## Use Cases +- Building production-ready Go microservices on Kubernetes - Starting a polyglot microservices project - Learning how to structure multi-language Bazel workspaces - Understanding gRPC service implementations across different languages diff --git a/WORKSPACE b/WORKSPACE index a9cde0b..be354e1 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -1,3 +1,7 @@ # Empty WORKSPACE file for IDE compatibility # Using Bzlmod (MODULE.bazel) for dependency management +load("//:deps.bzl", "go_dependencies") + +# gazelle:repository_macro deps.bzl%go_dependencies +go_dependencies() diff --git a/deps.bzl b/deps.bzl new file mode 100644 index 0000000..51e4fca --- /dev/null +++ b/deps.bzl @@ -0,0 +1,561 @@ +load("@gazelle//:deps.bzl", "go_repository") + +def go_dependencies(): + go_repository( + name = "build_buf_gen_go_bufbuild_protovalidate_protocolbuffers_go", + importpath = "buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go", + sum = "h1:tdpHgTbmbvEIARu+bixzmleMi14+3imnpoFXz+Qzjp4=", + version = "v1.31.0-20230802163732-1c33ebd9ecfa.1", + ) + go_repository( + name = "com_github_alecthomas_kingpin_v2", + importpath = "github.com/alecthomas/kingpin/v2", + sum = "h1:f48lwail6p8zpO1bC4TxtqACaGqHYA22qkHjHpqDjYY=", + version = "v2.4.0", + ) + go_repository( + name = "com_github_alecthomas_units", + importpath = "github.com/alecthomas/units", + sum = "h1:s6gZFSlWYmbqAuRjVTiNNhvNRfY2Wxp9nhfyel4rklc=", + version = "v0.0.0-20211218093645-b94a6e3cc137", + ) + go_repository( + name = "com_github_antlr_antlr4_runtime_go_antlr_v4", + importpath = "github.com/antlr/antlr4/runtime/Go/antlr/v4", + sum = "h1:goHVqTbFX3AIo0tzGr14pgfAW2ZfPChKO21Z9MGf/gk=", + version = "v4.0.0-20230512164433-5d1fd1a340c9", + ) + go_repository( + name = "com_github_beorn7_perks", + importpath = "github.com/beorn7/perks", + sum = "h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=", + version = "v1.0.1", + ) + go_repository( + name = "com_github_bufbuild_protovalidate_go", + importpath = "github.com/bufbuild/protovalidate-go", + sum = "h1:pJr07sYhliyfj/STAM7hU4J3FKpVeLVKvOBmOTN8j+s=", + version = "v0.2.1", + ) + go_repository( + name = "com_github_census_instrumentation_opencensus_proto", + importpath = "github.com/census-instrumentation/opencensus-proto", + sum = "h1:iKLQ0xPNFxR/2hzXZMrBo8f1j86j5WHzznCCQxV/b8g=", + version = "v0.4.1", + ) + go_repository( + name = "com_github_cespare_xxhash_v2", + importpath = "github.com/cespare/xxhash/v2", + sum = "h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=", + version = "v2.3.0", + ) + go_repository( + name = "com_github_cncf_xds_go", + importpath = "github.com/cncf/xds/go", + sum = "h1:QVw89YDxXxEe+l8gU8ETbOasdwEV+avkR75ZzsVV9WI=", + version = "v0.0.0-20240905190251-b4127c9b8d78", + ) + go_repository( + name = "com_github_cucumber_gherkin_go_v26", + importpath = "github.com/cucumber/gherkin/go/v26", + sum = "h1:EgIjePLWiPeslwIWmNQ3XHcypPsWAHoMCz/YEBKP4GI=", + version = "v26.2.0", + ) + go_repository( + name = "com_github_cucumber_godog", + importpath = "github.com/cucumber/godog", + sum = "h1:51AL8lBXF3f0cyA5CV4TnJFCTHpgiy+1x1Hb3TtZUmo=", + version = "v0.15.0", + ) + go_repository( + name = "com_github_cucumber_messages_go_v21", + importpath = "github.com/cucumber/messages/go/v21", + sum = "h1:wzA0LxwjlWQYZd32VTlAVDTkW6inOFmSM+RuOwHZiMI=", + version = "v21.0.1", + ) + go_repository( + name = "com_github_davecgh_go_spew", + importpath = "github.com/davecgh/go-spew", + sum = "h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=", + version = "v1.1.1", + ) + go_repository( + name = "com_github_envoyproxy_go_control_plane", + importpath = "github.com/envoyproxy/go-control-plane", + sum = "h1:vPfJZCkob6yTMEgS+0TwfTUfbHjfy/6vOJ8hUWX/uXE=", + version = "v0.13.1", + ) + go_repository( + name = "com_github_envoyproxy_protoc_gen_validate", + importpath = "github.com/envoyproxy/protoc-gen-validate", + sum = "h1:tntQDh69XqOCOZsDz0lVJQez/2L6Uu2PdjCQwWCJ3bM=", + version = "v1.1.0", + ) + go_repository( + name = "com_github_go_kit_log", + importpath = "github.com/go-kit/log", + sum = "h1:MRVx0/zhvdseW+Gza6N9rVzU/IVzaeE1SFI4raAhmBU=", + version = "v0.2.1", + ) + go_repository( + name = "com_github_go_logfmt_logfmt", + importpath = "github.com/go-logfmt/logfmt", + sum = "h1:otpy5pqBCBZ1ng9RQ0dPu4PN7ba75Y/aA+UpowDyNVA=", + version = "v0.5.1", + ) + go_repository( + name = "com_github_go_logr_logr", + importpath = "github.com/go-logr/logr", + sum = "h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=", + version = "v1.4.2", + ) + go_repository( + name = "com_github_go_logr_stdr", + importpath = "github.com/go-logr/stdr", + sum = "h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=", + version = "v1.2.2", + ) + go_repository( + name = "com_github_gofrs_uuid", + importpath = "github.com/gofrs/uuid", + sum = "h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA=", + version = "v4.4.0+incompatible", + ) + go_repository( + name = "com_github_golang_glog", + importpath = "github.com/golang/glog", + sum = "h1:1+mZ9upx1Dh6FmUTFR1naJ77miKiXgALjWOZ3NVFPmY=", + version = "v1.2.2", + ) + go_repository( + name = "com_github_golang_mock", + importpath = "github.com/golang/mock", + sum = "h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=", + version = "v1.6.0", + ) + go_repository( + name = "com_github_golang_protobuf", + importpath = "github.com/golang/protobuf", + sum = "h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=", + version = "v1.5.4", + ) + go_repository( + name = "com_github_google_cel_go", + importpath = "github.com/google/cel-go", + sum = "h1:s2151PDGy/eqpCI80/8dl4VL3xTkqI/YubXLXCFw0mw=", + version = "v0.17.1", + ) + go_repository( + name = "com_github_google_go_cmp", + importpath = "github.com/google/go-cmp", + sum = "h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=", + version = "v0.6.0", + ) + go_repository( + name = "com_github_google_uuid", + importpath = "github.com/google/uuid", + sum = "h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=", + version = "v1.6.0", + ) + go_repository( + name = "com_github_googlecloudplatform_opentelemetry_operations_go_detectors_gcp", + importpath = "github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp", + sum = "h1:cZpsGsWTIFKymTA0je7IIvi1O7Es7apb9CF3EQlOcfE=", + version = "v1.24.2", + ) + go_repository( + name = "com_github_grpc_ecosystem_go_grpc_middleware_v2", + importpath = "github.com/grpc-ecosystem/go-grpc-middleware/v2", + sum = "h1:kQ0NI7W1B3HwiN5gAYtY+XFItDPbLBwYRxAqbFTyDes=", + version = "v2.2.0", + ) + go_repository( + name = "com_github_hashicorp_go_immutable_radix", + importpath = "github.com/hashicorp/go-immutable-radix", + sum = "h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc=", + version = "v1.3.1", + ) + go_repository( + name = "com_github_hashicorp_go_memdb", + importpath = "github.com/hashicorp/go-memdb", + sum = "h1:XSL3NR682X/cVk2IeV0d70N4DZ9ljI885xAEU8IoK3c=", + version = "v1.3.4", + ) + go_repository( + name = "com_github_hashicorp_golang_lru", + importpath = "github.com/hashicorp/golang-lru", + sum = "h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c=", + version = "v1.0.2", + ) + go_repository( + name = "com_github_jackc_pgpassfile", + importpath = "github.com/jackc/pgpassfile", + sum = "h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=", + version = "v1.0.0", + ) + go_repository( + name = "com_github_jackc_pgservicefile", + importpath = "github.com/jackc/pgservicefile", + sum = "h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=", + version = "v0.0.0-20240606120523-5a60cdf6a761", + ) + go_repository( + name = "com_github_jackc_pgx_v5", + importpath = "github.com/jackc/pgx/v5", + sum = "h1:9wKznZrhWa2QiHL+NjTSPP6yjl3451BX3imWDnokYlg=", + version = "v5.7.4", + ) + go_repository( + name = "com_github_jackc_puddle_v2", + importpath = "github.com/jackc/puddle/v2", + sum = "h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=", + version = "v2.2.2", + ) + go_repository( + name = "com_github_jpillora_backoff", + importpath = "github.com/jpillora/backoff", + sum = "h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA=", + version = "v1.0.0", + ) + go_repository( + name = "com_github_json_iterator_go", + importpath = "github.com/json-iterator/go", + sum = "h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=", + version = "v1.1.12", + ) + go_repository( + name = "com_github_julienschmidt_httprouter", + importpath = "github.com/julienschmidt/httprouter", + sum = "h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U=", + version = "v1.3.0", + ) + go_repository( + name = "com_github_klauspost_compress", + importpath = "github.com/klauspost/compress", + sum = "h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=", + version = "v1.17.9", + ) + go_repository( + name = "com_github_kr_pretty", + importpath = "github.com/kr/pretty", + sum = "h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=", + version = "v0.3.1", + ) + go_repository( + name = "com_github_kr_text", + importpath = "github.com/kr/text", + sum = "h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=", + version = "v0.2.0", + ) + go_repository( + name = "com_github_kylelemons_godebug", + importpath = "github.com/kylelemons/godebug", + sum = "h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=", + version = "v1.1.0", + ) + go_repository( + name = "com_github_modern_go_concurrent", + importpath = "github.com/modern-go/concurrent", + sum = "h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=", + version = "v0.0.0-20180306012644-bacd9c7ef1dd", + ) + go_repository( + name = "com_github_modern_go_reflect2", + importpath = "github.com/modern-go/reflect2", + sum = "h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=", + version = "v1.0.2", + ) + go_repository( + name = "com_github_munnerz_goautoneg", + importpath = "github.com/munnerz/goautoneg", + sum = "h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=", + version = "v0.0.0-20191010083416-a7dc8b61c822", + ) + go_repository( + name = "com_github_mwitkow_go_conntrack", + importpath = "github.com/mwitkow/go-conntrack", + sum = "h1:KUppIJq7/+SVif2QVs3tOP0zanoHgBEVAwHxUSIzRqU=", + version = "v0.0.0-20190716064945-2f068394615f", + ) + go_repository( + name = "com_github_open_feature_go_sdk", + importpath = "github.com/open-feature/go-sdk", + sum = "h1:+B+Z94QS4HXPAn6OnaWWjMNAJkHlh6pIqW2Y1194yF8=", + version = "v1.14.0", + ) + go_repository( + name = "com_github_planetscale_vtprotobuf", + importpath = "github.com/planetscale/vtprotobuf", + sum = "h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo=", + version = "v0.6.1-0.20240319094008-0393e58bdf10", + ) + go_repository( + name = "com_github_pmezard_go_difflib", + importpath = "github.com/pmezard/go-difflib", + sum = "h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=", + version = "v1.0.0", + ) + go_repository( + name = "com_github_prometheus_client_golang", + importpath = "github.com/prometheus/client_golang", + sum = "h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y=", + version = "v1.20.5", + ) + go_repository( + name = "com_github_prometheus_client_model", + importpath = "github.com/prometheus/client_model", + sum = "h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=", + version = "v0.6.1", + ) + go_repository( + name = "com_github_prometheus_common", + importpath = "github.com/prometheus/common", + sum = "h1:3gv/GThfX0cV2lpO7gkTUwZru38mxevy90Bj8YFSRQQ=", + version = "v0.61.0", + ) + go_repository( + name = "com_github_prometheus_procfs", + importpath = "github.com/prometheus/procfs", + sum = "h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=", + version = "v0.15.1", + ) + go_repository( + name = "com_github_rogpeppe_go_internal", + importpath = "github.com/rogpeppe/go-internal", + sum = "h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=", + version = "v1.13.1", + ) + go_repository( + name = "com_github_spf13_pflag", + importpath = "github.com/spf13/pflag", + sum = "h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=", + version = "v1.0.5", + ) + go_repository( + name = "com_github_stoewer_go_strcase", + importpath = "github.com/stoewer/go-strcase", + sum = "h1:g0eASXYtp+yvN9fK8sH94oCIk0fau9uV1/ZdJ0AVEzs=", + version = "v1.3.0", + ) + go_repository( + name = "com_github_stretchr_objx", + importpath = "github.com/stretchr/objx", + sum = "h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4=", + version = "v0.1.0", + ) + go_repository( + name = "com_github_stretchr_testify", + importpath = "github.com/stretchr/testify", + sum = "h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=", + version = "v1.10.0", + ) + go_repository( + name = "com_github_xhit_go_str2duration_v2", + importpath = "github.com/xhit/go-str2duration/v2", + sum = "h1:lxklc02Drh6ynqX+DdPyp5pCKLUQpRT8bp8Ydu2Bstc=", + version = "v2.1.0", + ) + go_repository( + name = "com_github_yuin_goldmark", + importpath = "github.com/yuin/goldmark", + sum = "h1:fVcFKWvrslecOb/tg+Cc05dkeYx540o0FuFt3nUVDoE=", + version = "v1.4.13", + ) + go_repository( + name = "com_google_cloud_go_compute", + importpath = "cloud.google.com/go/compute", + sum = "h1:EBT9Nw4q3zyE7G45Wvv3MzolIrCJEuHys5muLY0wvAw=", + version = "v1.23.4", + ) + go_repository( + name = "com_google_cloud_go_compute_metadata", + importpath = "cloud.google.com/go/compute/metadata", + sum = "h1:UxK4uu/Tn+I3p2dYWTfiX4wva7aYlKixAHn3fyqngqo=", + version = "v0.5.2", + ) + go_repository( + name = "dev_cel_expr", + importpath = "cel.dev/expr", + sum = "h1:RwRhoH17VhAu9U5CMvMhH1PDVgf0tuz9FT+24AfMLfU=", + version = "v0.16.2", + ) + go_repository( + name = "in_gopkg_check_v1", + importpath = "gopkg.in/check.v1", + sum = "h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=", + version = "v1.0.0-20201130134442-10cb98267c6c", + ) + go_repository( + name = "in_gopkg_yaml_v2", + importpath = "gopkg.in/yaml.v2", + sum = "h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=", + version = "v2.4.0", + ) + go_repository( + name = "in_gopkg_yaml_v3", + importpath = "gopkg.in/yaml.v3", + sum = "h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=", + version = "v3.0.1", + ) + go_repository( + name = "io_opentelemetry_go_auto_sdk", + importpath = "go.opentelemetry.io/auto/sdk", + sum = "h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=", + version = "v1.1.0", + ) + go_repository( + name = "io_opentelemetry_go_contrib_detectors_gcp", + importpath = "go.opentelemetry.io/contrib/detectors/gcp", + sum = "h1:G1JQOreVrfhRkner+l4mrGxmfqYCAuy76asTDAo0xsA=", + version = "v1.31.0", + ) + go_repository( + name = "io_opentelemetry_go_contrib_instrumentation_google_golang_org_grpc_otelgrpc", + importpath = "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc", + sum = "h1:PS8wXpbyaDJQ2VDHHncMe9Vct0Zn1fEjpsjrLxGJoSc=", + version = "v0.58.0", + ) + go_repository( + name = "io_opentelemetry_go_otel", + importpath = "go.opentelemetry.io/otel", + sum = "h1:/FerN9bax5LoK51X/sI0SVYrjSE0/yUL7DpxW4K3FWw=", + version = "v1.33.0", + ) + go_repository( + name = "io_opentelemetry_go_otel_exporters_prometheus", + importpath = "go.opentelemetry.io/otel/exporters/prometheus", + sum = "h1:sSPw658Lk2NWAv74lkD3B/RSDb+xRFx46GjkrL3VUZo=", + version = "v0.55.0", + ) + go_repository( + name = "io_opentelemetry_go_otel_metric", + importpath = "go.opentelemetry.io/otel/metric", + sum = "h1:r+JOocAyeRVXD8lZpjdQjzMadVZp2M4WmQ+5WtEnklQ=", + version = "v1.33.0", + ) + go_repository( + name = "io_opentelemetry_go_otel_sdk", + importpath = "go.opentelemetry.io/otel/sdk", + sum = "h1:iax7M131HuAm9QkZotNHEfstof92xM+N8sr3uHXc2IM=", + version = "v1.33.0", + ) + go_repository( + name = "io_opentelemetry_go_otel_sdk_metric", + importpath = "go.opentelemetry.io/otel/sdk/metric", + sum = "h1:Gs5VK9/WUJhNXZgn8MR6ITatvAmKeIuCtNbsP3JkNqU=", + version = "v1.33.0", + ) + go_repository( + name = "io_opentelemetry_go_otel_trace", + importpath = "go.opentelemetry.io/otel/trace", + sum = "h1:cCJuF7LRjUFso9LPnEAHJDB2pqzp+hbO8eu1qqW2d/s=", + version = "v1.33.0", + ) + go_repository( + name = "org_golang_google_appengine", + importpath = "google.golang.org/appengine", + sum = "h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM=", + version = "v1.6.8", + ) + go_repository( + name = "org_golang_google_genproto_googleapis_api", + importpath = "google.golang.org/genproto/googleapis/api", + sum = "h1:fVoAXEKA4+yufmbdVYv+SE73+cPZbbbe8paLsHfkK+U=", + version = "v0.0.0-20241015192408-796eee8c2d53", + ) + go_repository( + name = "org_golang_google_genproto_googleapis_rpc", + importpath = "google.golang.org/genproto/googleapis/rpc", + sum = "h1:8ZmaLZE4XWrtU3MyClkYqqtl6Oegr3235h7jxsDyqCY=", + version = "v0.0.0-20241209162323-e6fa225c2576", + ) + go_repository( + name = "org_golang_google_grpc", + importpath = "google.golang.org/grpc", + sum = "h1:U3S9QEtbXC0bYNvRtcoklF3xGtLViumSYxWykJS+7AU=", + version = "v1.69.2", + ) + go_repository( + name = "org_golang_google_protobuf", + importpath = "google.golang.org/protobuf", + sum = "h1:yBPeRvTftaleIgM3PZ/WBIZ7XM/eEYAaEyCwvyjq/gk=", + version = "v1.36.1", + ) + go_repository( + name = "org_golang_x_crypto", + importpath = "golang.org/x/crypto", + sum = "h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=", + version = "v0.31.0", + ) + go_repository( + name = "org_golang_x_exp", + importpath = "golang.org/x/exp", + sum = "h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM=", + version = "v0.0.0-20240506185415-9bf2ced13842", + ) + go_repository( + name = "org_golang_x_mod", + importpath = "golang.org/x/mod", + sum = "h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=", + version = "v0.17.0", + ) + go_repository( + name = "org_golang_x_net", + importpath = "golang.org/x/net", + sum = "h1:ZqPmj8Kzc+Y6e0+skZsuACbx+wzMgo5MQsJh9Qd6aYI=", + version = "v0.32.0", + ) + go_repository( + name = "org_golang_x_oauth2", + importpath = "golang.org/x/oauth2", + sum = "h1:KTBBxWqUa0ykRPLtV69rRto9TLXcqYkeswu48x/gvNE=", + version = "v0.24.0", + ) + go_repository( + name = "org_golang_x_sync", + importpath = "golang.org/x/sync", + sum = "h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=", + version = "v0.10.0", + ) + go_repository( + name = "org_golang_x_sys", + importpath = "golang.org/x/sys", + sum = "h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=", + version = "v0.28.0", + ) + go_repository( + name = "org_golang_x_telemetry", + importpath = "golang.org/x/telemetry", + sum = "h1:IRJeR9r1pYWsHKTRe/IInb7lYvbBVIqOgsX/u0mbOWY=", + version = "v0.0.0-20240228155512-f48c80bd79b2", + ) + go_repository( + name = "org_golang_x_term", + importpath = "golang.org/x/term", + sum = "h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q=", + version = "v0.27.0", + ) + go_repository( + name = "org_golang_x_text", + importpath = "golang.org/x/text", + sum = "h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=", + version = "v0.21.0", + ) + go_repository( + name = "org_golang_x_time", + importpath = "golang.org/x/time", + sum = "h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY=", + version = "v0.9.0", + ) + go_repository( + name = "org_golang_x_tools", + importpath = "golang.org/x/tools", + sum = "h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg=", + version = "v0.21.1-0.20240508182429-e35e4ccd0d2d", + ) + go_repository( + name = "org_golang_x_xerrors", + importpath = "golang.org/x/xerrors", + sum = "h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=", + version = "v0.0.0-20191204190536-9bdfabe68543", + ) diff --git a/go.work b/go.work index 35bc9d1..9de6697 100644 --- a/go.work +++ b/go.work @@ -3,5 +3,6 @@ go 1.24 use ( ./starter ./grpc_example/go + ./scaffold ) diff --git a/grpc_example/go/BUILD.bazel b/grpc_example/go/BUILD.bazel index 548c616..23386bf 100644 --- a/grpc_example/go/BUILD.bazel +++ b/grpc_example/go/BUILD.bazel @@ -39,4 +39,3 @@ go_binary( embed = [":client_lib"], visibility = ["//visibility:public"], ) - diff --git a/grpc_example/proto/BUILD.bazel b/grpc_example/proto/BUILD.bazel index 03405e6..339cabe 100644 --- a/grpc_example/proto/BUILD.bazel +++ b/grpc_example/proto/BUILD.bazel @@ -1,5 +1,6 @@ +load("@protobuf//bazel:proto_library.bzl", "proto_library") +load("@rules_go//go:def.bzl", "go_library") load("@rules_go//proto:def.bzl", "go_proto_library") -load("@rules_proto//proto:defs.bzl", "proto_library") load("@rules_rust_prost//:defs.bzl", "rust_prost_library") exports_files(["greeter.proto"]) @@ -12,7 +13,10 @@ proto_library( go_proto_library( name = "greeter_go_proto", - compilers = ["@rules_go//proto:go_grpc"], + compilers = [ + "@rules_go//proto:go_proto", + "@rules_go//proto:go_grpc_v2", + ], importpath = "github.com/bazelment/hajime/grpc_example/proto/greeter", proto = ":greeter_proto", visibility = ["//visibility:public"], @@ -23,3 +27,10 @@ rust_prost_library( proto = ":greeter_proto", visibility = ["//visibility:public"], ) + +go_library( + name = "greeter", + embed = [":greeter_go_proto"], + importpath = "github.com/bazelment/hajime/grpc_example/proto/greeter", + visibility = ["//visibility:public"], +) diff --git a/grpc_example/rust/BUILD.bazel b/grpc_example/rust/BUILD.bazel index 01540cf..867e0e7 100644 --- a/grpc_example/rust/BUILD.bazel +++ b/grpc_example/rust/BUILD.bazel @@ -1,14 +1,14 @@ load("@rules_rust//rust:defs.bzl", "rust_binary") # IMPORTANT: tonic and tokio versions MUST match rules_rust_prost's internal versions -# +# # Why we pin to rules_rust_prost's versions: # - The proto-generated code uses rules_rust_prost's tonic (0.12.1) # - If we use @crates//:tonic, it may resolve to a different patch version (e.g., 0.12.3) # - Even minor version differences cause Rust to treat types as incompatible # - This results in trait implementation errors like: # "the trait bound `tonic::Request: tonic::request::IntoRequest` is not satisfied" -# +# # Solution: Use the exact tonic/tokio versions from rules_rust_prost to ensure # all code (generated + application) uses identical types. @@ -19,8 +19,8 @@ rust_binary( visibility = ["//visibility:public"], deps = [ "//grpc_example/proto:greeter_rust_proto", - "@rules_rust_prost//private/3rdparty/crates:tonic-0.12.1", "@rules_rust_prost//private/3rdparty/crates:tokio-1.39.3", + "@rules_rust_prost//private/3rdparty/crates:tonic-0.12.1", ], ) @@ -31,7 +31,7 @@ rust_binary( visibility = ["//visibility:public"], deps = [ "//grpc_example/proto:greeter_rust_proto", - "@rules_rust_prost//private/3rdparty/crates:tonic-0.12.1", "@rules_rust_prost//private/3rdparty/crates:tokio-1.39.3", + "@rules_rust_prost//private/3rdparty/crates:tonic-0.12.1", ], ) diff --git a/scaffold/BUILD.bazel b/scaffold/BUILD.bazel new file mode 100644 index 0000000..e69de29 diff --git a/scaffold/cmd/greeter/BUILD.bazel b/scaffold/cmd/greeter/BUILD.bazel new file mode 100644 index 0000000..15617da --- /dev/null +++ b/scaffold/cmd/greeter/BUILD.bazel @@ -0,0 +1,39 @@ +load("@rules_go//go:def.bzl", "go_binary", "go_library") + +go_library( + name = "greeter_lib", + srcs = ["main.go"], + importpath = "github.com/bazelment/hajime/scaffold/cmd/greeter", + visibility = ["//visibility:private"], + deps = [ + "//grpc_example/proto:greeter", + "//scaffold/greeterservice", + "//scaffold/internal/admin", + "//scaffold/internal/auth", + "//scaffold/internal/database", + "//scaffold/internal/errors", + "//scaffold/internal/featureflags", + "//scaffold/internal/logging", + "//scaffold/internal/observability", + "//scaffold/internal/ratelimit", + "//scaffold/internal/version", + "@com_github_grpc_ecosystem_go_grpc_middleware_v2//interceptors/recovery", + "@io_opentelemetry_go_contrib_instrumentation_google_golang_org_grpc_otelgrpc//:otelgrpc", + "@org_golang_google_grpc//:grpc", + "@org_golang_google_grpc//channelz/service", + "@org_golang_google_grpc//health", + "@org_golang_google_grpc//health/grpc_health_v1", + "@org_golang_google_grpc//reflection", + ], +) + +go_binary( + name = "greeter", + embed = [":greeter_lib"], + visibility = ["//visibility:public"], + x_defs = { + "github.com/bazelment/hajime/scaffold/internal/version.Version": "{STABLE_VERSION}", + "github.com/bazelment/hajime/scaffold/internal/version.Commit": "{STABLE_GIT_COMMIT}", + "github.com/bazelment/hajime/scaffold/internal/version.BuildTime": "{BUILD_TIMESTAMP}", + }, +) diff --git a/scaffold/cmd/greeter/main.go b/scaffold/cmd/greeter/main.go new file mode 100644 index 0000000..8ab7425 --- /dev/null +++ b/scaffold/cmd/greeter/main.go @@ -0,0 +1,179 @@ +package main + +import ( + "context" + "flag" + "fmt" + "log/slog" + "net" + "os" + "os/signal" + "strconv" + "syscall" + "time" + + "google.golang.org/grpc" + "google.golang.org/grpc/channelz/service" + "google.golang.org/grpc/health" + healthpb "google.golang.org/grpc/health/grpc_health_v1" + "google.golang.org/grpc/reflection" + + "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" + + grpcrecovery "github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/recovery" + + pb "github.com/bazelment/hajime/grpc_example/proto/greeter" + "github.com/bazelment/hajime/scaffold/greeterservice" + "github.com/bazelment/hajime/scaffold/internal/admin" + "github.com/bazelment/hajime/scaffold/internal/auth" + "github.com/bazelment/hajime/scaffold/internal/database" + scerrors "github.com/bazelment/hajime/scaffold/internal/errors" + "github.com/bazelment/hajime/scaffold/internal/featureflags" + "github.com/bazelment/hajime/scaffold/internal/logging" + "github.com/bazelment/hajime/scaffold/internal/observability" + "github.com/bazelment/hajime/scaffold/internal/ratelimit" + "github.com/bazelment/hajime/scaffold/internal/version" +) + +var ( + grpcPort = flag.Int("grpc-port", envInt("GRPC_PORT", 50051), "gRPC listen port") + adminPort = flag.Int("admin-port", envInt("ADMIN_PORT", 8081), "Admin HTTP port") + logLevel = flag.String("log-level", envStr("LOG_LEVEL", "info"), "Log level (debug, info, warn, error)") + databaseURL = flag.String("database-url", envStr("DATABASE_URL", ""), "PostgreSQL connection URL") + otelEndpoint = flag.String("otel-endpoint", envStr("OTEL_EXPORTER_OTLP_ENDPOINT", ""), "OTLP exporter endpoint") + rateLimitRPS = flag.Int("rate-limit", envInt("RATE_LIMIT", 100), "Max requests per second") +) + +func main() { + flag.Parse() + + ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGTERM, syscall.SIGINT) + defer stop() + + logger := logging.Setup(*logLevel, "greeter-service", version.Version) + logger.Info("starting service", + slog.String("version", version.Version), + slog.String("commit", version.Commit), + slog.String("build_time", version.BuildTime), + ) + + telemetry, err := observability.Setup(ctx, "greeter-service", version.Version) + if err != nil { + logger.Error("failed to setup observability", slog.String("error", err.Error())) + os.Exit(1) + } + + flagClient := featureflags.Setup(ctx) + + pool, err := database.Connect(ctx, *databaseURL) + if err != nil { + logger.Error("failed to connect to database", slog.String("error", err.Error())) + os.Exit(1) + } + if pool != nil { + logger.Info("connected to database") + } else { + logger.Info("running without database") + } + + limiter := ratelimit.New(*rateLimitRPS) + + s := grpc.NewServer( + grpc.StatsHandler(otelgrpc.NewServerHandler()), + grpc.ChainUnaryInterceptor( + grpcrecovery.UnaryServerInterceptor(), + limiter.UnaryServerInterceptor(), + auth.UnaryServerInterceptor(), + logging.UnaryServerInterceptor(logger), + scerrors.UnaryServerInterceptor(logger), + ), + grpc.ChainStreamInterceptor( + grpcrecovery.StreamServerInterceptor(), + auth.StreamServerInterceptor(), + ), + ) + + pb.RegisterGreeterServer(s, greeterservice.New(logger, flagClient, pool)) + + healthServer := health.NewServer() + healthpb.RegisterHealthServer(s, healthServer) + healthServer.SetServingStatus("", healthpb.HealthCheckResponse_SERVING) + + reflection.Register(s) + service.RegisterChannelzServiceToServer(s) + + adminSrv := admin.NewServer(fmt.Sprintf(":%d", *adminPort), telemetry.Registry) + go func() { + logger.Info("admin server listening", slog.Int("port", *adminPort)) + if err := adminSrv.ListenAndServe(); err != nil { + logger.Error("admin server error", slog.String("error", err.Error())) + } + }() + + if pool != nil { + go func() { + ticker := time.NewTicker(10 * time.Second) + defer ticker.Stop() + for { + select { + case <-ctx.Done(): + return + case <-ticker.C: + if err := database.HealthCheck(ctx, pool); err != nil { + healthServer.SetServingStatus("", healthpb.HealthCheckResponse_NOT_SERVING) + logger.Warn("database health check failed", slog.String("error", err.Error())) + } else { + healthServer.SetServingStatus("", healthpb.HealthCheckResponse_SERVING) + } + } + } + }() + } + + lis, err := net.Listen("tcp", fmt.Sprintf(":%d", *grpcPort)) + if err != nil { + logger.Error("failed to listen", slog.String("error", err.Error())) + os.Exit(1) + } + + go func() { + logger.Info("gRPC server listening", slog.Int("port", *grpcPort)) + if err := s.Serve(lis); err != nil { + logger.Error("gRPC server error", slog.String("error", err.Error())) + } + }() + + <-ctx.Done() + logger.Info("shutdown signal received") + + healthServer.SetServingStatus("", healthpb.HealthCheckResponse_NOT_SERVING) + time.Sleep(5 * time.Second) + + s.GracefulStop() + logger.Info("gRPC server stopped") + + shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + adminSrv.Shutdown(shutdownCtx) + logger.Info("admin server stopped") + + database.Close(pool) + telemetry.Shutdown(shutdownCtx) + logger.Info("shutdown complete") +} + +func envStr(key, fallback string) string { + if v := os.Getenv(key); v != "" { + return v + } + return fallback +} + +func envInt(key string, fallback int) int { + if v := os.Getenv(key); v != "" { + if i, err := strconv.Atoi(v); err == nil { + return i + } + } + return fallback +} diff --git a/scaffold/deploy/Dockerfile b/scaffold/deploy/Dockerfile new file mode 100644 index 0000000..93d6eaa --- /dev/null +++ b/scaffold/deploy/Dockerfile @@ -0,0 +1,16 @@ +FROM golang:1.24-bookworm AS builder + +RUN go install github.com/bazelbuild/bazelisk@latest +ENV PATH="/root/go/bin:${PATH}" + +WORKDIR /src +COPY . . +RUN bazel build //scaffold/cmd/greeter:greeter + +FROM gcr.io/distroless/base-debian12:nonroot + +COPY --from=builder /src/bazel-bin/scaffold/cmd/greeter/greeter_/greeter /greeter + +EXPOSE 50051 8081 + +ENTRYPOINT ["/greeter"] diff --git a/scaffold/deploy/deployment.yaml b/scaffold/deploy/deployment.yaml new file mode 100644 index 0000000..f613109 --- /dev/null +++ b/scaffold/deploy/deployment.yaml @@ -0,0 +1,72 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: greeter-service + labels: + app: greeter-service +spec: + replicas: 2 + selector: + matchLabels: + app: greeter-service + template: + metadata: + labels: + app: greeter-service + annotations: + prometheus.io/scrape: "true" + prometheus.io/port: "8081" + prometheus.io/path: "/metrics" + spec: + terminationGracePeriodSeconds: 30 + containers: + - name: greeter + image: greeter-service:latest + ports: + - containerPort: 50051 + name: grpc + protocol: TCP + - containerPort: 8081 + name: admin + protocol: TCP + env: + - name: GOMEMLIMIT + valueFrom: + resourceFieldRef: + resource: limits.memory + - name: GOMAXPROCS + valueFrom: + resourceFieldRef: + resource: limits.cpu + - name: LOG_LEVEL + value: "info" + - name: DATABASE_URL + valueFrom: + secretKeyRef: + name: greeter-service-secrets + key: database-url + optional: true + - name: OTEL_EXPORTER_OTLP_ENDPOINT + value: "" + resources: + requests: + cpu: 100m + memory: 128Mi + limits: + cpu: 500m + memory: 256Mi + livenessProbe: + grpc: + port: 50051 + initialDelaySeconds: 5 + periodSeconds: 10 + readinessProbe: + grpc: + port: 50051 + initialDelaySeconds: 3 + periodSeconds: 5 + startupProbe: + grpc: + port: 50051 + failureThreshold: 30 + periodSeconds: 2 diff --git a/scaffold/deploy/hpa.yaml b/scaffold/deploy/hpa.yaml new file mode 100644 index 0000000..82dc89b --- /dev/null +++ b/scaffold/deploy/hpa.yaml @@ -0,0 +1,18 @@ +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + name: greeter-service +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: greeter-service + minReplicas: 2 + maxReplicas: 10 + metrics: + - type: Resource + resource: + name: cpu + target: + type: Utilization + averageUtilization: 70 diff --git a/scaffold/deploy/service.yaml b/scaffold/deploy/service.yaml new file mode 100644 index 0000000..36f6143 --- /dev/null +++ b/scaffold/deploy/service.yaml @@ -0,0 +1,23 @@ +apiVersion: v1 +kind: Service +metadata: + name: greeter-service + labels: + app: greeter-service + annotations: + prometheus.io/scrape: "true" + prometheus.io/port: "8081" + prometheus.io/path: "/metrics" +spec: + type: ClusterIP + selector: + app: greeter-service + ports: + - name: grpc + port: 50051 + targetPort: grpc + protocol: TCP + - name: admin + port: 8081 + targetPort: admin + protocol: TCP diff --git a/scaffold/go.mod b/scaffold/go.mod new file mode 100644 index 0000000..49f2179 --- /dev/null +++ b/scaffold/go.mod @@ -0,0 +1,44 @@ +module github.com/bazelment/hajime/scaffold + +go 1.24 + +require ( + github.com/google/uuid v1.6.0 + github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.2.0 + github.com/jackc/pgx/v5 v5.7.4 + github.com/open-feature/go-sdk v1.14.0 + github.com/prometheus/client_golang v1.20.5 + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.58.0 + go.opentelemetry.io/otel v1.33.0 + go.opentelemetry.io/otel/exporters/prometheus v0.55.0 + go.opentelemetry.io/otel/sdk v1.33.0 + go.opentelemetry.io/otel/sdk/metric v1.33.0 + go.opentelemetry.io/otel/trace v1.33.0 + golang.org/x/time v0.9.0 + google.golang.org/genproto/googleapis/rpc v0.0.0-20241209162323-e6fa225c2576 + google.golang.org/grpc v1.69.2 + google.golang.org/protobuf v1.36.1 +) + +require ( + github.com/beorn7/perks v1.0.1 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/go-logr/logr v1.4.2 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect + github.com/jackc/puddle/v2 v2.2.2 // indirect + github.com/klauspost/compress v1.17.9 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/prometheus/client_model v0.6.1 // indirect + github.com/prometheus/common v0.61.0 // indirect + github.com/prometheus/procfs v0.15.1 // indirect + go.opentelemetry.io/auto/sdk v1.1.0 // indirect + go.opentelemetry.io/otel/metric v1.33.0 // indirect + golang.org/x/crypto v0.31.0 // indirect + golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 // indirect + golang.org/x/net v0.32.0 // indirect + golang.org/x/sync v0.10.0 // indirect + golang.org/x/sys v0.28.0 // indirect + golang.org/x/text v0.21.0 // indirect +) diff --git a/scaffold/go.sum b/scaffold/go.sum new file mode 100644 index 0000000..d83acaa --- /dev/null +++ b/scaffold/go.sum @@ -0,0 +1,93 @@ +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/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +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/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= +github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.2.0 h1:kQ0NI7W1B3HwiN5gAYtY+XFItDPbLBwYRxAqbFTyDes= +github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.2.0/go.mod h1:zrT2dxOAjNFPRGjTUe2Xmb4q4YdUwVvQFV6xiCSf+z0= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgx/v5 v5.7.4 h1:9wKznZrhWa2QiHL+NjTSPP6yjl3451BX3imWDnokYlg= +github.com/jackc/pgx/v5 v5.7.4/go.mod h1:ncY89UGWxg82EykZUwSpUKEfccBGGYq1xjrOpsbsfGQ= +github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= +github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= +github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= +github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= +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/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/open-feature/go-sdk v1.14.0 h1:+B+Z94QS4HXPAn6OnaWWjMNAJkHlh6pIqW2Y1194yF8= +github.com/open-feature/go-sdk v1.14.0/go.mod h1:t337k0VB/t/YxJ9S0prT30ISUHwYmUd/jhUZgFcOvGg= +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_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y= +github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= +github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= +github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= +github.com/prometheus/common v0.61.0 h1:3gv/GThfX0cV2lpO7gkTUwZru38mxevy90Bj8YFSRQQ= +github.com/prometheus/common v0.61.0/go.mod h1:zr29OCN/2BsJRaFwG8QOBr41D6kkchKbpeNH7pAjb/s= +github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= +github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= +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.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= +go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.58.0 h1:PS8wXpbyaDJQ2VDHHncMe9Vct0Zn1fEjpsjrLxGJoSc= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.58.0/go.mod h1:HDBUsEjOuRC0EzKZ1bSaRGZWUBAzo+MhAcUUORSr4D0= +go.opentelemetry.io/otel v1.33.0 h1:/FerN9bax5LoK51X/sI0SVYrjSE0/yUL7DpxW4K3FWw= +go.opentelemetry.io/otel v1.33.0/go.mod h1:SUUkR6csvUQl+yjReHu5uM3EtVV7MBm5FHKRlNx4I8I= +go.opentelemetry.io/otel/exporters/prometheus v0.55.0 h1:sSPw658Lk2NWAv74lkD3B/RSDb+xRFx46GjkrL3VUZo= +go.opentelemetry.io/otel/exporters/prometheus v0.55.0/go.mod h1:nC00vyCmQixoeaxF6KNyP42II/RHa9UdruK02qBmHvI= +go.opentelemetry.io/otel/metric v1.33.0 h1:r+JOocAyeRVXD8lZpjdQjzMadVZp2M4WmQ+5WtEnklQ= +go.opentelemetry.io/otel/metric v1.33.0/go.mod h1:L9+Fyctbp6HFTddIxClbQkjtubW6O9QS3Ann/M82u6M= +go.opentelemetry.io/otel/sdk v1.33.0 h1:iax7M131HuAm9QkZotNHEfstof92xM+N8sr3uHXc2IM= +go.opentelemetry.io/otel/sdk v1.33.0/go.mod h1:A1Q5oi7/9XaMlIWzPSxLRWOI8nG3FnzHJNbiENQuihM= +go.opentelemetry.io/otel/sdk/metric v1.33.0 h1:Gs5VK9/WUJhNXZgn8MR6ITatvAmKeIuCtNbsP3JkNqU= +go.opentelemetry.io/otel/sdk/metric v1.33.0/go.mod h1:dL5ykHZmm1B1nVRk9dDjChwDmt81MjVp3gLkQRwKf/Q= +go.opentelemetry.io/otel/trace v1.33.0 h1:cCJuF7LRjUFso9LPnEAHJDB2pqzp+hbO8eu1qqW2d/s= +go.opentelemetry.io/otel/trace v1.33.0/go.mod h1:uIcdVUZMpTAmz0tI1z04GoVSezK37CbGV4fr1f2nBck= +golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= +golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= +golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM= +golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc= +golang.org/x/net v0.32.0 h1:ZqPmj8Kzc+Y6e0+skZsuACbx+wzMgo5MQsJh9Qd6aYI= +golang.org/x/net v0.32.0/go.mod h1:CwU0IoeOlnQQWJ6ioyFrfRuomB8GKF6KbYXZVyeXNfs= +golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= +golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= +golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY= +golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241209162323-e6fa225c2576 h1:8ZmaLZE4XWrtU3MyClkYqqtl6Oegr3235h7jxsDyqCY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241209162323-e6fa225c2576/go.mod h1:5uTbfoYQed2U9p3KIj2/Zzm02PYhndfdmML0qC3q3FU= +google.golang.org/grpc v1.69.2 h1:U3S9QEtbXC0bYNvRtcoklF3xGtLViumSYxWykJS+7AU= +google.golang.org/grpc v1.69.2/go.mod h1:vyjdE6jLBI76dgpDojsFGNaHlxdjXN9ghpnd2o7JGZ4= +google.golang.org/protobuf v1.36.1 h1:yBPeRvTftaleIgM3PZ/WBIZ7XM/eEYAaEyCwvyjq/gk= +google.golang.org/protobuf v1.36.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/scaffold/greeterservice/BUILD.bazel b/scaffold/greeterservice/BUILD.bazel new file mode 100644 index 0000000..125051d --- /dev/null +++ b/scaffold/greeterservice/BUILD.bazel @@ -0,0 +1,28 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "greeterservice", + srcs = ["service.go"], + importpath = "github.com/bazelment/hajime/scaffold/greeterservice", + visibility = ["//visibility:public"], + deps = [ + "//grpc_example/proto:greeter", + "//scaffold/internal/database/db", + "//scaffold/internal/errors", + "//scaffold/internal/logging", + "@com_github_jackc_pgx_v5//pgxpool", + "@com_github_open_feature_go_sdk//openfeature", + ], +) + +go_test( + name = "greeterservice_test", + srcs = ["service_test.go"], + embed = [":greeterservice"], + deps = [ + "//grpc_example/proto:greeter", + "@com_github_open_feature_go_sdk//openfeature", + "@org_golang_google_grpc//codes", + "@org_golang_google_grpc//status", + ], +) diff --git a/scaffold/greeterservice/service.go b/scaffold/greeterservice/service.go new file mode 100644 index 0000000..82f9bd7 --- /dev/null +++ b/scaffold/greeterservice/service.go @@ -0,0 +1,79 @@ +package greeterservice + +import ( + "context" + "fmt" + "log/slog" + + "github.com/jackc/pgx/v5/pgxpool" + "github.com/open-feature/go-sdk/openfeature" + + pb "github.com/bazelment/hajime/grpc_example/proto/greeter" + "github.com/bazelment/hajime/scaffold/internal/database/db" + scerrors "github.com/bazelment/hajime/scaffold/internal/errors" + "github.com/bazelment/hajime/scaffold/internal/logging" +) + +type Service struct { + pb.UnimplementedGreeterServer + logger *slog.Logger + flags *openfeature.Client + pool *pgxpool.Pool +} + +func New(logger *slog.Logger, flags *openfeature.Client, pool *pgxpool.Pool) *Service { + return &Service{ + logger: logger, + flags: flags, + pool: pool, + } +} + +func (s *Service) SayHello(ctx context.Context, req *pb.HelloRequest) (*pb.HelloReply, error) { + name := req.GetName() + if name == "" { + return nil, scerrors.InvalidArgument("name", "name must not be empty") + } + + requestID := logging.RequestIDFromContext(ctx) + s.logger.InfoContext(ctx, "handling greeting", + slog.String("request_id", requestID), + slog.String("name", name), + ) + + if s.pool != nil { + dbLookup, _ := s.flags.BooleanValue(ctx, "greeting-db-lookup", false, openfeature.EvaluationContext{}) + if dbLookup { + queries := db.New(s.pool) + msg, err := queries.GetGreeting(ctx, name) + if err == nil { + return &pb.HelloReply{Message: msg}, nil + } + s.logger.DebugContext(ctx, "no db greeting found, using default", + slog.String("request_id", requestID), + slog.String("name", name), + ) + } + } + + message := fmt.Sprintf("Hello %s!", name) + + v2, _ := s.flags.BooleanValue(ctx, "greeting-v2", false, openfeature.EvaluationContext{}) + if v2 { + message = fmt.Sprintf("Greetings, %s! Welcome to the scaffold service.", name) + } + + if s.pool != nil { + go func() { + queries := db.New(s.pool) + if err := queries.SaveGreeting(context.WithoutCancel(ctx), name, message); err != nil { + s.logger.WarnContext(ctx, "failed to save greeting", + slog.String("request_id", requestID), + slog.String("error", err.Error()), + ) + } + }() + } + + return &pb.HelloReply{Message: message}, nil +} diff --git a/scaffold/greeterservice/service_test.go b/scaffold/greeterservice/service_test.go new file mode 100644 index 0000000..dac90b6 --- /dev/null +++ b/scaffold/greeterservice/service_test.go @@ -0,0 +1,46 @@ +package greeterservice + +import ( + "context" + "log/slog" + "testing" + + "github.com/open-feature/go-sdk/openfeature" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + + pb "github.com/bazelment/hajime/grpc_example/proto/greeter" +) + +func TestSayHello(t *testing.T) { + t.Parallel() + svc := newTestService() + + resp, err := svc.SayHello(context.Background(), &pb.HelloRequest{Name: "World"}) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if resp.Message != "Hello World!" { + t.Errorf("got %q, want %q", resp.Message, "Hello World!") + } +} + +func TestSayHello_EmptyName(t *testing.T) { + t.Parallel() + svc := newTestService() + + _, err := svc.SayHello(context.Background(), &pb.HelloRequest{Name: ""}) + if err == nil { + t.Fatal("expected error for empty name") + } + st := status.Convert(err) + if st.Code() != codes.InvalidArgument { + t.Errorf("got code %v, want %v", st.Code(), codes.InvalidArgument) + } +} + +func newTestService() *Service { + logger := slog.Default() + client := openfeature.NewClient("test") + return New(logger, client, nil) +} diff --git a/scaffold/internal/admin/BUILD.bazel b/scaffold/internal/admin/BUILD.bazel new file mode 100644 index 0000000..83d6ad0 --- /dev/null +++ b/scaffold/internal/admin/BUILD.bazel @@ -0,0 +1,13 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "admin", + srcs = ["admin.go"], + importpath = "github.com/bazelment/hajime/scaffold/internal/admin", + visibility = ["//scaffold:__subpackages__"], + deps = [ + "//scaffold/internal/version", + "@com_github_prometheus_client_golang//prometheus", + "@com_github_prometheus_client_golang//prometheus/promhttp", + ], +) diff --git a/scaffold/internal/admin/admin.go b/scaffold/internal/admin/admin.go new file mode 100644 index 0000000..2ade3a2 --- /dev/null +++ b/scaffold/internal/admin/admin.go @@ -0,0 +1,45 @@ +package admin + +import ( + "encoding/json" + "net/http" + "net/http/pprof" + + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promhttp" + + "github.com/bazelment/hajime/scaffold/internal/version" +) + +func NewServer(addr string, registry *prometheus.Registry) *http.Server { + mux := http.NewServeMux() + + mux.Handle("/metrics", promhttp.HandlerFor(registry, promhttp.HandlerOpts{})) + mux.HandleFunc("/healthz", handleHealthz) + mux.HandleFunc("/version", handleVersion) + + mux.HandleFunc("/debug/pprof/", pprof.Index) + mux.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline) + mux.HandleFunc("/debug/pprof/profile", pprof.Profile) + mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol) + mux.HandleFunc("/debug/pprof/trace", pprof.Trace) + + return &http.Server{ + Addr: addr, + Handler: mux, + } +} + +func handleHealthz(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + w.Write([]byte("ok")) +} + +func handleVersion(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(map[string]string{ + "version": version.Version, + "commit": version.Commit, + "build_time": version.BuildTime, + }) +} diff --git a/scaffold/internal/auth/BUILD.bazel b/scaffold/internal/auth/BUILD.bazel new file mode 100644 index 0000000..388c7fd --- /dev/null +++ b/scaffold/internal/auth/BUILD.bazel @@ -0,0 +1,14 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "auth", + srcs = ["auth.go"], + importpath = "github.com/bazelment/hajime/scaffold/internal/auth", + visibility = ["//scaffold:__subpackages__"], + deps = [ + "@org_golang_google_grpc//:grpc", + "@org_golang_google_grpc//codes", + "@org_golang_google_grpc//metadata", + "@org_golang_google_grpc//status", + ], +) diff --git a/scaffold/internal/auth/auth.go b/scaffold/internal/auth/auth.go new file mode 100644 index 0000000..b8879e9 --- /dev/null +++ b/scaffold/internal/auth/auth.go @@ -0,0 +1,81 @@ +package auth + +import ( + "context" + "strings" + + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/status" +) + +var skipMethods = map[string]bool{ + "/grpc.health.v1.Health/Check": true, + "/grpc.health.v1.Health/Watch": true, + "/grpc.reflection.v1alpha.ServerReflection/ServerReflectionInfo": true, + "/grpc.reflection.v1.ServerReflection/ServerReflectionInfo": true, + "/grpc.channelz.v1.Channelz/GetTopChannels": true, + "/grpc.channelz.v1.Channelz/GetServers": true, + "/grpc.channelz.v1.Channelz/GetChannel": true, + "/grpc.channelz.v1.Channelz/GetSubchannel": true, + "/grpc.channelz.v1.Channelz/GetSocket": true, +} + +func UnaryServerInterceptor() grpc.UnaryServerInterceptor { + return func(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) { + if skipMethods[info.FullMethod] { + return handler(ctx, req) + } + + token, err := extractToken(ctx) + if err != nil { + return nil, err + } + + // TODO: Replace with real JWT/OAuth2 validation. + // For now, accept any non-empty bearer token. + _ = token + + return handler(ctx, req) + } +} + +func StreamServerInterceptor() grpc.StreamServerInterceptor { + return func(srv any, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error { + if skipMethods[info.FullMethod] { + return handler(srv, ss) + } + + token, err := extractToken(ss.Context()) + if err != nil { + return err + } + + _ = token + + return handler(srv, ss) + } +} + +func extractToken(ctx context.Context) (string, error) { + md, ok := metadata.FromIncomingContext(ctx) + if !ok { + return "", status.Error(codes.Unauthenticated, "missing metadata") + } + + vals := md.Get("authorization") + if len(vals) == 0 { + return "", status.Error(codes.Unauthenticated, "missing authorization header") + } + + token := vals[0] + if strings.HasPrefix(token, "Bearer ") { + token = strings.TrimPrefix(token, "Bearer ") + } + if token == "" { + return "", status.Error(codes.Unauthenticated, "empty token") + } + + return token, nil +} diff --git a/scaffold/internal/database/BUILD.bazel b/scaffold/internal/database/BUILD.bazel new file mode 100644 index 0000000..7c7421a --- /dev/null +++ b/scaffold/internal/database/BUILD.bazel @@ -0,0 +1,9 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "database", + srcs = ["database.go"], + importpath = "github.com/bazelment/hajime/scaffold/internal/database", + visibility = ["//scaffold:__subpackages__"], + deps = ["@com_github_jackc_pgx_v5//pgxpool"], +) diff --git a/scaffold/internal/database/database.go b/scaffold/internal/database/database.go new file mode 100644 index 0000000..800f3ee --- /dev/null +++ b/scaffold/internal/database/database.go @@ -0,0 +1,52 @@ +package database + +import ( + "context" + "runtime" + "time" + + "github.com/jackc/pgx/v5/pgxpool" +) + +func Connect(ctx context.Context, databaseURL string) (*pgxpool.Pool, error) { + if databaseURL == "" { + return nil, nil + } + + cfg, err := pgxpool.ParseConfig(databaseURL) + if err != nil { + return nil, err + } + + cfg.MaxConns = int32(runtime.NumCPU()*2 + 1) + cfg.MinConns = 2 + cfg.MaxConnLifetime = time.Hour + cfg.MaxConnLifetimeJitter = 5 * time.Minute + cfg.MaxConnIdleTime = 30 * time.Minute + cfg.HealthCheckPeriod = time.Minute + + pool, err := pgxpool.NewWithConfig(ctx, cfg) + if err != nil { + return nil, err + } + + if err := pool.Ping(ctx); err != nil { + pool.Close() + return nil, err + } + + return pool, nil +} + +func HealthCheck(ctx context.Context, pool *pgxpool.Pool) error { + if pool == nil { + return nil + } + return pool.Ping(ctx) +} + +func Close(pool *pgxpool.Pool) { + if pool != nil { + pool.Close() + } +} diff --git a/scaffold/internal/database/db/BUILD.bazel b/scaffold/internal/database/db/BUILD.bazel new file mode 100644 index 0000000..7cbb75e --- /dev/null +++ b/scaffold/internal/database/db/BUILD.bazel @@ -0,0 +1,16 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "db", + srcs = [ + "db.go", + "models.go", + "query.sql.go", + ], + importpath = "github.com/bazelment/hajime/scaffold/internal/database/db", + visibility = ["//scaffold:__subpackages__"], + deps = [ + "@com_github_jackc_pgx_v5//:pgx", + "@com_github_jackc_pgx_v5//pgconn", + ], +) diff --git a/scaffold/internal/database/db/db.go b/scaffold/internal/database/db/db.go new file mode 100644 index 0000000..76e0160 --- /dev/null +++ b/scaffold/internal/database/db/db.go @@ -0,0 +1,26 @@ +package db + +import ( + "context" + + "github.com/jackc/pgx/v5" + "github.com/jackc/pgx/v5/pgconn" +) + +type DBTX interface { + Exec(context.Context, string, ...any) (pgconn.CommandTag, error) + Query(context.Context, string, ...any) (pgx.Rows, error) + QueryRow(context.Context, string, ...any) pgx.Row +} + +func New(db DBTX) *Queries { + return &Queries{db: db} +} + +type Queries struct { + db DBTX +} + +func (q *Queries) WithTx(tx pgx.Tx) *Queries { + return &Queries{db: tx} +} diff --git a/scaffold/internal/database/db/models.go b/scaffold/internal/database/db/models.go new file mode 100644 index 0000000..4ae56ff --- /dev/null +++ b/scaffold/internal/database/db/models.go @@ -0,0 +1,10 @@ +package db + +import "time" + +type Greeting struct { + ID int64 + Name string + Message string + CreatedAt time.Time +} diff --git a/scaffold/internal/database/db/query.sql.go b/scaffold/internal/database/db/query.sql.go new file mode 100644 index 0000000..c17495b --- /dev/null +++ b/scaffold/internal/database/db/query.sql.go @@ -0,0 +1,53 @@ +package db + +import ( + "context" + "time" +) + +const getGreeting = `-- name: GetGreeting :one +SELECT message FROM greetings WHERE name = $1 ORDER BY created_at DESC LIMIT 1 +` + +func (q *Queries) GetGreeting(ctx context.Context, name string) (string, error) { + row := q.db.QueryRow(ctx, getGreeting, name) + var message string + err := row.Scan(&message) + return message, err +} + +const saveGreeting = `-- name: SaveGreeting :exec +INSERT INTO greetings (name, message) VALUES ($1, $2) +` + +func (q *Queries) SaveGreeting(ctx context.Context, name, message string) error { + _, err := q.db.Exec(ctx, saveGreeting, name, message) + return err +} + +const listGreetings = `-- name: ListGreetings :many +SELECT name, message, created_at FROM greetings ORDER BY created_at DESC LIMIT $1 +` + +type ListGreetingsRow struct { + Name string + Message string + CreatedAt time.Time +} + +func (q *Queries) ListGreetings(ctx context.Context, limit int32) ([]ListGreetingsRow, error) { + rows, err := q.db.Query(ctx, listGreetings, limit) + if err != nil { + return nil, err + } + defer rows.Close() + var items []ListGreetingsRow + for rows.Next() { + var i ListGreetingsRow + if err := rows.Scan(&i.Name, &i.Message, &i.CreatedAt); err != nil { + return nil, err + } + items = append(items, i) + } + return items, rows.Err() +} diff --git a/scaffold/internal/database/query.sql b/scaffold/internal/database/query.sql new file mode 100644 index 0000000..226d0ec --- /dev/null +++ b/scaffold/internal/database/query.sql @@ -0,0 +1,8 @@ +-- name: GetGreeting :one +SELECT message FROM greetings WHERE name = $1 ORDER BY created_at DESC LIMIT 1; + +-- name: SaveGreeting :exec +INSERT INTO greetings (name, message) VALUES ($1, $2); + +-- name: ListGreetings :many +SELECT name, message, created_at FROM greetings ORDER BY created_at DESC LIMIT $1; diff --git a/scaffold/internal/database/schema.sql b/scaffold/internal/database/schema.sql new file mode 100644 index 0000000..750674d --- /dev/null +++ b/scaffold/internal/database/schema.sql @@ -0,0 +1,6 @@ +CREATE TABLE greetings ( + id BIGSERIAL PRIMARY KEY, + name TEXT NOT NULL, + message TEXT NOT NULL, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); diff --git a/scaffold/internal/database/sqlc.yaml b/scaffold/internal/database/sqlc.yaml new file mode 100644 index 0000000..6fc3310 --- /dev/null +++ b/scaffold/internal/database/sqlc.yaml @@ -0,0 +1,10 @@ +version: "2" +sql: + - engine: "postgresql" + queries: "query.sql" + schema: "schema.sql" + gen: + go: + package: "db" + out: "db" + sql_package: "pgx/v5" diff --git a/scaffold/internal/errors/BUILD.bazel b/scaffold/internal/errors/BUILD.bazel new file mode 100644 index 0000000..0ae83f0 --- /dev/null +++ b/scaffold/internal/errors/BUILD.bazel @@ -0,0 +1,17 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "errors", + srcs = ["errors.go"], + importpath = "github.com/bazelment/hajime/scaffold/internal/errors", + visibility = ["//scaffold:__subpackages__"], + deps = [ + "@io_opentelemetry_go_otel//codes", + "@io_opentelemetry_go_otel_trace//:trace", + "@org_golang_google_genproto_googleapis_rpc//errdetails", + "@org_golang_google_grpc//:grpc", + "@org_golang_google_grpc//codes", + "@org_golang_google_grpc//status", + "@org_golang_google_protobuf//types/known/durationpb", + ], +) diff --git a/scaffold/internal/errors/errors.go b/scaffold/internal/errors/errors.go new file mode 100644 index 0000000..630854a --- /dev/null +++ b/scaffold/internal/errors/errors.go @@ -0,0 +1,107 @@ +package errors + +import ( + "context" + "fmt" + "log/slog" + "runtime/debug" + "time" + + "google.golang.org/genproto/googleapis/rpc/errdetails" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + "google.golang.org/protobuf/types/known/durationpb" + + otelcodes "go.opentelemetry.io/otel/codes" + "go.opentelemetry.io/otel/trace" +) + +func InvalidArgument(field, description string) error { + st := status.New(codes.InvalidArgument, fmt.Sprintf("invalid %s", field)) + st, err := st.WithDetails(&errdetails.BadRequest{ + FieldViolations: []*errdetails.BadRequest_FieldViolation{ + {Field: field, Description: description}, + }, + }) + if err != nil { + return status.Error(codes.InvalidArgument, description) + } + return st.Err() +} + +func NotFound(resourceType, resourceID string) error { + st := status.New(codes.NotFound, fmt.Sprintf("%s not found: %s", resourceType, resourceID)) + st, err := st.WithDetails(&errdetails.ResourceInfo{ + ResourceType: resourceType, + ResourceName: resourceID, + Description: fmt.Sprintf("the requested %s was not found", resourceType), + }) + if err != nil { + return status.Error(codes.NotFound, resourceType+" not found") + } + return st.Err() +} + +func RateLimited(retryAfter time.Duration) error { + st := status.New(codes.ResourceExhausted, "rate limit exceeded") + st, err := st.WithDetails(&errdetails.RetryInfo{ + RetryDelay: durationpb.New(retryAfter), + }) + if err != nil { + return status.Error(codes.ResourceExhausted, "rate limit exceeded") + } + return st.Err() +} + +func Internal(err error) error { + st := status.New(codes.Internal, "internal error") + st, withErr := st.WithDetails(&errdetails.DebugInfo{ + StackEntries: []string{string(debug.Stack())}, + Detail: err.Error(), + }) + if withErr != nil { + return status.Error(codes.Internal, "internal error") + } + return st.Err() +} + +func UnaryServerInterceptor(logger *slog.Logger) grpc.UnaryServerInterceptor { + return func(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) { + resp, err := handler(ctx, req) + if err == nil { + return resp, nil + } + + span := trace.SpanFromContext(ctx) + span.RecordError(err) + span.SetStatus(otelcodes.Error, err.Error()) + + st := status.Convert(err) + if isClientError(st.Code()) { + logger.WarnContext(ctx, "client error", + slog.String("method", info.FullMethod), + slog.String("code", st.Code().String()), + slog.String("message", st.Message()), + ) + } else { + logger.ErrorContext(ctx, "server error", + slog.String("method", info.FullMethod), + slog.String("code", st.Code().String()), + slog.String("message", st.Message()), + ) + } + + return resp, err + } +} + +func isClientError(c codes.Code) bool { + switch c { + case codes.InvalidArgument, codes.NotFound, codes.AlreadyExists, + codes.PermissionDenied, codes.Unauthenticated, codes.FailedPrecondition, + codes.OutOfRange, codes.ResourceExhausted: + return true + } + return false +} diff --git a/scaffold/internal/featureflags/BUILD.bazel b/scaffold/internal/featureflags/BUILD.bazel new file mode 100644 index 0000000..e39de40 --- /dev/null +++ b/scaffold/internal/featureflags/BUILD.bazel @@ -0,0 +1,9 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "featureflags", + srcs = ["flags.go"], + importpath = "github.com/bazelment/hajime/scaffold/internal/featureflags", + visibility = ["//scaffold:__subpackages__"], + deps = ["@com_github_open_feature_go_sdk//openfeature"], +) diff --git a/scaffold/internal/featureflags/flags.go b/scaffold/internal/featureflags/flags.go new file mode 100644 index 0000000..37c4c9a --- /dev/null +++ b/scaffold/internal/featureflags/flags.go @@ -0,0 +1,12 @@ +package featureflags + +import ( + "context" + + "github.com/open-feature/go-sdk/openfeature" +) + +func Setup(ctx context.Context) *openfeature.Client { + openfeature.SetProvider(openfeature.NoopProvider{}) + return openfeature.NewClient("greeter-service") +} diff --git a/scaffold/internal/logging/BUILD.bazel b/scaffold/internal/logging/BUILD.bazel new file mode 100644 index 0000000..acfce81 --- /dev/null +++ b/scaffold/internal/logging/BUILD.bazel @@ -0,0 +1,14 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "logging", + srcs = ["logging.go"], + importpath = "github.com/bazelment/hajime/scaffold/internal/logging", + visibility = ["//scaffold:__subpackages__"], + deps = [ + "@com_github_google_uuid//:uuid", + "@org_golang_google_grpc//:grpc", + "@org_golang_google_grpc//metadata", + "@org_golang_google_grpc//peer", + ], +) diff --git a/scaffold/internal/logging/logging.go b/scaffold/internal/logging/logging.go new file mode 100644 index 0000000..bca4d4b --- /dev/null +++ b/scaffold/internal/logging/logging.go @@ -0,0 +1,95 @@ +package logging + +import ( + "context" + "log/slog" + "os" + "strings" + + "google.golang.org/grpc" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/peer" + + "github.com/google/uuid" +) + +type contextKey struct{} + +func Setup(level, serviceName, version string) *slog.Logger { + var lvl slog.Level + switch strings.ToLower(level) { + case "debug": + lvl = slog.LevelDebug + case "warn", "warning": + lvl = slog.LevelWarn + case "error": + lvl = slog.LevelError + default: + lvl = slog.LevelInfo + } + + handler := slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{Level: lvl}) + + logger := slog.New(handler).With( + slog.String("service.name", serviceName), + slog.String("service.version", version), + ) + + slog.SetDefault(logger) + return logger +} + +func RequestIDFromContext(ctx context.Context) string { + if v, ok := ctx.Value(contextKey{}).(string); ok { + return v + } + return "" +} + +func ContextWithRequestID(ctx context.Context, requestID string) context.Context { + return context.WithValue(ctx, contextKey{}, requestID) +} + +func UnaryServerInterceptor(logger *slog.Logger) grpc.UnaryServerInterceptor { + return func(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) { + requestID := extractOrGenerateRequestID(ctx) + ctx = ContextWithRequestID(ctx, requestID) + + peerAddr := "" + if p, ok := peer.FromContext(ctx); ok { + peerAddr = p.Addr.String() + } + + logger.InfoContext(ctx, "rpc started", + slog.String("request_id", requestID), + slog.String("method", info.FullMethod), + slog.String("peer", peerAddr), + ) + + resp, err := handler(ctx, req) + + if err != nil { + logger.WarnContext(ctx, "rpc failed", + slog.String("request_id", requestID), + slog.String("method", info.FullMethod), + slog.String("error", err.Error()), + ) + } else { + logger.InfoContext(ctx, "rpc completed", + slog.String("request_id", requestID), + slog.String("method", info.FullMethod), + ) + } + + return resp, err + } +} + +func extractOrGenerateRequestID(ctx context.Context) string { + if md, ok := metadata.FromIncomingContext(ctx); ok { + if vals := md.Get("x-request-id"); len(vals) > 0 { + return vals[0] + } + } + return uuid.New().String() +} diff --git a/scaffold/internal/observability/BUILD.bazel b/scaffold/internal/observability/BUILD.bazel new file mode 100644 index 0000000..c865f67 --- /dev/null +++ b/scaffold/internal/observability/BUILD.bazel @@ -0,0 +1,18 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "observability", + srcs = ["observability.go"], + importpath = "github.com/bazelment/hajime/scaffold/internal/observability", + visibility = ["//scaffold:__subpackages__"], + deps = [ + "@com_github_prometheus_client_golang//prometheus", + "@com_github_prometheus_client_golang//prometheus/collectors", + "@io_opentelemetry_go_otel//:otel", + "@io_opentelemetry_go_otel//semconv/v1.26.0:v1_26_0", + "@io_opentelemetry_go_otel_exporters_prometheus//:prometheus", + "@io_opentelemetry_go_otel_sdk//resource", + "@io_opentelemetry_go_otel_sdk//trace", + "@io_opentelemetry_go_otel_sdk_metric//:metric", + ], +) diff --git a/scaffold/internal/observability/observability.go b/scaffold/internal/observability/observability.go new file mode 100644 index 0000000..d8ddcf8 --- /dev/null +++ b/scaffold/internal/observability/observability.go @@ -0,0 +1,70 @@ +package observability + +import ( + "context" + + "github.com/prometheus/client_golang/prometheus" + promclient "github.com/prometheus/client_golang/prometheus/collectors" + "go.opentelemetry.io/otel" + otelprometheus "go.opentelemetry.io/otel/exporters/prometheus" + sdkmetric "go.opentelemetry.io/otel/sdk/metric" + "go.opentelemetry.io/otel/sdk/resource" + sdktrace "go.opentelemetry.io/otel/sdk/trace" + semconv "go.opentelemetry.io/otel/semconv/v1.26.0" +) + +type Telemetry struct { + Registry *prometheus.Registry + shutdown []func(context.Context) error +} + +func Setup(ctx context.Context, serviceName, version string) (*Telemetry, error) { + res, err := resource.New(ctx, + resource.WithAttributes( + semconv.ServiceName(serviceName), + semconv.ServiceVersion(version), + ), + ) + if err != nil { + return nil, err + } + + t := &Telemetry{ + Registry: prometheus.NewRegistry(), + } + + t.Registry.MustRegister(promclient.NewGoCollector()) + t.Registry.MustRegister(promclient.NewProcessCollector(promclient.ProcessCollectorOpts{})) + + promExporter, err := otelprometheus.New( + otelprometheus.WithRegisterer(t.Registry), + ) + if err != nil { + return nil, err + } + + mp := sdkmetric.NewMeterProvider( + sdkmetric.WithReader(promExporter), + sdkmetric.WithResource(res), + ) + otel.SetMeterProvider(mp) + t.shutdown = append(t.shutdown, mp.Shutdown) + + tp := sdktrace.NewTracerProvider( + sdktrace.WithResource(res), + ) + otel.SetTracerProvider(tp) + t.shutdown = append(t.shutdown, tp.Shutdown) + + return t, nil +} + +func (t *Telemetry) Shutdown(ctx context.Context) error { + var firstErr error + for _, fn := range t.shutdown { + if err := fn(ctx); err != nil && firstErr == nil { + firstErr = err + } + } + return firstErr +} diff --git a/scaffold/internal/ratelimit/BUILD.bazel b/scaffold/internal/ratelimit/BUILD.bazel new file mode 100644 index 0000000..ac1d0d9 --- /dev/null +++ b/scaffold/internal/ratelimit/BUILD.bazel @@ -0,0 +1,16 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "ratelimit", + srcs = ["ratelimit.go"], + importpath = "github.com/bazelment/hajime/scaffold/internal/ratelimit", + visibility = ["//scaffold:__subpackages__"], + deps = [ + "@org_golang_google_genproto_googleapis_rpc//errdetails", + "@org_golang_google_grpc//:grpc", + "@org_golang_google_grpc//codes", + "@org_golang_google_grpc//status", + "@org_golang_google_protobuf//types/known/durationpb", + "@org_golang_x_time//rate", + ], +) diff --git a/scaffold/internal/ratelimit/ratelimit.go b/scaffold/internal/ratelimit/ratelimit.go new file mode 100644 index 0000000..958b589 --- /dev/null +++ b/scaffold/internal/ratelimit/ratelimit.go @@ -0,0 +1,44 @@ +package ratelimit + +import ( + "context" + "time" + + "golang.org/x/time/rate" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + "google.golang.org/protobuf/types/known/durationpb" + + "google.golang.org/genproto/googleapis/rpc/errdetails" +) + +type Limiter struct { + limiter *rate.Limiter +} + +func New(requestsPerSecond int) *Limiter { + return &Limiter{ + limiter: rate.NewLimiter(rate.Limit(requestsPerSecond), requestsPerSecond), + } +} + +func (l *Limiter) UnaryServerInterceptor() grpc.UnaryServerInterceptor { + return func(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) { + if !l.limiter.Allow() { + return nil, rateLimitedError() + } + return handler(ctx, req) + } +} + +func rateLimitedError() error { + st := status.New(codes.ResourceExhausted, "rate limit exceeded") + st, err := st.WithDetails(&errdetails.RetryInfo{ + RetryDelay: durationpb.New(time.Second), + }) + if err != nil { + return status.Error(codes.ResourceExhausted, "rate limit exceeded") + } + return st.Err() +} diff --git a/scaffold/internal/version/BUILD.bazel b/scaffold/internal/version/BUILD.bazel new file mode 100644 index 0000000..85072d5 --- /dev/null +++ b/scaffold/internal/version/BUILD.bazel @@ -0,0 +1,8 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "version", + srcs = ["version.go"], + importpath = "github.com/bazelment/hajime/scaffold/internal/version", + visibility = ["//scaffold:__subpackages__"], +) diff --git a/scaffold/internal/version/version.go b/scaffold/internal/version/version.go new file mode 100644 index 0000000..a52b854 --- /dev/null +++ b/scaffold/internal/version/version.go @@ -0,0 +1,7 @@ +package version + +var ( + Version = "dev" + Commit = "unknown" + BuildTime = "unknown" +)