From 8f4eea00f3e70dbf6e1e338a74e53a1d816bfad0 Mon Sep 17 00:00:00 2001 From: Elias Arruda Date: Tue, 6 Jan 2026 13:17:02 -0300 Subject: [PATCH] Update spawn with latest changes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR includes: - Updated actor entity and lifecycle management - Enhanced state management and handoff controllers - Improved gRPC dispatcher and code generator - Updated protocol buffers and generated code - Proxy and SDK improvements - State store adapter updates πŸ€– Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- Dockerfile-elixir-example | 49 + Dockerfile-initializer | 45 + Dockerfile-operator | 60 + Dockerfile-proxy | 64 + Makefile | 19 - README.md | 224 +-- config/config.exs | 5 - config/test.exs | 16 - docker-compose.yaml | 102 ++ k3d-cluster-create.txt | 1 + kind-1000.yaml | 10 + kind-cluster-config.yaml | 10 + lib/_generated/io/cloudevents/v1/spec.pb.ex | 6 +- lib/_generated/spawn/actors/actor.pb.ex | 40 +- lib/_generated/spawn/actors/extensions.pb.ex | 6 +- lib/_generated/spawn/actors/healthcheck.pb.ex | 6 +- lib/_generated/spawn/actors/protocol.pb.ex | 50 +- lib/_generated/spawn/actors/state.pb.ex | 4 +- lib/_generated/spawn/actors/test.pb.ex | 10 +- lib/actors/actor/caller_consumer.ex | 91 +- lib/actors/actor/entity/entity.ex | 2 +- lib/actors/actor/entity/invocation.ex | 24 +- lib/actors/actor/entity/lifecycle.ex | 14 +- .../actor/entity/lifecycle/stream_consumer.ex | 134 +- .../entity/lifecycle/stream_initiator.ex | 4 +- lib/actors/actor/entity/pool.ex | 15 +- lib/actors/actor/entity/supervisor.ex | 76 +- lib/actors/actor/state_manager.ex | 8 +- lib/actors/registry/actor_registry.ex | 15 +- lib/actors/registry/host_actor.ex | 6 +- .../supervisors/actor_projection_handler.ex | 56 + lib/actors/supervisors/actor_supervisor.ex | 14 +- lib/sidecar/graceful_shutdown.ex | 2 +- lib/sidecar/grpc/code_generator.ex | 11 +- lib/sidecar/grpc/dispatcher.ex | 28 +- lib/sidecar/process_supervisor.ex | 1 + lib/spawn.ex | 111 +- .../controllers/nats_kv_controller.ex | 15 +- lib/spawn/cluster/state_handoff/manager.ex | 7 +- .../state_handoff/manager_supervisor.ex | 3 +- lib/spawn/supervisor.ex | 4 +- lib/spawn/utils/common.ex | 15 + mix.exs | 32 +- mix.lock | 92 +- observability/otel/compose.yaml | 22 + observability/otel/grafana-datasources.yaml | 10 + observability/otel/grafana.ini | 2 + observability/otel/tempo.yaml | 25 + priv/internal_versions.exs | 6 + spawn_activators/activator/.formatter.exs | 4 + spawn_activators/activator/.gitignore | 26 + spawn_activators/activator/README.md | 3 + spawn_activators/activator/lib/activator.ex | 25 + .../dispatcher/default_dispatcher.ex | 86 + .../lib/activator/encoder/cloudevent.ex | 38 + .../activator/lib/activator/supervisor.ex | 29 + spawn_activators/activator/lib/dispatcher.ex | 20 + spawn_activators/activator/lib/encoder.ex | 21 + spawn_activators/activator/mix.exs | 36 + spawn_activators/activator/mix.lock | 117 ++ .../activator/test/activator_test.exs | 4 + .../test/codec/cloudevents_codec_test.exs | 17 + .../activator/test/test_helper.exs | 1 + spawn_activators/activator_api/.env | 4 + spawn_activators/activator_api/.formatter.exs | 4 + spawn_activators/activator_api/.gitignore | 26 + spawn_activators/activator_api/README.md | 40 + .../activator_api/compile-example-pb.sh | 17 + .../activator_api/lib/activator_api.ex | 5 + .../lib/activator_api/api/discovery.ex | 190 ++ .../lib/activator_api/api/dispatcher.ex | 53 + .../api/dispatcher/stream_in_dispatcher.ex | 12 + .../api/dispatcher/stream_out_dispatcher.ex | 12 + .../api/dispatcher/streamed_dispatcher.ex | 12 + .../api/dispatcher/unary_dispatcher.ex | 83 + .../lib/activator_api/api/generator.ex | 82 + .../lib/activator_api/api/reflection.ex | 97 ++ .../activator_api/api/router_dispatcher.ex | 40 + .../lib/activator_api/application.ex | 87 + .../lib/activator_api/grpc_server.ex | 223 +++ .../lib/activator_api/grpc_utils.ex | 51 + .../protobuf/api/annotations.pb.ex | 6 + .../lib/activator_api/protobuf/api/auth.pb.ex | 437 +++++ .../lib/activator_api/protobuf/api/http.pb.ex | 235 +++ .../activator_api/protobuf/api/httpbody.pb.ex | 68 + .../protobuf/api/source_info.pb.ex | 38 + .../lib/activator_api/router/router.ex | 15 + .../lib/activator_api/routes/base.ex | 40 + .../lib/activator_api/routes/health.ex | 17 + spawn_activators/activator_api/mix.exs | 53 + .../priv/example/out/service.pb.ex | 107 ++ .../priv/example/out/user-api.desc | 10 + .../priv/protos/google/api/annotations.proto | 31 + .../priv/protos/google/api/any.proto | 161 ++ .../priv/protos/google/api/auth.proto | 236 +++ .../priv/protos/google/api/descriptor.proto | 975 +++++++++++ .../priv/protos/google/api/http.proto | 250 +++ .../priv/protos/google/api/httpbody.proto | 81 + .../priv/protos/google/api/source_info.proto | 31 + .../activator_api/priv/protos/service.proto | 26 + .../priv/templates/grpc_endpoint.ex.eex | 15 + .../priv/templates/grpc_service.ex.eex | 35 + .../test/activator_grpc_test.exs | 4 + .../activator_api/test/test_helper.exs | 1 + spawn_activators/activator_kafka/.env | 4 + .../activator_kafka/.formatter.exs | 4 + spawn_activators/activator_kafka/.gitignore | 26 + spawn_activators/activator_kafka/README.md | 21 + .../activator_kafka/lib/activator_kafka.ex | 18 + .../lib/activator_kafka/application.ex | 21 + .../lib/activator_kafka/router/router.ex | 15 + .../lib/activator_kafka/routes/base.ex | 40 + .../lib/activator_kafka/routes/health.ex | 17 + spawn_activators/activator_kafka/mix.exs | 55 + .../test/activator_kafka_test.exs | 8 + .../activator_kafka/test/test_helper.exs | 1 + spawn_activators/activator_pubsub/.env | 4 + .../activator_pubsub/.formatter.exs | 4 + spawn_activators/activator_pubsub/.gitignore | 26 + spawn_activators/activator_pubsub/README.md | 21 + .../activator_pubsub/lib/activator_pubsub.ex | 5 + .../lib/activator_pubsub/application.ex | 21 + .../lib/activator_pubsub/router/router.ex | 15 + .../lib/activator_pubsub/routes/base.ex | 40 + .../lib/activator_pubsub/routes/health.ex | 17 + spawn_activators/activator_pubsub/mix.exs | 55 + .../test/activator_pubsub_test.exs | 4 + .../activator_pubsub/test/test_helper.exs | 1 + spawn_activators/activator_rabbitmq/.env | 18 + .../activator_rabbitmq/.formatter.exs | 4 + .../activator_rabbitmq/.gitignore | 26 + spawn_activators/activator_rabbitmq/README.md | 21 + .../activator_rabbitmq/config.json | 50 + .../activator_rabbitmq/deployment.yaml | 103 ++ .../lib/activator_rabbitmq.ex | 5 + .../lib/activator_rabbitmq/application.ex | 21 + .../lib/activator_rabbitmq/router/router.ex | 15 + .../lib/activator_rabbitmq/routes/base.ex | 40 + .../lib/activator_rabbitmq/routes/health.ex | 17 + .../activator_rabbitmq/sources/rabbitmq.ex | 110 ++ .../sources/source_supervisor.ex | 49 + .../lib/activator_rabbitmq/supervisor.ex | 48 + spawn_activators/activator_rabbitmq/mix.exs | 55 + .../activator_rabbitmq/settings-cm.yaml | 56 + .../test/activator_rabbitmq_test.exs | 4 + .../activator_rabbitmq/test/test_helper.exs | 1 + spawn_activators/activator_simple/Cargo.lock | 1549 +++++++++++++++++ spawn_activators/activator_simple/Cargo.toml | 13 + spawn_activators/activator_simple/README.md | 9 + spawn_activators/activator_simple/src/main.rs | 64 + .../activator_simple/src/protocol_pb.rs | 592 +++++++ spawn_activators/activator_sqs/.env | 4 + spawn_activators/activator_sqs/.formatter.exs | 4 + spawn_activators/activator_sqs/.gitignore | 26 + spawn_activators/activator_sqs/README.md | 21 + .../activator_sqs/lib/activator_sqs.ex | 18 + .../lib/activator_sqs/application.ex | 21 + .../lib/activator_sqs/router/router.ex | 15 + .../lib/activator_sqs/routes/base.ex | 40 + .../lib/activator_sqs/routes/health.ex | 17 + spawn_activators/activator_sqs/mix.exs | 55 + .../activator_sqs/test/activator_sqs_test.exs | 8 + .../activator_sqs/test/test_helper.exs | 1 + .../spawn_operator/k8s/proxy/deployment.ex | 2 +- spawn_operator/spawn_operator/manifest.yaml | 549 ++++-- spawn_operator/spawn_operator/mix.lock | 1 - spawn_proxy/proxy/mix.exs | 4 - .../spawn_sdk/lib/system/spawn_system.ex | 7 +- .../lib/statestore_controller/supervisor.ex | 23 +- .../test/support/data_case.ex | 2 +- .../statestores/adapters/lookup_behaviour.ex | 4 +- .../adapters/projection_behaviour.ex | 4 +- .../adapters/snapshot_behaviour.ex | 14 +- .../lib/statestores/schemas/snapshot.ex | 6 +- .../statestores/lib/statestores/supervisor.ex | 20 +- .../statestores/lib/statestores/util.ex | 19 +- .../20220521162410_create_snapshots_table.exs | 24 + .../20220521162411_create_lookups_table.exs | 21 + ...2412_create_historical_snapshots_table.exs | 27 + ..._change_snapshots_primary_key_to_actor.exs | 28 + .../statestores/test/support/data_case.ex | 2 +- .../adapters/postgres_lookup_adapter.ex | 15 +- .../adapters/postgres_projection_adapter.ex | 15 +- .../adapters/postgres_snapshot_adapter.ex | 181 +- .../lib/statestores/postgres_repo.ex | 9 + .../test/postgres_projection_test.exs | 4 +- .../statestores_postgres/test/repo_test.exs | 46 +- .../test/support/data_case.ex | 6 +- test/support/data_case.ex | 2 +- test/support/factory.ex | 8 +- 190 files changed, 9338 insertions(+), 1157 deletions(-) create mode 100644 Dockerfile-elixir-example create mode 100644 Dockerfile-initializer create mode 100644 Dockerfile-operator create mode 100644 Dockerfile-proxy create mode 100644 docker-compose.yaml create mode 100644 k3d-cluster-create.txt create mode 100644 kind-1000.yaml create mode 100644 kind-cluster-config.yaml create mode 100644 lib/actors/supervisors/actor_projection_handler.ex create mode 100644 observability/otel/compose.yaml create mode 100644 observability/otel/grafana-datasources.yaml create mode 100644 observability/otel/grafana.ini create mode 100644 observability/otel/tempo.yaml create mode 100644 spawn_activators/activator/.formatter.exs create mode 100644 spawn_activators/activator/.gitignore create mode 100644 spawn_activators/activator/README.md create mode 100644 spawn_activators/activator/lib/activator.ex create mode 100644 spawn_activators/activator/lib/activator/dispatcher/default_dispatcher.ex create mode 100644 spawn_activators/activator/lib/activator/encoder/cloudevent.ex create mode 100644 spawn_activators/activator/lib/activator/supervisor.ex create mode 100644 spawn_activators/activator/lib/dispatcher.ex create mode 100644 spawn_activators/activator/lib/encoder.ex create mode 100644 spawn_activators/activator/mix.exs create mode 100644 spawn_activators/activator/mix.lock create mode 100644 spawn_activators/activator/test/activator_test.exs create mode 100644 spawn_activators/activator/test/codec/cloudevents_codec_test.exs create mode 100644 spawn_activators/activator/test/test_helper.exs create mode 100644 spawn_activators/activator_api/.env create mode 100644 spawn_activators/activator_api/.formatter.exs create mode 100644 spawn_activators/activator_api/.gitignore create mode 100644 spawn_activators/activator_api/README.md create mode 100755 spawn_activators/activator_api/compile-example-pb.sh create mode 100644 spawn_activators/activator_api/lib/activator_api.ex create mode 100644 spawn_activators/activator_api/lib/activator_api/api/discovery.ex create mode 100644 spawn_activators/activator_api/lib/activator_api/api/dispatcher.ex create mode 100644 spawn_activators/activator_api/lib/activator_api/api/dispatcher/stream_in_dispatcher.ex create mode 100644 spawn_activators/activator_api/lib/activator_api/api/dispatcher/stream_out_dispatcher.ex create mode 100644 spawn_activators/activator_api/lib/activator_api/api/dispatcher/streamed_dispatcher.ex create mode 100644 spawn_activators/activator_api/lib/activator_api/api/dispatcher/unary_dispatcher.ex create mode 100644 spawn_activators/activator_api/lib/activator_api/api/generator.ex create mode 100644 spawn_activators/activator_api/lib/activator_api/api/reflection.ex create mode 100644 spawn_activators/activator_api/lib/activator_api/api/router_dispatcher.ex create mode 100644 spawn_activators/activator_api/lib/activator_api/application.ex create mode 100644 spawn_activators/activator_api/lib/activator_api/grpc_server.ex create mode 100644 spawn_activators/activator_api/lib/activator_api/grpc_utils.ex create mode 100644 spawn_activators/activator_api/lib/activator_api/protobuf/api/annotations.pb.ex create mode 100644 spawn_activators/activator_api/lib/activator_api/protobuf/api/auth.pb.ex create mode 100644 spawn_activators/activator_api/lib/activator_api/protobuf/api/http.pb.ex create mode 100644 spawn_activators/activator_api/lib/activator_api/protobuf/api/httpbody.pb.ex create mode 100644 spawn_activators/activator_api/lib/activator_api/protobuf/api/source_info.pb.ex create mode 100644 spawn_activators/activator_api/lib/activator_api/router/router.ex create mode 100644 spawn_activators/activator_api/lib/activator_api/routes/base.ex create mode 100644 spawn_activators/activator_api/lib/activator_api/routes/health.ex create mode 100644 spawn_activators/activator_api/mix.exs create mode 100644 spawn_activators/activator_api/priv/example/out/service.pb.ex create mode 100644 spawn_activators/activator_api/priv/example/out/user-api.desc create mode 100644 spawn_activators/activator_api/priv/protos/google/api/annotations.proto create mode 100644 spawn_activators/activator_api/priv/protos/google/api/any.proto create mode 100644 spawn_activators/activator_api/priv/protos/google/api/auth.proto create mode 100644 spawn_activators/activator_api/priv/protos/google/api/descriptor.proto create mode 100644 spawn_activators/activator_api/priv/protos/google/api/http.proto create mode 100644 spawn_activators/activator_api/priv/protos/google/api/httpbody.proto create mode 100644 spawn_activators/activator_api/priv/protos/google/api/source_info.proto create mode 100644 spawn_activators/activator_api/priv/protos/service.proto create mode 100644 spawn_activators/activator_api/priv/templates/grpc_endpoint.ex.eex create mode 100644 spawn_activators/activator_api/priv/templates/grpc_service.ex.eex create mode 100644 spawn_activators/activator_api/test/activator_grpc_test.exs create mode 100644 spawn_activators/activator_api/test/test_helper.exs create mode 100644 spawn_activators/activator_kafka/.env create mode 100644 spawn_activators/activator_kafka/.formatter.exs create mode 100644 spawn_activators/activator_kafka/.gitignore create mode 100644 spawn_activators/activator_kafka/README.md create mode 100644 spawn_activators/activator_kafka/lib/activator_kafka.ex create mode 100644 spawn_activators/activator_kafka/lib/activator_kafka/application.ex create mode 100644 spawn_activators/activator_kafka/lib/activator_kafka/router/router.ex create mode 100644 spawn_activators/activator_kafka/lib/activator_kafka/routes/base.ex create mode 100644 spawn_activators/activator_kafka/lib/activator_kafka/routes/health.ex create mode 100644 spawn_activators/activator_kafka/mix.exs create mode 100644 spawn_activators/activator_kafka/test/activator_kafka_test.exs create mode 100644 spawn_activators/activator_kafka/test/test_helper.exs create mode 100644 spawn_activators/activator_pubsub/.env create mode 100644 spawn_activators/activator_pubsub/.formatter.exs create mode 100644 spawn_activators/activator_pubsub/.gitignore create mode 100644 spawn_activators/activator_pubsub/README.md create mode 100644 spawn_activators/activator_pubsub/lib/activator_pubsub.ex create mode 100644 spawn_activators/activator_pubsub/lib/activator_pubsub/application.ex create mode 100644 spawn_activators/activator_pubsub/lib/activator_pubsub/router/router.ex create mode 100644 spawn_activators/activator_pubsub/lib/activator_pubsub/routes/base.ex create mode 100644 spawn_activators/activator_pubsub/lib/activator_pubsub/routes/health.ex create mode 100644 spawn_activators/activator_pubsub/mix.exs create mode 100644 spawn_activators/activator_pubsub/test/activator_pubsub_test.exs create mode 100644 spawn_activators/activator_pubsub/test/test_helper.exs create mode 100644 spawn_activators/activator_rabbitmq/.env create mode 100644 spawn_activators/activator_rabbitmq/.formatter.exs create mode 100644 spawn_activators/activator_rabbitmq/.gitignore create mode 100644 spawn_activators/activator_rabbitmq/README.md create mode 100644 spawn_activators/activator_rabbitmq/config.json create mode 100644 spawn_activators/activator_rabbitmq/deployment.yaml create mode 100644 spawn_activators/activator_rabbitmq/lib/activator_rabbitmq.ex create mode 100644 spawn_activators/activator_rabbitmq/lib/activator_rabbitmq/application.ex create mode 100644 spawn_activators/activator_rabbitmq/lib/activator_rabbitmq/router/router.ex create mode 100644 spawn_activators/activator_rabbitmq/lib/activator_rabbitmq/routes/base.ex create mode 100644 spawn_activators/activator_rabbitmq/lib/activator_rabbitmq/routes/health.ex create mode 100644 spawn_activators/activator_rabbitmq/lib/activator_rabbitmq/sources/rabbitmq.ex create mode 100644 spawn_activators/activator_rabbitmq/lib/activator_rabbitmq/sources/source_supervisor.ex create mode 100644 spawn_activators/activator_rabbitmq/lib/activator_rabbitmq/supervisor.ex create mode 100644 spawn_activators/activator_rabbitmq/mix.exs create mode 100644 spawn_activators/activator_rabbitmq/settings-cm.yaml create mode 100644 spawn_activators/activator_rabbitmq/test/activator_rabbitmq_test.exs create mode 100644 spawn_activators/activator_rabbitmq/test/test_helper.exs create mode 100644 spawn_activators/activator_simple/Cargo.lock create mode 100644 spawn_activators/activator_simple/Cargo.toml create mode 100644 spawn_activators/activator_simple/README.md create mode 100644 spawn_activators/activator_simple/src/main.rs create mode 100644 spawn_activators/activator_simple/src/protocol_pb.rs create mode 100644 spawn_activators/activator_sqs/.env create mode 100644 spawn_activators/activator_sqs/.formatter.exs create mode 100644 spawn_activators/activator_sqs/.gitignore create mode 100644 spawn_activators/activator_sqs/README.md create mode 100644 spawn_activators/activator_sqs/lib/activator_sqs.ex create mode 100644 spawn_activators/activator_sqs/lib/activator_sqs/application.ex create mode 100644 spawn_activators/activator_sqs/lib/activator_sqs/router/router.ex create mode 100644 spawn_activators/activator_sqs/lib/activator_sqs/routes/base.ex create mode 100644 spawn_activators/activator_sqs/lib/activator_sqs/routes/health.ex create mode 100644 spawn_activators/activator_sqs/mix.exs create mode 100644 spawn_activators/activator_sqs/test/activator_sqs_test.exs create mode 100644 spawn_activators/activator_sqs/test/test_helper.exs create mode 100644 spawn_statestores/statestores/priv/postgres_repo/migrations/20220521162410_create_snapshots_table.exs create mode 100644 spawn_statestores/statestores/priv/postgres_repo/migrations/20220521162411_create_lookups_table.exs create mode 100644 spawn_statestores/statestores/priv/postgres_repo/migrations/20220521162412_create_historical_snapshots_table.exs create mode 100644 spawn_statestores/statestores/priv/postgres_repo/migrations/20250314013201_change_snapshots_primary_key_to_actor.exs create mode 100644 spawn_statestores/statestores_postgres/lib/statestores/postgres_repo.ex diff --git a/Dockerfile-elixir-example b/Dockerfile-elixir-example new file mode 100644 index 000000000..1cba0eda8 --- /dev/null +++ b/Dockerfile-elixir-example @@ -0,0 +1,49 @@ +FROM elixir:1.15-alpine AS builder + +ENV MIX_ENV=prod + +WORKDIR /app + +RUN apk add --no-cache --update git build-base ca-certificates zstd + +RUN mkdir config +COPY config/ ./config +COPY spawn_sdk/ ./spawn_sdk +COPY spawn_statestores/ ./spawn_statestores +COPY lib/ ./lib +COPY priv/ ./priv +COPY mix.exs . +COPY mix.lock . + +RUN mix local.rebar --force \ + && mix local.hex --force \ + && mix deps.get \ + && mix release.init + +RUN echo "-name spawn_sdk_elixir@${HOSTNAME}" >> ./rel/vm.args.eex \ + && echo "-setcookie ${RELEASE_COOKIE}" >> ./rel/vm.args.eex + +RUN cd spawn_sdk/spawn_sdk_example \ + && mix deps.get \ + && mix release spawn_sdk_example + +# ---- Application Stage ---- +FROM alpine:3.17.3 +RUN apk add --no-cache --update zstd ncurses-libs libstdc++ libgcc libcrypto1.1 + +WORKDIR /app +RUN chown nobody /app + +# Set runner ENV +ENV MIX_ENV=prod +ENV HOME=/app + +COPY --from=builder --chown=nobody:root /app/_build/${MIX_ENV}/rel/bakeware/ ./ + +RUN mkdir -p /app/.cache/bakeware/ && chmod 777 /app/.cache/bakeware/ +RUN touch /.erlang.cookie && chmod 777 /.erlang.cookie +RUN touch /app/.erlang.cookie && chmod 777 /app/.erlang.cookie + +USER nobody + +ENTRYPOINT ["./spawn_sdk_example", "start"] diff --git a/Dockerfile-initializer b/Dockerfile-initializer new file mode 100644 index 000000000..b3140100b --- /dev/null +++ b/Dockerfile-initializer @@ -0,0 +1,45 @@ +FROM elixir:1.15-alpine AS builder + +ENV MIX_ENV=prod + +WORKDIR /app + +RUN apk add --no-cache --update git build-base ca-certificates zstd gcc pkgconfig openssl-dev + +COPY spawn_initializer/ . + +RUN mix local.rebar --force \ + && mix local.hex --force \ + && mix deps.get \ + && mix release.init + +# This will be the basename of node +ENV RELEASE_NAME="spawn_initializer" + +# Disable Erlang Dist +ENV RELEASE_DISTRIBUTION=none + +RUN mix deps.get \ + && mix release spawn_initializer + +# ---- Application Stage ---- +FROM alpine:3.20 + +RUN apk add --no-cache --update zstd ncurses-libs libstdc++ libgcc + +WORKDIR /app +RUN chown nobody /app + +# Set runner ENV +ENV MIX_ENV=prod +ENV HOME=/app + +COPY --from=builder --chown=nobody:root /app/_build/${MIX_ENV}/rel/bakeware/ ./ + +RUN mkdir -p /app/.cache/bakeware/ && chmod 777 /app/.cache/bakeware/ +RUN touch /.erlang.cookie && chmod 777 /.erlang.cookie +RUN touch /app/.erlang.cookie && chmod 777 /app/.erlang.cookie + +USER nobody + +ENTRYPOINT ["./spawn_initializer"] diff --git a/Dockerfile-operator b/Dockerfile-operator new file mode 100644 index 000000000..7750ab6f5 --- /dev/null +++ b/Dockerfile-operator @@ -0,0 +1,60 @@ +FROM elixir:1.15-alpine AS builder + +ENV MIX_ENV=prod + +WORKDIR /app + +RUN apk add --no-cache --update git build-base ca-certificates zstd gcc pkgconfig openssl-dev + +RUN mkdir config +COPY config/ ./config +COPY spawn_operator/ ./spawn_operator +COPY spawn_statestores/ ./spawn_statestores +COPY lib/ ./lib +COPY priv/ ./priv +COPY mix.exs . +COPY mix.lock . + +RUN mix local.rebar --force \ + && mix local.hex --force \ + && mix deps.get \ + && mix release.init + +ENV RELEASE_DISTRIBUTION="name" + +# Overriden at runtime +ENV POD_IP="127.0.0.1" + +# This will be the basename of node +ENV RELEASE_NAME="spawn_operator" + +# This will be the full nodename +ENV RELEASE_NODE="${RELEASE_NAME}@${POD_IP}" + +RUN echo "-setcookie ${RELEASE_COOKIE}" >> ./rel/vm.args.eex + +RUN cd spawn_operator/spawn_operator \ + && mix deps.get \ + && mix release spawn_operator + +# ---- Application Stage ---- +FROM alpine:3.20 + +RUN apk add --no-cache --update zstd ncurses-libs libstdc++ libgcc + +WORKDIR /app +RUN chown nobody /app + +# Set runner ENV +ENV MIX_ENV=prod +ENV HOME=/app + +COPY --from=builder --chown=nobody:root /app/spawn_operator/spawn_operator/_build/${MIX_ENV}/rel/bakeware/ ./ + +RUN mkdir -p /app/.cache/bakeware/ && chmod 777 /app/.cache/bakeware/ +RUN touch /.erlang.cookie && chmod 777 /.erlang.cookie +RUN touch /app/.erlang.cookie && chmod 777 /app/.erlang.cookie + +USER nobody + +ENTRYPOINT [ "./spawn_operator", "start" ] \ No newline at end of file diff --git a/Dockerfile-proxy b/Dockerfile-proxy new file mode 100644 index 000000000..19107e10e --- /dev/null +++ b/Dockerfile-proxy @@ -0,0 +1,64 @@ +FROM elixir:1.15-alpine AS builder + +ENV MIX_ENV=prod + +WORKDIR /app + +RUN apk add --no-cache --update git build-base ca-certificates zstd gcc pkgconfig openssl-dev + +RUN mkdir config +COPY config/ ./config +COPY spawn_proxy/ ./spawn_proxy +COPY lib/ ./lib +COPY spawn_statestores/ ./spawn_statestores +COPY priv/ ./priv +COPY mix.exs . +COPY mix.lock . + +RUN mix local.rebar --force \ + && mix local.hex --force \ + && mix deps.get \ + && mix release.init + +ENV RELEASE_DISTRIBUTION="name" + +# Overriden at runtime +ENV POD_IP="127.0.0.1" + +# This will be the basename of node +ENV RELEASE_NAME="proxy" + +# This will be the full nodename +ENV RELEASE_NODE="${RELEASE_NAME}@${POD_IP}" + +#RUN echo "-setcookie ${RELEASE_COOKIE}" >> ./priv/rel/vm.args.eex + +RUN cd spawn_proxy/proxy \ + && mix deps.get \ + && mix release proxy + +# ---- Application Stage ---- +FROM alpine:3.20 + +RUN apk add --no-cache --update zstd ncurses-libs libstdc++ libgcc protobuf + +WORKDIR /app +RUN chown nobody /app + +# Set runner ENV +ENV MIX_ENV=prod +ENV HOME=/app + +COPY rel/overlays/mtls.ssl.conf . +COPY --from=builder --chown=nobody:root /app/_build/${MIX_ENV}/rel/proxy ./ + +RUN mkdir -p /app/.cache/bakeware/ && chmod 777 /app/.cache/bakeware/ +RUN mkdir -p /app/priv/generated_modules/ && chmod 777 /app/priv/generated_modules/ +RUN mkdir /data/ && chmod 777 /data/ +RUN touch /.erlang.cookie && chmod 777 /.erlang.cookie +RUN touch /app/.erlang.cookie && chmod 777 /app/.erlang.cookie + +USER nobody + +ENTRYPOINT ["/app/bin/proxy", "start"] + diff --git a/Makefile b/Makefile index aeba973f5..eb0108667 100644 --- a/Makefile +++ b/Makefile @@ -293,25 +293,6 @@ run-proxy-with-postgres: SPAWN_STATESTORE_KEY=3Jnb0hZiHIzHTOih7t2cTEPEpY98Tu1wvQkPfq/XwqE= \ iex --name spawn_a3@127.0.0.1 -S mix -run-proxy-with-mariadb: - cd spawn_proxy/proxy && mix deps.get && \ - OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=http://localhost:4317 \ - OTEL_EXPORTER_OTLP_TRACES_PROTOCOL=grpc \ - OTEL_EXPORTER_OTLP_TRACES_COMPRESSION=gzip \ - SPAWN_PROXY_LOGGER_LEVEL=info \ - PROXY_CLUSTER_STRATEGY=epmd \ - SPAWN_USE_INTERNAL_NATS=false \ - SPAWN_PUBSUB_ADAPTER=native \ - PROXY_DATABASE_PORT=3307 \ - PROXY_DATABASE_TYPE=mariadb \ - PROXY_DATABASE_USERNAME=admin \ - PROXY_DATABASE_SECRET=admin \ - PROXY_DATABASE_POOL_SIZE=30 \ - PROXY_HTTP_PORT=9001 \ - USER_FUNCTION_PORT=8090 \ - SPAWN_STATESTORE_KEY=3Jnb0hZiHIzHTOih7t2cTEPEpY98Tu1wvQkPfq/XwqE= \ - iex --name spawn_a3@127.0.0.1 -S mix - run-proxy-local-nodejs-test: ERL_ZFLAGS='-proto_dist inet_tls -ssl_dist_optfile rel/overlays/local-mtls.ssl.conf' \ cd spawn_proxy/proxy && mix deps.get && \ diff --git a/README.md b/README.md index 4ce47c116..b48fcf90e 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,6 @@ ![ci](https://github.com/eigr/spawn/actions/workflows/ci.yaml/badge.svg) [![docs-ci](https://github.com/eigr/spawn/actions/workflows/pages/pages-build-deployment/badge.svg)](https://github.com/eigr/spawn/actions/workflows/pages/pages-build-deployment) -[![OpenSSF Best Practices](https://www.bestpractices.dev/projects/10543/badge)](https://www.bestpractices.dev/projects/10543) ![last commit](https://img.shields.io/github/last-commit/eigr/spawn?style=social) [![join discord](https://badgen.net/badge/discord/Join%20Eigr%20on%20Discord/discord?icon=discord&label&color=blue)](https://discord.gg/2PcshvfS93) [![twitter](https://badgen.net/badge/twitter/@eigr_io/blue?label&icon=twitter)](https://twitter.com/eigr_io) @@ -19,192 +18,49 @@ ## Overview -**Spawn** is your actor-native service mesh for **durable, stateful computing** β€” **polyglot** by design, **protocol-agnostic** by nature. -**Write once, run over all protocols:** Erlang-native, gRPC, or HTTP with automatic transcoding. One implementation, accessible everywhere. No boilerplate, no glue code β€” just clean actor abstractions that speak all your transports fluently. +Spawn is an actor-based service mesh that abstracts away infrastructure complexities while offering a powerful, polyglot API that accelerates business development. Built by contributors to [Cloudstate](https://github.com/cloudstateio/cloudstate), Spawn simplifies stateful application design for cloud or on-premises environments. -Spawn simplifies the hardest part of distributed systems β€” **managing state at scale** β€” by wrapping it in an actor model that's native to modern protocols and tooling. Built by contributors to [Cloudstate](https://github.com/cloudstateio/cloudstate), it brings durable computing to your favorite language: Elixir, Java, TypeScript, Python, Rust, Go, Dart... you name it. +With support for multiple programming languages, Spawn empowers teams to work faster and smarter, using the best tools for the job. Whether you're scaling up or refining internal processes, Spawn’s architecture allows you to focus on what matters: delivering value to your business. -With Spawn, you stop worrying about infrastructure glue and start delivering business value faster. - -## Why You'll Love Spawn ❀️ - -* **Durable Computing via Actors:** Your business logic is always-on and stateful, with snapshotting, passivation, and automatic recovery baked in. - -* **Write Once, Run With All Protocols:** -Define your service once using Protobuf annotations: - - βœ… Native Erlang invocation. - - βœ… gRPC for high-performance APIs. - - βœ… HTTP transcoding for REST endpoints. - -* **Polyglot by Design:** -Code in Elixir, Go, Java, TypeScript, Python, Rust, Lua and more. Spawn speaks your language β€” literally. Use the best tool for each job without sacrificing interoperability. - -* **Workflows Made Easy:** -Spawn makes orchestrating complex business processes a breeze. Compose actions using **Side-effects**, chain logic with **Pipes**, or forward and routing requests seamlessly with **Forwards** β€” all first-class citizens in the Spawn model. - -* **Projection Actors:** -Need materialized views? Spawn supports **Projection Actors** β€” dedicated actors that derive and maintain views of your system state in real-time. Perfect for read optimization, analytics, or integrating with external systems. - -* **Seamless Scalability:** -Spawn’s actor model naturally scales to match your system’s needs, from on-premises deployments to massive cloud clusters. High availability and fault-tolerance come built-in. - -* **Simplified Infrastructure:** -Forget about wiring up complex distributed systems or state management libraries. Spawn abstracts the hard parts, so you can focus on building features that deliver real value. - -* **First-Class Observability:** -Out of the box, Spawn provides hooks for **metrics**, **tracing**, and **logging**. Gain full visibility into your actors and workflows to monitor health, diagnose issues, and optimize performance at scale. - -Example? Here you go: - -```proto -syntax = "proto3"; - -import "google/api/annotations.proto"; -import "spawn/actors/extensions.proto"; - -package example.actors; - -service ExampleActor { - option (spawn.actors.actor) = { - kind: NAMED - stateful: true - state_type: ".example.ExampleState" - snapshot_interval: 60000 //optional - deactivate_timeout: 3000 //optional - }; - - rpc Sum(.example.ValuePayload) returns (.example.SumResponse) { - option (google.api.http) = { - post: "/v1/example/sum" - body: "*" - }; - } -} -``` - -**Implement once, access everywhere.** - ---- - -Example Implementation (Elixir): - -```elixir -defmodule SpawnSdkExample.Actors.ExampleActor do - use SpawnSdk.Actor, name: "ExampleActor" - alias Example.ExampleState - alias Example.ValuePayload - alias Example.SumResponse - - @doc """ - Invoke with: - - alias Example.Actors.ExampleActor - - ExampleActor.sum(%Example.ValuePayload{value: 10}) - """ - action("Sum", fn %Context{state: state} = ctx, %ValuePayload{value: value} = data -> - new_value = state.value + value - - Value.of() - |> Value.state(%ExampleState{value: new_value}) - |> Value.response(%SumResponse{value: new_value}) - end) -end -``` - -**Now you can call it from multiple ways. Like from Java:** - -```java -package io.eigr.spawn.java.demo; - -import io.eigr.spawn.api.Spawn; -// other imports ommited - -public class Main { - public static void main(String[] args) { - Spawn spawnSystem = new Spawn.SpawnSystem() - .create("spawn-system") - .build(); - - spawnSystem.start(); - - // Create a reference to the actor "ExampleActor" - ActorRef exampleActor = spawnSystem.createActorRef( - ActorIdentity.of("spawn-system", "ExampleActor") - ); - - // Prepare the input message - ValuePayload payload = ValuePayload.newBuilder() - .setValue(10) - .build(); - - // Invoke the "Sum" action - Optional maybeResponse = exampleActor.invoke( - "Sum", - payload, - SumResponse.class - ); - - // Handle the response - if (maybeResponse.isPresent()) { - SumResponse response = maybeResponse.get(); - System.out.println("Sum result: " + response.getValue()); - } else { - System.out.println("No response received from the actor."); - } - } -} -``` - -**Or invoke directly with grpc** -```bash -grpcurl -d '{"value":10}' \ - -plaintext localhost:9000 \ - example.actors.ExampleActor/Sum -``` - -**Or also via HTTP/JSON transcoding with any client http** -```bash -curl -X POST http://localhost:9000/v1/example/sum \ - -H "Content-Type: application/json" \ - -d '{"value": 10}' - -``` +Explore how Spawn can help you meet your business objectives efficiently. Check out our [documentation](docs/index.md) and [installation guide](docs/install.md) to [get started](docs/getting_started.md). -**One contract β†’ multiple protocols β†’ instant productivity.** +## Why Spawn? ---- +- **Simplified Infrastructure:** Spawn abstracts the complex backend architecture so developers can focus on writing business logic, not managing infrastructure. +- **Stateful Computing Made Easy:** Spawn handles state management natively, allowing your applications to be both scalable and resilient across distributed systems. +- **Polyglot Flexibility:** Build in the language of your choice, from Elixir to Java to TypeScript and others, all within the same ecosystem. +- **Seamless Scalability:** Whether you’re deploying in the cloud or on-premises, Spawn grows with your business, ensuring high availability and performance. +- **Designed for Business Needs:** Optimize productivity by streamlining your development process with a runtime that enables rapid time-to-market for critical features. -## Built For Polyglot Teams 🌍 +## How Spawn Empowers Your Business -Spawn lets your team pick the right language for each service β€” Elixir, Java, TypeScript, Rust, Python β€” without reinventing the wheel. Actors are defined once and consumed seamlessly across your stack. +By abstracting infrastructure, Spawn lets engineers focus on what truly drives growthβ€”building features that matter. With automated state management and simplified configuration, Spawn accelerates development cycles and enhances team efficiency. ---- +For technical leaders, Spawn provides the confidence to meet business goals more efficiently, enhancing competitiveness without compromising on scalability or technical robustness. -## Infrastructure? Boring. Let Spawn Handle That. πŸͺ„ +## Latest Blogs -Forget about Kubernetes YAML hell, service meshes, and complex orchestration. Spawn abstracts all of that β€” actors get scheduled, state gets persisted, protocols get exposed β€” automatically with just one manifest. +* https://eigr.io/blog/spawn-the-actor-mesh/ -Focus on what matters: **business logic.** +* https://eigr.io/blog/beyond-monoliths-and-microservices/ -## How Spawn Empowers Your Team πŸš€ +* https://eigr.io/blog/distributed-elixir-made-easy-with-spawn/ -βœ… Accelerate development with consistent patterns. -βœ… Scale easily across cloud and on-premises. +## Engaging Talks -βœ… Run resilient, stateful apps out-of-the-box. +Watch some insightful talks on Eigr Community and Spawn: -βœ… Embrace polyglot architectures without friction. +- **Marcel Lanz on ACM SIGPLAN - Erlang 2021 - Lightning Talk**: https://www.youtube.com/watch?v=jVf0QqNb3qk _(English)_ +- **Marcel Lanz on Code Beam Europe 2022**: https://youtu.be/jgR7Oc_GXAg _(English)_ +- **Adriano Santos on Code Beam BR 2022**: https://www.youtube.com/watch?v=dXp0lyfmV_0&list=PLa5zLmv3pgCCiyWq8gltVAt2vTGuHz3eG&index=3 _(Portuguese)_ +- **Adriano Santos ElugSP 2023**: https://www.youtube.com/watch?v=MKTqiAtpK1E _(Portuguese)_ +- **Elias Arruda on Elixir CWB (Curitiba)**: https://www.youtube.com/live/yE_uWbPEWnI?si=5L3SORG3PERZpQ4V&t=463 _(Portuguese)_ -βœ… Deploy with confidence, backed by a robust runtime. +- **Adriano Santos NodeBR on Microsoft Reactor - API's Like a Boss**: https://www.youtube.com/live/ZXJJ3BdgVBk?si=Ai0kfBrVTl6V7toT&t=373 _(Portuguese)_ ---- -## Meet Sepp: Our Cyberpunk Mascot 🦌 +## Meet Sepp: Our Cyberpunk Mascot Say hello to Sepp, our cyberpunk mascot and proud [Ibex](https://alpshiking.swisshikingvacations.com/spotlight-on-the-ibex/). Sepp, hailing from the Swiss Alps, loves wandering through the Eiger mountains. Despite occasional Viking-like bluntness, he's a helpful companion who traces his lineage back to Viking times. @@ -215,34 +71,4 @@ This is our little tribute to one of the creators of the Erlang programming lang ![Sepp Rules](docs/images/sepp-rules-254-400.png#gh-light-mode-only) ![Sepp Rules](docs/images/sepp-rules-254-400.png#gh-dark-mode-only) ---- - -## Dive Deeper - -### πŸ“ Latest blogs: - -* [Spawn: The Actor Mesh](https://eigr.io/blog/spawn-the-actor-mesh/) - -* [Beyond Monoliths and Microservices](https://eigr.io/blog/beyond-monoliths-and-microservices/) - -* [Distributed Elixir Made Easy With Spawn](https://eigr.io/blog/distributed-elixir-made-easy-with-spawn/) - -### πŸŽ™οΈ Talks: - -* [Marcel Lanz at ACM SIGPLAN - Erlang 2021](https://www.youtube.com/watch?v=jVf0QqNb3qk) - -* [Marcel Lanz at Code Beam Europe 2022](https://youtu.be/jgR7Oc_GXAg) - -* [Adriano Santos at Code Beam BR 2022](https://www.youtube.com/watch?v=dXp0lyfmV_0) - -* [Adriano Santos at ElugSP 2023](https://www.youtube.com/watch?v=MKTqiAtpK1E) - -* [Elias Arruda at Elixir CWB](https://www.youtube.com/live/yE_uWbPEWnI?si=5L3SORG3PERZpQ4V&t=463) - -* [Adriano Santos at NodeBR - API's Like a Boss](https://www.youtube.com/live/ZXJJ3BdgVBk?si=Ai0kfBrVTl6V7toT&t=373) - ---- - -Explore how Spawn can help you meet your business objectives efficiently. Check out our [documentation](docs/index.md) and [installation guide](docs/install.md) to [get started](docs/getting_started.md). - Write less code and be happy! πŸŒπŸš€ diff --git a/config/config.exs b/config/config.exs index 08886a793..2b4b07a83 100644 --- a/config/config.exs +++ b/config/config.exs @@ -59,9 +59,4 @@ config :spawn, Spawn.Cache.LookupCache, gc_cleanup_min_timeout: :timer.seconds(60), gc_cleanup_max_timeout: :timer.minutes(10) -config :mnesiac, - stores: [Statestores.Adapters.Native.SnapshotStore], - schema_type: :disc_copies, - table_load_timeout: 600_000 - import_config "#{config_env()}.exs" diff --git a/config/test.exs b/config/test.exs index beef3027c..0e7d3dfac 100644 --- a/config/test.exs +++ b/config/test.exs @@ -1,19 +1,3 @@ import Config -config :spawn_statestores, Statestores.Adapters.MariaDBSnapshotAdapter, - pool: Ecto.Adapters.SQL.Sandbox, - ownership_timeout: :infinity, - pool_size: 24, - prepare: :unnamed, - queue_target: 5_000, - queue_interval: 500 - -config :spawn_statestores, Statestores.Adapters.MariaDBProjectionAdapter, - pool: Ecto.Adapters.SQL.Sandbox, - ownership_timeout: :infinity, - pool_size: 24, - prepare: :unnamed, - queue_target: 5_000, - queue_interval: 500 - config :spawn, http_node_client: NodeClientMock diff --git a/docker-compose.yaml b/docker-compose.yaml new file mode 100644 index 000000000..533d51799 --- /dev/null +++ b/docker-compose.yaml @@ -0,0 +1,102 @@ +version: "3.8" + +services: + otel: + image: otel/opentelemetry-collector-contrib:0.60.0 + command: [ "--config=/conf/otel-collector-config.yaml" ] + privileged: true + ports: + - 4317:4317 + - 4318:4318 + - 55681:55681 + volumes: + - ./config/otel-collector-config.yaml:/conf/otel-collector-config.yaml + links: + - zipkin + + zipkin: + image: openzipkin/zipkin-slim + ports: + - 9411:9411 + privileged: true + + postgres: + image: postgres + restart: always + environment: + - POSTGRES_USER=postgres + - POSTGRES_PASSWORD=postgres + - POSTGRES_DATABASE=eigr-functions-db + - PGDATA=/var/lib/postgresql/data/pgdata + ports: + - "5432:5432" + volumes: + - postgres:/var/lib/postgresql/data + + mysql: + image: mysql:5.7 + environment: + MYSQL_USER: admin + MYSQL_PASSWORD: "admin" + MYSQL_ROOT_PASSWORD: "admin" + MYSQL_DATABASE: "eigr-functions-db" + ports: + - "3306:3306" + volumes: + - mysql:/var/lib/mysql + networks: + - mysql-compose-network + + mariadb: + image: mariadb + environment: + MYSQL_ROOT_PASSWORD: admin + MYSQL_DATABASE: eigr-functions-db + MYSQL_USER: admin + MYSQL_PASSWORD: admin + volumes: + - mariadb:/var/lib/mysql + ports: + - "3307:3306" + networks: + - mysql-compose-network + command: ["--max_connections=1000"] + + adminer: + image: adminer + ports: + - 8080:8080 + networks: + - mysql-compose-network + + nats: + image: 'nats:latest' + ports: + - "4222:4222" + # spawn-proxy: + # image: eigr/spawn-proxy:2.0.0-RC9 + # restart: always + # environment: + # PROXY_APP_NAME: spawn + # PROXY_HTTP_PORT: 9001 + # PROXY_DATABASE_TYPE: postgres + # PROXY_DATABASE_NAME: eigr-functions-db + # PROXY_DATABASE_USERNAME: postgres + # PROXY_DATABASE_SECRET: password + # PROXY_DATABASE_HOST: localhost + # PROXY_DATABASE_PORT: 5432 + # SPAWN_STATESTORE_KEY: 3Jnb0hZiHIzHTOih7t2cTEPEpY98Tu1wvQkPfq/XwqE= + # USER_FUNCTION_HOST: 0.0.0.0 # Your NodeJS runtime host + # USER_FUNCTION_PORT: 8090 # Your NodeJS runtime exposed port + # # network_mode: host # only uncomment this if you're running your nodejs locally in Linux, check note below for Windows + # ports: + # - "9001:9001" + +networks: + mysql-compose-network: + driver: bridge + +volumes: + mysql: + postgres: + mariadb: diff --git a/k3d-cluster-create.txt b/k3d-cluster-create.txt new file mode 100644 index 000000000..e032d9882 --- /dev/null +++ b/k3d-cluster-create.txt @@ -0,0 +1 @@ +k3d cluster create eigr-spawn --agents 3 \ No newline at end of file diff --git a/kind-1000.yaml b/kind-1000.yaml new file mode 100644 index 000000000..be7c7833e --- /dev/null +++ b/kind-1000.yaml @@ -0,0 +1,10 @@ +kind: Cluster +apiVersion: kind.x-k8s.io/v1alpha4 +nodes: + - role: control-plane + kubeadmConfigPatches: + - | + kind: InitConfiguration + nodeRegistration: + kubeletExtraArgs: + max-pods: "1000" diff --git a/kind-cluster-config.yaml b/kind-cluster-config.yaml new file mode 100644 index 000000000..7ded7385b --- /dev/null +++ b/kind-cluster-config.yaml @@ -0,0 +1,10 @@ +kind: Cluster +apiVersion: kind.x-k8s.io/v1alpha4 +name: eigr-spawn +featureGates: + ImageVolume: true +nodes: + - role: control-plane + image: kindest/node:v1.31.4@sha256:2cb39f7295fe7eafee0842b1052a599a4fb0f8bcf3f83d96c7f4864c357c6c30 + - role: worker + image: kindest/node:v1.31.4@sha256:2cb39f7295fe7eafee0842b1052a599a4fb0f8bcf3f83d96c7f4864c357c6c30 diff --git a/lib/_generated/io/cloudevents/v1/spec.pb.ex b/lib/_generated/io/cloudevents/v1/spec.pb.ex index 4bfef8517..6d5c5dd8a 100644 --- a/lib/_generated/io/cloudevents/v1/spec.pb.ex +++ b/lib/_generated/io/cloudevents/v1/spec.pb.ex @@ -1,6 +1,6 @@ defmodule Io.Cloudevents.V1.CloudEvent.AttributesEntry do @moduledoc false - use Protobuf, map: true, protoc_gen_elixir_version: "0.15.0", syntax: :proto3 + use Protobuf, map: true, protoc_gen_elixir_version: "0.14.0", syntax: :proto3 def descriptor do # credo:disable-for-next-line @@ -64,7 +64,7 @@ end defmodule Io.Cloudevents.V1.CloudEvent.CloudEventAttributeValue do @moduledoc false - use Protobuf, protoc_gen_elixir_version: "0.15.0", syntax: :proto3 + use Protobuf, protoc_gen_elixir_version: "0.14.0", syntax: :proto3 def descriptor do # credo:disable-for-next-line @@ -197,7 +197,7 @@ end defmodule Io.Cloudevents.V1.CloudEvent do @moduledoc false - use Protobuf, protoc_gen_elixir_version: "0.15.0", syntax: :proto3 + use Protobuf, protoc_gen_elixir_version: "0.14.0", syntax: :proto3 def descriptor do # credo:disable-for-next-line diff --git a/lib/_generated/spawn/actors/actor.pb.ex b/lib/_generated/spawn/actors/actor.pb.ex index 813379297..b470a1c6a 100644 --- a/lib/_generated/spawn/actors/actor.pb.ex +++ b/lib/_generated/spawn/actors/actor.pb.ex @@ -1,6 +1,6 @@ defmodule Spawn.Actors.Kind do @moduledoc false - use Protobuf, enum: true, protoc_gen_elixir_version: "0.15.0", syntax: :proto3 + use Protobuf, enum: true, protoc_gen_elixir_version: "0.14.0", syntax: :proto3 def descriptor do # credo:disable-for-next-line @@ -68,7 +68,7 @@ end defmodule Spawn.Actors.Registry.ActorsEntry do @moduledoc false - use Protobuf, map: true, protoc_gen_elixir_version: "0.15.0", syntax: :proto3 + use Protobuf, map: true, protoc_gen_elixir_version: "0.14.0", syntax: :proto3 def descriptor do # credo:disable-for-next-line @@ -132,7 +132,7 @@ end defmodule Spawn.Actors.Registry do @moduledoc false - use Protobuf, protoc_gen_elixir_version: "0.15.0", syntax: :proto3 + use Protobuf, protoc_gen_elixir_version: "0.14.0", syntax: :proto3 def descriptor do # credo:disable-for-next-line @@ -224,7 +224,7 @@ end defmodule Spawn.Actors.ActorSystem do @moduledoc false - use Protobuf, protoc_gen_elixir_version: "0.15.0", syntax: :proto3 + use Protobuf, protoc_gen_elixir_version: "0.14.0", syntax: :proto3 def descriptor do # credo:disable-for-next-line @@ -278,7 +278,7 @@ end defmodule Spawn.Actors.ActorSnapshotStrategy do @moduledoc false - use Protobuf, protoc_gen_elixir_version: "0.15.0", syntax: :proto3 + use Protobuf, protoc_gen_elixir_version: "0.14.0", syntax: :proto3 def descriptor do # credo:disable-for-next-line @@ -325,7 +325,7 @@ end defmodule Spawn.Actors.ActorDeactivationStrategy do @moduledoc false - use Protobuf, protoc_gen_elixir_version: "0.15.0", syntax: :proto3 + use Protobuf, protoc_gen_elixir_version: "0.14.0", syntax: :proto3 def descriptor do # credo:disable-for-next-line @@ -372,7 +372,7 @@ end defmodule Spawn.Actors.TimeoutStrategy do @moduledoc false - use Protobuf, protoc_gen_elixir_version: "0.15.0", syntax: :proto3 + use Protobuf, protoc_gen_elixir_version: "0.14.0", syntax: :proto3 def descriptor do # credo:disable-for-next-line @@ -411,7 +411,7 @@ end defmodule Spawn.Actors.Action do @moduledoc false - use Protobuf, protoc_gen_elixir_version: "0.15.0", syntax: :proto3 + use Protobuf, protoc_gen_elixir_version: "0.14.0", syntax: :proto3 def descriptor do # credo:disable-for-next-line @@ -450,7 +450,7 @@ end defmodule Spawn.Actors.FixedTimerAction do @moduledoc false - use Protobuf, protoc_gen_elixir_version: "0.15.0", syntax: :proto3 + use Protobuf, protoc_gen_elixir_version: "0.14.0", syntax: :proto3 def descriptor do # credo:disable-for-next-line @@ -504,7 +504,7 @@ end defmodule Spawn.Actors.ActorState.TagsEntry do @moduledoc false - use Protobuf, map: true, protoc_gen_elixir_version: "0.15.0", syntax: :proto3 + use Protobuf, map: true, protoc_gen_elixir_version: "0.14.0", syntax: :proto3 def descriptor do # credo:disable-for-next-line @@ -568,7 +568,7 @@ end defmodule Spawn.Actors.ActorState do @moduledoc false - use Protobuf, protoc_gen_elixir_version: "0.15.0", syntax: :proto3 + use Protobuf, protoc_gen_elixir_version: "0.14.0", syntax: :proto3 def descriptor do # credo:disable-for-next-line @@ -675,7 +675,7 @@ end defmodule Spawn.Actors.Metadata.TagsEntry do @moduledoc false - use Protobuf, map: true, protoc_gen_elixir_version: "0.15.0", syntax: :proto3 + use Protobuf, map: true, protoc_gen_elixir_version: "0.14.0", syntax: :proto3 def descriptor do # credo:disable-for-next-line @@ -739,7 +739,7 @@ end defmodule Spawn.Actors.Metadata do @moduledoc false - use Protobuf, protoc_gen_elixir_version: "0.15.0", syntax: :proto3 + use Protobuf, protoc_gen_elixir_version: "0.14.0", syntax: :proto3 def descriptor do # credo:disable-for-next-line @@ -846,7 +846,7 @@ end defmodule Spawn.Actors.Channel do @moduledoc false - use Protobuf, protoc_gen_elixir_version: "0.15.0", syntax: :proto3 + use Protobuf, protoc_gen_elixir_version: "0.14.0", syntax: :proto3 def descriptor do # credo:disable-for-next-line @@ -900,7 +900,7 @@ end defmodule Spawn.Actors.ProjectionSubject do @moduledoc false - use Protobuf, protoc_gen_elixir_version: "0.15.0", syntax: :proto3 + use Protobuf, protoc_gen_elixir_version: "0.14.0", syntax: :proto3 def descriptor do # credo:disable-for-next-line @@ -984,7 +984,7 @@ end defmodule Spawn.Actors.EventsRetentionStrategy do @moduledoc false - use Protobuf, protoc_gen_elixir_version: "0.15.0", syntax: :proto3 + use Protobuf, protoc_gen_elixir_version: "0.14.0", syntax: :proto3 def descriptor do # credo:disable-for-next-line @@ -1046,7 +1046,7 @@ end defmodule Spawn.Actors.ProjectionSettings do @moduledoc false - use Protobuf, protoc_gen_elixir_version: "0.15.0", syntax: :proto3 + use Protobuf, protoc_gen_elixir_version: "0.14.0", syntax: :proto3 def descriptor do # credo:disable-for-next-line @@ -1135,7 +1135,7 @@ end defmodule Spawn.Actors.ActorSettings do @moduledoc false - use Protobuf, protoc_gen_elixir_version: "0.15.0", syntax: :proto3 + use Protobuf, protoc_gen_elixir_version: "0.14.0", syntax: :proto3 def descriptor do # credo:disable-for-next-line @@ -1293,7 +1293,7 @@ end defmodule Spawn.Actors.ActorId do @moduledoc false - use Protobuf, protoc_gen_elixir_version: "0.15.0", syntax: :proto3 + use Protobuf, protoc_gen_elixir_version: "0.14.0", syntax: :proto3 def descriptor do # credo:disable-for-next-line @@ -1362,7 +1362,7 @@ end defmodule Spawn.Actors.Actor do @moduledoc false - use Protobuf, protoc_gen_elixir_version: "0.15.0", syntax: :proto3 + use Protobuf, protoc_gen_elixir_version: "0.14.0", syntax: :proto3 def descriptor do # credo:disable-for-next-line diff --git a/lib/_generated/spawn/actors/extensions.pb.ex b/lib/_generated/spawn/actors/extensions.pb.ex index 15bd019b1..af4fe371c 100644 --- a/lib/_generated/spawn/actors/extensions.pb.ex +++ b/lib/_generated/spawn/actors/extensions.pb.ex @@ -1,6 +1,6 @@ defmodule Spawn.Actors.PbExtension do @moduledoc false - use Protobuf, protoc_gen_elixir_version: "0.15.0", syntax: :proto3 + use Protobuf, protoc_gen_elixir_version: "0.14.0", syntax: :proto3 extend(Google.Protobuf.FieldOptions, :actor_id, 9999, optional: true, @@ -23,7 +23,7 @@ end defmodule Spawn.Actors.ActorOpts do @moduledoc false - use Protobuf, protoc_gen_elixir_version: "0.15.0", syntax: :proto3 + use Protobuf, protoc_gen_elixir_version: "0.14.0", syntax: :proto3 def descriptor do # credo:disable-for-next-line @@ -187,7 +187,7 @@ end defmodule Spawn.Actors.ActorViewOption do @moduledoc false - use Protobuf, protoc_gen_elixir_version: "0.15.0", syntax: :proto3 + use Protobuf, protoc_gen_elixir_version: "0.14.0", syntax: :proto3 def descriptor do # credo:disable-for-next-line diff --git a/lib/_generated/spawn/actors/healthcheck.pb.ex b/lib/_generated/spawn/actors/healthcheck.pb.ex index 56622b938..4fdd29423 100644 --- a/lib/_generated/spawn/actors/healthcheck.pb.ex +++ b/lib/_generated/spawn/actors/healthcheck.pb.ex @@ -1,6 +1,6 @@ defmodule Spawn.Actors.Healthcheck.Status do @moduledoc false - use Protobuf, protoc_gen_elixir_version: "0.15.0", syntax: :proto3 + use Protobuf, protoc_gen_elixir_version: "0.14.0", syntax: :proto3 def descriptor do # credo:disable-for-next-line @@ -69,7 +69,7 @@ end defmodule Spawn.Actors.Healthcheck.HealthCheckReply do @moduledoc false - use Protobuf, protoc_gen_elixir_version: "0.15.0", syntax: :proto3 + use Protobuf, protoc_gen_elixir_version: "0.14.0", syntax: :proto3 def descriptor do # credo:disable-for-next-line @@ -109,7 +109,7 @@ end defmodule Spawn.Actors.Healthcheck.HealthCheckActor.Service do use GRPC.Service, name: "spawn.actors.healthcheck.HealthCheckActor", - protoc_gen_elixir_version: "0.15.0" + protoc_gen_elixir_version: "0.14.0" def descriptor do # credo:disable-for-next-line diff --git a/lib/_generated/spawn/actors/protocol.pb.ex b/lib/_generated/spawn/actors/protocol.pb.ex index 566c83db7..f61b23752 100644 --- a/lib/_generated/spawn/actors/protocol.pb.ex +++ b/lib/_generated/spawn/actors/protocol.pb.ex @@ -1,6 +1,6 @@ defmodule Spawn.Status do @moduledoc false - use Protobuf, enum: true, protoc_gen_elixir_version: "0.15.0", syntax: :proto3 + use Protobuf, enum: true, protoc_gen_elixir_version: "0.14.0", syntax: :proto3 def descriptor do # credo:disable-for-next-line @@ -47,7 +47,7 @@ end defmodule Spawn.Context.MetadataEntry do @moduledoc false - use Protobuf, map: true, protoc_gen_elixir_version: "0.15.0", syntax: :proto3 + use Protobuf, map: true, protoc_gen_elixir_version: "0.14.0", syntax: :proto3 def descriptor do # credo:disable-for-next-line @@ -111,7 +111,7 @@ end defmodule Spawn.Context.TagsEntry do @moduledoc false - use Protobuf, map: true, protoc_gen_elixir_version: "0.15.0", syntax: :proto3 + use Protobuf, map: true, protoc_gen_elixir_version: "0.14.0", syntax: :proto3 def descriptor do # credo:disable-for-next-line @@ -175,7 +175,7 @@ end defmodule Spawn.Context do @moduledoc false - use Protobuf, protoc_gen_elixir_version: "0.15.0", syntax: :proto3 + use Protobuf, protoc_gen_elixir_version: "0.14.0", syntax: :proto3 def descriptor do # credo:disable-for-next-line @@ -379,7 +379,7 @@ end defmodule Spawn.Noop do @moduledoc false - use Protobuf, protoc_gen_elixir_version: "0.15.0", syntax: :proto3 + use Protobuf, protoc_gen_elixir_version: "0.14.0", syntax: :proto3 def descriptor do # credo:disable-for-next-line @@ -401,7 +401,7 @@ end defmodule Spawn.JSONType do @moduledoc false - use Protobuf, protoc_gen_elixir_version: "0.15.0", syntax: :proto3 + use Protobuf, protoc_gen_elixir_version: "0.14.0", syntax: :proto3 def descriptor do # credo:disable-for-next-line @@ -440,7 +440,7 @@ end defmodule Spawn.RegistrationRequest do @moduledoc false - use Protobuf, protoc_gen_elixir_version: "0.15.0", syntax: :proto3 + use Protobuf, protoc_gen_elixir_version: "0.14.0", syntax: :proto3 def descriptor do # credo:disable-for-next-line @@ -494,7 +494,7 @@ end defmodule Spawn.RegistrationResponse do @moduledoc false - use Protobuf, protoc_gen_elixir_version: "0.15.0", syntax: :proto3 + use Protobuf, protoc_gen_elixir_version: "0.14.0", syntax: :proto3 def descriptor do # credo:disable-for-next-line @@ -548,7 +548,7 @@ end defmodule Spawn.ServiceInfo do @moduledoc false - use Protobuf, protoc_gen_elixir_version: "0.15.0", syntax: :proto3 + use Protobuf, protoc_gen_elixir_version: "0.14.0", syntax: :proto3 def descriptor do # credo:disable-for-next-line @@ -677,7 +677,7 @@ end defmodule Spawn.SpawnRequest do @moduledoc false - use Protobuf, protoc_gen_elixir_version: "0.15.0", syntax: :proto3 + use Protobuf, protoc_gen_elixir_version: "0.14.0", syntax: :proto3 def descriptor do # credo:disable-for-next-line @@ -716,7 +716,7 @@ end defmodule Spawn.SpawnResponse do @moduledoc false - use Protobuf, protoc_gen_elixir_version: "0.15.0", syntax: :proto3 + use Protobuf, protoc_gen_elixir_version: "0.14.0", syntax: :proto3 def descriptor do # credo:disable-for-next-line @@ -755,7 +755,7 @@ end defmodule Spawn.ProxyInfo do @moduledoc false - use Protobuf, protoc_gen_elixir_version: "0.15.0", syntax: :proto3 + use Protobuf, protoc_gen_elixir_version: "0.14.0", syntax: :proto3 def descriptor do # credo:disable-for-next-line @@ -839,7 +839,7 @@ end defmodule Spawn.SideEffect do @moduledoc false - use Protobuf, protoc_gen_elixir_version: "0.15.0", syntax: :proto3 + use Protobuf, protoc_gen_elixir_version: "0.14.0", syntax: :proto3 def descriptor do # credo:disable-for-next-line @@ -878,7 +878,7 @@ end defmodule Spawn.Broadcast do @moduledoc false - use Protobuf, protoc_gen_elixir_version: "0.15.0", syntax: :proto3 + use Protobuf, protoc_gen_elixir_version: "0.14.0", syntax: :proto3 def descriptor do # credo:disable-for-next-line @@ -955,7 +955,7 @@ end defmodule Spawn.Pipe do @moduledoc false - use Protobuf, protoc_gen_elixir_version: "0.15.0", syntax: :proto3 + use Protobuf, protoc_gen_elixir_version: "0.14.0", syntax: :proto3 def descriptor do # credo:disable-for-next-line @@ -1024,7 +1024,7 @@ end defmodule Spawn.Forward do @moduledoc false - use Protobuf, protoc_gen_elixir_version: "0.15.0", syntax: :proto3 + use Protobuf, protoc_gen_elixir_version: "0.14.0", syntax: :proto3 def descriptor do # credo:disable-for-next-line @@ -1093,7 +1093,7 @@ end defmodule Spawn.Fact.MetadataEntry do @moduledoc false - use Protobuf, map: true, protoc_gen_elixir_version: "0.15.0", syntax: :proto3 + use Protobuf, map: true, protoc_gen_elixir_version: "0.14.0", syntax: :proto3 def descriptor do # credo:disable-for-next-line @@ -1157,7 +1157,7 @@ end defmodule Spawn.Fact do @moduledoc false - use Protobuf, protoc_gen_elixir_version: "0.15.0", syntax: :proto3 + use Protobuf, protoc_gen_elixir_version: "0.14.0", syntax: :proto3 def descriptor do # credo:disable-for-next-line @@ -1294,7 +1294,7 @@ end defmodule Spawn.Workflow do @moduledoc false - use Protobuf, protoc_gen_elixir_version: "0.15.0", syntax: :proto3 + use Protobuf, protoc_gen_elixir_version: "0.14.0", syntax: :proto3 def descriptor do # credo:disable-for-next-line @@ -1386,7 +1386,7 @@ end defmodule Spawn.InvocationRequest.MetadataEntry do @moduledoc false - use Protobuf, map: true, protoc_gen_elixir_version: "0.15.0", syntax: :proto3 + use Protobuf, map: true, protoc_gen_elixir_version: "0.14.0", syntax: :proto3 def descriptor do # credo:disable-for-next-line @@ -1450,7 +1450,7 @@ end defmodule Spawn.InvocationRequest do @moduledoc false - use Protobuf, protoc_gen_elixir_version: "0.15.0", syntax: :proto3 + use Protobuf, protoc_gen_elixir_version: "0.14.0", syntax: :proto3 def descriptor do # credo:disable-for-next-line @@ -1700,7 +1700,7 @@ end defmodule Spawn.ActorInvocation do @moduledoc false - use Protobuf, protoc_gen_elixir_version: "0.15.0", syntax: :proto3 + use Protobuf, protoc_gen_elixir_version: "0.14.0", syntax: :proto3 def descriptor do # credo:disable-for-next-line @@ -1822,7 +1822,7 @@ end defmodule Spawn.ActorInvocationResponse do @moduledoc false - use Protobuf, protoc_gen_elixir_version: "0.15.0", syntax: :proto3 + use Protobuf, protoc_gen_elixir_version: "0.14.0", syntax: :proto3 def descriptor do # credo:disable-for-next-line @@ -1959,7 +1959,7 @@ end defmodule Spawn.InvocationResponse do @moduledoc false - use Protobuf, protoc_gen_elixir_version: "0.15.0", syntax: :proto3 + use Protobuf, protoc_gen_elixir_version: "0.14.0", syntax: :proto3 def descriptor do # credo:disable-for-next-line @@ -2066,7 +2066,7 @@ end defmodule Spawn.RequestStatus do @moduledoc false - use Protobuf, protoc_gen_elixir_version: "0.15.0", syntax: :proto3 + use Protobuf, protoc_gen_elixir_version: "0.14.0", syntax: :proto3 def descriptor do # credo:disable-for-next-line diff --git a/lib/_generated/spawn/actors/state.pb.ex b/lib/_generated/spawn/actors/state.pb.ex index 71b1dbb22..9c0cc77a3 100644 --- a/lib/_generated/spawn/actors/state.pb.ex +++ b/lib/_generated/spawn/actors/state.pb.ex @@ -1,6 +1,6 @@ defmodule Spawn.State.Revision do @moduledoc false - use Protobuf, protoc_gen_elixir_version: "0.15.0", syntax: :proto3 + use Protobuf, protoc_gen_elixir_version: "0.14.0", syntax: :proto3 def descriptor do # credo:disable-for-next-line @@ -39,7 +39,7 @@ end defmodule Spawn.State.Checkpoint do @moduledoc false - use Protobuf, protoc_gen_elixir_version: "0.15.0", syntax: :proto3 + use Protobuf, protoc_gen_elixir_version: "0.14.0", syntax: :proto3 def descriptor do # credo:disable-for-next-line diff --git a/lib/_generated/spawn/actors/test.pb.ex b/lib/_generated/spawn/actors/test.pb.ex index ae7de14aa..b2b4718ea 100644 --- a/lib/_generated/spawn/actors/test.pb.ex +++ b/lib/_generated/spawn/actors/test.pb.ex @@ -1,6 +1,6 @@ defmodule Test.EnumTest do @moduledoc false - use Protobuf, enum: true, protoc_gen_elixir_version: "0.15.0", syntax: :proto3 + use Protobuf, enum: true, protoc_gen_elixir_version: "0.14.0", syntax: :proto3 def descriptor do # credo:disable-for-next-line @@ -40,7 +40,7 @@ end defmodule Test.TestMessage.Address.Country do @moduledoc false - use Protobuf, protoc_gen_elixir_version: "0.15.0", syntax: :proto3 + use Protobuf, protoc_gen_elixir_version: "0.14.0", syntax: :proto3 def descriptor do # credo:disable-for-next-line @@ -94,7 +94,7 @@ end defmodule Test.TestMessage.Address do @moduledoc false - use Protobuf, protoc_gen_elixir_version: "0.15.0", syntax: :proto3 + use Protobuf, protoc_gen_elixir_version: "0.14.0", syntax: :proto3 def descriptor do # credo:disable-for-next-line @@ -236,7 +236,7 @@ end defmodule Test.TestMessage.AttributesEntry do @moduledoc false - use Protobuf, map: true, protoc_gen_elixir_version: "0.15.0", syntax: :proto3 + use Protobuf, map: true, protoc_gen_elixir_version: "0.14.0", syntax: :proto3 def descriptor do # credo:disable-for-next-line @@ -300,7 +300,7 @@ end defmodule Test.TestMessage do @moduledoc false - use Protobuf, protoc_gen_elixir_version: "0.15.0", syntax: :proto3 + use Protobuf, protoc_gen_elixir_version: "0.14.0", syntax: :proto3 def descriptor do # credo:disable-for-next-line diff --git a/lib/actors/actor/caller_consumer.ex b/lib/actors/actor/caller_consumer.ex index 1c27c9237..b06d67ec5 100644 --- a/lib/actors/actor/caller_consumer.ex +++ b/lib/actors/actor/caller_consumer.ex @@ -13,7 +13,6 @@ defmodule Actors.Actor.CallerConsumer do alias Actors.Config.PersistentTermConfig, as: Config alias Actors.Actor.Entity, as: ActorEntity alias Actors.Actor.Entity.Supervisor, as: ActorEntitySupervisor - alias Actors.Actor.InvocationScheduler alias Actors.Actor.Pool, as: ActorPool alias Actors.Registry.{ActorRegistry, HostActor} @@ -172,14 +171,17 @@ defmodule Actors.Actor.CallerConsumer do if Sidecar.GracefulShutdown.running?() do actors |> Map.values() - |> Enum.map(fn actor -> ActorPool.create_actor_host_pool(actor, opts) end) + |> Enum.map(fn actor -> ActorPool.create_actor_host_pool(actor.id, opts) end) |> List.flatten() |> Enum.filter(&(&1.node == Node.self())) |> ActorRegistry.register() - |> tap(fn _sts -> warmup_actors(actor_system, actors, opts) end) |> case do :ok -> status = %RequestStatus{status: :OK, message: "Accepted"} + :persistent_term.put(:registered_actors, actors) + + warmup_actors(actor_system, actors, opts) + {:ok, %RegistrationResponse{proxy_info: get_proxy_info(), status: status}} _ -> @@ -479,9 +481,8 @@ defmodule Actors.Actor.CallerConsumer do end end) - error -> - raise ArgumentError, - "You are trying to create an actor from an Unnamed actor that has never been registered before. ActorId: #{inspect(id)}. Details. #{inspect(error)}" + _error -> + ActorPool.create_actor_host_pool(id, opts) end end) |> List.flatten() @@ -577,7 +578,16 @@ defmodule Actors.Actor.CallerConsumer do value -> value end - retry_while with: exponential_backoff() |> randomize |> expiry(timeout) do + retry_strategy = + if fail_backoff do + exponential_backoff() |> randomize |> expiry(timeout) + else + # When fail_backoff is off, retry only once after 100s + # Cap at 1 retry + constant_backoff(101) |> expiry(101) |> cap(1) + end + + retry_while with: retry_strategy do try do Tracer.add_event("lookup", [{"target", actor.id.name}]) @@ -595,7 +605,7 @@ defmodule Actors.Actor.CallerConsumer do # Choose the first result (which is now a random result) host = hd(shuffled_actor_hosts) - {pooled?, system.name, host.actor.id.parent, actor_id} + {pooled?, system.name, host.actor_id.parent, actor_id} _ -> fqdn = @@ -621,7 +631,7 @@ defmodule Actors.Actor.CallerConsumer do if is_nil(request.scheduled_to) || request.scheduled_to == 0 do maybe_invoke_async(async?, actor_ref, request_params, opts) else - InvocationScheduler.schedule_invoke(request_params) + #InvocationScheduler.schedule_invoke(request_params) {:ok, :async} end @@ -644,9 +654,8 @@ defmodule Actors.Actor.CallerConsumer do {:halt, result} {:error, :actor_invoke, error} -> - keep_retrying_action = if fail_backoff, do: :cont, else: :halt - - {keep_retrying_action, {:error, error}} + # Always retry for actor_invoke errors, but limited by the retry strategy + {:cont, {:error, error}} {:error, _msg} = result -> {:cont, result} @@ -663,13 +672,7 @@ defmodule Actors.Actor.CallerConsumer do end defp to_spawn_hosts(id, actor_hosts, spawned_opts) do - Enum.map(actor_hosts, fn %HostActor{ - node: node, - actor: %Actor{} = unnamed_actor, - opts: opts - } = _host -> - spawned_actor = %Actor{unnamed_actor | id: id} - + Enum.map(actor_hosts, fn %HostActor{node: node, opts: opts} -> new_opts = if Keyword.has_key?(spawned_opts, :revision) do Keyword.put(opts, :revision, Keyword.get(spawned_opts, :revision, 0)) @@ -677,7 +680,7 @@ defmodule Actors.Actor.CallerConsumer do opts end - %HostActor{node: node, actor: spawned_actor, opts: new_opts} + %HostActor{node: node, actor_id: id, opts: new_opts} end) end @@ -732,7 +735,16 @@ defmodule Actors.Actor.CallerConsumer do Tracer.with_span "actor-lookup" do Tracer.set_attributes([{:actor_fqdn, actor_fqdn}]) - case Spawn.Cluster.Node.Registry.lookup(Actors.Actor.Entity, parent) do + lookup_response = + case RaRegistry.lookup(Spawn.RaRegistry, parent) do + [{actor_ref, actor_ref_id}] when is_pid(actor_ref) -> + [{actor_ref, actor_ref_id}] + + _ -> + [] + end + + case lookup_response do [{actor_ref, actor_ref_id}] -> Tracer.add_event("actor-status", [{"alive", true}]) Tracer.set_attributes([{"actor-pid", "#{inspect(actor_ref)}"}]) @@ -757,11 +769,11 @@ defmodule Actors.Actor.CallerConsumer do filter_by_parent: pooled, parent: parent ) do - {:ok, %HostActor{node: node, actor: actor, opts: opts}} -> + {:ok, %HostActor{node: node, actor_id: actor_id, opts: opts}} -> do_call( system, node, - actor, + actor_id, actor_fqdn, action_fun, opts @@ -815,7 +827,7 @@ defmodule Actors.Actor.CallerConsumer do defp do_call( system, node, - actor, + actor_id, {pooled, _system_name, _parent, actor_name} = _actor_fqdn, action_fun, opts @@ -825,7 +837,7 @@ defmodule Actors.Actor.CallerConsumer do node, __MODULE__, :try_reactivate_actor, - [system, actor, opts], + [system, actor_id, opts], @erpc_timeout ) do {:ok, actor_ref} -> @@ -837,24 +849,20 @@ defmodule Actors.Actor.CallerConsumer do if pooled, # Ensures that the name change will not affect the host function call - do: action_fun.(actor_ref, %ActorId{actor.id | name: actor_name.name}), - else: action_fun.(actor_ref, actor.id) + do: action_fun.(actor_ref, %ActorId{actor_id | name: actor_name.name}), + else: action_fun.(actor_ref, actor_id) _ -> raise ErlangError end catch :exit, reason -> - Logger.error( - "Failed to call Actor #{inspect(actor.id)} on Node #{inspect(node)}: #{inspect(reason)}" - ) + Logger.error(Exception.format(:error, reason, __STACKTRACE__)) :error :error, error -> - Logger.error( - "Failed to call Actor #{inspect(actor.id)} on Node #{inspect(node)}: #{inspect(error)}" - ) + Logger.error(Exception.format(:error, error, __STACKTRACE__)) :error end @@ -875,15 +883,16 @@ defmodule Actors.Actor.CallerConsumer do Reactivation is attempted by looking up the actor in the registry or creating a new actor if not found. """ - @spec try_reactivate_actor(ActorSystem.t(), Actor.t(), any()) :: {:ok, any()} | {:error, any()} - def try_reactivate_actor(system, actor, opts \\ []) + @spec try_reactivate_actor(ActorSystem.t(), ActorId.t(), any()) :: + {:ok, any()} | {:error, any()} + def try_reactivate_actor(system, actor_id, opts \\ []) def try_reactivate_actor( %ActorSystem{} = system, - %Actor{id: %ActorId{name: name} = _id} = actor, + %ActorId{name: name} = actor_id, opts ) do - case ActorEntitySupervisor.lookup_or_create_actor(system, actor, opts) do + case ActorEntitySupervisor.lookup_or_create_actor(system, actor_id, opts) do {:ok, actor_ref} -> Logger.debug("Actor #{name} reactivated. ActorRef PID: #{inspect(actor_ref)}") {:ok, actor_ref} @@ -895,8 +904,8 @@ defmodule Actors.Actor.CallerConsumer do end # To lookup all actors - def try_reactivate_actor(nil, %Actor{id: %ActorId{name: name} = _id} = actor, opts) do - case ActorEntitySupervisor.lookup_or_create_actor(nil, actor, opts) do + def try_reactivate_actor(nil, %Actor{id: %ActorId{name: name} = id}, opts) do + case ActorEntitySupervisor.lookup_or_create_actor(nil, id, opts) do {:ok, actor_ref} -> Logger.debug("Actor #{name} reactivated. ActorRef PID: #{inspect(actor_ref)}") {:ok, actor_ref} @@ -932,7 +941,7 @@ defmodule Actors.Actor.CallerConsumer do @spec lookup_or_create_actor(ActorSystem.t(), String.t(), Actor.t(), any()) :: {:ok, pid()} | {:error, String.t()} defp lookup_or_create_actor(actor_system, actor_name, actor, opts) do - case ActorEntitySupervisor.lookup_or_create_actor(actor_system, actor, opts) do + case ActorEntitySupervisor.lookup_or_create_actor(actor_system, actor.id, opts) do {:ok, pid} -> {:ok, pid} @@ -945,7 +954,7 @@ defmodule Actors.Actor.CallerConsumer do defp is_selectable?( {_actor_name, %Actor{ - metadata: %Metadata{channel_group: channel_group}, + metadata: %Metadata{channel_group: _channel_group}, settings: %ActorSettings{stateful: stateful, kind: kind} } = _actor} ) do diff --git a/lib/actors/actor/entity/entity.ex b/lib/actors/actor/entity/entity.ex index d62fad535..ece4821c5 100644 --- a/lib/actors/actor/entity/entity.ex +++ b/lib/actors/actor/entity/entity.ex @@ -641,6 +641,6 @@ defmodule Actors.Actor.Entity do defp reply_to_noreply({:noreply, _response, state, opts}), do: {:noreply, state, opts} defp via(name) do - {:via, Horde.Registry, {Spawn.Cluster.Node.Registry, {__MODULE__, name}}} + {:via, RaRegistry, {Spawn.RaRegistry, {__MODULE__, name}}} end end diff --git a/lib/actors/actor/entity/invocation.ex b/lib/actors/actor/entity/invocation.ex index f31cba966..0c78b0c20 100644 --- a/lib/actors/actor/entity/invocation.ex +++ b/lib/actors/actor/entity/invocation.ex @@ -9,7 +9,6 @@ defmodule Actors.Actor.Entity.Invocation do alias Actors.Actor.Entity.EntityState alias Actors.Actor.Entity.Lifecycle alias Actors.Actor.Entity.Lifecycle.StreamInitiator - alias Actors.Actor.InvocationScheduler alias Actors.Exceptions.NotAuthorizedException alias Actors.Actor.Pubsub alias Actors.Actor.StateManager @@ -137,28 +136,7 @@ defmodule Actors.Actor.Entity.Invocation do def handle_timers([], _system, _actor), do: :ok - def handle_timers(timers, system, actor) when is_list(timers) do - invocations = - Enum.map(timers, fn %FixedTimerAction{action: %Action{name: action}, seconds: delay} -> - invocation_request = %InvocationRequest{ - actor: actor, - action_name: action, - payload: {:noop, %Noop{}}, - async: true, - scheduled_to: 0, - caller: actor.id, - system: %ActorSystem{name: system} - } - - scheduled_to = - DateTime.utc_now() - |> DateTime.add(delay, :millisecond) - - {invocation_request, scheduled_to, delay} - end) - - InvocationScheduler.schedule_fixed_invocations(invocations) - + def handle_timers(timers, _system, _actor) when is_list(timers) do :ok catch error -> Logger.error("Error on handle timers #{inspect(error)}") diff --git a/lib/actors/actor/entity/lifecycle.ex b/lib/actors/actor/entity/lifecycle.ex index 577172e34..7390e253f 100644 --- a/lib/actors/actor/entity/lifecycle.ex +++ b/lib/actors/actor/entity/lifecycle.ex @@ -70,13 +70,13 @@ defmodule Actors.Actor.Entity.Lifecycle do :ok = handle_metadata(name, system, metadata) :ok = Invocation.handle_timers(timer_actions, system, state.actor) - :ok = - Spawn.Cluster.Node.Registry.update_entry_value( - Actors.Actor.Entity, - actor_name_key, - self(), - actor.id - ) + # :ok = + # Spawn.Cluster.Node.Registry.update_entry_value( + # Actors.Actor.Entity, + # actor_name_key, + # self(), + # actor.id + # ) schedule_deactivate(actor, deactivation_strategy, get_jitter()) diff --git a/lib/actors/actor/entity/lifecycle/stream_consumer.ex b/lib/actors/actor/entity/lifecycle/stream_consumer.ex index 572092ee6..ef691b08f 100644 --- a/lib/actors/actor/entity/lifecycle/stream_consumer.ex +++ b/lib/actors/actor/entity/lifecycle/stream_consumer.ex @@ -1,12 +1,18 @@ defmodule Actors.Actor.Entity.Lifecycle.StreamConsumer do @moduledoc false - use Broadway + use Gnat.Jetstream.PullConsumer + + require Logger + require OpenTelemetry.Tracer, as: Tracer - alias Broadway.Message alias Spawn.Utils.Nats alias Spawn.Fact alias Google.Protobuf.Timestamp alias Sidecar.GracefulShutdown + alias Spawn.Actors.ActorSystem + alias Spawn.Actors.Actor + alias Spawn.Actors.ActorId + alias Spawn.InvocationRequest @type fact :: %Fact{} @@ -16,85 +22,79 @@ defmodule Actors.Actor.Entity.Lifecycle.StreamConsumer do strict_ordering: boolean() } - @spec start_link(opts :: opts()) :: :ignore | {:error, any()} | {:ok, pid()} def start_link(opts) do - Broadway.start_link( - __MODULE__, - # there will be not a lot so probably fine to convert to atom - name: String.to_atom(opts.actor_name), - context: opts, - producer: [ - module: { - OffBroadway.Jetstream.Producer, - connection_name: Nats.connection_name(), - stream_name: opts.actor_name, - consumer_name: opts.actor_name - }, - concurrency: build_concurrency(opts) - ], - processors: [ - default: [concurrency: build_concurrency(opts)] - ], - batchers: [ - default: [ - concurrency: build_concurrency(opts), - # Avoi big batches, micro batches is better - batch_size: 10, - batch_timeout: 2_000 - ] - ] + Gnat.Jetstream.PullConsumer.start_link(__MODULE__, opts, + name: String.to_atom(opts.actor_name) ) end - @spec handle_message(any(), Message.t(), any()) :: Message.t() - def handle_message(_processor_name, message, _context) do - if GracefulShutdown.running?() do - message - |> build_fact() - |> Message.configure_ack(on_success: :term) - else - message - |> Message.failed("Failed to deliver because app is draining") - end + @impl true + def init(opts) do + {:ok, opts, + connection_name: Nats.connection_name(), + stream_name: opts.actor_name, + consumer_name: opts.actor_name} end - @spec handle_batch(any(), Message.t(), any(), opts()) :: list(Message.t()) - def handle_batch(_, messages, _, context) do - GenServer.cast(context.projection_pid, {:process_projection_events, messages}) + def handle_message(message, state) do + if GracefulShutdown.running?() do + payload = message.body - messages - end + metadata = + Enum.reduce(message.headers, %{}, fn {key, value}, acc -> + Map.put(acc, key, value) + end) + |> Map.put("topic", message.topic) + + time = DateTime.utc_now() |> DateTime.to_unix(:second) - @spec build_fact(Message.t()) :: Message.t() - defp build_fact(message) do - # %Broadway.Message{data: "{\"ACTION\":\"KEY_ADDED\",\"KEY\":\"MYKEY\",\"VALUE\":\"MYVALUE\"}", metadata: %{headers: [], topic: "actors.mike"}, acknowledger: {OffBroadway.Jetstream.Acknowledger, #Reference<0.743380651.807927811.227242>, %{on_success: :term, reply_to: "$JS.ACK.newtest.projectionviewertest.1.11.11.1725657673932595345.21"}}, batcher: :default, batch_key: :default, batch_mode: :bulk, status: :ok} + fact = %Fact{ + uuid: UUID.uuid4(:hex), + metadata: metadata, + state: payload, + timestamp: %Timestamp{seconds: time} + } - message - |> Message.put_data(process_data(message)) + process_message(fact, state) + + {:ack, state} + else + {:nack, state} + end end - @spec process_data(Message.t()) :: fact() - defp process_data(message) do - payload = message.data + # Process a single message and invoke the actor + defp process_message(%Fact{} = message, state) do + actor_name = state.actor_name |> String.split("-") |> List.last() + actor_settings = :persistent_term.get("actor-#{actor_name}") - metadata = - Enum.reduce(message.metadata.headers, %{}, fn {key, value}, acc -> - Map.put(acc, key, value) - end) - |> Map.put("topic", message.metadata.topic) + system_name = Map.get(message.metadata, "spawn-system") + parent = Map.get(message.metadata, "actor-parent") + name = Map.get(message.metadata, "actor-name") + source_action = Map.get(message.metadata, "actor-action") - time = DateTime.utc_now() |> DateTime.to_unix(:seconds) + action_metadata = + case Map.get(message.metadata, "action-metadata") do + nil -> %{} + metadata -> Jason.decode!(metadata) + end - %Fact{ - uuid: UUID.uuid4(:hex), - metadata: metadata, - state: payload, - timestamp: %Timestamp{seconds: time} + action = + actor_settings.subjects + |> Enum.find(fn subject -> subject.source_action == source_action end) + |> Map.get(:action) + + invocation = %InvocationRequest{ + system: %ActorSystem{name: system_name}, + actor: %Actor{id: %ActorId{name: actor_name, system: system_name}}, + metadata: action_metadata, + action_name: action, + payload: {:value, Google.Protobuf.Any.decode(message.state)}, + caller: %ActorId{name: name, system: system_name, parent: parent} } - end - # Projections are like long-lasting threads and therefore concurrency should be avoided - # if the intention is to have some notion of ordering. - defp build_concurrency(%{strict_ordering: true}), do: 1 - defp build_concurrency(%{strict_ordering: false}), do: System.schedulers_online() + # If this raises or throws, the error will be caught in handle_batch + # and the message will be marked as failed for Broadway to retry + {:ok, _response} = Actors.invoke(invocation, span_ctx: Tracer.current_span_ctx()) + end end diff --git a/lib/actors/actor/entity/lifecycle/stream_initiator.ex b/lib/actors/actor/entity/lifecycle/stream_initiator.ex index 380a66b41..78c85a25b 100644 --- a/lib/actors/actor/entity/lifecycle/stream_initiator.ex +++ b/lib/actors/actor/entity/lifecycle/stream_initiator.ex @@ -27,7 +27,7 @@ defmodule Actors.Actor.Entity.Lifecycle.StreamInitiator do with {:create_stream, :ok} <- {:create_stream, create_stream(actor, true)}, {:create_consumer, :ok} <- {:create_consumer, create_consumer(actor, deliver_policy: :all)} do - start_pipeline(actor) + {:ok, self()} else {:create_stream, error} -> Logger.error( @@ -252,7 +252,7 @@ defmodule Actors.Actor.Entity.Lifecycle.StreamInitiator do }) end - defp stop_pipeline(pid), do: Broadway.stop(pid) + defp stop_pipeline(pid), do: Process.exit(pid, :shutdown) def stream_name(actor, actor_name \\ nil) def stream_name(%Actor{} = actor, actor_name), do: stream_name(actor.id, actor_name) diff --git a/lib/actors/actor/entity/pool.ex b/lib/actors/actor/entity/pool.ex index 58b2a10ca..aa86ae843 100644 --- a/lib/actors/actor/entity/pool.ex +++ b/lib/actors/actor/entity/pool.ex @@ -6,13 +6,7 @@ defmodule Actors.Actor.Pool do require Logger alias Actors.Registry.HostActor - - alias Spawn.Actors.{ - Actor, - ActorId, - ActorSettings - } - + alias Spawn.Actors.{Actor, ActorId} alias Spawn.Utils.Common @doc """ @@ -26,12 +20,9 @@ defmodule Actors.Actor.Pool do Returns a list of `HostActor` structs representing the hosts in the pool. """ @spec create_actor_host_pool(Actor.t(), keyword()) :: list(HostActor.t()) - def create_actor_host_pool( - %Actor{id: %ActorId{} = _id, settings: %ActorSettings{} = _settings} = actor, - opts - ) do + def create_actor_host_pool(%ActorId{} = id, opts) do opts = Keyword.merge(opts, hash: Common.actor_host_hash()) - [%HostActor{node: Node.self(), actor: actor, opts: opts}] + [%HostActor{node: Node.self(), actor_id: id, opts: opts}] end end diff --git a/lib/actors/actor/entity/supervisor.ex b/lib/actors/actor/entity/supervisor.ex index 49be9077f..083deef1a 100644 --- a/lib/actors/actor/entity/supervisor.ex +++ b/lib/actors/actor/entity/supervisor.ex @@ -11,7 +11,7 @@ defmodule Actors.Actor.Entity.Supervisor do alias Actors.Actor.Entity.EntityState alias Actors.Config.PersistentTermConfig, as: Config - alias Spawn.Actors.{Actor, ActorSystem} + alias Spawn.Actors.{ActorId, ActorSystem} @default_number_of_partitions 8 @shutdown_timeout_ms 330_000 @@ -43,48 +43,74 @@ defmodule Actors.Actor.Entity.Supervisor do @doc """ Adds a Actor to the dynamic supervisor. """ - @spec lookup_or_create_actor(ActorSystem.t(), Actor.t(), any()) :: {:ok, any} - def lookup_or_create_actor(system, actor, opts \\ []) + @spec lookup_or_create_actor(ActorSystem.t(), ActorId.t(), any()) :: {:ok, any} + def lookup_or_create_actor(system, actor_id, opts \\ []) def lookup_or_create_actor( actor_system, - %Actor{} = actor, + %ActorId{} = actor_id, opts ) do - revision = Keyword.get(opts, :revision, 0) - - entity_state = %EntityState{ - system: Map.get(actor_system || %{}, :name), - actor: actor, - revision: revision, - opts: opts - } - - child_spec = %{ - id: Actors.Actor.Entity, - start: {Actors.Actor.Entity, :start_link, [entity_state]}, - restart: :transient, - # wait until for 5 and a half minutes - shutdown: @shutdown_timeout_ms - } + if actor = :persistent_term.get(:registered_actors, nil) do + actor_name = + if actor_id.parent == "" or is_nil(actor_id.parent) do + actor_id.name + else + actor_id.parent + end + + actor = actor |> Map.get(actor_name) |> Map.put(:id, actor_id) + + revision = Keyword.get(opts, :revision, 0) + + entity_state = + %EntityState{ + system: Map.get(actor_system || %{}, :name), + actor: actor, + revision: revision, + opts: opts + } + + child_spec = %{ + id: Actors.Actor.Entity, + start: {Actors.Actor.Entity, :start_link, [entity_state]}, + restart: :transient, + # wait until for 5 and a half minutes + shutdown: @shutdown_timeout_ms + } + + start_child(child_spec) + |> case do + {:ok, pid} -> + {:ok, pid} + + error -> + error + end + else + :actors_not_registered + end + end + defp start_child(child_spec) do case DynamicSupervisor.start_child(via(child_spec), child_spec) do - {:error, {:already_started, pid}} -> + {:error, {:already_started, pid}} when is_pid(pid) -> {:ok, pid} - {:ok, pid} -> + {:ok, pid} when is_pid(pid) -> {:ok, pid} {:error, {:name_conflict, {{Actors.Actor.Entity, name}, _f}, _registry, pid}} -> Logger.warning("Name conflict on start Actor #{name} from PID #{inspect(pid)}.") :ignore + + _ -> + :invalid_process end end - defp get_key(spec), do: :erlang.phash2(Map.drop(spec, [:id])) - - defp via(spec), do: {:via, PartitionSupervisor, {__MODULE__, get_key(spec)}} + defp via(spec), do: {:via, PartitionSupervisor, {__MODULE__, self()}} defp get_number_of_partitions() do if System.schedulers_online() > 1 do diff --git a/lib/actors/actor/state_manager.ex b/lib/actors/actor/state_manager.ex index 1d867590e..c3c3d75f7 100644 --- a/lib/actors/actor/state_manager.ex +++ b/lib/actors/actor/state_manager.ex @@ -110,8 +110,7 @@ if Code.ensure_loaded?(Statestores.Supervisor) do hash <- :crypto.hash(:sha256, bytes_from_state), key <- generate_key(actor_id) do %Snapshot{ - id: key, - actor: name, + actor: key, system: system, status: status, node: node, @@ -169,8 +168,7 @@ if Code.ensure_loaded?(Statestores.Supervisor) do key = generate_key(actor_id) %Snapshot{ - id: key, - actor: name, + actor: key, system: system, status: status, node: node, @@ -209,7 +207,7 @@ if Code.ensure_loaded?(Statestores.Supervisor) do end end - defp generate_key(id), do: :erlang.phash2(id) + defp generate_key(%{name: name}), do: name defp inserted_successfully?(ref, pid) do receive do diff --git a/lib/actors/registry/actor_registry.ex b/lib/actors/registry/actor_registry.ex index 6ee41091b..623f977f6 100644 --- a/lib/actors/registry/actor_registry.ex +++ b/lib/actors/registry/actor_registry.ex @@ -30,11 +30,11 @@ defmodule Actors.Registry.ActorRegistry do atoms: [:error, :too_many_requests], rescue_only: [ErlangError] do try do - StateHandoff.set(host.actor.id, host) + StateHandoff.set(host.actor_id, host) rescue e -> Logger.error( - "Error to register actor #{inspect(host.actor.id)} for host #{inspect(host)}: #{inspect(e)}" + "Error to register actor_id #{inspect(host.actor_id)} for host #{inspect(host)}: #{inspect(e)}" ) reraise e, __STACKTRACE__ @@ -110,7 +110,7 @@ defmodule Actors.Registry.ActorRegistry do {:not_found, []} hosts -> - Enum.filter(hosts, &(&1.actor.id.name == actor_name)) + Enum.filter(hosts, &(&1.actor_id.name == actor_name)) |> then(fn [] -> {:not_found, []} hosts -> {:ok, hosts} @@ -124,16 +124,15 @@ defmodule Actors.Registry.ActorRegistry do # As always, when prioritizing performance, defp filter(hosts, filter_by_parent?, %ActorId{name: actor_name, parent: parent_name}) do Enum.filter(hosts, fn ac -> - (filter_by_parent? && ac.actor.id.parent == parent_name) || - (not filter_by_parent? && ac.actor.id.name == actor_name) + (filter_by_parent? && ac.actor_id.parent == parent_name) || + (not filter_by_parent? && ac.actor_id.name == actor_name) end) end defp choose_hosts(hosts, filter_by_parent?, %ActorId{name: _actor_name} = _id, parent_name) do if filter_by_parent? do - %HostActor{node: _node, actor: actor, opts: opts} = host = Enum.random(hosts) - new_actor = %Actor{actor | id: %ActorId{actor.id | name: parent_name}} - {:ok, %HostActor{host | actor: new_actor, opts: opts}} + %HostActor{node: _node, actor_id: actor_id, opts: opts} = host = Enum.random(hosts) + {:ok, %HostActor{host | actor_id: %ActorId{actor_id | name: parent_name}, opts: opts}} else case LoadBalancer.next_host(hosts) do {:ok, node_host, _updated_hosts} -> diff --git a/lib/actors/registry/host_actor.ex b/lib/actors/registry/host_actor.ex index 3f17bbd47..ce3413fb1 100644 --- a/lib/actors/registry/host_actor.ex +++ b/lib/actors/registry/host_actor.ex @@ -3,13 +3,13 @@ defmodule Actors.Registry.HostActor do `HostActor` Defines the type of Actor that will be registered in `ActorRegistry`. """ - alias Spawn.Actors.Actor + alias Spawn.Actors.ActorId - defstruct actor: nil, node: nil, opts: nil + defstruct actor_id: nil, node: nil, opts: nil @type t :: %__MODULE__{ node: node(), - actor: Actor.t(), + actor_id: ActorId.t(), opts: Keyword.t() } end diff --git a/lib/actors/supervisors/actor_projection_handler.ex b/lib/actors/supervisors/actor_projection_handler.ex new file mode 100644 index 000000000..577f042a9 --- /dev/null +++ b/lib/actors/supervisors/actor_projection_handler.ex @@ -0,0 +1,56 @@ +defmodule Actors.Supervisors.ActorProjectionHandlerSupervisor do + @moduledoc false + use Supervisor + require Logger + + import Spawn.Utils.Common, only: [supervisor_process_logger: 1] + + alias Actors.Config.PersistentTermConfig, as: Config + alias Actors.Actor.Entity.Lifecycle.StreamConsumer + + def start_link(opts) do + Supervisor.start_link(__MODULE__, opts, name: __MODULE__) + end + + def child_spec(opts) do + %{ + id: __MODULE__, + start: {__MODULE__, :start_link, [opts]} + } + end + + @impl true + def init(_opts) do + projections = :persistent_term.get("projections", []) + Logger.info("Starting projection handlers for: #{inspect(projections)}") + + projection_children = + projections + |> Enum.uniq() + |> Enum.map(fn projection_name -> + config = :persistent_term.get("actor-#{projection_name}", %{}) + system = Config.get(:actor_system_name) + actor_name = String.replace("#{system}-#{projection_name}", ".", "-") + + %{ + id: actor_name, + start: + {StreamConsumer, :start_link, + [ + %{ + actor_name: actor_name, + projection_pid: self(), + strict_ordering: config.strict_events_ordering + } + ]} + } + end) + + children = + [ + supervisor_process_logger(__MODULE__) + ] ++ projection_children + + Supervisor.init(children, strategy: :one_for_one) + end +end diff --git a/lib/actors/supervisors/actor_supervisor.ex b/lib/actors/supervisors/actor_supervisor.ex index 01b947682..aea82b37b 100644 --- a/lib/actors/supervisors/actor_supervisor.ex +++ b/lib/actors/supervisors/actor_supervisor.ex @@ -30,22 +30,13 @@ defmodule Actors.Supervisors.ActorSupervisor do Protobuf.load_extensions() get_acl_manager().load_acl_policies("#{@base_app_dir}/policies") - consumers = - Enum.into(1..@max_consumers, [], fn index -> - %{ - id: index, - start: {Actors.Actor.CallerConsumer, :start_link, [[id: index, opts: opts]]} - } - end) - children = [ supervisor_process_logger(__MODULE__), get_pubsub_adapter(opts), Actors.Actor.Pubsub, Actors.Actor.Entity.Supervisor.child_spec(opts) - ] ++ - maybe_add_invocation_scheduler(opts) ++ [{CallerProducer, []}] ++ consumers + ] ++ maybe_add_invocation_scheduler(opts) Supervisor.init(children, strategy: :one_for_one) end @@ -55,7 +46,8 @@ defmodule Actors.Supervisors.ActorSupervisor do defp maybe_add_invocation_scheduler(_opts) do if Config.get(:delayed_invokes) do - [{Highlander, Actors.Actor.InvocationScheduler.child_spec()}] + # [{Highlander, Actors.Actor.InvocationScheduler.child_spec()}] + [] else [] end diff --git a/lib/sidecar/graceful_shutdown.ex b/lib/sidecar/graceful_shutdown.ex index d71f6d5b2..e15d0d25d 100644 --- a/lib/sidecar/graceful_shutdown.ex +++ b/lib/sidecar/graceful_shutdown.ex @@ -20,7 +20,7 @@ defmodule Sidecar.GracefulShutdown do defmodule State do @moduledoc false - defstruct init_stop?: true, shutdown_delay_ms: 6_000, notify_pid: nil + defstruct init_stop?: true, shutdown_delay_ms: 0, notify_pid: nil def new(opts) do Map.merge(%__MODULE__{}, Enum.into(opts, %{})) diff --git a/lib/sidecar/grpc/code_generator.ex b/lib/sidecar/grpc/code_generator.ex index d0978c496..c14280ab3 100644 --- a/lib/sidecar/grpc/code_generator.ex +++ b/lib/sidecar/grpc/code_generator.ex @@ -15,7 +15,6 @@ defmodule Sidecar.GRPC.CodeGenerator do alias Actors.Config.PersistentTermConfig, as: Config alias Spawn.Actors.ActorViewOption - alias Protobuf.Protoc.Generator.Util alias Mix.Tasks.Protobuf.Generate alias Spawn.Utils.AnySerializer @@ -154,6 +153,13 @@ defmodule Sidecar.GRPC.CodeGenerator do actor_opts = Map.get(option_extensions, {Spawn.Actors.PbExtension, :actor}) if not is_nil(actor_opts) do + if actor_opts.kind == :PROJECTION do + # put all projections in a persistent term list + projections = :persistent_term.get("projections", []) + + :persistent_term.put("projections", [svc.name | projections]) + end + :persistent_term.put("actor-#{svc.name}", actor_opts) end @@ -255,7 +261,8 @@ defmodule Sidecar.GRPC.CodeGenerator do def compile_modules(module), do: do_compile(module) defp do_compile(module) do - Code.compile_string(module) + # Code.compile_string(module) + :ok rescue error in UndefinedFunctionError -> Logger.error("Error in Module definition. Make sure the service name is correct") diff --git a/lib/sidecar/grpc/dispatcher.ex b/lib/sidecar/grpc/dispatcher.ex index 77b76a9fa..6dc0e63d7 100644 --- a/lib/sidecar/grpc/dispatcher.ex +++ b/lib/sidecar/grpc/dispatcher.ex @@ -269,9 +269,9 @@ defmodule Sidecar.GRPC.Dispatcher do end defp build_actor_id(system_name, actor_name, message) do - with {:ok, %HostActor{actor: %Actor{settings: %ActorSettings{} = actor_settings}}} <- + with {:ok, %HostActor{} = host} <- ActorRegistry.lookup(%ActorId{system: system_name, name: actor_name}) do - build_actor_id_from_settings(system_name, actor_name, actor_settings, message) + %ActorId{system: system_name, name: actor_name, parent: actor_name} else {:not_found, _} -> log_and_raise_error( @@ -282,30 +282,6 @@ defmodule Sidecar.GRPC.Dispatcher do end end - defp build_actor_id_from_settings( - system_name, - actor_name, - %ActorSettings{kind: kind}, - _message - ) - when kind in [:NAMED, :PROJECTION, :TASK] do - %ActorId{system: system_name, name: actor_name} - end - - defp build_actor_id_from_settings( - system_name, - actor_name, - %ActorSettings{kind: :UNNAMED} = _settings, - message - ) do - {ctype, name} = find_actor_name_and_ctype(message) - actor_id_name = get_actor_id_name(ctype, message, name) - - %ActorId{system: system_name, name: actor_id_name, parent: actor_name} - end - - defp build_actor_id_from_settings(_, _, _, _), do: nil - defp find_actor_name_and_ctype(message) do module = message.__struct__ descriptor_proto = apply(module, :descriptor, []) diff --git a/lib/sidecar/process_supervisor.ex b/lib/sidecar/process_supervisor.ex index ae58b0699..172f75c81 100644 --- a/lib/sidecar/process_supervisor.ex +++ b/lib/sidecar/process_supervisor.ex @@ -28,6 +28,7 @@ defmodule Sidecar.ProcessSupervisor do Spawn.Supervisor.child_spec(opts), statestores(), Actors.Supervisors.ActorSupervisor.child_spec(opts), + Actors.Supervisors.ActorProjectionHandlerSupervisor.child_spec(opts), Actors.Supervisors.ProtocolSupervisor.child_spec(opts), %{ id: :healthcheck_actor_init, diff --git a/lib/spawn.ex b/lib/spawn.ex index 92efa5721..5b2818c25 100644 --- a/lib/spawn.ex +++ b/lib/spawn.ex @@ -1,105 +1,10 @@ defmodule Spawn do - @moduledoc """ - Spawn provides an actor-based runtime for building durable, stateful systems. - It enables you to implement business logic as actors that maintain persistent - state across failures, scale horizontally, and communicate through modern - protocols such as gRPC and HTTP. - - Actors are defined once using Protobuf service definitions. Spawn then - automatically exposes them over gRPC and supports JSON transcoding for HTTP - clients without requiring additional code changes. - - ## Main features: - - * - * - * - * - - - ## Installation: - - def deps do - [ - {:spawn_sdk, "~> 2.0.0-RC9"}, - # You can uncomment one of those dependencies if you are going to use Persistent Actors - # {:spawn_statestores_mariadb, "~> 2.0.0-RC9"}, - # {:spawn_statestores_postgres, "~> 2.0.0-RC9"} - ] - end - - ## Code generation - - After creating an Elixir application project, create the protobuf files for your business domain. - It is common practice to do this under the **priv/** folder. Let's demonstrate an example: - - ```protobuf - syntax = "proto3"; - - package io.eigr.spawn.example; - - message MyState { - int32 value = 1; - } - - message MyBusinessMessage { - int32 value = 1; - } - ``` - - It is important to try to separate the type of message that must be stored as the actors' state from the type of messages - that will be exchanged between their actors' operations calls. In other words, the Actor's internal state is also represented - as a protobuf type, and it is a good practice to separate these types of messages from the others in its business domain. - - In the above case `MyState` is the type protobuf that represents the state of the Actor that we will create later - while `MyBusiness` is the type of message that we will send and receive from this Actor. - - Now that we have defined our input and output types as Protobuf types we will need to compile these files to generate their respective Elixir modules. An example of how to do this can be found [here](https://github.com/eigr/spawn/blob/main/spawn_sdk/spawn_sdk_example/compile-example-pb.sh) - - You need to have installed the elixir plugin for protoc. More information on how to obtain and install the necessary tools can be found here [here](https://github.com/elixir-protobuf/protobuf#usage) - - Now that the protobuf types have been created we can proceed with the code. - - ## Basic Example - - defmodule SpawnSdkExample.Actors.MyActor do - use SpawnSdk.Actor, - name: "jose", # Default is Full Qualified Module name a.k.a __MODULE__ - kind: :named, # Default is already :named. Valid are :named | :unnamed - stateful: true, # Default is already true - state_type: Io.Eigr.Spawn.Example.MyState, # or :json if you don't care about protobuf types - - require Logger - - alias Io.Eigr.Spawn.Example.{MyState, MyBusinessMessage} - - # The callback could also be referenced to an existing function: - # action "SomeAction", &some_defp_handler/0 - # action "SomeAction", &SomeModule.handler/1 - # action "SomeAction", &SomeModule.handler/2 - - init fn %Context{state: state} = ctx -> - Logger.info("[joe] Received activation request") - - Value.of() - |> Value.state(state) - end - - action "Sum", fn %Context{state: state} = ctx, %MyBusinessMessage{value: value} = data -> - Logger.info("Received Request. Doing something...") - - new_value = if is_nil(state), do: value, else: (state.value || 0) + value - - Value.of(%MyBusinessMessage{value: new_value}, %MyState{value: new_value}) - end - end - - - """ - @version Spawn.MixProject.project()[:version] - - @doc """ - Returns version of this project. - """ - def version, do: @version + if Mix.env() == :prod do + @moduledoc false + else + @moduledoc "README.md" + |> File.read!() + |> String.split("") + |> Enum.fetch!(1) + end end diff --git a/lib/spawn/cluster/state_handoff/controllers/nats_kv_controller.ex b/lib/spawn/cluster/state_handoff/controllers/nats_kv_controller.ex index 35a85311c..d77afc87d 100644 --- a/lib/spawn/cluster/state_handoff/controllers/nats_kv_controller.ex +++ b/lib/spawn/cluster/state_handoff/controllers/nats_kv_controller.ex @@ -94,7 +94,10 @@ defmodule Spawn.Cluster.StateHandoff.Controllers.NatsKvController do "Found a host that is likely registered to a dead node. Flushing node from hosts list for key #{inspect(key)}" ) - new_hosts = hosts |> :erlang.term_to_binary() + new_hosts = + hosts + |> Enum.reject(fn host -> Map.get(host, :actor) end) + |> :erlang.term_to_binary() :ok = Jetstream.API.KV.put_value( @@ -125,8 +128,12 @@ defmodule Spawn.Cluster.StateHandoff.Controllers.NatsKvController do hosts = get_hosts(id) + # new_hosts = [host] |> :erlang.term_to_binary() new_hosts = - ([host] ++ hosts) |> Enum.uniq_by(&{&1.node, &1.actor.id}) |> :erlang.term_to_binary() + ([host] ++ hosts) + |> Enum.reject(fn host -> Map.get(host, :actor) end) + |> Enum.uniq_by(&{&1.node, &1.actor_id}) + |> :erlang.term_to_binary() :ok = Jetstream.API.KV.put_value( @@ -156,7 +163,9 @@ defmodule Spawn.Cluster.StateHandoff.Controllers.NatsKvController do if is_nil(message.data) do [] else - [:erlang.binary_to_term(message.data)] |> List.flatten() + [:erlang.binary_to_term(message.data)] + |> List.flatten() + |> Enum.reject(fn host -> Map.get(host, :actor) end) end {:error, _} -> diff --git a/lib/spawn/cluster/state_handoff/manager.ex b/lib/spawn/cluster/state_handoff/manager.ex index 9169a7c5d..6da0dfe54 100644 --- a/lib/spawn/cluster/state_handoff/manager.ex +++ b/lib/spawn/cluster/state_handoff/manager.ex @@ -21,12 +21,7 @@ defmodule Spawn.Cluster.StateHandoff.Manager do @impl true def init(opts) do - controller = - Application.get_env( - :spawn, - :state_handoff_controller_adapter, - Spawn.Cluster.StateHandoff.Controllers.CrdtController - ) + controller = Application.get_env(:spawn, :state_handoff_controller_adapter) do_init(opts) diff --git a/lib/spawn/cluster/state_handoff/manager_supervisor.ex b/lib/spawn/cluster/state_handoff/manager_supervisor.ex index 682656789..8603ff045 100644 --- a/lib/spawn/cluster/state_handoff/manager_supervisor.ex +++ b/lib/spawn/cluster/state_handoff/manager_supervisor.ex @@ -21,8 +21,7 @@ defmodule Spawn.Cluster.StateHandoff.ManagerSupervisor do def init(opts) do children = [ supervisor_process_logger(__MODULE__), - Spawn.Cluster.StateHandoff.Manager.child_spec(:state_handoff_manager, opts), - Spawn.Cluster.StateHandoff.InvocationSchedulerState.child_spec(opts) + Spawn.Cluster.StateHandoff.Manager.child_spec(:state_handoff_manager, opts) ] Supervisor.init(children, diff --git a/lib/spawn/supervisor.ex b/lib/spawn/supervisor.ex index 48e07be4c..ac752ae08 100644 --- a/lib/spawn/supervisor.ex +++ b/lib/spawn/supervisor.ex @@ -32,7 +32,9 @@ defmodule Spawn.Supervisor do {Spawn.Cache.LookupCache, []}, Spawn.Cluster.StateHandoff.ManagerSupervisor.child_spec(opts), {Spawn.Cluster.ClusterSupervisor, []}, - Spawn.Cluster.Node.Registry.child_spec() + {RaRegistry, name: Spawn.RaRegistry, keys: :unique, ra_config: %{data_dir: ~c"/tmp/ra"}} + # + # Spawn.Cluster.Node.Registry.child_spec() ]) Supervisor.init(children, strategy: :one_for_one) diff --git a/lib/spawn/utils/common.ex b/lib/spawn/utils/common.ex index 582db4751..19ca75432 100644 --- a/lib/spawn/utils/common.ex +++ b/lib/spawn/utils/common.ex @@ -79,4 +79,19 @@ defmodule Spawn.Utils.Common do tuple end end + + def distributed_alive(pid) when is_pid(pid) do + owner_node = node(pid) + self = Node.self() + + if owner_node == self do + Process.alive?(pid) + else + :erpc.call(owner_node, Process, :alive?, [pid]) + end + rescue + _ -> false + end + + def distributed_alive(_pid), do: false end diff --git a/mix.exs b/mix.exs index 194d8bf4e..9136e8fef 100644 --- a/mix.exs +++ b/mix.exs @@ -32,6 +32,7 @@ defmodule Spawn.MixProject do ] end + # Run "mix help compile.app" to learn about applications. def application do [ extra_applications: [ @@ -53,21 +54,20 @@ defmodule Spawn.MixProject do defp docs do [ - main: "Spawn", - logo: "docs/images/sepp-elixir-254-400.png", + main: "readme", source_url: @source_url, source_ref: "v#{@version}", - source_url_pattern: "#{@source_url}/blob/v#{@version}/spawn/%{path}#L%{line}", formatter_opts: [gfm: true], extras: [ - "README.md", - "guides/getting_started/quickstart.livemd" + "README.md" ] ] end + # Run "mix help deps" to learn about dependencies. defp deps do [ + # Core deps {:decimal, "~> 2.0"}, {:decorator, "~> 1.4"}, {:iter, "~> 0.1.2"}, @@ -77,14 +77,13 @@ defmodule Spawn.MixProject do {:castore, "~> 1.0"}, {:protobuf, "~> 0.14"}, {:protobuf_generate, "~> 0.1"}, - {:grpc, "~> 0.10"}, - {:grpc_reflection, "~> 0.3.0"}, + {:grpc, "~> 0.8"}, + {:grpc_reflection, "~> 0.1.5"}, {:finch, "~> 0.18"}, {:flame_k8s_backend, "~> 0.5"}, {:retry, "~> 0.17"}, {:flow, "~> 1.2"}, {:libcluster, "~> 3.3"}, - {:horde, "~> 0.9"}, {:highlander, "~> 0.2.1"}, {:phoenix_pubsub, "~> 2.1"}, {:phoenix_pubsub_nats, "~> 0.2"}, @@ -97,26 +96,21 @@ defmodule Spawn.MixProject do {:broadway, "~> 1.1"}, # temporary until bandit releases 1.5.4 {:hpax, "~> 0.1.1"}, - # Add cowlib override for gun compatibility - {:cowlib, "~> 2.15", override: true}, # Metrics & Tracing deps {:telemetry_poller, "~> 1.0"}, {:telemetry_metrics, "~> 1.0"}, {:telemetry_metrics_prometheus_core, "~> 1.2.1"}, - {:opentelemetry_api, "~> 1.5"}, - {:opentelemetry, "~> 1.7"}, + {:opentelemetry_api, "~> 1.0"}, + {:opentelemetry, "~> 1.0"}, {:opentelemetry_ecto, "~> 1.2"}, {:opentelemetry_exporter, "~> 1.0"}, # Statestores deps - {:spawn_statestores_mariadb, - path: "./spawn_statestores/statestores_mariadb", optional: false}, - {:spawn_statestores_postgres, - path: "./spawn_statestores/statestores_postgres", optional: false}, - {:spawn_statestores_native, - path: "./spawn_statestores/statestores_native", optional: false}, + {:spawn_statestores_postgres, path: "./spawn_statestores/statestores_postgres", optional: false}, {:pluggable, "~> 1.0"}, + # Non runtime deps {:credo, "~> 1.6", only: [:dev, :test], runtime: false}, - {:ex_doc, ">= 0.0.0", only: :dev, runtime: false} + {:ex_doc, ">= 0.0.0", only: :dev, runtime: false}, + {:ra_registry, "~> 0.1.2"} ] end diff --git a/mix.lock b/mix.lock index 38986e04d..6c6ec1ddd 100644 --- a/mix.lock +++ b/mix.lock @@ -1,105 +1,105 @@ %{ "acceptor_pool": {:hex, :acceptor_pool, "1.0.0", "43c20d2acae35f0c2bcd64f9d2bde267e459f0f3fd23dab26485bf518c281b21", [:rebar3], [], "hexpm", "0cbcd83fdc8b9ad2eee2067ef8b91a14858a5883cb7cd800e6fcd5803e158788"}, + "aten": {:hex, :aten, "0.6.0", "7a57b275a6daf515ac3683fb9853e280b4d0dcdd74292fd66ac4a01c8694f8c7", [:rebar3], [], "hexpm", "5f39a164206ae3f211ef5880b1f7819415686436e3229d30b6a058564fbaa168"}, "bakeware": {:hex, :bakeware, "0.2.4", "0aaf49b34f4bab2aa433f9ff1485d9401e421603160abd6d269c469fc7b65212", [:make, :mix], [{:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "7b97bcf6fbeee53bb32441d6c495bf478d26f9575633cfef6831e421e86ada6d"}, "bandit": {:hex, :bandit, "1.5.3", "c7ee44871da696371a5674dd2c2062e974a18cd787732efcf50cc70b98c78fdc", [:mix], [{:hpax, "~> 0.1.1", [hex: :hpax, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:thousand_island, "~> 1.0", [hex: :thousand_island, repo: "hexpm", optional: false]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "3812ed5e48c1f1e3109edb5c463c6f8aaf25ecfac2826606be3e5237550116ef"}, "broadway": {:hex, :broadway, "1.2.1", "83a1567423c26885e15f6cd8670ca790370af2fcff2ede7fa88c5ea793087a67", [:mix], [{:gen_stage, "~> 1.0", [hex: :gen_stage, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.3.7 or ~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "68ae63d83b55bdca0f95cd49feee5fb74c5a6bec557caf940860fe07dbc8a4fb"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, - "castore": {:hex, :castore, "1.0.17", "4f9770d2d45fbd91dcf6bd404cf64e7e58fed04fadda0923dc32acca0badffa2", [:mix], [], "hexpm", "12d24b9d80b910dd3953e165636d68f147a31db945d2dcb9365e441f8b5351e5"}, + "castore": {:hex, :castore, "1.0.12", "053f0e32700cbec356280c0e835df425a3be4bc1e0627b714330ad9d0f05497f", [:mix], [], "hexpm", "3dca286b2186055ba0c9449b4e95b97bf1b57b47c1f2644555879e659960c224"}, "chacha20": {:hex, :chacha20, "1.0.4", "0359d8f9a32269271044c1b471d5cf69660c362a7c61a98f73a05ef0b5d9eb9e", [:mix], [], "hexpm", "2027f5d321ae9903f1f0da7f51b0635ad6b8819bc7fe397837930a2011bc2349"}, "chatterbox": {:hex, :ts_chatterbox, "0.15.1", "5cac4d15dd7ad61fc3c4415ce4826fc563d4643dee897a558ec4ea0b1c835c9c", [:rebar3], [{:hpack, "~> 0.3.0", [hex: :hpack_erl, repo: "hexpm", optional: false]}], "hexpm", "4f75b91451338bc0da5f52f3480fa6ef6e3a2aeecfc33686d6b3d0a0948f31aa"}, "cloak": {:hex, :cloak, "1.1.4", "aba387b22ea4d80d92d38ab1890cc528b06e0e7ef2a4581d71c3fdad59e997e7", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "92b20527b9aba3d939fab0dd32ce592ff86361547cfdc87d74edce6f980eb3d7"}, "cloak_ecto": {:hex, :cloak_ecto, "1.3.0", "0de127c857d7452ba3c3367f53fb814b0410ff9c680a8d20fbe8b9a3c57a1118", [:mix], [{:cloak, "~> 1.1.1", [hex: :cloak, repo: "hexpm", optional: false]}, {:ecto, "~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}], "hexpm", "314beb0c123b8a800418ca1d51065b27ba3b15f085977e65c0f7b2adab2de1cc"}, "connection": {:hex, :connection, "1.1.0", "ff2a49c4b75b6fb3e674bfc5536451607270aac754ffd1bdfe175abe4a6d7a68", [:mix], [], "hexpm", "722c1eb0a418fbe91ba7bd59a47e28008a189d47e37e0e7bb85585a016b2869c"}, - "cowboy": {:hex, :cowboy, "2.14.2", "4008be1df6ade45e4f2a4e9e2d22b36d0b5aba4e20b0a0d7049e28d124e34847", [:make, :rebar3], [{:cowlib, ">= 2.16.0 and < 3.0.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, ">= 1.8.0 and < 3.0.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "569081da046e7b41b5df36aa359be71a0c8874e5b9cff6f747073fc57baf1ab9"}, - "cowlib": {:hex, :cowlib, "2.16.0", "54592074ebbbb92ee4746c8a8846e5605052f29309d3a873468d76cdf932076f", [:make, :rebar3], [], "hexpm", "7f478d80d66b747344f0ea7708c187645cfcc08b11aa424632f78e25bf05db51"}, - "credo": {:hex, :credo, "1.7.14", "c7e75216cea8d978ba8c60ed9dede4cc79a1c99a266c34b3600dd2c33b96bc92", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "12a97d6bb98c277e4fb1dff45aaf5c137287416009d214fb46e68147bd9e0203"}, + "cowboy": {:hex, :cowboy, "2.12.0", "f276d521a1ff88b2b9b4c54d0e753da6c66dd7be6c9fca3d9418b561828a3731", [:make, :rebar3], [{:cowlib, "2.13.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "8a7abe6d183372ceb21caa2709bec928ab2b72e18a3911aa1771639bef82651e"}, + "cowlib": {:hex, :cowlib, "2.13.0", "db8f7505d8332d98ef50a3ef34b34c1afddec7506e4ee4dd4a3a266285d282ca", [:make, :rebar3], [], "hexpm", "e1e1284dc3fc030a64b1ad0d8382ae7e99da46c3246b815318a4b848873800a4"}, + "credo": {:hex, :credo, "1.7.11", "d3e805f7ddf6c9c854fd36f089649d7cf6ba74c42bc3795d587814e3c9847102", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "56826b4306843253a66e47ae45e98e7d284ee1f95d53d1612bb483f88a8cf219"}, "ctx": {:hex, :ctx, "0.6.0", "8ff88b70e6400c4df90142e7f130625b82086077a45364a78d208ed3ed53c7fe", [:rebar3], [], "hexpm", "a14ed2d1b67723dbebbe423b28d7615eb0bdcba6ff28f2d1f1b0a7e1d4aa5fc2"}, "curve25519": {:hex, :curve25519, "1.0.5", "f801179424e4012049fcfcfcda74ac04f65d0ffceeb80e7ef1d3352deb09f5bb", [:mix], [], "hexpm", "0fba3ad55bf1154d4d5fc3ae5fb91b912b77b13f0def6ccb3a5d58168ff4192d"}, - "db_connection": {:hex, :db_connection, "2.8.1", "9abdc1e68c34c6163f6fb96a96532272d13ad7ca45262156ae8b7ec6d9dc4bec", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a61a3d489b239d76f326e03b98794fb8e45168396c925ef25feb405ed09da8fd"}, + "db_connection": {:hex, :db_connection, "2.7.0", "b99faa9291bb09892c7da373bb82cba59aefa9b36300f6145c5f201c7adf48ec", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "dcf08f31b2701f857dfc787fbad78223d61a32204f217f15e881dd93e4bdd3ff"}, "decimal": {:hex, :decimal, "2.3.0", "3ad6255aa77b4a3c4f818171b12d237500e63525c2fd056699967a3e7ea20f62", [:mix], [], "hexpm", "a4d66355cb29cb47c3cf30e71329e58361cfcb37c34235ef3bf1d7bf3773aeac"}, "decorator": {:hex, :decorator, "1.4.0", "a57ac32c823ea7e4e67f5af56412d12b33274661bb7640ec7fc882f8d23ac419", [:mix], [], "hexpm", "0a07cedd9083da875c7418dea95b78361197cf2bf3211d743f6f7ce39656597f"}, - "delta_crdt": {:hex, :delta_crdt, "0.6.5", "c7bb8c2c7e60f59e46557ab4e0224f67ba22f04c02826e273738f3dcc4767adc", [:mix], [{:merkle_map, "~> 0.2.0", [hex: :merkle_map, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c6ae23a525d30f96494186dd11bf19ed9ae21d9fe2c1f1b217d492a7cc7294ae"}, - "earmark_parser": {:hex, :earmark_parser, "1.4.44", "f20830dd6b5c77afe2b063777ddbbff09f9759396500cdbe7523efd58d7a339c", [:mix], [], "hexpm", "4778ac752b4701a5599215f7030989c989ffdc4f6df457c5f36938cc2d2a2750"}, - "ecto": {:hex, :ecto, "3.13.5", "9d4a69700183f33bf97208294768e561f5c7f1ecf417e0fa1006e4a91713a834", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "df9efebf70cf94142739ba357499661ef5dbb559ef902b68ea1f3c1fabce36de"}, + "earmark_parser": {:hex, :earmark_parser, "1.4.43", "34b2f401fe473080e39ff2b90feb8ddfeef7639f8ee0bbf71bb41911831d77c5", [:mix], [], "hexpm", "970a3cd19503f5e8e527a190662be2cee5d98eed1ff72ed9b3d1a3d466692de8"}, + "ecto": {:hex, :ecto, "3.12.5", "4a312960ce612e17337e7cefcf9be45b95a3be6b36b6f94dfb3d8c361d631866", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "6eb18e80bef8bb57e17f5a7f068a1719fbda384d40fc37acb8eb8aeca493b6ea"}, "ecto_mysql_extras": {:hex, :ecto_mysql_extras, "0.6.3", "fdd487de7f7fd949f2e599fd205cda23bc47c34fdf8e972088b912aea45eb0e4", [:mix], [{:ecto_sql, "~> 3.7", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:myxql, "~> 0.5", [hex: :myxql, repo: "hexpm", optional: false]}, {:table_rex, "~> 3.1 or ~> 4.0", [hex: :table_rex, repo: "hexpm", optional: true]}], "hexpm", "24bd4f41e621f434c1d8312e4d76a513828f59cbc35191865e574dbfd893cd80"}, - "ecto_psql_extras": {:hex, :ecto_psql_extras, "0.8.8", "aa02529c97f69aed5722899f5dc6360128735a92dd169f23c5d50b1f7fdede08", [:mix], [{:ecto_sql, "~> 3.7", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:postgrex, "> 0.16.0", [hex: :postgrex, repo: "hexpm", optional: false]}, {:table_rex, "~> 3.1.1 or ~> 4.0", [hex: :table_rex, repo: "hexpm", optional: false]}], "hexpm", "04c63d92b141723ad6fed2e60a4b461ca00b3594d16df47bbc48f1f4534f2c49"}, - "ecto_sql": {:hex, :ecto_sql, "3.13.2", "a07d2461d84107b3d037097c822ffdd36ed69d1cf7c0f70e12a3d1decf04e2e1", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.13.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.7", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.19 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "539274ab0ecf1a0078a6a72ef3465629e4d6018a3028095dc90f60a19c371717"}, - "ed25519": {:hex, :ed25519, "1.4.3", "d1422c643fb691f8efc65e66c733bcc92338485858a9469f24a528b915809377", [:mix], [], "hexpm", "37f9de6be4a0e67d56f1b69ec2b79d4d96fea78365f45f5d5d344c48cf81d487"}, + "ecto_psql_extras": {:hex, :ecto_psql_extras, "0.8.3", "0c1df205bd051eaf599b3671e75356b121aa71eac09b63ecf921cb1a080c072e", [:mix], [{:ecto_sql, "~> 3.7", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:postgrex, "> 0.16.0 and < 0.20.0", [hex: :postgrex, repo: "hexpm", optional: false]}, {:table_rex, "~> 3.1.1 or ~> 4.0.0", [hex: :table_rex, repo: "hexpm", optional: false]}], "hexpm", "d0e35ea160359e759a2993a00c3a5389a9ca7ece6df5d0753fa927f988c7351a"}, + "ecto_sql": {:hex, :ecto_sql, "3.12.1", "c0d0d60e85d9ff4631f12bafa454bc392ce8b9ec83531a412c12a0d415a3a4d0", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.12", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.7", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.19 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "aff5b958a899762c5f09028c847569f7dfb9cc9d63bdb8133bff8a5546de6bf5"}, + "ed25519": {:hex, :ed25519, "1.4.1", "479fb83c3e31987c9cad780e6aeb8f2015fb5a482618cdf2a825c9aff809afc4", [:mix], [], "hexpm", "0dacb84f3faa3d8148e81019ca35f9d8dcee13232c32c9db5c2fb8ff48c80ec7"}, "elixir_make": {:hex, :elixir_make, "0.9.0", "6484b3cd8c0cee58f09f05ecaf1a140a8c97670671a6a0e7ab4dc326c3109726", [:mix], [], "hexpm", "db23d4fd8b757462ad02f8aa73431a426fe6671c80b200d9710caf3d1dd0ffdb"}, "equivalex": {:hex, :equivalex, "1.0.3", "170d9a82ae066e0020dfe1cf7811381669565922eb3359f6c91d7e9a1124ff74", [:mix], [], "hexpm", "46fa311adb855117d36e461b9c0ad2598f72110ad17ad73d7533c78020e045fc"}, - "ex_doc": {:hex, :ex_doc, "0.39.3", "519c6bc7e84a2918b737aec7ef48b96aa4698342927d080437f61395d361dcee", [:mix], [{:earmark_parser, "~> 1.4.44", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "0590955cf7ad3b625780ee1c1ea627c28a78948c6c0a9b0322bd976a079996e1"}, - "file_system": {:hex, :file_system, "1.1.1", "31864f4685b0148f25bd3fbef2b1228457c0c89024ad67f7a81a3ffbc0bbad3a", [:mix], [], "hexpm", "7a15ff97dfe526aeefb090a7a9d3d03aa907e100e262a0f8f7746b78f8f87a5d"}, - "finch": {:hex, :finch, "0.20.0", "5330aefb6b010f424dcbbc4615d914e9e3deae40095e73ab0c1bb0968933cadf", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.6.2 or ~> 1.7", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 1.1", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "2658131a74d051aabfcba936093c903b8e89da9a1b63e430bee62045fa9b2ee2"}, + "ex_doc": {:hex, :ex_doc, "0.37.2", "2a3aa7014094f0e4e286a82aa5194a34dd17057160988b8509b15aa6c292720c", [:mix], [{:earmark_parser, "~> 1.4.42", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "4dfa56075ce4887e4e8b1dcc121cd5fcb0f02b00391fd367ff5336d98fa49049"}, + "file_system": {:hex, :file_system, "1.0.1", "79e8ceaddb0416f8b8cd02a0127bdbababe7bf4a23d2a395b983c1f8b3f73edd", [:mix], [], "hexpm", "4414d1f38863ddf9120720cd976fce5bdde8e91d8283353f0e31850fa89feb9e"}, + "finch": {:hex, :finch, "0.19.0", "c644641491ea854fc5c1bbaef36bfc764e3f08e7185e1f084e35e0672241b76d", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.6.2 or ~> 1.7", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 1.1", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "fc5324ce209125d1e2fa0fcd2634601c52a787aff1cd33ee833664a5af4ea2b6"}, "flame": {:hex, :flame, "0.5.2", "d46c4daa19b8921b71e0e57dc69edc01ce1311b1976c160192b05d4253b336e8", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: true]}, {:jason, ">= 0.0.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "82560ebef6ab3c277875493d0c93494740c930db0b1a3ff1a570eee9206cc6c0"}, "flame_k8s_backend": {:hex, :flame_k8s_backend, "0.5.7", "d1085a0ee47b551da6603018a06c7477cd2023d0a27f21dede8e2a24a17435b7", [:mix], [{:flame, "~> 0.4.0 or ~> 0.5.0", [hex: :flame, repo: "hexpm", optional: false]}], "hexpm", "8eeeb7688f7bf79a2a2bb340f8a66ad757651dff76f7a2e9e92415cb224abc48"}, "flow": {:hex, :flow, "1.2.4", "1dd58918287eb286656008777cb32714b5123d3855956f29aa141ebae456922d", [:mix], [{:gen_stage, "~> 1.0", [hex: :gen_stage, repo: "hexpm", optional: false]}], "hexpm", "874adde96368e71870f3510b91e35bc31652291858c86c0e75359cbdd35eb211"}, - "gen_stage": {:hex, :gen_stage, "1.3.2", "7c77e5d1e97de2c6c2f78f306f463bca64bf2f4c3cdd606affc0100b89743b7b", [:mix], [], "hexpm", "0ffae547fa777b3ed889a6b9e1e64566217413d018cabd825f786e843ffe63e7"}, - "gnat": {:hex, :gnat, "1.12.1", "45ce77e0d8fb68f60bae1c9e3a8d08a1b1774266f2c33a110c1ad5da53b50fc3", [:mix], [{:connection, "~> 1.1", [hex: :connection, repo: "hexpm", optional: false]}, {:cowlib, "~> 2.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 0.5 or ~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}, {:nkeys, "~> 0.2", [hex: :nkeys, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "d38940d3d6641fd923021d5430a15faafdb2c61d7c95c9e68c90071ce1bf4d02"}, - "googleapis": {:hex, :googleapis, "0.1.0", "13770f3f75f5b863fb9acf41633c7bc71bad788f3f553b66481a096d083ee20e", [:mix], [{:protobuf, "~> 0.12", [hex: :protobuf, repo: "hexpm", optional: false]}], "hexpm", "1989a7244fd17d3eb5f3de311a022b656c3736b39740db46506157c4604bd212"}, + "gen_batch_server": {:hex, :gen_batch_server, "0.8.9", "1c6bc0f530bf8c17e8b4acc20c2cc369ffa5bee2b46de01e21410745f24b1bc9", [:rebar3], [], "hexpm", "c8581fe4a4b6bccf91e53ce6a8c7e6c27c8c591bab5408b160166463f5579c22"}, + "gen_stage": {:hex, :gen_stage, "1.2.1", "19d8b5e9a5996d813b8245338a28246307fd8b9c99d1237de199d21efc4c76a1", [:mix], [], "hexpm", "83e8be657fa05b992ffa6ac1e3af6d57aa50aace8f691fcf696ff02f8335b001"}, + "gnat": {:hex, :gnat, "1.9.1", "7fd16d40ba927cc88256fae2a8a96089d4e017f3bf7fb83e2e6f5093893b7461", [:mix], [{:connection, "~> 1.1", [hex: :connection, repo: "hexpm", optional: false]}, {:cowlib, "~> 2.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 0.5 or ~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}, {:nkeys, "~> 0.2", [hex: :nkeys, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "e49eb63c2bbc57b099dd2a5ef602963d395fa2790968054ba9d24dd1390e0ab5"}, "gproc": {:hex, :gproc, "0.9.1", "f1df0364423539cf0b80e8201c8b1839e229e5f9b3ccb944c5834626998f5b8c", [:rebar3], [], "hexpm", "905088e32e72127ed9466f0bac0d8e65704ca5e73ee5a62cb073c3117916d507"}, - "grpc": {:hex, :grpc, "0.11.5", "5dbde9420718b58712779ad98fff1ef50349ca0fa7cc0858ae0f826015068654", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:cowboy, "~> 2.10", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowlib, "~> 2.12", [hex: :cowlib, repo: "hexpm", optional: false]}, {:flow, "~> 1.2", [hex: :flow, repo: "hexpm", optional: false]}, {:googleapis, "~> 0.1.0", [hex: :googleapis, repo: "hexpm", optional: false]}, {:gun, "~> 2.0", [hex: :gun, repo: "hexpm", optional: false]}, {:jason, ">= 0.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mint, "~> 1.5", [hex: :mint, repo: "hexpm", optional: false]}, {:protobuf, "~> 0.14", [hex: :protobuf, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "0a5d8673ef16649bef0903bca01c161acfc148e4d269133b6834b2af1f07f45e"}, - "grpc_reflection": {:hex, :grpc_reflection, "0.3.0", "f93d86d956c5673557e6244aee06d0451454fc8b58f1fbb945962fa546ac5368", [:mix], [{:grpc, "~> 0.11.1", [hex: :grpc, repo: "hexpm", optional: false]}, {:protobuf, "~> 0.15.0", [hex: :protobuf, repo: "hexpm", optional: false]}], "hexpm", "ff6ebd1ea1a3e14ed779f77f9b831915bbfef45796573ab022d24819d5e9886e"}, + "grpc": {:hex, :grpc, "0.9.0", "1b930a57272d4356ea65969b984c2eb04f3dab81420e1e28f0e6ec04b8f88515", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:cowboy, "~> 2.10", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowlib, "~> 2.12", [hex: :cowlib, repo: "hexpm", optional: false]}, {:gun, "~> 2.0", [hex: :gun, repo: "hexpm", optional: false]}, {:jason, ">= 0.0.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mint, "~> 1.5", [hex: :mint, repo: "hexpm", optional: false]}, {:protobuf, "~> 0.11", [hex: :protobuf, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7c059698248738fcf7ad551f1d78f4a3d2e0642a72a5834f2a0b0db4b9f3d2b5"}, + "grpc_reflection": {:hex, :grpc_reflection, "0.1.5", "d00cdf8ef2638edb9578248eedc742e1b34eda9100e61be764c552c10f4b46cb", [:mix], [{:grpc, "~> 0.9", [hex: :grpc, repo: "hexpm", optional: false]}, {:protobuf, "~> 0.14", [hex: :protobuf, repo: "hexpm", optional: false]}], "hexpm", "848334d16029aee33728603be6171fc8bfcdfa3508cd6885ec1729e2e6ac60a5"}, "grpcbox": {:hex, :grpcbox, "0.17.1", "6e040ab3ef16fe699ffb513b0ef8e2e896da7b18931a1ef817143037c454bcce", [:rebar3], [{:acceptor_pool, "~> 1.0.0", [hex: :acceptor_pool, repo: "hexpm", optional: false]}, {:chatterbox, "~> 0.15.1", [hex: :ts_chatterbox, repo: "hexpm", optional: false]}, {:ctx, "~> 0.6.0", [hex: :ctx, repo: "hexpm", optional: false]}, {:gproc, "~> 0.9.1", [hex: :gproc, repo: "hexpm", optional: false]}], "hexpm", "4a3b5d7111daabc569dc9cbd9b202a3237d81c80bf97212fbc676832cb0ceb17"}, - "gun": {:hex, :gun, "2.2.0", "b8f6b7d417e277d4c2b0dc3c07dfdf892447b087f1cc1caff9c0f556b884e33d", [:make, :rebar3], [{:cowlib, ">= 2.15.0 and < 3.0.0", [hex: :cowlib, repo: "hexpm", optional: false]}], "hexpm", "76022700c64287feb4df93a1795cff6741b83fb37415c40c34c38d2a4645261a"}, + "gun": {:hex, :gun, "2.1.0", "b4e4cbbf3026d21981c447e9e7ca856766046eff693720ba43114d7f5de36e87", [:make, :rebar3], [{:cowlib, "2.13.0", [hex: :cowlib, repo: "hexpm", optional: false]}], "hexpm", "52fc7fc246bfc3b00e01aea1c2854c70a366348574ab50c57dfe796d24a0101d"}, "highlander": {:hex, :highlander, "0.2.1", "e59b459f857e89daf73f2598bf2b2c0479a435481e6101ea389fd3625919b052", [:mix], [], "hexpm", "5ba19a18358803d82a923511acec8ee85fac30731c5ca056f2f934bc3d3afd9a"}, - "horde": {:hex, :horde, "0.10.0", "31c6a633057c3ec4e73064d7b11ba409c9f3c518aa185377d76bee441b76ceb0", [:mix], [{:delta_crdt, "~> 0.6.2", [hex: :delta_crdt, repo: "hexpm", optional: false]}, {:libring, "~> 1.7", [hex: :libring, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:telemetry_poller, "~> 0.5.0 or ~> 1.0", [hex: :telemetry_poller, repo: "hexpm", optional: false]}], "hexpm", "0b51c435cb698cac9bf9c17391dce3ebb1376ae6154c81f077fc61db771b9432"}, "hpack": {:hex, :hpack_erl, "0.3.0", "2461899cc4ab6a0ef8e970c1661c5fc6a52d3c25580bc6dd204f84ce94669926", [:rebar3], [], "hexpm", "d6137d7079169d8c485c6962dfe261af5b9ef60fbc557344511c1e65e3d95fb0"}, "hpax": {:hex, :hpax, "0.1.2", "09a75600d9d8bbd064cdd741f21fc06fc1f4cf3d0fcc335e5aa19be1a7235c84", [:mix], [], "hexpm", "2c87843d5a23f5f16748ebe77969880e29809580efdaccd615cd3bed628a8c13"}, - "iter": {:hex, :iter, "0.1.3", "6f8ce1c682f9ee8252c28b8e2867713135ab9e62ff7873071619538ef2acf6c5", [:mix], [], "hexpm", "735f9846185cd1cb37876bb1b2ac2e96346fdea19bd2a8d041d3a59e7a9898be"}, + "iter": {:hex, :iter, "0.1.2", "bd5dbba48ba67e0f134889a4a29f2b377db6cdcee0661f3c29439e7b649e317a", [:mix], [], "hexpm", "e79f53ed36105ae72582fd3ef224ca2539ccc00cdc27e6e7fe69c49119c4e39b"}, "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, "jetstream": {:hex, :jetstream, "0.0.9", "f5943c992a98cedd11015436d054c14d1eec544884db0ba959f700363c60fa8f", [:mix], [{:broadway, "~> 1.0", [hex: :broadway, repo: "hexpm", optional: true]}, {:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:gnat, "~> 1.1", [hex: :gnat, repo: "hexpm", optional: false]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "ca519b254d8b0720865bcf371f50c6122c846d70d25d11fae648b46617577bc1"}, - "k8s": {:hex, :k8s, "2.8.0", "fe92de96bcb3541c6b2893f251b1fc93e2331b6006cb5897e9a4936fad06298e", [:mix], [{:castore, "~> 1.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mint, "~> 1.0", [hex: :mint, repo: "hexpm", optional: false]}, {:mint_web_socket, "~> 1.0", [hex: :mint_web_socket, repo: "hexpm", optional: false]}, {:poolboy, "~> 1.5", [hex: :poolboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:yaml_elixir, "~> 2.8", [hex: :yaml_elixir, repo: "hexpm", optional: false]}], "hexpm", "90f15a713d2d0f7bf84a35c0d855c0ece62a40b4dbe9d08de3cf88d9d323269e"}, + "k8s": {:hex, :k8s, "2.6.2", "c5d6e51398371704322e64be0527179be619a4e6c82c07bb5729c66e1ee7bdf5", [:mix], [{:castore, "~> 1.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mint, "~> 1.0", [hex: :mint, repo: "hexpm", optional: false]}, {:mint_web_socket, "~> 1.0", [hex: :mint_web_socket, repo: "hexpm", optional: false]}, {:poolboy, "~> 1.5", [hex: :poolboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:yaml_elixir, "~> 2.8", [hex: :yaml_elixir, repo: "hexpm", optional: false]}], "hexpm", "e75c3b4c430e54efdccaf7201931c00f98f8540d3e3f439c3d3b2d72bb6cb9ba"}, "k8s_webhoox": {:hex, :k8s_webhoox, "0.2.0", "5ef0968a426a0e5d168dd54db7075e0ee222dddfa5da2cf29f25f01a7d02ffd0", [:mix], [{:k8s, "~> 2.0", [hex: :k8s, repo: "hexpm", optional: false]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}, {:pluggable, "~> 1.0", [hex: :pluggable, repo: "hexpm", optional: false]}, {:x509, "~> 0.8.5", [hex: :x509, repo: "hexpm", optional: false]}, {:yaml_elixir, "~> 2.0", [hex: :yaml_elixir, repo: "hexpm", optional: false]}], "hexpm", "4917e1bf43bcbae3c2fa53fa4206f444cc029e757dc4e2b7d550cb0ae8752543"}, - "kcl": {:hex, :kcl, "1.4.4", "b02fbacc42640096a2ac5f558bb31c60c91de4a1c3eb89692a549500bcc97305", [:mix], [{:curve25519, ">= 1.0.4", [hex: :curve25519, repo: "hexpm", optional: false]}, {:ed25519, "~> 1.3", [hex: :ed25519, repo: "hexpm", optional: false]}, {:poly1305, "~> 1.0", [hex: :poly1305, repo: "hexpm", optional: false]}, {:salsa20, "~> 1.0", [hex: :salsa20, repo: "hexpm", optional: false]}], "hexpm", "d156c708e8c3cadf204e21cac7f239795917614660b205c731f385c8098c39ae"}, + "kcl": {:hex, :kcl, "1.4.2", "8b73a55a14899dc172fcb05a13a754ac171c8165c14f65043382d567922f44ab", [:mix], [{:curve25519, ">= 1.0.4", [hex: :curve25519, repo: "hexpm", optional: false]}, {:ed25519, "~> 1.3", [hex: :ed25519, repo: "hexpm", optional: false]}, {:poly1305, "~> 1.0", [hex: :poly1305, repo: "hexpm", optional: false]}, {:salsa20, "~> 1.0", [hex: :salsa20, repo: "hexpm", optional: false]}], "hexpm", "9f083dd3844d902df6834b258564a82b21a15eb9f6acdc98e8df0c10feeabf05"}, "libcluster": {:hex, :libcluster, "3.5.0", "5ee4cfde4bdf32b2fef271e33ce3241e89509f4344f6c6a8d4069937484866ba", [:mix], [{:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.3", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ebf6561fcedd765a4cd43b4b8c04b1c87f4177b5fb3cbdfe40a780499d72f743"}, "libring": {:hex, :libring, "1.7.0", "4f245d2f1476cd7ed8f03740f6431acba815401e40299208c7f5c640e1883bda", [:mix], [], "hexpm", "070e3593cb572e04f2c8470dd0c119bc1817a7a0a7f88229f43cf0345268ec42"}, "makeup": {:hex, :makeup, "1.2.1", "e90ac1c65589ef354378def3ba19d401e739ee7ee06fb47f94c687016e3713d1", [:mix], [{:nimble_parsec, "~> 1.4", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "d36484867b0bae0fea568d10131197a4c2e47056a6fbe84922bf6ba71c8d17ce"}, "makeup_elixir": {:hex, :makeup_elixir, "1.0.1", "e928a4f984e795e41e3abd27bfc09f51db16ab8ba1aebdba2b3a575437efafc2", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "7284900d412a3e5cfd97fdaed4f5ed389b8f2b4cb49efc0eb3bd10e2febf9507"}, "makeup_erlang": {:hex, :makeup_erlang, "1.0.2", "03e1804074b3aa64d5fad7aa64601ed0fb395337b982d9bcf04029d68d51b6a7", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "af33ff7ef368d5893e4a267933e7744e46ce3cf1f61e2dccf53a111ed3aa3727"}, "merkle_map": {:hex, :merkle_map, "0.2.1", "01a88c87a6b9fb594c67c17ebaf047ee55ffa34e74297aa583ed87148006c4c8", [:mix], [], "hexpm", "fed4d143a5c8166eee4fa2b49564f3c4eace9cb252f0a82c1613bba905b2d04d"}, - "mime": {:hex, :mime, "2.0.7", "b8d739037be7cd402aee1ba0306edfdef982687ee7e9859bee6198c1e7e2f128", [:mix], [], "hexpm", "6171188e399ee16023ffc5b76ce445eb6d9672e2e241d2df6050f3c771e80ccd"}, + "mime": {:hex, :mime, "2.0.6", "8f18486773d9b15f95f4f4f1e39b710045fa1de891fada4516559967276e4dc2", [:mix], [], "hexpm", "c9945363a6b26d747389aac3643f8e0e09d30499a138ad64fe8fd1d13d9b153e"}, "mint": {:hex, :mint, "1.7.1", "113fdb2b2f3b59e47c7955971854641c61f378549d73e829e1768de90fc1abf1", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1 or ~> 0.2.0 or ~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "fceba0a4d0f24301ddee3024ae116df1c3f4bb7a563a731f45fdfeb9d39a231b"}, - "mint_web_socket": {:hex, :mint_web_socket, "1.0.5", "60354efeb49b1eccf95dfb75f55b08d692e211970fe735a5eb3188b328be2a90", [:mix], [{:mint, ">= 1.4.1 and < 2.0.0-0", [hex: :mint, repo: "hexpm", optional: false]}], "hexpm", "04b35663448fc758f3356cce4d6ac067ca418bbafe6972a3805df984b5f12e61"}, - "mnesiac": {:hex, :mnesiac, "0.3.14", "5ea3f1f3e615073629d0822bcf2297be73149beee2d1f7e482c1943894f59b53", [:mix], [{:libcluster, "~> 3.3", [hex: :libcluster, repo: "hexpm", optional: true]}], "hexpm", "e51b38bf983b9320aba56d5dce79dbf50cbff07f7495e70b89eb45461b8d32fa"}, - "myxql": {:hex, :myxql, "0.8.0", "60c60e87c7320d2f5759416aa1758c8e7534efbae07b192861977f8455e35acd", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:geo, "~> 3.4 or ~> 4.0", [hex: :geo, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "1ec0ceb26fb3cd0f8756519cf4f0e4f9348177a020705223bdf4742a2c44d774"}, - "nebulex": {:hex, :nebulex, "2.6.5", "b1caa82ef46e9cf8c28f170b432c6938747741ab5d84b2c944277180b8ad8695", [:mix], [{:decorator, "~> 1.4", [hex: :decorator, repo: "hexpm", optional: true]}, {:shards, "~> 1.1", [hex: :shards, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "4eb4092058ba53289cb4d5a1b109de6fd094883dfc84a1c2f2ccc57e61a24935"}, + "mint_web_socket": {:hex, :mint_web_socket, "1.0.4", "0b539116dbb3d3f861cdf5e15e269a933cb501c113a14db7001a3157d96ffafd", [:mix], [{:mint, ">= 1.4.1 and < 2.0.0-0", [hex: :mint, repo: "hexpm", optional: false]}], "hexpm", "027d4c5529c45a4ba0ce27a01c0f35f284a5468519c045ca15f43decb360a991"}, + "nebulex": {:hex, :nebulex, "2.6.4", "4b00706e0e676474783d988962abf74614480e13c0a32645acb89bb32b660e09", [:mix], [{:decorator, "~> 1.4", [hex: :decorator, repo: "hexpm", optional: true]}, {:shards, "~> 1.1", [hex: :shards, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "25bdabf3fb86035c8151bba60bda20f80f96ae0261db7bd4090878ff63b03581"}, "nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"}, "nimble_parsec": {:hex, :nimble_parsec, "1.4.2", "8efba0122db06df95bfaa78f791344a89352ba04baedd3849593bfce4d0dc1c6", [:mix], [], "hexpm", "4b21398942dda052b403bbe1da991ccd03a053668d147d53fb8c4e0efe09c973"}, "nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"}, - "nkeys": {:hex, :nkeys, "0.3.1", "5129d5df23f6762b26b1c18506942958015f4d81174ab15bf42e9509e4a45b2c", [:mix], [{:ed25519, "~> 1.3", [hex: :ed25519, repo: "hexpm", optional: false]}, {:kcl, "~> 1.4", [hex: :kcl, repo: "hexpm", optional: false]}], "hexpm", "80d8d1d62ac9c5127ad776d8f435e5e1cc732985f6235b22e6c157808b44c108"}, + "nkeys": {:hex, :nkeys, "0.3.0", "837add5261a3cdd8ff75b54e0475062313093929ab5e042fa48e010f33b10d16", [:mix], [{:ed25519, "~> 1.3", [hex: :ed25519, repo: "hexpm", optional: false]}, {:kcl, "~> 1.4", [hex: :kcl, repo: "hexpm", optional: false]}], "hexpm", "b5af773a296620ee8eeb1ec6dc5b68f716386f7e53f7bda8c4ac23515823dfe4"}, "observer_cli": {:hex, :observer_cli, "1.8.1", "edfe0c0f983631961599326f239f6e99750aba7387515002b1284dcfe7fcd6d2", [:mix, :rebar3], [{:recon, "~> 2.5.6", [hex: :recon, repo: "hexpm", optional: false]}], "hexpm", "a3cd6300dd8290ade93d688fbd79c872e393b01256309dd7a653feb13c434fb4"}, - "opentelemetry": {:hex, :opentelemetry, "1.7.0", "20d0f12d3d1c398d3670fd44fd1a7c495dd748ab3e5b692a7906662e2fb1a38a", [:rebar3], [{:opentelemetry_api, "~> 1.5.0", [hex: :opentelemetry_api, repo: "hexpm", optional: false]}], "hexpm", "a9173b058c4549bf824cbc2f1d2fa2adc5cdedc22aa3f0f826951187bbd53131"}, - "opentelemetry_api": {:hex, :opentelemetry_api, "1.5.0", "1a676f3e3340cab81c763e939a42e11a70c22863f645aa06aafefc689b5550cf", [:mix, :rebar3], [], "hexpm", "f53ec8a1337ae4a487d43ac89da4bd3a3c99ddf576655d071deed8b56a2d5dda"}, + "opentelemetry": {:hex, :opentelemetry, "1.5.0", "7dda6551edfc3050ea4b0b40c0d2570423d6372b97e9c60793263ef62c53c3c2", [:rebar3], [{:opentelemetry_api, "~> 1.4", [hex: :opentelemetry_api, repo: "hexpm", optional: false]}], "hexpm", "cdf4f51d17b592fc592b9a75f86a6f808c23044ba7cf7b9534debbcc5c23b0ee"}, + "opentelemetry_api": {:hex, :opentelemetry_api, "1.4.0", "63ca1742f92f00059298f478048dfb826f4b20d49534493d6919a0db39b6db04", [:mix, :rebar3], [], "hexpm", "3dfbbfaa2c2ed3121c5c483162836c4f9027def469c41578af5ef32589fcfc58"}, "opentelemetry_ecto": {:hex, :opentelemetry_ecto, "1.2.0", "2382cb47ddc231f953d3b8263ed029d87fbf217915a1da82f49159d122b64865", [:mix], [{:opentelemetry_api, "~> 1.0", [hex: :opentelemetry_api, repo: "hexpm", optional: false]}, {:opentelemetry_process_propagator, "~> 0.2", [hex: :opentelemetry_process_propagator, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "70dfa2e79932e86f209df00e36c980b17a32f82d175f0068bf7ef9a96cf080cf"}, - "opentelemetry_exporter": {:hex, :opentelemetry_exporter, "1.10.0", "972e142392dbfa679ec959914664adefea38399e4f56ceba5c473e1cabdbad79", [:rebar3], [{:grpcbox, ">= 0.0.0", [hex: :grpcbox, repo: "hexpm", optional: false]}, {:opentelemetry, "~> 1.7.0", [hex: :opentelemetry, repo: "hexpm", optional: false]}, {:opentelemetry_api, "~> 1.5.0", [hex: :opentelemetry_api, repo: "hexpm", optional: false]}, {:tls_certificate_check, "~> 1.18", [hex: :tls_certificate_check, repo: "hexpm", optional: false]}], "hexpm", "33a116ed7304cb91783f779dec02478f887c87988077bfd72840f760b8d4b952"}, + "opentelemetry_exporter": {:hex, :opentelemetry_exporter, "1.8.0", "5d546123230771ef4174e37bedfd77e3374913304cd6ea3ca82a2add49cd5d56", [:rebar3], [{:grpcbox, ">= 0.0.0", [hex: :grpcbox, repo: "hexpm", optional: false]}, {:opentelemetry, "~> 1.5.0", [hex: :opentelemetry, repo: "hexpm", optional: false]}, {:opentelemetry_api, "~> 1.4.0", [hex: :opentelemetry_api, repo: "hexpm", optional: false]}, {:tls_certificate_check, "~> 1.18", [hex: :tls_certificate_check, repo: "hexpm", optional: false]}], "hexpm", "a1f9f271f8d3b02b81462a6bfef7075fd8457fdb06adff5d2537df5e2264d9af"}, "opentelemetry_process_propagator": {:hex, :opentelemetry_process_propagator, "0.3.0", "ef5b2059403a1e2b2d2c65914e6962e56371570b8c3ab5323d7a8d3444fb7f84", [:mix, :rebar3], [{:opentelemetry_api, "~> 1.0", [hex: :opentelemetry_api, repo: "hexpm", optional: false]}], "hexpm", "7243cb6de1523c473cba5b1aefa3f85e1ff8cc75d08f367104c1e11919c8c029"}, - "phoenix_pubsub": {:hex, :phoenix_pubsub, "2.2.0", "ff3a5616e1bed6804de7773b92cbccfc0b0f473faf1f63d7daf1206c7aeaaa6f", [:mix], [], "hexpm", "adc313a5bf7136039f63cfd9668fde73bba0765e0614cba80c06ac9460ff3e96"}, - "phoenix_pubsub_nats": {:hex, :phoenix_pubsub_nats, "0.2.3", "a735d63e77c2639a21369e4e6553a4a88f6ee828adc26157fc016b1087df6a38", [:mix], [{:gnat, "~> 1.10", [hex: :gnat, repo: "hexpm", optional: false]}, {:jason, "~> 1.3", [hex: :jason, repo: "hexpm", optional: false]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}], "hexpm", "3a4bfe68787a5eda3c9240d893ef289cb972d9b5a070251fd173defef379b3f4"}, - "plug": {:hex, :plug, "1.18.1", "5067f26f7745b7e31bc3368bc1a2b818b9779faa959b49c934c17730efc911cf", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "57a57db70df2b422b564437d2d33cf8d33cd16339c1edb190cd11b1a3a546cc2"}, - "plug_crypto": {:hex, :plug_crypto, "2.1.1", "19bda8184399cb24afa10be734f84a16ea0a2bc65054e23a62bb10f06bc89491", [:mix], [], "hexpm", "6470bce6ffe41c8bd497612ffde1a7e4af67f36a15eea5f921af71cf3e11247c"}, + "phoenix_pubsub": {:hex, :phoenix_pubsub, "2.1.3", "3168d78ba41835aecad272d5e8cd51aa87a7ac9eb836eabc42f6e57538e3731d", [:mix], [], "hexpm", "bba06bc1dcfd8cb086759f0edc94a8ba2bc8896d5331a1e2c2902bf8e36ee502"}, + "phoenix_pubsub_nats": {:hex, :phoenix_pubsub_nats, "0.2.2", "aedfbda3552299a399cc5d1486f05c313f9eb81e0364e9916e6b3b9ffb40ff41", [:mix], [{:gnat, "~> 1.6", [hex: :gnat, repo: "hexpm", optional: false]}, {:jason, "~> 1.3", [hex: :jason, repo: "hexpm", optional: false]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}], "hexpm", "63d117a379f5cc6ba3f9b61a322f821365d3a9b197e43243e0e3b7e47b462a7d"}, + "plug": {:hex, :plug, "1.16.1", "40c74619c12f82736d2214557dedec2e9762029b2438d6d175c5074c933edc9d", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a13ff6b9006b03d7e33874945b2755253841b238c34071ed85b0e86057f8cddc"}, + "plug_crypto": {:hex, :plug_crypto, "2.1.0", "f44309c2b06d249c27c8d3f65cfe08158ade08418cf540fd4f72d4d6863abb7b", [:mix], [], "hexpm", "131216a4b030b8f8ce0f26038bc4421ae60e4bb95c5cf5395e1421437824c4fa"}, "pluggable": {:hex, :pluggable, "1.1.0", "7eba3bc70c0caf4d9056c63c882df8862f7534f0145da7ab3a47ca73e4adb1e4", [:mix], [], "hexpm", "d12eb00ea47b21e92cd2700d6fbe3737f04b64e71b63aad1c0accde87c751637"}, "poly1305": {:hex, :poly1305, "1.0.4", "7cdc8961a0a6e00a764835918cdb8ade868044026df8ef5d718708ea6cc06611", [:mix], [{:chacha20, "~> 1.0", [hex: :chacha20, repo: "hexpm", optional: false]}, {:equivalex, "~> 1.0", [hex: :equivalex, repo: "hexpm", optional: false]}], "hexpm", "e14e684661a5195e149b3139db4a1693579d4659d65bba115a307529c47dbc3b"}, "poolboy": {:hex, :poolboy, "1.5.2", "392b007a1693a64540cead79830443abf5762f5d30cf50bc95cb2c1aaafa006b", [:rebar3], [], "hexpm", "dad79704ce5440f3d5a3681c8590b9dc25d1a561e8f5a9c995281012860901e3"}, - "postgrex": {:hex, :postgrex, "0.21.1", "2c5cc830ec11e7a0067dd4d623c049b3ef807e9507a424985b8dcf921224cd88", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "27d8d21c103c3cc68851b533ff99eef353e6a0ff98dc444ea751de43eb48bdac"}, - "protobuf": {:hex, :protobuf, "0.15.0", "c9fc1e9fc1682b05c601df536d5ff21877b55e2023e0466a3855cc1273b74dcb", [:mix], [{:jason, "~> 1.2", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "5d7bb325319db1d668838d2691c31c7b793c34111aec87d5ee467a39dac6e051"}, - "protobuf_generate": {:hex, :protobuf_generate, "0.2.0", "51debf7ea8840f201a4d1f1600df45a88205ed589402b706c463a58720da6577", [:mix], [{:protobuf, "~> 0.12", [hex: :protobuf, repo: "hexpm", optional: false]}], "hexpm", "0f8e167a03b893b0b7b48dfe206f3d7df166ccce70ae89db35b3d30e8ca358a1"}, - "ranch": {:hex, :ranch, "2.2.0", "25528f82bc8d7c6152c57666ca99ec716510fe0925cb188172f41ce93117b1b0", [:make, :rebar3], [], "hexpm", "fa0b99a1780c80218a4197a59ea8d3bdae32fbff7e88527d7d8a4787eff4f8e7"}, + "postgrex": {:hex, :postgrex, "0.19.3", "a0bda6e3bc75ec07fca5b0a89bffd242ca209a4822a9533e7d3e84ee80707e19", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "d31c28053655b78f47f948c85bb1cf86a9c1f8ead346ba1aa0d0df017fa05b61"}, + "protobuf": {:hex, :protobuf, "0.14.1", "9ac0582170df27669ccb2ef6cb0a3d55020d58896edbba330f20d0748881530a", [:mix], [{:jason, "~> 1.2", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "39a9d49d346e3ed597e5ae3168a43d9603870fc159419617f584cdf6071f0e25"}, + "protobuf_generate": {:hex, :protobuf_generate, "0.1.3", "57841bc60e2135e190748119d83f78669ee7820c0ad6555ada3cd3cd7df93143", [:mix], [{:protobuf, "~> 0.12", [hex: :protobuf, repo: "hexpm", optional: false]}], "hexpm", "dae4139b00ba77a279251a0ceb5593b1bae745e333b4ce1ab7e81e8e4906016b"}, + "ra": {:hex, :ra, "2.16.6", "1af93ba2c3be43ff7f499e08f1a6a5ce71c9b635c5c8bcf1b9343e93030b41a6", [:rebar3], [{:aten, "0.6.0", [hex: :aten, repo: "hexpm", optional: false]}, {:gen_batch_server, "0.8.9", [hex: :gen_batch_server, repo: "hexpm", optional: false]}, {:seshat, "0.6.0", [hex: :seshat, repo: "hexpm", optional: false]}], "hexpm", "66268b9454ed402dd5ee2252e3d1b238e972976188ff721ba0666cdaf24d414e"}, + "ra_registry": {:hex, :ra_registry, "0.1.0", "b96f4b9c356e01a8c1721ffbf942627a92dadf5711b5bc9841fb02ef3ac7e470", [:mix], [{:ra, "~> 2.7", [hex: :ra, repo: "hexpm", optional: false]}], "hexpm", "44aac6b644496860da78b32a78282f2ebc760490e74f2e11e9310b1b797582e2"}, + "ranch": {:hex, :ranch, "1.8.0", "8c7a100a139fd57f17327b6413e4167ac559fbc04ca7448e9be9057311597a1d", [:make, :rebar3], [], "hexpm", "49fbcfd3682fab1f5d109351b61257676da1a2fdbe295904176d5e521a2ddfe5"}, "recon": {:hex, :recon, "2.5.6", "9052588e83bfedfd9b72e1034532aee2a5369d9d9343b61aeb7fbce761010741", [:mix, :rebar3], [], "hexpm", "96c6799792d735cc0f0fd0f86267e9d351e63339cbe03df9d162010cefc26bb0"}, - "retry": {:hex, :retry, "0.19.0", "aeb326d87f62295d950f41e1255fe6f43280a1b390d36e280b7c9b00601ccbc2", [:mix], [], "hexpm", "85ef376aa60007e7bff565c366310966ec1bd38078765a0e7f20ec8a220d02ca"}, + "retry": {:hex, :retry, "0.18.0", "dc58ebe22c95aa00bc2459f9e0c5400e6005541cf8539925af0aa027dc860543", [:mix], [], "hexpm", "9483959cc7bf69c9e576d9dfb2b678b71c045d3e6f39ab7c9aa1489df4492d73"}, "salsa20": {:hex, :salsa20, "1.0.4", "404cbea1fa8e68a41bcc834c0a2571ac175580fec01cc38cc70c0fb9ffc87e9b", [:mix], [], "hexpm", "745ddcd8cfa563ddb0fd61e7ce48d5146279a2cf7834e1da8441b369fdc58ac6"}, + "seshat": {:hex, :seshat, "0.6.0", "3172eb1d7a2a4f66108cd6933a4e465aff80f84aa90ed83f047b92f636123ccd", [:rebar3], [], "hexpm", "7cef700f92831dd7cae6a6dd223ccc55ac88ecce0631ee9ab0f2b5fb70e79b90"}, "shards": {:hex, :shards, "1.1.1", "8b42323457d185b26b15d05187784ce6c5d1e181b35c46fca36c45f661defe02", [:make, :rebar3], [], "hexpm", "169a045dae6668cda15fbf86d31bf433d0dbbaec42c8c23ca4f8f2d405ea8eda"}, "sobelow": {:hex, :sobelow, "0.13.0", "218afe9075904793f5c64b8837cc356e493d88fddde126a463839351870b8d1e", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "cd6e9026b85fc35d7529da14f95e85a078d9dd1907a9097b3ba6ac7ebbe34a0d"}, "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.7", "354c321cf377240c7b8716899e182ce4890c5938111a1296add3ec74cf1715df", [:make, :mix, :rebar3], [], "hexpm", "fe4c190e8f37401d30167c8c405eda19469f34577987c76dde613e838bbc67f8"}, - "table_rex": {:hex, :table_rex, "4.1.0", "fbaa8b1ce154c9772012bf445bfb86b587430fb96f3b12022d3f35ee4a68c918", [:mix], [], "hexpm", "95932701df195d43bc2d1c6531178fc8338aa8f38c80f098504d529c43bc2601"}, + "table_rex": {:hex, :table_rex, "4.0.0", "3c613a68ebdc6d4d1e731bc973c233500974ec3993c99fcdabb210407b90959b", [:mix], [], "hexpm", "c35c4d5612ca49ebb0344ea10387da4d2afe278387d4019e4d8111e815df8f55"}, "telemetry": {:hex, :telemetry, "1.3.0", "fedebbae410d715cf8e7062c96a1ef32ec22e764197f70cda73d82778d61e7a2", [:rebar3], [], "hexpm", "7015fc8919dbe63764f4b4b87a95b7c0996bd539e0d499be6ec9d7f3875b79e6"}, "telemetry_metrics": {:hex, :telemetry_metrics, "1.1.0", "5bd5f3b5637e0abea0426b947e3ce5dd304f8b3bc6617039e2b5a008adc02f8f", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "e7b79e8ddfde70adb6db8a6623d1778ec66401f366e9a8f5dd0955c56bc8ce67"}, "telemetry_metrics_prometheus_core": {:hex, :telemetry_metrics_prometheus_core, "1.2.1", "c9755987d7b959b557084e6990990cb96a50d6482c683fb9622a63837f3cd3d8", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6 or ~> 1.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "5e2c599da4983c4f88a33e9571f1458bf98b0cf6ba930f1dc3a6e8cf45d5afb6"}, - "telemetry_poller": {:hex, :telemetry_poller, "1.3.0", "d5c46420126b5ac2d72bc6580fb4f537d35e851cc0f8dbd571acf6d6e10f5ec7", [:rebar3], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "51f18bed7128544a50f75897db9974436ea9bfba560420b646af27a9a9b35211"}, + "telemetry_poller": {:hex, :telemetry_poller, "1.1.0", "58fa7c216257291caaf8d05678c8d01bd45f4bdbc1286838a28c4bb62ef32999", [:rebar3], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "9eb9d9cbfd81cbd7cdd24682f8711b6e2b691289a0de6826e58452f28c103c8f"}, "thousand_island": {:hex, :thousand_island, "1.3.9", "095db3e2650819443e33237891271943fad3b7f9ba341073947581362582ab5a", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "25ab4c07badadf7f87adb4ab414e0ed374e5f19e72503aa85132caa25776e54f"}, - "tls_certificate_check": {:hex, :tls_certificate_check, "1.30.0", "ef9bdfcb5b551b747cad231a65ebd449623628bb72471c1d2aefcbbc5730683d", [:rebar3], [{:ssl_verify_fun, "~> 1.1", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm", "dfbada8fddb80ae19b8aeaac34d95cfce98c91393b0db9c3ee079b488fd68d81"}, + "tls_certificate_check": {:hex, :tls_certificate_check, "1.25.0", "702b1835fe718a52310509537392abd067dbe941ebc05fe72409d2b2f8061651", [:rebar3], [{:ssl_verify_fun, "~> 1.1", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm", "167343ccf50538cf2faf61a3f1460e749b3edf2ecef55516af2b5834362abcb1"}, "uuid": {:hex, :uuid, "1.1.8", "e22fc04499de0de3ed1116b770c7737779f226ceefa0badb3592e64d5cfb4eb9", [:mix], [], "hexpm", "c790593b4c3b601f5dc2378baae7efaf5b3d73c4c6456ba85759905be792f2ac"}, "websock": {:hex, :websock, "0.5.3", "2f69a6ebe810328555b6fe5c831a851f485e303a7c8ce6c5f675abeb20ebdadc", [:mix], [], "hexpm", "6105453d7fac22c712ad66fab1d45abdf049868f253cf719b625151460b8b453"}, "x509": {:hex, :x509, "0.8.10", "5d1ec6d5f4db31982f9dc34e6a1eebd631d04599e0b6c1c259f1dadd4495e11f", [:mix], [], "hexpm", "a191221665af28b9bdfff0c986ef55f80e126d8ce751bbdf6cefa846410140c0"}, "yamerl": {:hex, :yamerl, "0.10.0", "4ff81fee2f1f6a46f1700c0d880b24d193ddb74bd14ef42cb0bcf46e81ef2f8e", [:rebar3], [], "hexpm", "346adb2963f1051dc837a2364e4acf6eb7d80097c0f53cbdc3046ec8ec4b4e6e"}, - "yaml_elixir": {:hex, :yaml_elixir, "2.12.0", "30343ff5018637a64b1b7de1ed2a3ca03bc641410c1f311a4dbdc1ffbbf449c7", [:mix], [{:yamerl, "~> 0.10", [hex: :yamerl, repo: "hexpm", optional: false]}], "hexpm", "ca6bacae7bac917a7155dca0ab6149088aa7bc800c94d0fe18c5238f53b313c6"}, + "yaml_elixir": {:hex, :yaml_elixir, "2.11.0", "9e9ccd134e861c66b84825a3542a1c22ba33f338d82c07282f4f1f52d847bd50", [:mix], [{:yamerl, "~> 0.10", [hex: :yamerl, repo: "hexpm", optional: false]}], "hexpm", "53cc28357ee7eb952344995787f4bb8cc3cecbf189652236e9b163e8ce1bc242"}, } diff --git a/observability/otel/compose.yaml b/observability/otel/compose.yaml new file mode 100644 index 000000000..f63f3dbda --- /dev/null +++ b/observability/otel/compose.yaml @@ -0,0 +1,22 @@ +services: + grafana: + image: grafana/grafana:9.2.0 + ports: + - 5050:3000 + volumes: + - ./grafana-datasources.yaml:/etc/grafana/provisioning/datasources/datasources.yaml + - ./grafana.ini:/etc/grafana/grafana.ini + environment: + - GF_AUTH_ANONYMOUS_ENABLED=true + - GF_AUTH_ANONYMOUS_ORG_ROLE=Admin + - GF_AUTH_DISABLE_LOGIN_FORM=true + depends_on: + - tempo + tempo: + image: grafana/tempo:1.5.0 + command: [ "-config.file=/etc/tempo.yaml" ] + volumes: + - ./tempo.yaml:/etc/tempo.yaml + ports: + - 8001:8000 # tempo + - 55681:4318 # otlp http diff --git a/observability/otel/grafana-datasources.yaml b/observability/otel/grafana-datasources.yaml new file mode 100644 index 000000000..585e5aa8e --- /dev/null +++ b/observability/otel/grafana-datasources.yaml @@ -0,0 +1,10 @@ +apiVersion: 1 + +datasources: + - name: Tempo + type: tempo + access: proxy + url: http://tempo:8000 + version: 1 + editable: false + uid: tempo diff --git a/observability/otel/grafana.ini b/observability/otel/grafana.ini new file mode 100644 index 000000000..21b3cdbda --- /dev/null +++ b/observability/otel/grafana.ini @@ -0,0 +1,2 @@ +[feature_toggles] +enable = tempoSearch tempoBackendSearch diff --git a/observability/otel/tempo.yaml b/observability/otel/tempo.yaml new file mode 100644 index 000000000..e57fcbbe9 --- /dev/null +++ b/observability/otel/tempo.yaml @@ -0,0 +1,25 @@ +server: + http_listen_port: 8000 + +distributor: + receivers: + otlp: + protocols: + http: + log_received_spans: + enabled: true + include_all_attributes: true + filter_by_status_error: true + +storage: + trace: + backend: local + block: + encoding: zstd + wal: + path: /tmp/tempo/wal + encoding: snappy + local: + path: /tmp/tempo/blocks + +search_enabled: true diff --git a/priv/internal_versions.exs b/priv/internal_versions.exs index 6d1621578..5fcf71f00 100644 --- a/priv/internal_versions.exs +++ b/priv/internal_versions.exs @@ -7,6 +7,12 @@ defmodule InternalVersions do spawn_statestores_postgres: "2.0.0-RC9", spawn: "2.0.0-RC9", spawn_sdk: "2.0.0-RC9", + activator: "2.0.0-RC9", + activator_api: "2.0.0-RC9", + activator_kafka: "2.0.0-RC9", + activator_pubsub: "2.0.0-RC9", + activator_rabbitmq: "2.0.0-RC9", + activator_sqs: "2.0.0-RC9", proxy: "2.0.0-RC9", spawn_operator: "2.0.0-RC9", spawnctl: "2.0.0-RC9" diff --git a/spawn_activators/activator/.formatter.exs b/spawn_activators/activator/.formatter.exs new file mode 100644 index 000000000..d2cda26ed --- /dev/null +++ b/spawn_activators/activator/.formatter.exs @@ -0,0 +1,4 @@ +# Used by "mix format" +[ + inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"] +] diff --git a/spawn_activators/activator/.gitignore b/spawn_activators/activator/.gitignore new file mode 100644 index 000000000..635b07b24 --- /dev/null +++ b/spawn_activators/activator/.gitignore @@ -0,0 +1,26 @@ +# The directory Mix will write compiled artifacts to. +/_build/ + +# If you run "mix test --cover", coverage assets end up here. +/cover/ + +# The directory Mix downloads your dependencies sources to. +/deps/ + +# Where third-party dependencies like ExDoc output generated docs. +/doc/ + +# Ignore .fetch files in case you like to edit your project deps locally. +/.fetch + +# If the VM crashes, it generates a dump, let's ignore it too. +erl_crash.dump + +# Also ignore archive artifacts (built via "mix archive.build"). +*.ez + +# Ignore package tarball (built via "mix hex.build"). +activators-*.tar + +# Temporary files, for example, from tests. +/tmp/ diff --git a/spawn_activators/activator/README.md b/spawn_activators/activator/README.md new file mode 100644 index 000000000..951cbc7aa --- /dev/null +++ b/spawn_activators/activator/README.md @@ -0,0 +1,3 @@ +# Activators + +**TODO: Add description** diff --git a/spawn_activators/activator/lib/activator.ex b/spawn_activators/activator/lib/activator.ex new file mode 100644 index 000000000..67fdddf1c --- /dev/null +++ b/spawn_activators/activator/lib/activator.ex @@ -0,0 +1,25 @@ +defmodule Activator do + @moduledoc """ + Documentation for `Activator`. + """ + + alias Actors.Config.PersistentTermConfig, as: Config + + def get_http_port(_opts), do: Config.get(:http_port) + + def read_config_from_file(file_path) do + case File.read(file_path) do + {:ok, file_content} -> + case Jason.decode(file_content) do + {:ok, json_data} -> + {:ok, json_data} + + {:error, reason} -> + {:error, "Failed to parse JSON config file #{file_path}. Reason: #{inspect(reason)}"} + end + + {:error, reason} -> + {:error, "Failed to read file: #{inspect(reason)}"} + end + end +end diff --git a/spawn_activators/activator/lib/activator/dispatcher/default_dispatcher.ex b/spawn_activators/activator/lib/activator/dispatcher/default_dispatcher.ex new file mode 100644 index 000000000..3ba1ee2f4 --- /dev/null +++ b/spawn_activators/activator/lib/activator/dispatcher/default_dispatcher.ex @@ -0,0 +1,86 @@ +defmodule Activator.Dispatcher.DefaultDispatcher do + @behaviour Activator.Dispatcher + + require Logger + + alias Spawn.Actors.{Actor, ActorId, ActorSystem} + + alias Spawn.{ + InvocationRequest, + Noop + } + + @impl Activator.Dispatcher + @spec dispatch(any, Activator.Dispatcher.options()) :: :ok | {:error, any()} + def dispatch(data, options) when is_nil(data) do + do_dispatch(nil, options) + end + + def dispatch(data, options) do + encoder = Keyword.get(options, :encoder, Activator.Encoder.CloudEvent) + + case encoder.decode(data) do + {:ok, source, id, payload} -> + Logger.debug("Decoded event: #{inspect(payload)}") + do_dispatch(payload, Keyword.merge(options, actor: id, action: source)) + + {:error, error} -> + Logger.error("Failure on decode event. Error: #{inspect(error)}") + {:error, error} + end + end + + defp do_dispatch(payload, opts) when is_nil(payload) do + async? = Keyword.get(opts, :async, true) + actor_name = Keyword.fetch!(opts, :actor) + action = Keyword.fetch!(opts, :action) + system_name = Keyword.fetch!(opts, :system) + + actor = %Actor{id: %ActorId{name: actor_name, system: system_name}} + system = %ActorSystem{name: system_name} + + Logger.info("Dispaching message to Actor #{inspect(actor_name)}") + + Logger.debug( + "Request for Activate Actor [#{actor_name}] using action [#{action}] without payload" + ) + + req = %InvocationRequest{ + system: system, + actor: actor, + payload: {:noop, %Noop{}}, + action_name: action, + async: async?, + caller: nil + } + + Actors.invoke_with_nats(req, opts) + end + + defp do_dispatch(payload, opts) do + async? = Keyword.get(opts, :async, true) + actor_name = Keyword.fetch!(opts, :actor) + action = Keyword.fetch!(opts, :action) + system_name = Keyword.fetch!(opts, :system) + + actor = %Actor{id: %ActorId{name: actor_name, system: system_name}} + system = %ActorSystem{name: system_name} + + Logger.info("Dispaching message to Actor #{inspect(actor_name)}") + + Logger.debug( + "Request for Activate Actor [#{actor_name}] using action [#{action}] with payload: #{inspect(payload)}" + ) + + req = %InvocationRequest{ + system: system, + actor: actor, + payload: {:value, payload}, + action_name: action, + async: async?, + caller: nil + } + + Actors.invoke_with_nats(req, opts) + end +end diff --git a/spawn_activators/activator/lib/activator/encoder/cloudevent.ex b/spawn_activators/activator/lib/activator/encoder/cloudevent.ex new file mode 100644 index 000000000..5389b2e09 --- /dev/null +++ b/spawn_activators/activator/lib/activator/encoder/cloudevent.ex @@ -0,0 +1,38 @@ +defmodule Activator.Encoder.CloudEvent do + @doc """ + `CloudEvent` + """ + @behaviour Activator.Encoder + + alias Google.Protobuf.Any + alias Io.Cloudevents.V1.CloudEvent + + def encode(data) do + {:ok, data} + end + + @spec decode(any) :: {:error, any} | {:ok, any} + def decode(data) when is_binary(data) do + case CloudEvent.decode(data) do + %Io.Cloudevents.V1.CloudEvent{ + attributes: _attributes, + data: {:binary_data, payload}, + id: id, + source: source, + spec_version: _spec, + type: _type + } = _decoded_data -> + {:ok, source, id, Any.decode(payload)} + + error -> + {:error, "Error on try decode data. Error #{inspect(error)}"} + end + end + + def decode(%CloudEvent{source: source, id: id, data: nil} = _data), do: {:ok, source, id, nil} + + def decode(%CloudEvent{source: source, id: id, data: {:binary_data, payload}} = _data), + do: {:ok, source, id, Any.decode(payload)} + + def decode(_), do: {:error, "Error on try decode data. Data must be a binary type"} +end diff --git a/spawn_activators/activator/lib/activator/supervisor.ex b/spawn_activators/activator/lib/activator/supervisor.ex new file mode 100644 index 000000000..c3ba8aa49 --- /dev/null +++ b/spawn_activators/activator/lib/activator/supervisor.ex @@ -0,0 +1,29 @@ +defmodule Activator.Supervisor do + @moduledoc false + use Supervisor + require Logger + import Spawn.Utils.Common, only: [supervisor_process_logger: 1] + + alias Spawn.Cluster.Node.ConnectionSupervisor + + def start_link(opts) do + Supervisor.start_link(__MODULE__, opts, name: __MODULE__) + end + + def child_spec(opts) do + %{ + id: __MODULE__, + start: {__MODULE__, :start_link, [opts]} + } + end + + @impl true + def init(opts) do + children = [ + supervisor_process_logger(__MODULE__), + ConnectionSupervisor.child_spec(opts) + ] + + Supervisor.init(children, strategy: :one_for_one) + end +end diff --git a/spawn_activators/activator/lib/dispatcher.ex b/spawn_activators/activator/lib/dispatcher.ex new file mode 100644 index 000000000..6810335b2 --- /dev/null +++ b/spawn_activators/activator/lib/dispatcher.ex @@ -0,0 +1,20 @@ +defmodule Activator.Dispatcher do + @moduledoc """ + `Dispatcher` + """ + + @type data :: + Cloudevents.Format.V_1_0.Event.t() + | Cloudevents.Format.V_0_2.Event.t() + | Cloudevents.Format.V_0_1.Event.t() + + @type options() :: [option()] + @type option :: + {:encoder, encoder :: module()} + | {:system, system :: String.t()} + | {:actor, actor :: String.t()} + | {:action, action :: String.t()} + | {:kind, kind :: atom()} + + @callback dispatch(data, options) :: :ok | {:error, any()} +end diff --git a/spawn_activators/activator/lib/encoder.ex b/spawn_activators/activator/lib/encoder.ex new file mode 100644 index 000000000..91b5760fa --- /dev/null +++ b/spawn_activators/activator/lib/encoder.ex @@ -0,0 +1,21 @@ +defmodule Activator.Encoder do + @moduledoc """ + Encoder convert one type to another + """ + + @type data :: binary() + + @type source :: String.t() + + @type id :: String.t() + + @doc """ + Encode data to certain type. + """ + @callback encode(data()) :: {:ok, any()} | {:error, any()} + + @doc """ + Decode data to certain type. + """ + @callback decode(data) :: {:ok, source(), id(), term()} | {:error, any()} +end diff --git a/spawn_activators/activator/mix.exs b/spawn_activators/activator/mix.exs new file mode 100644 index 000000000..7db981280 --- /dev/null +++ b/spawn_activators/activator/mix.exs @@ -0,0 +1,36 @@ +defmodule Activators.MixProject do + use Mix.Project + + @app :activator + @version "0.0.0-local.dev" + + def project do + [ + app: @app, + version: @version, + build_path: "./_build", + config_path: "../../config/config.exs", + deps_path: "./deps", + lockfile: "./mix.lock", + elixir: "~> 1.15", + start_permanent: Mix.env() == :prod, + deps: deps() + ] + end + + # Run "mix help compile.app" to learn about applications. + def application do + [ + extra_applications: [:logger] + ] + end + + # Run "mix help deps" to learn about dependencies. + defp deps do + [ + {:spawn, path: "../../"}, + {:cloudevents, "~> 0.6.1"}, + {:hackney, "~> 1.9"} + ] + end +end diff --git a/spawn_activators/activator/mix.lock b/spawn_activators/activator/mix.lock new file mode 100644 index 000000000..a3a822436 --- /dev/null +++ b/spawn_activators/activator/mix.lock @@ -0,0 +1,117 @@ +%{ + "acceptor_pool": {:hex, :acceptor_pool, "1.0.0", "43c20d2acae35f0c2bcd64f9d2bde267e459f0f3fd23dab26485bf518c281b21", [:rebar3], [], "hexpm", "0cbcd83fdc8b9ad2eee2067ef8b91a14858a5883cb7cd800e6fcd5803e158788"}, + "amqp": {:hex, :amqp, "3.3.2", "6cad7469957b29c517a26a27474828f1db28278a13bcc2e7970db9854a3d3080", [:mix], [{:amqp_client, "~> 3.9", [hex: :amqp_client, repo: "hexpm", optional: false]}], "hexpm", "f977c41d81b65a21234a9158e6491b2296f8bd5bda48d5b611a64b6e0d2c3f31"}, + "amqp_client": {:hex, :amqp_client, "3.12.14", "2b677bc3f2e2234ba7517042b25d72071a79735042e91f9116bd3c176854b622", [:make, :rebar3], [{:credentials_obfuscation, "3.4.0", [hex: :credentials_obfuscation, repo: "hexpm", optional: false]}, {:rabbit_common, "3.12.14", [hex: :rabbit_common, repo: "hexpm", optional: false]}], "hexpm", "5f70b6c3b1a739790080da4fddc94a867e99f033c4b1edc20d6ff8b8fb4bd160"}, + "bakeware": {:hex, :bakeware, "0.2.4", "0aaf49b34f4bab2aa433f9ff1485d9401e421603160abd6d269c469fc7b65212", [:make, :mix], [{:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "7b97bcf6fbeee53bb32441d6c495bf478d26f9575633cfef6831e421e86ada6d"}, + "bandit": {:hex, :bandit, "1.5.3", "c7ee44871da696371a5674dd2c2062e974a18cd787732efcf50cc70b98c78fdc", [:mix], [{:hpax, "~> 0.1.1", [hex: :hpax, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:thousand_island, "~> 1.0", [hex: :thousand_island, repo: "hexpm", optional: false]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "3812ed5e48c1f1e3109edb5c463c6f8aaf25ecfac2826606be3e5237550116ef"}, + "broadway": {:hex, :broadway, "1.1.0", "8ed3aea01fd6f5640b3e1515b90eca51c4fc1fac15fb954cdcf75dc054ae719c", [:mix], [{:gen_stage, "~> 1.0", [hex: :gen_stage, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.3.7 or ~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "25e315ef1afe823129485d981dcc6d9b221cea30e625fd5439e9b05f44fb60e4"}, + "broadway_kafka": {:hex, :broadway_kafka, "0.4.4", "ebcaa4b2495c672f459bd8ea12b81ae64dfcfd12ec0a77ef65779e35bffd48e0", [:mix], [{:broadway, "~> 1.0", [hex: :broadway, repo: "hexpm", optional: false]}, {:brod, "~> 3.16 or ~> 4.0", [hex: :brod, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c595b6d6b6d1eac6bb291b151ad92f7a1a101ec8af173f282a0d4fd8fa88b253"}, + "broadway_rabbitmq": {:hex, :broadway_rabbitmq, "0.8.2", "087e2fb0ea2fe6fd941246be6985eccda93ea601bf678c3e8bd5d2a830acb058", [:mix], [{:amqp, "~> 1.3 or ~> 2.0 or ~> 3.0 or ~> 4.0", [hex: :amqp, repo: "hexpm", optional: false]}, {:broadway, "~> 1.0", [hex: :broadway, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.3.5 or ~> 0.4.0 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "762cf2fa7c20027c20ebaf1708570c2101c78824237eb2ab7e3f1158e4003a5a"}, + "broadway_sqs": {:hex, :broadway_sqs, "0.7.4", "ab89b298f9253adb8534f92095b56d4879e35fe2f5a0730256f7e824572c637f", [:mix], [{:broadway, "~> 1.0", [hex: :broadway, repo: "hexpm", optional: false]}, {:ex_aws_sqs, "~> 3.2.1 or ~> 3.3", [hex: :ex_aws_sqs, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.3.7 or ~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:saxy, "~> 1.1", [hex: :saxy, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7140085c4f7c4b27886b3a8f3d0942976f39f195fdbc2f652c5d7b157f93ae28"}, + "brod": {:hex, :brod, "4.3.3", "deff96d806af05b15da092b5fd732932bb54616056211a6f928366a182e9c164", [:rebar3], [{:kafka_protocol, "4.1.10", [hex: :kafka_protocol, repo: "hexpm", optional: false]}], "hexpm", "e5a7aefcc0590c548a9759d66b929bef03876194cea521a775baa4dfdda8ed16"}, + "castore": {:hex, :castore, "1.0.12", "053f0e32700cbec356280c0e835df425a3be4bc1e0627b714330ad9d0f05497f", [:mix], [], "hexpm", "3dca286b2186055ba0c9449b4e95b97bf1b57b47c1f2644555879e659960c224"}, + "certifi": {:hex, :certifi, "2.12.0", "2d1cca2ec95f59643862af91f001478c9863c2ac9cb6e2f89780bfd8de987329", [:rebar3], [], "hexpm", "ee68d85df22e554040cdb4be100f33873ac6051387baf6a8f6ce82272340ff1c"}, + "chacha20": {:hex, :chacha20, "1.0.4", "0359d8f9a32269271044c1b471d5cf69660c362a7c61a98f73a05ef0b5d9eb9e", [:mix], [], "hexpm", "2027f5d321ae9903f1f0da7f51b0635ad6b8819bc7fe397837930a2011bc2349"}, + "chatterbox": {:hex, :ts_chatterbox, "0.15.1", "5cac4d15dd7ad61fc3c4415ce4826fc563d4643dee897a558ec4ea0b1c835c9c", [:rebar3], [{:hpack, "~> 0.3.0", [hex: :hpack_erl, repo: "hexpm", optional: false]}], "hexpm", "4f75b91451338bc0da5f52f3480fa6ef6e3a2aeecfc33686d6b3d0a0948f31aa"}, + "cloak": {:hex, :cloak, "1.1.2", "7e0006c2b0b98d976d4f559080fabefd81f0e0a50a3c4b621f85ceeb563e80bb", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "940d5ac4fcd51b252930fd112e319ea5ae6ab540b722f3ca60a85666759b9585"}, + "cloak_ecto": {:hex, :cloak_ecto, "1.2.0", "e86a3df3bf0dc8980f70406bcb0af2858bac247d55494d40bc58a152590bd402", [:mix], [{:cloak, "~> 1.1.1", [hex: :cloak, repo: "hexpm", optional: false]}, {:ecto, "~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}], "hexpm", "8bcc677185c813fe64b786618bd6689b1707b35cd95acaae0834557b15a0c62f"}, + "cloudevents": {:hex, :cloudevents, "0.6.1", "d3f467a615c00712cf3c9632f6d131695fd3e1d29c10477d2d2fbbec06350522", [:mix], [{:avrora, "~> 0.21", [hex: :avrora, repo: "hexpm", optional: true]}, {:jason, "~> 1.2", [hex: :jason, repo: "hexpm", optional: false]}, {:typed_struct, "~> 0.3.0", [hex: :typed_struct, repo: "hexpm", optional: false]}], "hexpm", "f0055549bc651bd6702347328dd5824d3f08fbf308d2c7212252e34e345bcb9c"}, + "connection": {:hex, :connection, "1.1.0", "ff2a49c4b75b6fb3e674bfc5536451607270aac754ffd1bdfe175abe4a6d7a68", [:mix], [], "hexpm", "722c1eb0a418fbe91ba7bd59a47e28008a189d47e37e0e7bb85585a016b2869c"}, + "cowboy": {:hex, :cowboy, "2.12.0", "f276d521a1ff88b2b9b4c54d0e753da6c66dd7be6c9fca3d9418b561828a3731", [:make, :rebar3], [{:cowlib, "2.13.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "8a7abe6d183372ceb21caa2709bec928ab2b72e18a3911aa1771639bef82651e"}, + "cowlib": {:hex, :cowlib, "2.13.0", "db8f7505d8332d98ef50a3ef34b34c1afddec7506e4ee4dd4a3a266285d282ca", [:make, :rebar3], [], "hexpm", "e1e1284dc3fc030a64b1ad0d8382ae7e99da46c3246b815318a4b848873800a4"}, + "crc32cer": {:hex, :crc32cer, "0.1.11", "b550da6d615feb72a882d15d020f8f7dee72dfb2cb1bcdf3b1ee8dc2afd68cfc", [:rebar3], [], "hexpm", "a39b8f0b1990ac1bf06c3a247fc6a178b740cdfc33c3b53688dc7dd6b1855942"}, + "credentials_obfuscation": {:hex, :credentials_obfuscation, "3.4.0", "34e18b126b3aefd6e8143776fbe1ceceea6792307c99ac5ee8687911f048cfd7", [:rebar3], [], "hexpm", "738ace0ed5545d2710d3f7383906fc6f6b582d019036e5269c4dbd85dbced566"}, + "ctx": {:hex, :ctx, "0.6.0", "8ff88b70e6400c4df90142e7f130625b82086077a45364a78d208ed3ed53c7fe", [:rebar3], [], "hexpm", "a14ed2d1b67723dbebbe423b28d7615eb0bdcba6ff28f2d1f1b0a7e1d4aa5fc2"}, + "curve25519": {:hex, :curve25519, "1.0.5", "f801179424e4012049fcfcfcda74ac04f65d0ffceeb80e7ef1d3352deb09f5bb", [:mix], [], "hexpm", "0fba3ad55bf1154d4d5fc3ae5fb91b912b77b13f0def6ccb3a5d58168ff4192d"}, + "db_connection": {:hex, :db_connection, "2.7.0", "b99faa9291bb09892c7da373bb82cba59aefa9b36300f6145c5f201c7adf48ec", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "dcf08f31b2701f857dfc787fbad78223d61a32204f217f15e881dd93e4bdd3ff"}, + "decimal": {:hex, :decimal, "2.3.0", "3ad6255aa77b4a3c4f818171b12d237500e63525c2fd056699967a3e7ea20f62", [:mix], [], "hexpm", "a4d66355cb29cb47c3cf30e71329e58361cfcb37c34235ef3bf1d7bf3773aeac"}, + "decorator": {:hex, :decorator, "1.4.0", "a57ac32c823ea7e4e67f5af56412d12b33274661bb7640ec7fc882f8d23ac419", [:mix], [], "hexpm", "0a07cedd9083da875c7418dea95b78361197cf2bf3211d743f6f7ce39656597f"}, + "delta_crdt": {:hex, :delta_crdt, "0.6.4", "79d235eef82a58bb0cb668bc5b9558d2e65325ccb46b74045f20b36fd41671da", [:mix], [{:merkle_map, "~> 0.2.0", [hex: :merkle_map, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "4a81f579c06aeeb625db54c6c109859a38aa00d837e3e7f8ac27b40cea34885a"}, + "ecto": {:hex, :ecto, "3.12.5", "4a312960ce612e17337e7cefcf9be45b95a3be6b36b6f94dfb3d8c361d631866", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "6eb18e80bef8bb57e17f5a7f068a1719fbda384d40fc37acb8eb8aeca493b6ea"}, + "ecto_mysql_extras": {:hex, :ecto_mysql_extras, "0.6.3", "fdd487de7f7fd949f2e599fd205cda23bc47c34fdf8e972088b912aea45eb0e4", [:mix], [{:ecto_sql, "~> 3.7", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:myxql, "~> 0.5", [hex: :myxql, repo: "hexpm", optional: false]}, {:table_rex, "~> 3.1 or ~> 4.0", [hex: :table_rex, repo: "hexpm", optional: true]}], "hexpm", "24bd4f41e621f434c1d8312e4d76a513828f59cbc35191865e574dbfd893cd80"}, + "ecto_psql_extras": {:hex, :ecto_psql_extras, "0.8.3", "0c1df205bd051eaf599b3671e75356b121aa71eac09b63ecf921cb1a080c072e", [:mix], [{:ecto_sql, "~> 3.7", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:postgrex, "> 0.16.0 and < 0.20.0", [hex: :postgrex, repo: "hexpm", optional: false]}, {:table_rex, "~> 3.1.1 or ~> 4.0.0", [hex: :table_rex, repo: "hexpm", optional: false]}], "hexpm", "d0e35ea160359e759a2993a00c3a5389a9ca7ece6df5d0753fa927f988c7351a"}, + "ecto_sql": {:hex, :ecto_sql, "3.12.1", "c0d0d60e85d9ff4631f12bafa454bc392ce8b9ec83531a412c12a0d415a3a4d0", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.12", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.7", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.19 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "aff5b958a899762c5f09028c847569f7dfb9cc9d63bdb8133bff8a5546de6bf5"}, + "ed25519": {:hex, :ed25519, "1.4.1", "479fb83c3e31987c9cad780e6aeb8f2015fb5a482618cdf2a825c9aff809afc4", [:mix], [], "hexpm", "0dacb84f3faa3d8148e81019ca35f9d8dcee13232c32c9db5c2fb8ff48c80ec7"}, + "elixir_make": {:hex, :elixir_make, "0.9.0", "6484b3cd8c0cee58f09f05ecaf1a140a8c97670671a6a0e7ab4dc326c3109726", [:mix], [], "hexpm", "db23d4fd8b757462ad02f8aa73431a426fe6671c80b200d9710caf3d1dd0ffdb"}, + "equivalex": {:hex, :equivalex, "1.0.3", "170d9a82ae066e0020dfe1cf7811381669565922eb3359f6c91d7e9a1124ff74", [:mix], [], "hexpm", "46fa311adb855117d36e461b9c0ad2598f72110ad17ad73d7533c78020e045fc"}, + "ex_aws": {:hex, :ex_aws, "2.5.8", "0393cfbc5e4a9e7017845451a015d836a670397100aa4c86901980e2a2c5f7d4", [:mix], [{:configparser_ex, "~> 4.0", [hex: :configparser_ex, repo: "hexpm", optional: true]}, {:hackney, "~> 1.16", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: true]}, {:jsx, "~> 2.8 or ~> 3.0", [hex: :jsx, repo: "hexpm", optional: true]}, {:mime, "~> 1.2 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:req, "~> 0.3", [hex: :req, repo: "hexpm", optional: true]}, {:sweet_xml, "~> 0.7", [hex: :sweet_xml, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "8f79777b7932168956c8cc3a6db41f5783aa816eb50de356aed3165a71e5f8c3"}, + "ex_aws_sqs": {:hex, :ex_aws_sqs, "3.4.0", "f7c4d0177c1c954776363d3dc05e5dfd37ddf0e2c65ec3f047e5c9c7dd1b71ac", [:mix], [{:ex_aws, "~> 2.1", [hex: :ex_aws, repo: "hexpm", optional: false]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: true]}, {:saxy, "~> 1.1", [hex: :saxy, repo: "hexpm", optional: true]}, {:sweet_xml, ">= 0.0.0", [hex: :sweet_xml, repo: "hexpm", optional: true]}], "hexpm", "b504482206ccaf767b714888e9d41a1cfcdcb241577985517114191c812f155a"}, + "finch": {:hex, :finch, "0.18.0", "944ac7d34d0bd2ac8998f79f7a811b21d87d911e77a786bc5810adb75632ada4", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: false]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.3", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 0.2.6 or ~> 1.0", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "69f5045b042e531e53edc2574f15e25e735b522c37e2ddb766e15b979e03aa65"}, + "flame": {:hex, :flame, "0.5.2", "d46c4daa19b8921b71e0e57dc69edc01ce1311b1976c160192b05d4253b336e8", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: true]}, {:jason, ">= 0.0.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "82560ebef6ab3c277875493d0c93494740c930db0b1a3ff1a570eee9206cc6c0"}, + "flame_k8s_backend": {:hex, :flame_k8s_backend, "0.5.7", "d1085a0ee47b551da6603018a06c7477cd2023d0a27f21dede8e2a24a17435b7", [:mix], [{:flame, "~> 0.4.0 or ~> 0.5.0", [hex: :flame, repo: "hexpm", optional: false]}], "hexpm", "8eeeb7688f7bf79a2a2bb340f8a66ad757651dff76f7a2e9e92415cb224abc48"}, + "flow": {:hex, :flow, "1.2.4", "1dd58918287eb286656008777cb32714b5123d3855956f29aa141ebae456922d", [:mix], [{:gen_stage, "~> 1.0", [hex: :gen_stage, repo: "hexpm", optional: false]}], "hexpm", "874adde96368e71870f3510b91e35bc31652291858c86c0e75359cbdd35eb211"}, + "gen_stage": {:hex, :gen_stage, "1.2.1", "19d8b5e9a5996d813b8245338a28246307fd8b9c99d1237de199d21efc4c76a1", [:mix], [], "hexpm", "83e8be657fa05b992ffa6ac1e3af6d57aa50aace8f691fcf696ff02f8335b001"}, + "gnat": {:hex, :gnat, "1.9.0", "e73ad4279b02e4ad917b7d326342e830b1c6a1eb6677079d8b373faaa7d92f56", [:mix], [{:connection, "~> 1.1", [hex: :connection, repo: "hexpm", optional: false]}, {:cowlib, "~> 2.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 0.5 or ~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}, {:nkeys, "~> 0.2", [hex: :nkeys, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ea91a083dc11984cd9b9aa28c7cbb7a2ff597325ba3c2a400d9d73c2d893e3f3"}, + "gproc": {:hex, :gproc, "0.9.1", "f1df0364423539cf0b80e8201c8b1839e229e5f9b3ccb944c5834626998f5b8c", [:rebar3], [], "hexpm", "905088e32e72127ed9466f0bac0d8e65704ca5e73ee5a62cb073c3117916d507"}, + "grpc": {:hex, :grpc, "0.9.0", "1b930a57272d4356ea65969b984c2eb04f3dab81420e1e28f0e6ec04b8f88515", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:cowboy, "~> 2.10", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowlib, "~> 2.12", [hex: :cowlib, repo: "hexpm", optional: false]}, {:gun, "~> 2.0", [hex: :gun, repo: "hexpm", optional: false]}, {:jason, ">= 0.0.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mint, "~> 1.5", [hex: :mint, repo: "hexpm", optional: false]}, {:protobuf, "~> 0.11", [hex: :protobuf, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7c059698248738fcf7ad551f1d78f4a3d2e0642a72a5834f2a0b0db4b9f3d2b5"}, + "grpc_reflection": {:hex, :grpc_reflection, "0.1.5", "d00cdf8ef2638edb9578248eedc742e1b34eda9100e61be764c552c10f4b46cb", [:mix], [{:grpc, "~> 0.9", [hex: :grpc, repo: "hexpm", optional: false]}, {:protobuf, "~> 0.14", [hex: :protobuf, repo: "hexpm", optional: false]}], "hexpm", "848334d16029aee33728603be6171fc8bfcdfa3508cd6885ec1729e2e6ac60a5"}, + "grpcbox": {:hex, :grpcbox, "0.17.1", "6e040ab3ef16fe699ffb513b0ef8e2e896da7b18931a1ef817143037c454bcce", [:rebar3], [{:acceptor_pool, "~> 1.0.0", [hex: :acceptor_pool, repo: "hexpm", optional: false]}, {:chatterbox, "~> 0.15.1", [hex: :ts_chatterbox, repo: "hexpm", optional: false]}, {:ctx, "~> 0.6.0", [hex: :ctx, repo: "hexpm", optional: false]}, {:gproc, "~> 0.9.1", [hex: :gproc, repo: "hexpm", optional: false]}], "hexpm", "4a3b5d7111daabc569dc9cbd9b202a3237d81c80bf97212fbc676832cb0ceb17"}, + "gun": {:hex, :gun, "2.1.0", "b4e4cbbf3026d21981c447e9e7ca856766046eff693720ba43114d7f5de36e87", [:make, :rebar3], [{:cowlib, "2.13.0", [hex: :cowlib, repo: "hexpm", optional: false]}], "hexpm", "52fc7fc246bfc3b00e01aea1c2854c70a366348574ab50c57dfe796d24a0101d"}, + "hackney": {:hex, :hackney, "1.20.1", "8d97aec62ddddd757d128bfd1df6c5861093419f8f7a4223823537bad5d064e2", [:rebar3], [{:certifi, "~> 2.12.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~> 6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~> 1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~> 1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.4.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~> 1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "fe9094e5f1a2a2c0a7d10918fee36bfec0ec2a979994cff8cfe8058cd9af38e3"}, + "highlander": {:hex, :highlander, "0.2.1", "e59b459f857e89daf73f2598bf2b2c0479a435481e6101ea389fd3625919b052", [:mix], [], "hexpm", "5ba19a18358803d82a923511acec8ee85fac30731c5ca056f2f934bc3d3afd9a"}, + "horde": {:hex, :horde, "0.9.0", "522342bd7149aeed453c97692a8bca9cf7c9368c5a489afd802e575dc8df54a6", [:mix], [{:delta_crdt, "~> 0.6.2", [hex: :delta_crdt, repo: "hexpm", optional: false]}, {:libring, "~> 1.4", [hex: :libring, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:telemetry_poller, "~> 0.5.0 or ~> 1.0", [hex: :telemetry_poller, repo: "hexpm", optional: false]}], "hexpm", "fae11e5bc9c980038607d0c3338cdf7f97124a5d5382fd4b6fb6beaab8e214fe"}, + "hpack": {:hex, :hpack_erl, "0.3.0", "2461899cc4ab6a0ef8e970c1661c5fc6a52d3c25580bc6dd204f84ce94669926", [:rebar3], [], "hexpm", "d6137d7079169d8c485c6962dfe261af5b9ef60fbc557344511c1e65e3d95fb0"}, + "hpax": {:hex, :hpax, "0.1.2", "09a75600d9d8bbd064cdd741f21fc06fc1f4cf3d0fcc335e5aa19be1a7235c84", [:mix], [], "hexpm", "2c87843d5a23f5f16748ebe77969880e29809580efdaccd615cd3bed628a8c13"}, + "idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"}, + "iter": {:hex, :iter, "0.1.2", "bd5dbba48ba67e0f134889a4a29f2b377db6cdcee0661f3c29439e7b649e317a", [:mix], [], "hexpm", "e79f53ed36105ae72582fd3ef224ca2539ccc00cdc27e6e7fe69c49119c4e39b"}, + "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, + "jetstream": {:hex, :jetstream, "0.0.9", "f5943c992a98cedd11015436d054c14d1eec544884db0ba959f700363c60fa8f", [:mix], [{:broadway, "~> 1.0", [hex: :broadway, repo: "hexpm", optional: true]}, {:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:gnat, "~> 1.1", [hex: :gnat, repo: "hexpm", optional: false]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "ca519b254d8b0720865bcf371f50c6122c846d70d25d11fae648b46617577bc1"}, + "k8s": {:hex, :k8s, "2.5.0", "16ceef480cf1503ad561529ef93c87d921b4baa29d73ee16cebae13d37e218f4", [:mix], [{:castore, "~> 1.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mint, "~> 1.0", [hex: :mint, repo: "hexpm", optional: false]}, {:mint_web_socket, "~> 1.0", [hex: :mint_web_socket, repo: "hexpm", optional: false]}, {:poolboy, "~> 1.5", [hex: :poolboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:yaml_elixir, "~> 2.8", [hex: :yaml_elixir, repo: "hexpm", optional: false]}], "hexpm", "c46032a4eef273000d32608efbc2edbdfde480d5f24df69e0938daa01026d1eb"}, + "k8s_webhoox": {:hex, :k8s_webhoox, "0.2.0", "5ef0968a426a0e5d168dd54db7075e0ee222dddfa5da2cf29f25f01a7d02ffd0", [:mix], [{:k8s, "~> 2.0", [hex: :k8s, repo: "hexpm", optional: false]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}, {:pluggable, "~> 1.0", [hex: :pluggable, repo: "hexpm", optional: false]}, {:x509, "~> 0.8.5", [hex: :x509, repo: "hexpm", optional: false]}, {:yaml_elixir, "~> 2.0", [hex: :yaml_elixir, repo: "hexpm", optional: false]}], "hexpm", "4917e1bf43bcbae3c2fa53fa4206f444cc029e757dc4e2b7d550cb0ae8752543"}, + "kafka_protocol": {:hex, :kafka_protocol, "4.1.10", "f917b6c90c8df0de2b40a87d6b9ae1cfce7788e91a65818e90e40cf76111097a", [:rebar3], [{:crc32cer, "0.1.11", [hex: :crc32cer, repo: "hexpm", optional: false]}], "hexpm", "df680a3706ead8695f8b306897c0a33e8063c690da9308db87b462cfd7029d04"}, + "kcl": {:hex, :kcl, "1.4.2", "8b73a55a14899dc172fcb05a13a754ac171c8165c14f65043382d567922f44ab", [:mix], [{:curve25519, ">= 1.0.4", [hex: :curve25519, repo: "hexpm", optional: false]}, {:ed25519, "~> 1.3", [hex: :ed25519, repo: "hexpm", optional: false]}, {:poly1305, "~> 1.0", [hex: :poly1305, repo: "hexpm", optional: false]}, {:salsa20, "~> 1.0", [hex: :salsa20, repo: "hexpm", optional: false]}], "hexpm", "9f083dd3844d902df6834b258564a82b21a15eb9f6acdc98e8df0c10feeabf05"}, + "libcluster": {:hex, :libcluster, "3.3.3", "a4f17721a19004cfc4467268e17cff8b1f951befe428975dd4f6f7b84d927fe0", [:mix], [{:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "7c0a2275a0bb83c07acd17dab3c3bfb4897b145106750eeccc62d302e3bdfee5"}, + "libring": {:hex, :libring, "1.6.0", "d5dca4bcb1765f862ab59f175b403e356dec493f565670e0bacc4b35e109ce0d", [:mix], [], "hexpm", "5e91ece396af4bce99953d49ee0b02f698cd38326d93cd068361038167484319"}, + "merkle_map": {:hex, :merkle_map, "0.2.1", "01a88c87a6b9fb594c67c17ebaf047ee55ffa34e74297aa583ed87148006c4c8", [:mix], [], "hexpm", "fed4d143a5c8166eee4fa2b49564f3c4eace9cb252f0a82c1613bba905b2d04d"}, + "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"}, + "mime": {:hex, :mime, "2.0.6", "8f18486773d9b15f95f4f4f1e39b710045fa1de891fada4516559967276e4dc2", [:mix], [], "hexpm", "c9945363a6b26d747389aac3643f8e0e09d30499a138ad64fe8fd1d13d9b153e"}, + "mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"}, + "mint": {:hex, :mint, "1.7.1", "113fdb2b2f3b59e47c7955971854641c61f378549d73e829e1768de90fc1abf1", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1 or ~> 0.2.0 or ~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "fceba0a4d0f24301ddee3024ae116df1c3f4bb7a563a731f45fdfeb9d39a231b"}, + "mint_web_socket": {:hex, :mint_web_socket, "1.0.3", "aab42fff792a74649916236d0b01f560a0b3f03ca5dea693c230d1c44736b50e", [:mix], [{:mint, ">= 1.4.1 and < 2.0.0-0", [hex: :mint, repo: "hexpm", optional: false]}], "hexpm", "ca3810ca44cc8532e3dce499cc17f958596695d226bb578b2fbb88c09b5954b0"}, + "myxql": {:hex, :myxql, "0.7.1", "7c7b75aa82227cd2bc9b7fbd4de774fb19a1cdb309c219f411f82ca8860f8e01", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:geo, "~> 3.4", [hex: :geo, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "a491cdff53353a09b5850ac2d472816ebe19f76c30b0d36a43317a67c9004936"}, + "nebulex": {:hex, :nebulex, "2.6.0", "6e581c0b53aab80a1431488d367a41c6a8ee53763f86e7a7a6754ee571ecfdab", [:mix], [{:decorator, "~> 1.4", [hex: :decorator, repo: "hexpm", optional: true]}, {:shards, "~> 1.1", [hex: :shards, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "cf4a0040bd6d58b8d0204f668641973520fdbd78bd8618e1cdb7a11e7bc560cf"}, + "nimble_options": {:hex, :nimble_options, "0.5.2", "42703307b924880f8c08d97719da7472673391905f528259915782bb346e0a1b", [:mix], [], "hexpm", "4da7f904b915fd71db549bcdc25f8d56f378ef7ae07dc1d372cbe72ba950dce0"}, + "nimble_parsec": {:hex, :nimble_parsec, "1.4.0", "51f9b613ea62cfa97b25ccc2c1b4216e81df970acd8e16e8d1bdc58fef21370d", [:mix], [], "hexpm", "9c565862810fb383e9838c1dd2d7d2c437b3d13b267414ba6af33e50d2d1cf28"}, + "nimble_pool": {:hex, :nimble_pool, "1.0.0", "5eb82705d138f4dd4423f69ceb19ac667b3b492ae570c9f5c900bb3d2f50a847", [:mix], [], "hexpm", "80be3b882d2d351882256087078e1b1952a28bf98d0a287be87e4a24a710b67a"}, + "nkeys": {:hex, :nkeys, "0.3.0", "837add5261a3cdd8ff75b54e0475062313093929ab5e042fa48e010f33b10d16", [:mix], [{:ed25519, "~> 1.3", [hex: :ed25519, repo: "hexpm", optional: false]}, {:kcl, "~> 1.4", [hex: :kcl, repo: "hexpm", optional: false]}], "hexpm", "b5af773a296620ee8eeb1ec6dc5b68f716386f7e53f7bda8c4ac23515823dfe4"}, + "opentelemetry": {:hex, :opentelemetry, "1.3.1", "f0a342a74379e3540a634e7047967733da4bc8b873ec9026e224b2bd7369b1fc", [:rebar3], [{:opentelemetry_api, "~> 1.2.2", [hex: :opentelemetry_api, repo: "hexpm", optional: false]}, {:opentelemetry_semantic_conventions, "~> 0.2", [hex: :opentelemetry_semantic_conventions, repo: "hexpm", optional: false]}], "hexpm", "de476b2ac4faad3e3fe3d6e18b35dec9cb338c3b9910c2ce9317836dacad3483"}, + "opentelemetry_api": {:hex, :opentelemetry_api, "1.2.2", "693f47b0d8c76da2095fe858204cfd6350c27fe85d00e4b763deecc9588cf27a", [:mix, :rebar3], [{:opentelemetry_semantic_conventions, "~> 0.2", [hex: :opentelemetry_semantic_conventions, repo: "hexpm", optional: false]}], "hexpm", "dc77b9a00f137a858e60a852f14007bb66eda1ffbeb6c05d5fe6c9e678b05e9d"}, + "opentelemetry_ecto": {:hex, :opentelemetry_ecto, "1.2.0", "2382cb47ddc231f953d3b8263ed029d87fbf217915a1da82f49159d122b64865", [:mix], [{:opentelemetry_api, "~> 1.0", [hex: :opentelemetry_api, repo: "hexpm", optional: false]}, {:opentelemetry_process_propagator, "~> 0.2", [hex: :opentelemetry_process_propagator, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "70dfa2e79932e86f209df00e36c980b17a32f82d175f0068bf7ef9a96cf080cf"}, + "opentelemetry_exporter": {:hex, :opentelemetry_exporter, "1.6.0", "f4fbf69aa9f1541b253813221b82b48a9863bc1570d8ecc517bc510c0d1d3d8c", [:rebar3], [{:grpcbox, ">= 0.0.0", [hex: :grpcbox, repo: "hexpm", optional: false]}, {:opentelemetry, "~> 1.3", [hex: :opentelemetry, repo: "hexpm", optional: false]}, {:opentelemetry_api, "~> 1.2", [hex: :opentelemetry_api, repo: "hexpm", optional: false]}, {:tls_certificate_check, "~> 1.18", [hex: :tls_certificate_check, repo: "hexpm", optional: false]}], "hexpm", "1802d1dca297e46f21e5832ecf843c451121e875f73f04db87355a6cb2ba1710"}, + "opentelemetry_process_propagator": {:hex, :opentelemetry_process_propagator, "0.3.0", "ef5b2059403a1e2b2d2c65914e6962e56371570b8c3ab5323d7a8d3444fb7f84", [:mix, :rebar3], [{:opentelemetry_api, "~> 1.0", [hex: :opentelemetry_api, repo: "hexpm", optional: false]}], "hexpm", "7243cb6de1523c473cba5b1aefa3f85e1ff8cc75d08f367104c1e11919c8c029"}, + "opentelemetry_semantic_conventions": {:hex, :opentelemetry_semantic_conventions, "0.2.0", "b67fe459c2938fcab341cb0951c44860c62347c005ace1b50f8402576f241435", [:mix, :rebar3], [], "hexpm", "d61fa1f5639ee8668d74b527e6806e0503efc55a42db7b5f39939d84c07d6895"}, + "parse_trans": {:hex, :parse_trans, "3.4.1", "6e6aa8167cb44cc8f39441d05193be6e6f4e7c2946cb2759f015f8c56b76e5ff", [:rebar3], [], "hexpm", "620a406ce75dada827b82e453c19cf06776be266f5a67cff34e1ef2cbb60e49a"}, + "phoenix_pubsub": {:hex, :phoenix_pubsub, "2.1.3", "3168d78ba41835aecad272d5e8cd51aa87a7ac9eb836eabc42f6e57538e3731d", [:mix], [], "hexpm", "bba06bc1dcfd8cb086759f0edc94a8ba2bc8896d5331a1e2c2902bf8e36ee502"}, + "phoenix_pubsub_nats": {:hex, :phoenix_pubsub_nats, "0.2.2", "aedfbda3552299a399cc5d1486f05c313f9eb81e0364e9916e6b3b9ffb40ff41", [:mix], [{:gnat, "~> 1.6", [hex: :gnat, repo: "hexpm", optional: false]}, {:jason, "~> 1.3", [hex: :jason, repo: "hexpm", optional: false]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}], "hexpm", "63d117a379f5cc6ba3f9b61a322f821365d3a9b197e43243e0e3b7e47b462a7d"}, + "plug": {:hex, :plug, "1.16.1", "40c74619c12f82736d2214557dedec2e9762029b2438d6d175c5074c933edc9d", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a13ff6b9006b03d7e33874945b2755253841b238c34071ed85b0e86057f8cddc"}, + "plug_crypto": {:hex, :plug_crypto, "2.1.0", "f44309c2b06d249c27c8d3f65cfe08158ade08418cf540fd4f72d4d6863abb7b", [:mix], [], "hexpm", "131216a4b030b8f8ce0f26038bc4421ae60e4bb95c5cf5395e1421437824c4fa"}, + "pluggable": {:hex, :pluggable, "1.0.1", "ffd91303879d0ccfde2cbf2b5609f4f602608653e6165c44f5867c32e645e337", [:mix], [], "hexpm", "bce3403fe24dd5e14846b97e64ffa424b7ccda327829a4f6d1067cfc7a87d4a2"}, + "poly1305": {:hex, :poly1305, "1.0.4", "7cdc8961a0a6e00a764835918cdb8ade868044026df8ef5d718708ea6cc06611", [:mix], [{:chacha20, "~> 1.0", [hex: :chacha20, repo: "hexpm", optional: false]}, {:equivalex, "~> 1.0", [hex: :equivalex, repo: "hexpm", optional: false]}], "hexpm", "e14e684661a5195e149b3139db4a1693579d4659d65bba115a307529c47dbc3b"}, + "poolboy": {:hex, :poolboy, "1.5.2", "392b007a1693a64540cead79830443abf5762f5d30cf50bc95cb2c1aaafa006b", [:rebar3], [], "hexpm", "dad79704ce5440f3d5a3681c8590b9dc25d1a561e8f5a9c995281012860901e3"}, + "postgrex": {:hex, :postgrex, "0.19.3", "a0bda6e3bc75ec07fca5b0a89bffd242ca209a4822a9533e7d3e84ee80707e19", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "d31c28053655b78f47f948c85bb1cf86a9c1f8ead346ba1aa0d0df017fa05b61"}, + "protobuf": {:hex, :protobuf, "0.14.1", "9ac0582170df27669ccb2ef6cb0a3d55020d58896edbba330f20d0748881530a", [:mix], [{:jason, "~> 1.2", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "39a9d49d346e3ed597e5ae3168a43d9603870fc159419617f584cdf6071f0e25"}, + "protobuf_generate": {:hex, :protobuf_generate, "0.1.3", "57841bc60e2135e190748119d83f78669ee7820c0ad6555ada3cd3cd7df93143", [:mix], [{:protobuf, "~> 0.12", [hex: :protobuf, repo: "hexpm", optional: false]}], "hexpm", "dae4139b00ba77a279251a0ceb5593b1bae745e333b4ce1ab7e81e8e4906016b"}, + "rabbit_common": {:hex, :rabbit_common, "3.12.14", "466123ee7346a3cdac078c0c302bcd36da4523e8acd678c1b992f7b4df1f7914", [:make, :rebar3], [{:credentials_obfuscation, "3.4.0", [hex: :credentials_obfuscation, repo: "hexpm", optional: false]}, {:recon, "2.5.3", [hex: :recon, repo: "hexpm", optional: false]}, {:thoas, "1.0.0", [hex: :thoas, repo: "hexpm", optional: false]}], "hexpm", "70c31a51f7401cc0204ddef2745d98680c2e0df67e3b0c9e198916881fde3293"}, + "ranch": {:hex, :ranch, "1.8.0", "8c7a100a139fd57f17327b6413e4167ac559fbc04ca7448e9be9057311597a1d", [:make, :rebar3], [], "hexpm", "49fbcfd3682fab1f5d109351b61257676da1a2fdbe295904176d5e521a2ddfe5"}, + "recon": {:hex, :recon, "2.5.3", "739107b9050ea683c30e96de050bc59248fd27ec147696f79a8797ff9fa17153", [:mix, :rebar3], [], "hexpm", "6c6683f46fd4a1dfd98404b9f78dcabc7fcd8826613a89dcb984727a8c3099d7"}, + "retry": {:hex, :retry, "0.18.0", "dc58ebe22c95aa00bc2459f9e0c5400e6005541cf8539925af0aa027dc860543", [:mix], [], "hexpm", "9483959cc7bf69c9e576d9dfb2b678b71c045d3e6f39ab7c9aa1489df4492d73"}, + "salsa20": {:hex, :salsa20, "1.0.4", "404cbea1fa8e68a41bcc834c0a2571ac175580fec01cc38cc70c0fb9ffc87e9b", [:mix], [], "hexpm", "745ddcd8cfa563ddb0fd61e7ce48d5146279a2cf7834e1da8441b369fdc58ac6"}, + "saxy": {:hex, :saxy, "1.6.0", "02cb4e9bd045f25ac0c70fae8164754878327ee393c338a090288210b02317ee", [:mix], [], "hexpm", "ef42eb4ac983ca77d650fbdb68368b26570f6cc5895f0faa04d34a6f384abad3"}, + "shards": {:hex, :shards, "1.1.0", "ed3032e63ae99f0eaa6d012b8b9f9cead48b9a810b3f91aeac266cfc4118eff6", [:make, :rebar3], [], "hexpm", "1d188e565a54a458a7a601c2fd1e74f5cfeba755c5a534239266d28b7ff124c7"}, + "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.7", "354c321cf377240c7b8716899e182ce4890c5938111a1296add3ec74cf1715df", [:make, :mix, :rebar3], [], "hexpm", "fe4c190e8f37401d30167c8c405eda19469f34577987c76dde613e838bbc67f8"}, + "table_rex": {:hex, :table_rex, "4.0.0", "3c613a68ebdc6d4d1e731bc973c233500974ec3993c99fcdabb210407b90959b", [:mix], [], "hexpm", "c35c4d5612ca49ebb0344ea10387da4d2afe278387d4019e4d8111e815df8f55"}, + "telemetry": {:hex, :telemetry, "1.3.0", "fedebbae410d715cf8e7062c96a1ef32ec22e764197f70cda73d82778d61e7a2", [:rebar3], [], "hexpm", "7015fc8919dbe63764f4b4b87a95b7c0996bd539e0d499be6ec9d7f3875b79e6"}, + "telemetry_metrics": {:hex, :telemetry_metrics, "1.0.0", "29f5f84991ca98b8eb02fc208b2e6de7c95f8bb2294ef244a176675adc7775df", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "f23713b3847286a534e005126d4c959ebcca68ae9582118ce436b521d1d47d5d"}, + "telemetry_metrics_prometheus_core": {:hex, :telemetry_metrics_prometheus_core, "1.2.1", "c9755987d7b959b557084e6990990cb96a50d6482c683fb9622a63837f3cd3d8", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6 or ~> 1.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "5e2c599da4983c4f88a33e9571f1458bf98b0cf6ba930f1dc3a6e8cf45d5afb6"}, + "telemetry_poller": {:hex, :telemetry_poller, "1.0.0", "db91bb424e07f2bb6e73926fcafbfcbcb295f0193e0a00e825e589a0a47e8453", [:rebar3], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "b3a24eafd66c3f42da30fc3ca7dda1e9d546c12250a2d60d7b81d264fbec4f6e"}, + "thoas": {:hex, :thoas, "1.0.0", "567c03902920827a18a89f05b79a37b5bf93553154b883e0131801600cf02ce0", [:rebar3], [], "hexpm", "fc763185b932ecb32a554fb735ee03c3b6b1b31366077a2427d2a97f3bd26735"}, + "thousand_island": {:hex, :thousand_island, "1.3.7", "1da7598c0f4f5f50562c097a3f8af308ded48cd35139f0e6f17d9443e4d0c9c5", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "0139335079953de41d381a6134d8b618d53d084f558c734f2662d1a72818dd12"}, + "tls_certificate_check": {:hex, :tls_certificate_check, "1.21.0", "042ab2c0c860652bc5cf69c94e3a31f96676d14682e22ec7813bd173ceff1788", [:rebar3], [{:ssl_verify_fun, "~> 1.1", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm", "6cee6cffc35a390840d48d463541d50746a7b0e421acaadb833cfc7961e490e7"}, + "typed_struct": {:hex, :typed_struct, "0.3.0", "939789e3c1dca39d7170c87f729127469d1315dcf99fee8e152bb774b17e7ff7", [:mix], [], "hexpm", "c50bd5c3a61fe4e198a8504f939be3d3c85903b382bde4865579bc23111d1b6d"}, + "unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"}, + "uuid": {:hex, :uuid, "1.1.8", "e22fc04499de0de3ed1116b770c7737779f226ceefa0badb3592e64d5cfb4eb9", [:mix], [], "hexpm", "c790593b4c3b601f5dc2378baae7efaf5b3d73c4c6456ba85759905be792f2ac"}, + "websock": {:hex, :websock, "0.5.3", "2f69a6ebe810328555b6fe5c831a851f485e303a7c8ce6c5f675abeb20ebdadc", [:mix], [], "hexpm", "6105453d7fac22c712ad66fab1d45abdf049868f253cf719b625151460b8b453"}, + "x509": {:hex, :x509, "0.8.8", "aaf5e58b19a36a8e2c5c5cff0ad30f64eef5d9225f0fd98fb07912ee23f7aba3", [:mix], [], "hexpm", "ccc3bff61406e5bb6a63f06d549f3dba3a1bbb456d84517efaaa210d8a33750f"}, + "yamerl": {:hex, :yamerl, "0.10.0", "4ff81fee2f1f6a46f1700c0d880b24d193ddb74bd14ef42cb0bcf46e81ef2f8e", [:rebar3], [], "hexpm", "346adb2963f1051dc837a2364e4acf6eb7d80097c0f53cbdc3046ec8ec4b4e6e"}, + "yaml_elixir": {:hex, :yaml_elixir, "2.9.0", "9a256da867b37b8d2c1ffd5d9de373a4fda77a32a45b452f1708508ba7bbcb53", [:mix], [{:yamerl, "~> 0.10", [hex: :yamerl, repo: "hexpm", optional: false]}], "hexpm", "0cb0e7d4c56f5e99a6253ed1a670ed0e39c13fc45a6da054033928607ac08dfc"}, +} diff --git a/spawn_activators/activator/test/activator_test.exs b/spawn_activators/activator/test/activator_test.exs new file mode 100644 index 000000000..7ae440bcb --- /dev/null +++ b/spawn_activators/activator/test/activator_test.exs @@ -0,0 +1,4 @@ +defmodule ActivatorTest do + use ExUnit.Case + doctest Activator +end diff --git a/spawn_activators/activator/test/codec/cloudevents_codec_test.exs b/spawn_activators/activator/test/codec/cloudevents_codec_test.exs new file mode 100644 index 000000000..3dfc30924 --- /dev/null +++ b/spawn_activators/activator/test/codec/cloudevents_codec_test.exs @@ -0,0 +1,17 @@ +defmodule Activator.Codec.CloudEventTest do + use ExUnit.Case + + # alias Activator.Codec.CloudEvent, as: Codec + # alias Io.Cloudevents.V1.CloudEvent + + # test "decode should return cloudevent module" do + # cloudevent_struct = CloudEvent.new(id: "test", type: "test-type") + # encoded = CloudEvent.encode(cloudevent_struct) + # IO.inspect(is_binary(encoded), label: "Is Binary ------ ") + # IO.inspect(encoded, label: "Encoded ------ ") + + # decoded = Codec.decode(encoded) + # IO.inspect(decoded, label: "Decoded ------ ") + # assert true + # end +end diff --git a/spawn_activators/activator/test/test_helper.exs b/spawn_activators/activator/test/test_helper.exs new file mode 100644 index 000000000..869559e70 --- /dev/null +++ b/spawn_activators/activator/test/test_helper.exs @@ -0,0 +1 @@ +ExUnit.start() diff --git a/spawn_activators/activator_api/.env b/spawn_activators/activator_api/.env new file mode 100644 index 000000000..ff5b8ede3 --- /dev/null +++ b/spawn_activators/activator_api/.env @@ -0,0 +1,4 @@ +PROXY_APP_NAME=spawn-activator-grpc +PROXY_HTTP_PORT=9002 +PROXY_CLUSTER_STRATEGY=gossip +PROXY_CLUSTER_POLLING=3000 diff --git a/spawn_activators/activator_api/.formatter.exs b/spawn_activators/activator_api/.formatter.exs new file mode 100644 index 000000000..d2cda26ed --- /dev/null +++ b/spawn_activators/activator_api/.formatter.exs @@ -0,0 +1,4 @@ +# Used by "mix format" +[ + inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"] +] diff --git a/spawn_activators/activator_api/.gitignore b/spawn_activators/activator_api/.gitignore new file mode 100644 index 000000000..02c64eeac --- /dev/null +++ b/spawn_activators/activator_api/.gitignore @@ -0,0 +1,26 @@ +# The directory Mix will write compiled artifacts to. +/_build/ + +# If you run "mix test --cover", coverage assets end up here. +/cover/ + +# The directory Mix downloads your dependencies sources to. +/deps/ + +# Where third-party dependencies like ExDoc output generated docs. +/doc/ + +# Ignore .fetch files in case you like to edit your project deps locally. +/.fetch + +# If the VM crashes, it generates a dump, let's ignore it too. +erl_crash.dump + +# Also ignore archive artifacts (built via "mix archive.build"). +*.ez + +# Ignore package tarball (built via "mix hex.build"). +activator_api-*.tar + +# Temporary files, for example, from tests. +/tmp/ diff --git a/spawn_activators/activator_api/README.md b/spawn_activators/activator_api/README.md new file mode 100644 index 000000000..b1da20144 --- /dev/null +++ b/spawn_activators/activator_api/README.md @@ -0,0 +1,40 @@ +# Activator GRPC + +**TODO: Add description** + +## Installation + +If [available in Hex](https://hex.pm/docs/publish), the package can be installed +by adding `activator_api` to your list of dependencies in `mix.exs`: + +```elixir +def deps do + [ + {:activator_api, "~> 0.6.0"} + ] +end +``` + +Documentation can be generated with [ExDoc](https://github.com/elixir-lang/ex_doc) +and published on [HexDocs](https://hexdocs.pm). Once published, the docs can +be found at . + +# Compile protos + +```shell +protoc --descriptor_set_out=priv/example/out/user-api.desc \ + --proto_path=priv/protos priv/protos/service.proto \ + --elixir_out=gen_descriptors=true:./priv/example/out # elixir_out or another language protoc plugin +``` + +Create a kubernetes secrets containing the descriptor file created above. + +```shell +kubectl -n default create secret generic protobuf-file-descriptors-secret --from-file=description=priv/example/out/user-api.desc +``` + +```elixir +entities = [%{service_name: "io.eigr.spawn.example.TestService"}] +config = %{entities: entities, proto_file_path: "priv/example/out/user-api.desc", proto: nil} +ActivatorAPI.Api.Discovery.discover(config) +``` \ No newline at end of file diff --git a/spawn_activators/activator_api/compile-example-pb.sh b/spawn_activators/activator_api/compile-example-pb.sh new file mode 100755 index 000000000..cd0a81cd5 --- /dev/null +++ b/spawn_activators/activator_api/compile-example-pb.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env bash + +set -o nounset +set -o errexit +set -o pipefail + +# Compile Protobuf HTTP protos +#protoc --elixir_out=gen_descriptors=true,plugins=grpc:./lib/protobuf --proto_path=priv/protos/google/ priv/protos/google/protobuf/descriptor.proto +protoc --elixir_out=gen_descriptors=true,plugins=grpc:./lib/activator_api/protobuf/api --proto_path=priv/protos/google/api/ priv/protos/google/api/http.proto +protoc --elixir_out=gen_descriptors=true,plugins=grpc:./lib/activator_api/protobuf/api --proto_path=priv/protos/google/api/ priv/protos/google/api/annotations.proto +protoc --elixir_out=gen_descriptors=true,plugins=grpc:./lib/activator_api/protobuf/api --proto_path=priv/protos/google/api/ priv/protos/google/api/httpbody.proto +protoc --elixir_out=gen_descriptors=true,plugins=grpc:./lib/activator_api/protobuf/api --proto_path=priv/protos/google/api/ priv/protos/google/api/auth.proto +protoc --elixir_out=gen_descriptors=true,plugins=grpc:./lib/activator_api/protobuf/api --proto_path=priv/protos/google/api/ priv/protos/google/api/source_info.proto + +protoc --descriptor_set_out=priv/example/out/user-api.desc \ + --proto_path=priv/protos priv/protos/service.proto \ + --elixir_out=gen_descriptors=true,plugins=grpc:./priv/example/out \ No newline at end of file diff --git a/spawn_activators/activator_api/lib/activator_api.ex b/spawn_activators/activator_api/lib/activator_api.ex new file mode 100644 index 000000000..9c6f02d31 --- /dev/null +++ b/spawn_activators/activator_api/lib/activator_api.ex @@ -0,0 +1,5 @@ +defmodule ActivatorAPI do + @moduledoc """ + Documentation for `ActivatorAPI`. + """ +end diff --git a/spawn_activators/activator_api/lib/activator_api/api/discovery.ex b/spawn_activators/activator_api/lib/activator_api/api/discovery.ex new file mode 100644 index 000000000..e75a0f5df --- /dev/null +++ b/spawn_activators/activator_api/lib/activator_api/api/discovery.ex @@ -0,0 +1,190 @@ +defmodule ActivatorAPI.Api.Discovery do + @moduledoc false + require Logger + + alias Google.Protobuf.FileDescriptorSet + alias ActivatorAPI.GrpcUtils, as: Util + + def discover(%{endpoint_builders: _builders, proto_file_path: path} = args) do + with {:load_file, {:ok, proto}} <- {:load_file, File.read(path)}, + {:parse, {:ok, descriptors, endpoints}} <- + {:parse, parse(%{args | proto: proto})} do + {:ok, descriptors, endpoints} + else + {:load_file, {:error, reason}} -> + Logger.error("Failure to load protobuf file descriptor. Details: #{inspect(reason)}") + {:error, {:load_file, reason}} + + {:parse, error} -> + Logger.error("Failure to parse protobuf file descriptor. Details: #{inspect(error)}") + {:error, {:parse, error}} + end + end + + defp parse(args) do + args + |> validate() + |> to_endpoints() + end + + defp validate(args) do + builders = args.endpoint_builders + + if Enum.empty?(builders) do + Logger.error("No endpoint were reported by the discover call!") + raise "No endpoints were reported by the discover call!" + end + + if !is_binary(args.proto) do + Logger.error("No descriptors found") + raise "No descriptors found!" + end + + {:ok, args} + end + + defp to_endpoints({:ok, args}) do + builders = args.endpoint_builders + descriptor = FileDescriptorSet.decode(args.proto) + file_descriptors = descriptor.file + + endpoints = + builders + |> Flow.from_enumerable() + |> Flow.map(&build_endpoint(&1, file_descriptors)) + |> Enum.to_list() + + Logger.debug("Found #{Enum.count(endpoints)} Endpoints to processing.") + {:ok, file_descriptors, endpoints} + end + + defp build_endpoint(endpoint, file_descriptors) do + messages = + file_descriptors + |> Flow.from_enumerable() + |> Flow.map(&extract_messages/1) + |> Enum.reduce([], fn elem, acc -> + acc ++ [elem] + end) + |> List.flatten() + + services = + file_descriptors + |> Flow.from_enumerable() + |> Flow.map(&extract_services(&1, endpoint.service_name)) + |> Enum.reduce([], fn elem, acc -> + acc ++ [elem] + end) + |> List.flatten() + + %{ + service_name: endpoint.service_name, + dispatcher_options: endpoint, + messages: Enum.filter(messages, fn x -> x != [] end), + services: Enum.filter(services, fn x -> x != [] end) + } + end + + defp extract_messages(file) do + file.message_type + |> Flow.from_enumerable() + |> Flow.map(&to_message_item/1) + |> Enum.reduce([], fn elem, acc -> + acc ++ [elem] + end) + |> List.flatten() + end + + defp extract_services(file, service_name) do + name = + service_name + |> String.split(".") + |> List.last() + + file.service + |> Flow.from_enumerable() + |> Flow.filter(fn service -> + String.trim(service.name) != "" && service.name == name + end) + |> Flow.map(&to_service_item/1) + |> Enum.reduce([], fn elem, acc -> + acc ++ [elem] + end) + |> List.flatten() + end + + defp to_message_item(message) do + attributes = + message.field + |> Flow.from_enumerable() + |> Flow.map(&extract_field_attributes/1) + |> Enum.to_list() + + %{name: message.name, attributes: attributes} + end + + defp to_service_item(service) do + methods = + service.method + |> Flow.from_enumerable() + |> Flow.map(&extract_service_method/1) + |> Enum.to_list() + + %{name: service.name, methods: methods} + end + + defp extract_field_attributes(field) do + type_options = + if field.options != nil && field.options.ctype != nil do + field.options.ctype + end + + %{ + name: field.name, + number: field.number, + type: field.type, + label: field.label, + options: %{type: type_options} + } + end + + defp extract_service_method(method) do + http_options = + if method.options != nil do + http_rules = Util.get_http_rule(method) + Logger.debug("Mehod Options: #{inspect(http_rules)}") + + %{type: "http", data: http_rules} + end + + svc = %{ + name: method.name, + unary: is_unary(method), + streamed: is_streamed(method), + input_type: method.input_type, + output_type: method.output_type, + stream_in: method.client_streaming, + stream_out: method.server_streaming, + options: [http_options] + } + + Logger.debug("Service mapped #{inspect(svc)}") + svc + end + + defp is_unary(method) do + if method.client_streaming == false && method.server_streaming == false do + true + else + false + end + end + + defp is_streamed(method) do + if method.client_streaming == true && method.server_streaming == true do + true + else + false + end + end +end diff --git a/spawn_activators/activator_api/lib/activator_api/api/dispatcher.ex b/spawn_activators/activator_api/lib/activator_api/api/dispatcher.ex new file mode 100644 index 000000000..75afb2ec6 --- /dev/null +++ b/spawn_activators/activator_api/lib/activator_api/api/dispatcher.ex @@ -0,0 +1,53 @@ +defmodule ActivatorAPI.Api.Dispatcher do + @moduledoc """ + Dispatch behaviour to requests gRPC requests to Actor Actions. + """ + + @type message :: any() + + @type stream :: GRPC.Server.Stream.t() + + @type opts :: [ + action: String.t(), + actor_name: String.t(), + async: String.t() | boolean(), + authentication_kind: String.t(), + authentication_secret: String.t(), + service_name: String.t(), + original_method: String.t(), + system_name: String.t(), + system_name: String.t(), + invocation_type: String.t(), + request_type: String.t(), + input_type: String.t(), + output_type: String.t(), + pooled: String.t() | boolean(), + timeout: non_neg_integer(), + stream_out_from_channel: String.t() + ] + + @doc """ + + + ``` + [ + service_name: "io.eigr.spawn.example.TestService", + original_method: "Sum", + actor_name: "joe", + action: "sum" + system_name: "spawn-system", + invocation_type: "invoke", + request_type: "unary", + input_type: Io.Eigr.Spawn.Example.MyBusinessMessage, + output_type: Io.Eigr.Spawn.Example.MyBusinessMessage, + pooled: "false", + timeout: "30000", + async: "false", + stream_out_from_channel: "my-channel", + authentication_kind: "basic", + authentication_secret: "" + ] + ``` + """ + @callback dispatch(message(), stream(), opts()) :: any() +end diff --git a/spawn_activators/activator_api/lib/activator_api/api/dispatcher/stream_in_dispatcher.ex b/spawn_activators/activator_api/lib/activator_api/api/dispatcher/stream_in_dispatcher.ex new file mode 100644 index 000000000..9bdc60162 --- /dev/null +++ b/spawn_activators/activator_api/lib/activator_api/api/dispatcher/stream_in_dispatcher.ex @@ -0,0 +1,12 @@ +defmodule ActivatorAPI.Api.Dispatcher.StreamInDispatcher do + @moduledoc """ + `StreamInDispatcher` + """ + @behaviour ActivatorAPI.Api.Dispatcher + + require Logger + + @impl true + def dispatch(_message, _stream, _opts) do + end +end diff --git a/spawn_activators/activator_api/lib/activator_api/api/dispatcher/stream_out_dispatcher.ex b/spawn_activators/activator_api/lib/activator_api/api/dispatcher/stream_out_dispatcher.ex new file mode 100644 index 000000000..dd24fba61 --- /dev/null +++ b/spawn_activators/activator_api/lib/activator_api/api/dispatcher/stream_out_dispatcher.ex @@ -0,0 +1,12 @@ +defmodule ActivatorAPI.Api.Dispatcher.StreamOutDispatcher do + @moduledoc """ + `StreamOutDispatcher` + """ + @behaviour ActivatorAPI.Api.Dispatcher + + require Logger + + @impl true + def dispatch(_message, _stream, _opts) do + end +end diff --git a/spawn_activators/activator_api/lib/activator_api/api/dispatcher/streamed_dispatcher.ex b/spawn_activators/activator_api/lib/activator_api/api/dispatcher/streamed_dispatcher.ex new file mode 100644 index 000000000..b77d7c1d6 --- /dev/null +++ b/spawn_activators/activator_api/lib/activator_api/api/dispatcher/streamed_dispatcher.ex @@ -0,0 +1,12 @@ +defmodule ActivatorAPI.Api.Dispatcher.StreamedDispatcher do + @moduledoc """ + `StreamedDispatcher` + """ + @behaviour ActivatorAPI.Api.Dispatcher + + require Logger + + @impl true + def dispatch(_message, _stream, _opts) do + end +end diff --git a/spawn_activators/activator_api/lib/activator_api/api/dispatcher/unary_dispatcher.ex b/spawn_activators/activator_api/lib/activator_api/api/dispatcher/unary_dispatcher.ex new file mode 100644 index 000000000..23dd0ccbb --- /dev/null +++ b/spawn_activators/activator_api/lib/activator_api/api/dispatcher/unary_dispatcher.ex @@ -0,0 +1,83 @@ +defmodule ActivatorAPI.Api.Dispatcher.UnaryDispatcher do + @moduledoc """ + `UnaryDispatcher` + """ + @behaviour ActivatorAPI.Api.Dispatcher + + require Logger + + alias Spawn.Actors.{ + Actor, + ActorId, + ActorSystem + } + + alias Spawn.{ + ActorInvocationResponse, + InvocationRequest, + Noop + } + + import Spawn.Utils.AnySerializer + + @impl true + def dispatch(message, stream, opts \\ []), do: handle_unary(message, stream, opts) + + defp handle_unary(message, _stream, opts) do + actor_name = Keyword.fetch!(opts, :actor_name) + system = Keyword.fetch!(opts, :system_name) + action = Keyword.get(opts, :action) + async = Keyword.get(opts, :async, false) + pooled = Keyword.get(opts, :pooled, false) + _timeout = Keyword.get(opts, :timeout, 30_000) + metadata = Keyword.get(opts, :metadata, %{}) + _authentication_kind = Keyword.get(opts, :authentication_kind, "none") + + opts = [] + payload = parse_payload(message) + + req = %InvocationRequest{ + system: %ActorSystem{name: system}, + actor: %Actor{ + id: %ActorId{name: actor_name, system: system} + }, + metadata: metadata, + payload: payload, + action_name: action, + async: cast(async, :boolean), + caller: nil, + pooled: cast(pooled, :boolean) + } + + case Actors.invoke_with_nats(req, opts) do + {:ok, :async} -> + Logger.debug("Asynchronous Request ok. Send response to caller") + :ok + + {:ok, %ActorInvocationResponse{payload: payload}} -> + Logger.debug("Synchronous Request ok. Send response to caller") + unpack_unknown(payload) + + error -> + Logger.debug("Error on send Request. #{inspect(error)}") + error + end + end + + defp cast(value, :boolean), do: to_boolean(value) + + defp to_boolean("false"), do: false + defp to_boolean("true"), do: true + defp to_boolean(value) when is_boolean(value), do: value + + defp parse_payload(message) do + case message do + nil -> {:noop, %Noop{}} + %Noop{} = noop -> {:noop, noop} + {:noop, %Noop{} = noop} -> {:noop, noop} + {_, nil} -> {:noop, %Noop{}} + {:value, message} -> {:value, any_pack!(message)} + message -> {:value, any_pack!(message)} + end + end +end diff --git a/spawn_activators/activator_api/lib/activator_api/api/generator.ex b/spawn_activators/activator_api/lib/activator_api/api/generator.ex new file mode 100644 index 000000000..1e58dda0d --- /dev/null +++ b/spawn_activators/activator_api/lib/activator_api/api/generator.ex @@ -0,0 +1,82 @@ +defmodule ActivatorAPI.API.Generator do + @moduledoc false + require Logger + alias Protobuf.Protoc.Generator.Message, as: MessageGenerator + alias Protobuf.Protoc.Generator.Enum, as: EnumGenerator + alias Protobuf.Protoc.Generator.Service, as: ServiceGenerator + alias Protobuf.Protoc.Generator.Extension, as: ExtensionGenerator + + @locals_without_parens [field: 2, field: 3, oneof: 2, rpc: 3, extend: 4, extensions: 1] + + @spec generate_content(Context.t(), Google.Protobuf.FileDescriptorProto.t()) :: String.t() + def generate_content(ctx, desc) do + ctx = %{ + ctx + | package: desc.package || "", + syntax: syntax(desc.syntax), + dep_type_mapping: get_dep_type_mapping(ctx, desc.dependency, desc.name) + } + + ctx = Map.put(ctx, :module_prefix, ctx.package || "") + ctx = Protobuf.Protoc.Context.custom_file_options_from_file_desc(ctx, desc) + {enums, msgs} = MessageGenerator.generate_list(ctx, desc.message_type) + + list = + enums ++ + Enum.map(desc.enum_type, fn d -> EnumGenerator.generate(ctx, d) end) ++ + msgs ++ + if Enum.member?(ctx.plugins, "grpc") do + Enum.map(desc.service, fn d -> ServiceGenerator.generate(ctx, d) end) + end + + # TODO Verify get_nested_extensions(ctx, desc.message_type)/2 error + # nested_extensions = + # ExtensionGenerator.get_nested_extensions(ctx, desc.message_type) + # |> Enum.reverse() + + # list = list ++ [ExtensionGenerator.generate(ctx, desc, nested_extensions)] + + list + |> List.flatten() + |> Enum.filter(&(!is_nil(&1))) + |> Enum.map(fn {_, v} -> v end) + |> Enum.join("\n") + |> format_code() + end + + @doc false + def get_dep_pkgs(%{pkg_mapping: mapping, package: pkg}, deps) do + pkgs = deps |> Enum.map(fn dep -> mapping[dep] end) + pkgs = if pkg && byte_size(pkg) > 0, do: [pkg | pkgs], else: pkgs + Enum.sort(pkgs, &(byte_size(&2) <= byte_size(&1))) + end + + def get_dep_type_mapping(%{global_type_mapping: global_mapping}, deps, file_name) do + mapping = + Enum.reduce(deps, %{}, fn dep, acc -> + Map.merge(acc, global_mapping[dep]) + end) + + Map.merge(mapping, global_mapping[file_name]) + end + + defp syntax("proto3"), do: :proto3 + defp syntax(_), do: :proto2 + + def format_code(code) do + formatted = + if Code.ensure_loaded?(Code) && function_exported?(Code, :format_string!, 2) do + code + |> Code.format_string!(locals_without_parens: @locals_without_parens) + |> IO.iodata_to_binary() + else + code + end + + if formatted == "" do + formatted + else + formatted <> "\n" + end + end +end diff --git a/spawn_activators/activator_api/lib/activator_api/api/reflection.ex b/spawn_activators/activator_api/lib/activator_api/api/reflection.ex new file mode 100644 index 000000000..2ca1803ea --- /dev/null +++ b/spawn_activators/activator_api/lib/activator_api/api/reflection.ex @@ -0,0 +1,97 @@ +defmodule ActivatorAPI.API.Reflection do + @moduledoc false + require Logger + + alias ActivatorAPI.API.Generator + + def prepare(file_descriptor) do + ctx = + %Protobuf.Protoc.Context{gen_descriptors?: true, plugins: ["grpc"]} + |> find_types(file_descriptor) + + file_descriptor + |> Stream.map(fn desc -> + {desc.name, Generator.generate_content(ctx, desc)} + end) + |> Enum.to_list() + end + + @doc false + def find_types(ctx, descs) do + find_types(ctx, descs, %{}) + end + + @doc false + def find_types(ctx, [], acc) do + %{ctx | global_type_mapping: acc} + end + + def find_types(ctx, [desc | t], acc) do + types = find_types_in_proto(desc) + find_types(ctx, t, Map.put(acc, desc.name, types)) + end + + @doc false + def find_types_in_proto(%Google.Protobuf.FileDescriptorProto{} = desc) do + desc_ctx = %Protobuf.Protoc.Context{ + package: desc.package, + namespace: [] + } + + ctx = %{desc_ctx | module_prefix: desc_ctx.package || ""} + + %{} + |> find_types_in_proto(ctx, desc.message_type) + |> find_types_in_proto(ctx, desc.enum_type) + end + + defp find_types_in_proto(types, ctx, descs) when is_list(descs) do + Enum.reduce(descs, types, fn desc, acc -> + find_types_in_proto(acc, ctx, desc) + end) + end + + defp find_types_in_proto(types, ctx, %Google.Protobuf.DescriptorProto{name: name} = desc) do + new_ctx = append_ns(ctx, name) + + types + |> update_types(ctx, name) + |> find_types_in_proto(new_ctx, desc.enum_type) + |> find_types_in_proto(new_ctx, desc.nested_type) + end + + defp find_types_in_proto(types, ctx, %Google.Protobuf.EnumDescriptorProto{name: name}) do + update_types(types, ctx, name) + end + + defp append_ns(%{namespace: ns} = ctx, name) do + new_ns = ns ++ [name] + Map.put(ctx, :namespace, new_ns) + end + + defp update_types(types, %{namespace: ns, package: pkg, module_prefix: prefix}, name) do + type_name = + join_names(prefix || pkg, ns, name) + |> normalize_type_name() + + Map.put(types, "." <> join_names(pkg, ns, name), %{type_name: type_name}) + end + + defp join_names(pkg, ns, name) do + ns_str = Enum.join(ns, ".") + + [pkg, ns_str, name] + |> Enum.filter(&(&1 && &1 != "")) + |> Enum.join(".") + end + + defp normalize_type_name(name) do + name + |> String.split(".") + |> Enum.map_join(".", &trans_name/1) + end + + defp trans_name(name) do + Macro.camelize(name) + end +end diff --git a/spawn_activators/activator_api/lib/activator_api/api/router_dispatcher.ex b/spawn_activators/activator_api/lib/activator_api/api/router_dispatcher.ex new file mode 100644 index 000000000..e720d8f3a --- /dev/null +++ b/spawn_activators/activator_api/lib/activator_api/api/router_dispatcher.ex @@ -0,0 +1,40 @@ +defmodule ActivatorAPI.Api.RouterDispatcher do + @moduledoc """ + Dispatch requests to Actors Actions. + """ + require Logger + + @behaviour ActivatorAPI.Api.Dispatcher + + alias ActivatorAPI.Api.Dispatcher.{ + StreamInDispatcher, + StreamOutDispatcher, + StreamedDispatcher, + UnaryDispatcher + } + + @impl true + def dispatch(message, stream, opts \\ []) do + Logger.debug( + "Received message #{inspect(message)} from stream #{inspect(stream)} with options #{inspect(opts)}." + ) + + Keyword.get(opts, :request_type, "unary") + |> case do + "unary" -> + UnaryDispatcher.dispatch(message, stream, opts) + + "stream_in" -> + StreamInDispatcher.dispatch(message, stream, opts) + + "stream_out" -> + StreamOutDispatcher.dispatch(message, stream, opts) + + "streamed" -> + StreamedDispatcher.dispatch(message, stream, opts) + + _ -> + raise ArgumentError, "Router not found for options #{inspect(opts)}!" + end + end +end diff --git a/spawn_activators/activator_api/lib/activator_api/application.ex b/spawn_activators/activator_api/lib/activator_api/application.ex new file mode 100644 index 000000000..e8e97a9ca --- /dev/null +++ b/spawn_activators/activator_api/lib/activator_api/application.ex @@ -0,0 +1,87 @@ +defmodule ActivatorAPI.Application do + @moduledoc false + + use Application + + alias Actors.Config.PersistentTermConfig, as: Config + alias ActivatorAPI.Api.Discovery + alias ActivatorAPI.GrpcServer, as: Server + + import Activator, only: [get_http_port: 1] + + @impl true + def start(_type, _args) do + Config.load() + + children = + [ + Activator.Supervisor.child_spec([]), + {Bandit, plug: ActivatorAPI.Router, scheme: :http, port: get_http_port()} + ] + |> put_grpc_server() + + opts = [strategy: :one_for_one, name: ActivatorAPI.Supervisor] + Supervisor.start_link(children, opts) + end + + defp put_grpc_server(children) do + builders = [ + %{ + service_name: "io.eigr.spawn.example.TestService", + protocol: "grpc", + system: "spawn-system", + actor: "joe", + action: "sum", + parent_actor: nil, + options: %{ + # valids are: "invoke", "spawn-invoke" + invocation_type: "invoke", + pooled: false, + timeout: 30_000, + async: false, + stream_out_from_channel: "my-channel", + authentication: %{ + # valids are :none, basic, token + kind: "basic", + secret_key: "" + } + } + } + # %{ + # service_name: "io.eigr.spawn.example.TestService", + # protocol: "grpc", + # system: "spawn-system", + # actor: "robert", + # action: "sum", + # parent_actor: "unnamed_actor", + # options: %{ + # # valids are: "invoke", "spawn-invoke" + # invocation_type: "spawn-invoke", + # pooled: false, + # timeout: 30_000, + # async: false, + # stream_out_from_channel: "my-channel", + # authentication: %{ + # # valids are :none, basic, token + # kind: "basic", + # secret_key: "" + # } + # } + # } + ] + + route_config = %{ + endpoint_builders: builders, + proto_file_path: "priv/example/out/user-api.desc", + proto: nil + } + + case Discovery.discover(route_config) do + {:ok, descriptors, endpoints} -> + children ++ [Server.child_spec(descriptors, endpoints)] + + error -> + raise ArgumentError, "Unable to start the application #{inspect(error)}" + end + end +end diff --git a/spawn_activators/activator_api/lib/activator_api/grpc_server.ex b/spawn_activators/activator_api/lib/activator_api/grpc_server.ex new file mode 100644 index 000000000..f5fe62e23 --- /dev/null +++ b/spawn_activators/activator_api/lib/activator_api/grpc_server.ex @@ -0,0 +1,223 @@ +defmodule ActivatorAPI.GrpcServer do + @moduledoc false + require Logger + + alias ActivatorAPI.API.Reflection + alias ActivatorAPI.GrpcUtils, as: Util + + def child_spec(descriptors, endpoints) do + {u_secs, spec} = + :timer.tc(fn -> + spec = + with {:ok, descriptors} <- descriptors |> compile(), + {:ok, _} <- generate_services(endpoints), + {:ok, _} <- generate_endpoints(endpoints) do + build_child_spec(descriptors, endpoints) + else + _ -> + Logger.error("Error during gRPC Server initialization") + nil + end + + spec + end) + + Logger.info("Started gRPC Server in #{u_secs / 1_000_000}ms") + spec + end + + defp compile(descriptors) do + files = + descriptors + |> Reflection.prepare() + + for {name, file} <- files do + case Util.compile(file) do + modules when is_list(modules) -> + Logger.debug("Compiled #{name} module!") + + _ -> + Logger.debug("Fail to compile service") + end + end + + {:ok, descriptors} + end + + defp generate_services(endpoints) do + root_template_path = + Application.get_env( + :activator_api, + :proxy_root_template_path, + :code.priv_dir(:activator_api) + ) + + grpc_template_path = + Path.expand( + "./templates/grpc_service.ex.eex", + root_template_path + ) + + for endpoint <- endpoints do + name = Enum.join([Util.normalize_service_name(endpoint.service_name), "Service"], ".") + + Stream.map(endpoint.services, fn service -> + methods = + service.methods + |> Flow.from_enumerable() + |> Flow.map(&Util.normalize_method_name(&1.name)) + |> Enum.to_list() + + Logger.info("Generating Service #{name} with Methods: #{inspect(methods)}") + + original_methods = get_method_names(service) + input_types = get_input_type(service) + output_types = get_output_type(service) + request_types = get_request_type(service) + + mod = + Util.get_module( + grpc_template_path, + mod_name: name, + name: name, + methods: methods, + original_methods: original_methods, + handler: "ActivatorAPI.Api.RouterDispatcher", + service_name: endpoint.service_name, + input_types: input_types, + output_types: output_types, + request_types: request_types, + dispatcher_actor_name: endpoint.dispatcher_options.actor, + dispatcher_actor_action: endpoint.dispatcher_options.action, + dispatcher_actor_parent: endpoint.dispatcher_options.parent_actor, + dispatcher_system_name: endpoint.dispatcher_options.system, + dispatcher_option_invocation_type: + endpoint.dispatcher_options.options.invocation_type, + dispatcher_option_pooled: endpoint.dispatcher_options.options.pooled, + dispatcher_option_timeout: endpoint.dispatcher_options.options.timeout, + dispatcher_option_async: endpoint.dispatcher_options.options.async, + dispatcher_option_output_channel: + endpoint.dispatcher_options.options.stream_out_from_channel, + dispatcher_option_authentication_kind: + endpoint.dispatcher_options.options.authentication.kind, + dispatcher_option_authentication_secret: + endpoint.dispatcher_options.options.authentication.secret_key + ) + + Logger.debug("Service Definition:\n#{mod}") + Util.compile(mod) + Logger.debug("Service compilation finish!") + end) + |> Stream.run() + end + + {:ok, endpoints} + end + + defp generate_endpoints(entities) do + root_template_path = + Application.get_env( + :activator_api, + :proxy_root_template_path, + :code.priv_dir(:activator_api) + ) + + grpc_endpoint_template_path = + Path.expand( + "./templates/grpc_endpoint.ex.eex", + root_template_path + ) + + services = + entities + |> Flow.from_enumerable() + |> Flow.map( + &Enum.join([Util.normalize_service_name(&1.service_name), "Service.ProxyService"], ".") + ) + |> Enum.to_list() + + mod = + Util.get_module( + grpc_endpoint_template_path, + service_names: services + ) + + Logger.debug("Endpoint Definition:\n#{mod}") + Util.compile(mod) + Logger.debug("Endpoint compilation finish!") + + {:ok, entities} + end + + defp build_child_spec(_descriptors, _entities) do + Application.put_env(:grpc, :start_server, true, persistent: true) + + opts = get_grpc_options() + grpc_spec = {GRPC.Server.Supervisor, opts} + # http_spec = HttpRouter.child_spec(entities) + # reflection_spec = MassaProxy.Reflection.Server.child_spec(descriptors) + grpc_spec + end + + defp get_grpc_options() do + port = Application.get_env(:activator_api, :proxy_port, 9009) + + if Application.get_env(:activator_api, :tls) do + cert_path = Application.get_env(:activator_api, :tls_cert_path) + key_path = Application.get_env(:activator_api, :tls_key_path) + cred = GRPC.Credential.new(ssl: [certfile: cert_path, keyfile: key_path]) + + {ActivatorAPI.Server.Grpc.ProxyEndpoint, port, cred: cred} + else + {ActivatorAPI.Server.Grpc.ProxyEndpoint, port} + end + end + + defp get_method_names(services), + do: + Enum.reduce(services.methods, %{}, fn method, acc -> + Map.put( + acc, + Util.normalize_method_name(method.name), + method.name + ) + end) + + defp get_input_type(services), + do: + Enum.reduce(services.methods, %{}, fn method, acc -> + Map.put( + acc, + Util.normalize_method_name(method.name), + String.replace_leading(Util.normalize_service_name(method.input_type), ".", "") + ) + end) + + defp get_output_type(services), + do: + Enum.reduce(services.methods, %{}, fn method, acc -> + Map.put( + acc, + Util.normalize_method_name(method.name), + String.replace_leading(Util.normalize_service_name(method.output_type), ".", "") + ) + end) + + defp get_request_type(services), + do: + Enum.reduce(services.methods, %{}, fn method, acc -> + Map.put(acc, Util.normalize_method_name(method.name), get_type(method)) + end) + + defp get_type(method) do + type = + cond do + method.unary == true -> "unary" + method.streamed == true -> "streamed" + method.stream_in == true -> "stream_in" + method.stream_out == true -> "stream_out" + end + + type + end +end diff --git a/spawn_activators/activator_api/lib/activator_api/grpc_utils.ex b/spawn_activators/activator_api/lib/activator_api/grpc_utils.ex new file mode 100644 index 000000000..a3d67d1ff --- /dev/null +++ b/spawn_activators/activator_api/lib/activator_api/grpc_utils.ex @@ -0,0 +1,51 @@ +defmodule ActivatorAPI.GrpcUtils do + @moduledoc false + require Logger + + def get_http_rule(method_descriptor) do + Logger.debug("MethodOptions HTTP Rules: #{inspect(method_descriptor)}") + + Google.Protobuf.MethodOptions.get_extension( + method_descriptor.options, + Google.Api.PbExtension, + :http + ) + end + + def get_type_url(type) do + parts = + type + |> to_string + |> String.replace("Elixir.", "") + |> String.split(".") + + package_name = + with {_, list} <- parts |> List.pop_at(-1), + do: list |> Stream.map(&String.downcase(&1)) |> Enum.join(".") + + type_name = parts |> List.last() + "type.googleapis.com/#{package_name}.#{type_name}" + end + + def compile(file) do + Code.compile_string(file) + rescue + error in UndefinedFunctionError -> + Logger.error("Error in Module definition. Make sure the service name is correct") + raise error + + error -> + Logger.error("Error during Service compilation phase #{inspect(error)}") + end + + def normalize_service_name(name) do + name + |> String.split(".") + |> Stream.map(&Macro.camelize(&1)) + |> Enum.join(".") + end + + def normalize_method_name(name), do: Macro.underscore(name) + + def get_module(filename, bindings \\ []), do: EEx.eval_file(filename, bindings) +end diff --git a/spawn_activators/activator_api/lib/activator_api/protobuf/api/annotations.pb.ex b/spawn_activators/activator_api/lib/activator_api/protobuf/api/annotations.pb.ex new file mode 100644 index 000000000..e172e169f --- /dev/null +++ b/spawn_activators/activator_api/lib/activator_api/protobuf/api/annotations.pb.ex @@ -0,0 +1,6 @@ +defmodule Google.Api.PbExtension do + @moduledoc false + use Protobuf, protoc_gen_elixir_version: "0.10.0", syntax: :proto3 + + extend(Google.Protobuf.MethodOptions, :http, 72_295_728, optional: true, type: HttpRule) +end diff --git a/spawn_activators/activator_api/lib/activator_api/protobuf/api/auth.pb.ex b/spawn_activators/activator_api/lib/activator_api/protobuf/api/auth.pb.ex new file mode 100644 index 000000000..05870db78 --- /dev/null +++ b/spawn_activators/activator_api/lib/activator_api/protobuf/api/auth.pb.ex @@ -0,0 +1,437 @@ +defmodule Google.Api.Authentication do + @moduledoc false + use Protobuf, protoc_gen_elixir_version: "0.10.0", syntax: :proto3 + + def descriptor do + # credo:disable-for-next-line + %Google.Protobuf.DescriptorProto{ + __unknown_fields__: [], + enum_type: [], + extension: [], + extension_range: [], + field: [ + %Google.Protobuf.FieldDescriptorProto{ + __unknown_fields__: [], + default_value: nil, + extendee: nil, + json_name: "rules", + label: :LABEL_REPEATED, + name: "rules", + number: 3, + oneof_index: nil, + options: nil, + proto3_optional: nil, + type: :TYPE_MESSAGE, + type_name: ".google.api.AuthenticationRule" + }, + %Google.Protobuf.FieldDescriptorProto{ + __unknown_fields__: [], + default_value: nil, + extendee: nil, + json_name: "providers", + label: :LABEL_REPEATED, + name: "providers", + number: 4, + oneof_index: nil, + options: nil, + proto3_optional: nil, + type: :TYPE_MESSAGE, + type_name: ".google.api.AuthProvider" + } + ], + name: "Authentication", + nested_type: [], + oneof_decl: [], + options: nil, + reserved_name: [], + reserved_range: [] + } + end + + field(:rules, 3, repeated: true, type: Google.Api.AuthenticationRule) + field(:providers, 4, repeated: true, type: Google.Api.AuthProvider) +end + +defmodule Google.Api.AuthenticationRule do + @moduledoc false + use Protobuf, protoc_gen_elixir_version: "0.10.0", syntax: :proto3 + + def descriptor do + # credo:disable-for-next-line + %Google.Protobuf.DescriptorProto{ + __unknown_fields__: [], + enum_type: [], + extension: [], + extension_range: [], + field: [ + %Google.Protobuf.FieldDescriptorProto{ + __unknown_fields__: [], + default_value: nil, + extendee: nil, + json_name: "selector", + label: :LABEL_OPTIONAL, + name: "selector", + number: 1, + oneof_index: nil, + options: nil, + proto3_optional: nil, + type: :TYPE_STRING, + type_name: nil + }, + %Google.Protobuf.FieldDescriptorProto{ + __unknown_fields__: [], + default_value: nil, + extendee: nil, + json_name: "oauth", + label: :LABEL_OPTIONAL, + name: "oauth", + number: 2, + oneof_index: nil, + options: nil, + proto3_optional: nil, + type: :TYPE_MESSAGE, + type_name: ".google.api.OAuthRequirements" + }, + %Google.Protobuf.FieldDescriptorProto{ + __unknown_fields__: [], + default_value: nil, + extendee: nil, + json_name: "allowWithoutCredential", + label: :LABEL_OPTIONAL, + name: "allow_without_credential", + number: 5, + oneof_index: nil, + options: nil, + proto3_optional: nil, + type: :TYPE_BOOL, + type_name: nil + }, + %Google.Protobuf.FieldDescriptorProto{ + __unknown_fields__: [], + default_value: nil, + extendee: nil, + json_name: "requirements", + label: :LABEL_REPEATED, + name: "requirements", + number: 7, + oneof_index: nil, + options: nil, + proto3_optional: nil, + type: :TYPE_MESSAGE, + type_name: ".google.api.AuthRequirement" + } + ], + name: "AuthenticationRule", + nested_type: [], + oneof_decl: [], + options: nil, + reserved_name: [], + reserved_range: [] + } + end + + field(:selector, 1, type: :string) + field(:oauth, 2, type: Google.Api.OAuthRequirements) + field(:allow_without_credential, 5, type: :bool, json_name: "allowWithoutCredential") + field(:requirements, 7, repeated: true, type: Google.Api.AuthRequirement) +end + +defmodule Google.Api.JwtLocation do + @moduledoc false + use Protobuf, protoc_gen_elixir_version: "0.10.0", syntax: :proto3 + + def descriptor do + # credo:disable-for-next-line + %Google.Protobuf.DescriptorProto{ + __unknown_fields__: [], + enum_type: [], + extension: [], + extension_range: [], + field: [ + %Google.Protobuf.FieldDescriptorProto{ + __unknown_fields__: [], + default_value: nil, + extendee: nil, + json_name: "header", + label: :LABEL_OPTIONAL, + name: "header", + number: 1, + oneof_index: 0, + options: nil, + proto3_optional: nil, + type: :TYPE_STRING, + type_name: nil + }, + %Google.Protobuf.FieldDescriptorProto{ + __unknown_fields__: [], + default_value: nil, + extendee: nil, + json_name: "query", + label: :LABEL_OPTIONAL, + name: "query", + number: 2, + oneof_index: 0, + options: nil, + proto3_optional: nil, + type: :TYPE_STRING, + type_name: nil + }, + %Google.Protobuf.FieldDescriptorProto{ + __unknown_fields__: [], + default_value: nil, + extendee: nil, + json_name: "cookie", + label: :LABEL_OPTIONAL, + name: "cookie", + number: 4, + oneof_index: 0, + options: nil, + proto3_optional: nil, + type: :TYPE_STRING, + type_name: nil + }, + %Google.Protobuf.FieldDescriptorProto{ + __unknown_fields__: [], + default_value: nil, + extendee: nil, + json_name: "valuePrefix", + label: :LABEL_OPTIONAL, + name: "value_prefix", + number: 3, + oneof_index: nil, + options: nil, + proto3_optional: nil, + type: :TYPE_STRING, + type_name: nil + } + ], + name: "JwtLocation", + nested_type: [], + oneof_decl: [ + %Google.Protobuf.OneofDescriptorProto{__unknown_fields__: [], name: "in", options: nil} + ], + options: nil, + reserved_name: [], + reserved_range: [] + } + end + + oneof(:in, 0) + + field(:header, 1, type: :string, oneof: 0) + field(:query, 2, type: :string, oneof: 0) + field(:cookie, 4, type: :string, oneof: 0) + field(:value_prefix, 3, type: :string, json_name: "valuePrefix") +end + +defmodule Google.Api.AuthProvider do + @moduledoc false + use Protobuf, protoc_gen_elixir_version: "0.10.0", syntax: :proto3 + + def descriptor do + # credo:disable-for-next-line + %Google.Protobuf.DescriptorProto{ + __unknown_fields__: [], + enum_type: [], + extension: [], + extension_range: [], + field: [ + %Google.Protobuf.FieldDescriptorProto{ + __unknown_fields__: [], + default_value: nil, + extendee: nil, + json_name: "id", + label: :LABEL_OPTIONAL, + name: "id", + number: 1, + oneof_index: nil, + options: nil, + proto3_optional: nil, + type: :TYPE_STRING, + type_name: nil + }, + %Google.Protobuf.FieldDescriptorProto{ + __unknown_fields__: [], + default_value: nil, + extendee: nil, + json_name: "issuer", + label: :LABEL_OPTIONAL, + name: "issuer", + number: 2, + oneof_index: nil, + options: nil, + proto3_optional: nil, + type: :TYPE_STRING, + type_name: nil + }, + %Google.Protobuf.FieldDescriptorProto{ + __unknown_fields__: [], + default_value: nil, + extendee: nil, + json_name: "jwksUri", + label: :LABEL_OPTIONAL, + name: "jwks_uri", + number: 3, + oneof_index: nil, + options: nil, + proto3_optional: nil, + type: :TYPE_STRING, + type_name: nil + }, + %Google.Protobuf.FieldDescriptorProto{ + __unknown_fields__: [], + default_value: nil, + extendee: nil, + json_name: "audiences", + label: :LABEL_OPTIONAL, + name: "audiences", + number: 4, + oneof_index: nil, + options: nil, + proto3_optional: nil, + type: :TYPE_STRING, + type_name: nil + }, + %Google.Protobuf.FieldDescriptorProto{ + __unknown_fields__: [], + default_value: nil, + extendee: nil, + json_name: "authorizationUrl", + label: :LABEL_OPTIONAL, + name: "authorization_url", + number: 5, + oneof_index: nil, + options: nil, + proto3_optional: nil, + type: :TYPE_STRING, + type_name: nil + }, + %Google.Protobuf.FieldDescriptorProto{ + __unknown_fields__: [], + default_value: nil, + extendee: nil, + json_name: "jwtLocations", + label: :LABEL_REPEATED, + name: "jwt_locations", + number: 6, + oneof_index: nil, + options: nil, + proto3_optional: nil, + type: :TYPE_MESSAGE, + type_name: ".google.api.JwtLocation" + } + ], + name: "AuthProvider", + nested_type: [], + oneof_decl: [], + options: nil, + reserved_name: [], + reserved_range: [] + } + end + + field(:id, 1, type: :string) + field(:issuer, 2, type: :string) + field(:jwks_uri, 3, type: :string, json_name: "jwksUri") + field(:audiences, 4, type: :string) + field(:authorization_url, 5, type: :string, json_name: "authorizationUrl") + + field(:jwt_locations, 6, + repeated: true, + type: Google.Api.JwtLocation, + json_name: "jwtLocations" + ) +end + +defmodule Google.Api.OAuthRequirements do + @moduledoc false + use Protobuf, protoc_gen_elixir_version: "0.10.0", syntax: :proto3 + + def descriptor do + # credo:disable-for-next-line + %Google.Protobuf.DescriptorProto{ + __unknown_fields__: [], + enum_type: [], + extension: [], + extension_range: [], + field: [ + %Google.Protobuf.FieldDescriptorProto{ + __unknown_fields__: [], + default_value: nil, + extendee: nil, + json_name: "canonicalScopes", + label: :LABEL_OPTIONAL, + name: "canonical_scopes", + number: 1, + oneof_index: nil, + options: nil, + proto3_optional: nil, + type: :TYPE_STRING, + type_name: nil + } + ], + name: "OAuthRequirements", + nested_type: [], + oneof_decl: [], + options: nil, + reserved_name: [], + reserved_range: [] + } + end + + field(:canonical_scopes, 1, type: :string, json_name: "canonicalScopes") +end + +defmodule Google.Api.AuthRequirement do + @moduledoc false + use Protobuf, protoc_gen_elixir_version: "0.10.0", syntax: :proto3 + + def descriptor do + # credo:disable-for-next-line + %Google.Protobuf.DescriptorProto{ + __unknown_fields__: [], + enum_type: [], + extension: [], + extension_range: [], + field: [ + %Google.Protobuf.FieldDescriptorProto{ + __unknown_fields__: [], + default_value: nil, + extendee: nil, + json_name: "providerId", + label: :LABEL_OPTIONAL, + name: "provider_id", + number: 1, + oneof_index: nil, + options: nil, + proto3_optional: nil, + type: :TYPE_STRING, + type_name: nil + }, + %Google.Protobuf.FieldDescriptorProto{ + __unknown_fields__: [], + default_value: nil, + extendee: nil, + json_name: "audiences", + label: :LABEL_OPTIONAL, + name: "audiences", + number: 2, + oneof_index: nil, + options: nil, + proto3_optional: nil, + type: :TYPE_STRING, + type_name: nil + } + ], + name: "AuthRequirement", + nested_type: [], + oneof_decl: [], + options: nil, + reserved_name: [], + reserved_range: [] + } + end + + field(:provider_id, 1, type: :string, json_name: "providerId") + field(:audiences, 2, type: :string) +end diff --git a/spawn_activators/activator_api/lib/activator_api/protobuf/api/http.pb.ex b/spawn_activators/activator_api/lib/activator_api/protobuf/api/http.pb.ex new file mode 100644 index 000000000..10999a254 --- /dev/null +++ b/spawn_activators/activator_api/lib/activator_api/protobuf/api/http.pb.ex @@ -0,0 +1,235 @@ +defmodule HttpRule do + @moduledoc false + use Protobuf, protoc_gen_elixir_version: "0.10.0", syntax: :proto3 + + def descriptor do + # credo:disable-for-next-line + %Google.Protobuf.DescriptorProto{ + __unknown_fields__: [], + enum_type: [], + extension: [], + extension_range: [], + field: [ + %Google.Protobuf.FieldDescriptorProto{ + __unknown_fields__: [], + default_value: nil, + extendee: nil, + json_name: "selector", + label: :LABEL_OPTIONAL, + name: "selector", + number: 1, + oneof_index: nil, + options: nil, + proto3_optional: nil, + type: :TYPE_STRING, + type_name: nil + }, + %Google.Protobuf.FieldDescriptorProto{ + __unknown_fields__: [], + default_value: nil, + extendee: nil, + json_name: "get", + label: :LABEL_OPTIONAL, + name: "get", + number: 2, + oneof_index: 0, + options: nil, + proto3_optional: nil, + type: :TYPE_STRING, + type_name: nil + }, + %Google.Protobuf.FieldDescriptorProto{ + __unknown_fields__: [], + default_value: nil, + extendee: nil, + json_name: "put", + label: :LABEL_OPTIONAL, + name: "put", + number: 3, + oneof_index: 0, + options: nil, + proto3_optional: nil, + type: :TYPE_STRING, + type_name: nil + }, + %Google.Protobuf.FieldDescriptorProto{ + __unknown_fields__: [], + default_value: nil, + extendee: nil, + json_name: "post", + label: :LABEL_OPTIONAL, + name: "post", + number: 4, + oneof_index: 0, + options: nil, + proto3_optional: nil, + type: :TYPE_STRING, + type_name: nil + }, + %Google.Protobuf.FieldDescriptorProto{ + __unknown_fields__: [], + default_value: nil, + extendee: nil, + json_name: "delete", + label: :LABEL_OPTIONAL, + name: "delete", + number: 5, + oneof_index: 0, + options: nil, + proto3_optional: nil, + type: :TYPE_STRING, + type_name: nil + }, + %Google.Protobuf.FieldDescriptorProto{ + __unknown_fields__: [], + default_value: nil, + extendee: nil, + json_name: "patch", + label: :LABEL_OPTIONAL, + name: "patch", + number: 6, + oneof_index: 0, + options: nil, + proto3_optional: nil, + type: :TYPE_STRING, + type_name: nil + }, + %Google.Protobuf.FieldDescriptorProto{ + __unknown_fields__: [], + default_value: nil, + extendee: nil, + json_name: "custom", + label: :LABEL_OPTIONAL, + name: "custom", + number: 8, + oneof_index: 0, + options: nil, + proto3_optional: nil, + type: :TYPE_MESSAGE, + type_name: ".CustomHttpPattern" + }, + %Google.Protobuf.FieldDescriptorProto{ + __unknown_fields__: [], + default_value: nil, + extendee: nil, + json_name: "body", + label: :LABEL_OPTIONAL, + name: "body", + number: 7, + oneof_index: nil, + options: nil, + proto3_optional: nil, + type: :TYPE_STRING, + type_name: nil + }, + %Google.Protobuf.FieldDescriptorProto{ + __unknown_fields__: [], + default_value: nil, + extendee: nil, + json_name: "responseBody", + label: :LABEL_OPTIONAL, + name: "response_body", + number: 12, + oneof_index: nil, + options: nil, + proto3_optional: nil, + type: :TYPE_STRING, + type_name: nil + }, + %Google.Protobuf.FieldDescriptorProto{ + __unknown_fields__: [], + default_value: nil, + extendee: nil, + json_name: "additionalBindings", + label: :LABEL_REPEATED, + name: "additional_bindings", + number: 11, + oneof_index: nil, + options: nil, + proto3_optional: nil, + type: :TYPE_MESSAGE, + type_name: ".HttpRule" + } + ], + name: "HttpRule", + nested_type: [], + oneof_decl: [ + %Google.Protobuf.OneofDescriptorProto{ + __unknown_fields__: [], + name: "pattern", + options: nil + } + ], + options: nil, + reserved_name: [], + reserved_range: [] + } + end + + oneof(:pattern, 0) + + field(:selector, 1, type: :string) + field(:get, 2, type: :string, oneof: 0) + field(:put, 3, type: :string, oneof: 0) + field(:post, 4, type: :string, oneof: 0) + field(:delete, 5, type: :string, oneof: 0) + field(:patch, 6, type: :string, oneof: 0) + field(:custom, 8, type: CustomHttpPattern, oneof: 0) + field(:body, 7, type: :string) + field(:response_body, 12, type: :string, json_name: "responseBody") + field(:additional_bindings, 11, repeated: true, type: HttpRule, json_name: "additionalBindings") +end + +defmodule CustomHttpPattern do + @moduledoc false + use Protobuf, protoc_gen_elixir_version: "0.10.0", syntax: :proto3 + + def descriptor do + # credo:disable-for-next-line + %Google.Protobuf.DescriptorProto{ + __unknown_fields__: [], + enum_type: [], + extension: [], + extension_range: [], + field: [ + %Google.Protobuf.FieldDescriptorProto{ + __unknown_fields__: [], + default_value: nil, + extendee: nil, + json_name: "kind", + label: :LABEL_OPTIONAL, + name: "kind", + number: 1, + oneof_index: nil, + options: nil, + proto3_optional: nil, + type: :TYPE_STRING, + type_name: nil + }, + %Google.Protobuf.FieldDescriptorProto{ + __unknown_fields__: [], + default_value: nil, + extendee: nil, + json_name: "path", + label: :LABEL_OPTIONAL, + name: "path", + number: 2, + oneof_index: nil, + options: nil, + proto3_optional: nil, + type: :TYPE_STRING, + type_name: nil + } + ], + name: "CustomHttpPattern", + nested_type: [], + oneof_decl: [], + options: nil, + reserved_name: [], + reserved_range: [] + } + end + + field(:kind, 1, type: :string) + field(:path, 2, type: :string) +end diff --git a/spawn_activators/activator_api/lib/activator_api/protobuf/api/httpbody.pb.ex b/spawn_activators/activator_api/lib/activator_api/protobuf/api/httpbody.pb.ex new file mode 100644 index 000000000..82a993450 --- /dev/null +++ b/spawn_activators/activator_api/lib/activator_api/protobuf/api/httpbody.pb.ex @@ -0,0 +1,68 @@ +defmodule Google.Api.HttpBody do + @moduledoc false + use Protobuf, protoc_gen_elixir_version: "0.10.0", syntax: :proto3 + + def descriptor do + # credo:disable-for-next-line + %Google.Protobuf.DescriptorProto{ + __unknown_fields__: [], + enum_type: [], + extension: [], + extension_range: [], + field: [ + %Google.Protobuf.FieldDescriptorProto{ + __unknown_fields__: [], + default_value: nil, + extendee: nil, + json_name: "contentType", + label: :LABEL_OPTIONAL, + name: "content_type", + number: 1, + oneof_index: nil, + options: nil, + proto3_optional: nil, + type: :TYPE_STRING, + type_name: nil + }, + %Google.Protobuf.FieldDescriptorProto{ + __unknown_fields__: [], + default_value: nil, + extendee: nil, + json_name: "data", + label: :LABEL_OPTIONAL, + name: "data", + number: 2, + oneof_index: nil, + options: nil, + proto3_optional: nil, + type: :TYPE_BYTES, + type_name: nil + }, + %Google.Protobuf.FieldDescriptorProto{ + __unknown_fields__: [], + default_value: nil, + extendee: nil, + json_name: "extensions", + label: :LABEL_REPEATED, + name: "extensions", + number: 3, + oneof_index: nil, + options: nil, + proto3_optional: nil, + type: :TYPE_MESSAGE, + type_name: ".google.protobuf.Any" + } + ], + name: "HttpBody", + nested_type: [], + oneof_decl: [], + options: nil, + reserved_name: [], + reserved_range: [] + } + end + + field(:content_type, 1, type: :string, json_name: "contentType") + field(:data, 2, type: :bytes) + field(:extensions, 3, repeated: true, type: Google.Protobuf.Any) +end diff --git a/spawn_activators/activator_api/lib/activator_api/protobuf/api/source_info.pb.ex b/spawn_activators/activator_api/lib/activator_api/protobuf/api/source_info.pb.ex new file mode 100644 index 000000000..f41bc4206 --- /dev/null +++ b/spawn_activators/activator_api/lib/activator_api/protobuf/api/source_info.pb.ex @@ -0,0 +1,38 @@ +defmodule Google.Api.SourceInfo do + @moduledoc false + use Protobuf, protoc_gen_elixir_version: "0.10.0", syntax: :proto3 + + def descriptor do + # credo:disable-for-next-line + %Google.Protobuf.DescriptorProto{ + __unknown_fields__: [], + enum_type: [], + extension: [], + extension_range: [], + field: [ + %Google.Protobuf.FieldDescriptorProto{ + __unknown_fields__: [], + default_value: nil, + extendee: nil, + json_name: "sourceFiles", + label: :LABEL_REPEATED, + name: "source_files", + number: 1, + oneof_index: nil, + options: nil, + proto3_optional: nil, + type: :TYPE_MESSAGE, + type_name: ".google.protobuf.Any" + } + ], + name: "SourceInfo", + nested_type: [], + oneof_decl: [], + options: nil, + reserved_name: [], + reserved_range: [] + } + end + + field(:source_files, 1, repeated: true, type: Google.Protobuf.Any, json_name: "sourceFiles") +end diff --git a/spawn_activators/activator_api/lib/activator_api/router/router.ex b/spawn_activators/activator_api/lib/activator_api/router/router.ex new file mode 100644 index 000000000..b6e00b2b3 --- /dev/null +++ b/spawn_activators/activator_api/lib/activator_api/router/router.ex @@ -0,0 +1,15 @@ +defmodule ActivatorAPI.Router do + use Plug.Router + + plug(Plug.Logger) + + plug(:match) + plug(Plug.Parsers, parsers: [:json], json_decoder: Jason) + plug(:dispatch) + + forward("/health", to: ActivatorAPI.Routes.Health) + + match _ do + send_resp(conn, 404, "Not found!") + end +end diff --git a/spawn_activators/activator_api/lib/activator_api/routes/base.ex b/spawn_activators/activator_api/lib/activator_api/routes/base.ex new file mode 100644 index 000000000..858738d08 --- /dev/null +++ b/spawn_activators/activator_api/lib/activator_api/routes/base.ex @@ -0,0 +1,40 @@ +defmodule ActivatorAPI.Routes.Base do + defmacro __using__([]) do + quote do + use Plug.Router + + plug(Plug.Logger) + + plug(:match) + + plug(Plug.Parsers, + parsers: [:json], + json_decoder: Jason + ) + + plug(:dispatch) + + def send!(conn, code, data, content_type) + when is_integer(code) and content_type == "application/json" do + conn + |> Plug.Conn.put_resp_content_type(content_type) + |> send_resp(code, Jason.encode!(data)) + end + + def send!(conn, code, data, content_type) when is_atom(code) do + code = + case code do + :ok -> 200 + :not_found -> 404 + :malformed_data -> 400 + :non_authenticated -> 401 + :forbidden_access -> 403 + :server_error -> 500 + :error -> 504 + end + + send!(conn, code, data, content_type) + end + end + end +end diff --git a/spawn_activators/activator_api/lib/activator_api/routes/health.ex b/spawn_activators/activator_api/lib/activator_api/routes/health.ex new file mode 100644 index 000000000..1187efae1 --- /dev/null +++ b/spawn_activators/activator_api/lib/activator_api/routes/health.ex @@ -0,0 +1,17 @@ +defmodule ActivatorAPI.Routes.Health do + use ActivatorAPI.Routes.Base + + @content_type "application/json" + + get "/" do + send!(conn, 200, %{status: "up"}, @content_type) + end + + get "/liveness" do + send!(conn, 200, %{status: "up"}, @content_type) + end + + get "/readiness" do + send!(conn, 200, %{status: "up"}, @content_type) + end +end diff --git a/spawn_activators/activator_api/mix.exs b/spawn_activators/activator_api/mix.exs new file mode 100644 index 000000000..d070ec98a --- /dev/null +++ b/spawn_activators/activator_api/mix.exs @@ -0,0 +1,53 @@ +defmodule ActivatorAPI.MixProject do + use Mix.Project + + @app :activator_api + @version "0.0.0-local.dev" + + def project do + [ + app: @app, + version: @version, + build_path: "../activator/_build", + config_path: "../../config/config.exs", + deps_path: "../activator/deps", + lockfile: "../activator/mix.lock", + elixir: "~> 1.15", + start_permanent: Mix.env() == :prod, + deps: deps(), + releases: releases() + ] + end + + # Run "mix help compile.app" to learn about applications. + def application do + [ + extra_applications: [:logger], + mod: {ActivatorAPI.Application, []} + ] + end + + # Run "mix help deps" to learn about dependencies. + defp deps do + [ + {:activator, path: "../activator"}, + {:grpc, "~> 0.8"}, + {:gun, "~> 2.0", override: true}, + {:cowlib, "~> 2.11", override: true} + ] + end + + defp releases do + [ + activator_api: [ + include_executables_for: [:unix], + applications: [activator_api: :permanent], + steps: [ + :assemble, + &Bakeware.assemble/1 + ], + bakeware: [compression_level: 19] + ] + ] + end +end diff --git a/spawn_activators/activator_api/priv/example/out/service.pb.ex b/spawn_activators/activator_api/priv/example/out/service.pb.ex new file mode 100644 index 000000000..8350f5f60 --- /dev/null +++ b/spawn_activators/activator_api/priv/example/out/service.pb.ex @@ -0,0 +1,107 @@ +defmodule Io.Eigr.Spawn.Example.MyState do + @moduledoc false + use Protobuf, protoc_gen_elixir_version: "0.10.0", syntax: :proto3 + + def descriptor do + # credo:disable-for-next-line + %Google.Protobuf.DescriptorProto{ + __unknown_fields__: [], + enum_type: [], + extension: [], + extension_range: [], + field: [ + %Google.Protobuf.FieldDescriptorProto{ + __unknown_fields__: [], + default_value: nil, + extendee: nil, + json_name: "value", + label: :LABEL_OPTIONAL, + name: "value", + number: 1, + oneof_index: nil, + options: nil, + proto3_optional: nil, + type: :TYPE_INT32, + type_name: nil + } + ], + name: "MyState", + nested_type: [], + oneof_decl: [], + options: nil, + reserved_name: [], + reserved_range: [] + } + end + + field :value, 1, type: :int32 +end +defmodule Io.Eigr.Spawn.Example.MyBusinessMessage do + @moduledoc false + use Protobuf, protoc_gen_elixir_version: "0.10.0", syntax: :proto3 + + def descriptor do + # credo:disable-for-next-line + %Google.Protobuf.DescriptorProto{ + __unknown_fields__: [], + enum_type: [], + extension: [], + extension_range: [], + field: [ + %Google.Protobuf.FieldDescriptorProto{ + __unknown_fields__: [], + default_value: nil, + extendee: nil, + json_name: "value", + label: :LABEL_OPTIONAL, + name: "value", + number: 1, + oneof_index: nil, + options: nil, + proto3_optional: nil, + type: :TYPE_INT32, + type_name: nil + } + ], + name: "MyBusinessMessage", + nested_type: [], + oneof_decl: [], + options: nil, + reserved_name: [], + reserved_range: [] + } + end + + field :value, 1, type: :int32 +end +defmodule Io.Eigr.Spawn.Example.TestService.Service do + @moduledoc false + use GRPC.Service, name: "io.eigr.spawn.example.TestService", protoc_gen_elixir_version: "0.10.0" + + def descriptor do + # credo:disable-for-next-line + %Google.Protobuf.ServiceDescriptorProto{ + __unknown_fields__: [], + method: [ + %Google.Protobuf.MethodDescriptorProto{ + __unknown_fields__: [], + client_streaming: false, + input_type: ".io.eigr.spawn.example.MyBusinessMessage", + name: "Sum", + options: nil, + output_type: ".io.eigr.spawn.example.MyBusinessMessage", + server_streaming: false + } + ], + name: "TestService", + options: nil + } + end + + rpc :Sum, Io.Eigr.Spawn.Example.MyBusinessMessage, Io.Eigr.Spawn.Example.MyBusinessMessage +end + +defmodule Io.Eigr.Spawn.Example.TestService.Stub do + @moduledoc false + use GRPC.Stub, service: Io.Eigr.Spawn.Example.TestService.Service +end diff --git a/spawn_activators/activator_api/priv/example/out/user-api.desc b/spawn_activators/activator_api/priv/example/out/user-api.desc new file mode 100644 index 000000000..5d1eb2f5f --- /dev/null +++ b/spawn_activators/activator_api/priv/example/out/user-api.desc @@ -0,0 +1,10 @@ + +Ž + service.protoio.eigr.spawn.example" +MyState +value (Rvalue") +MyBusinessMessage +value (Rvalue2h + TestServiceY +Sum(.io.eigr.spawn.example.MyBusinessMessage(.io.eigr.spawn.example.MyBusinessMessageB( +io.eigr.spawn.exampleB ExampleProtosPbproto3 \ No newline at end of file diff --git a/spawn_activators/activator_api/priv/protos/google/api/annotations.proto b/spawn_activators/activator_api/priv/protos/google/api/annotations.proto new file mode 100644 index 000000000..b157cd671 --- /dev/null +++ b/spawn_activators/activator_api/priv/protos/google/api/annotations.proto @@ -0,0 +1,31 @@ +// Copyright 2015 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package google.api; + +import "http.proto"; +import "descriptor.proto"; + +option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; +option java_multiple_files = true; +option java_outer_classname = "AnnotationsProto"; +option java_package = "com.google.api"; +option objc_class_prefix = "GAPI"; + +extend google.protobuf.MethodOptions { + // See `HttpRule`. + HttpRule http = 72295728; +} \ No newline at end of file diff --git a/spawn_activators/activator_api/priv/protos/google/api/any.proto b/spawn_activators/activator_api/priv/protos/google/api/any.proto new file mode 100644 index 000000000..151977180 --- /dev/null +++ b/spawn_activators/activator_api/priv/protos/google/api/any.proto @@ -0,0 +1,161 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +syntax = "proto3"; + +package google.protobuf; + +option go_package = "google.golang.org/protobuf/types/known/anypb"; +option java_package = "com.google.protobuf"; +option java_outer_classname = "AnyProto"; +option java_multiple_files = true; +option objc_class_prefix = "GPB"; +option csharp_namespace = "Google.Protobuf.WellKnownTypes"; + +// `Any` contains an arbitrary serialized protocol buffer message along with a +// URL that describes the type of the serialized message. +// +// Protobuf library provides support to pack/unpack Any values in the form +// of utility functions or additional generated methods of the Any type. +// +// Example 1: Pack and unpack a message in C++. +// +// Foo foo = ...; +// Any any; +// any.PackFrom(foo); +// ... +// if (any.UnpackTo(&foo)) { +// ... +// } +// +// Example 2: Pack and unpack a message in Java. +// +// Foo foo = ...; +// Any any = Any.pack(foo); +// ... +// if (any.is(Foo.class)) { +// foo = any.unpack(Foo.class); +// } +// // or ... +// if (any.isSameTypeAs(Foo.getDefaultInstance())) { +// foo = any.unpack(Foo.getDefaultInstance()); +// } +// +// Example 3: Pack and unpack a message in Python. +// +// foo = Foo(...) +// any = Any() +// any.Pack(foo) +// ... +// if any.Is(Foo.DESCRIPTOR): +// any.Unpack(foo) +// ... +// +// Example 4: Pack and unpack a message in Go +// +// foo := &pb.Foo{...} +// any, err := anypb.New(foo) +// if err != nil { +// ... +// } +// ... +// foo := &pb.Foo{} +// if err := any.UnmarshalTo(foo); err != nil { +// ... +// } +// +// The pack methods provided by protobuf library will by default use +// 'type.googleapis.com/full.type.name' as the type URL and the unpack +// methods only use the fully qualified type name after the last '/' +// in the type URL, for example "foo.bar.com/x/y.z" will yield type +// name "y.z". +// +// JSON +// +// The JSON representation of an `Any` value uses the regular +// representation of the deserialized, embedded message, with an +// additional field `@type` which contains the type URL. Example: +// +// package google.profile; +// message Person { +// string first_name = 1; +// string last_name = 2; +// } +// +// { +// "@type": "type.googleapis.com/google.profile.Person", +// "firstName": , +// "lastName": +// } +// +// If the embedded message type is well-known and has a custom JSON +// representation, that representation will be embedded adding a field +// `value` which holds the custom JSON in addition to the `@type` +// field. Example (for message [google.protobuf.Duration][]): +// +// { +// "@type": "type.googleapis.com/google.protobuf.Duration", +// "value": "1.212s" +// } +// +message Any { + // A URL/resource name that uniquely identifies the type of the serialized + // protocol buffer message. This string must contain at least + // one "/" character. The last segment of the URL's path must represent + // the fully qualified name of the type (as in + // `path/google.protobuf.Duration`). The name should be in a canonical form + // (e.g., leading "." is not accepted). + // + // In practice, teams usually precompile into the binary all types that they + // expect it to use in the context of Any. However, for URLs which use the + // scheme `http`, `https`, or no scheme, one can optionally set up a type + // server that maps type URLs to message definitions as follows: + // + // * If no scheme is provided, `https` is assumed. + // * An HTTP GET on the URL must yield a [google.protobuf.Type][] + // value in binary format, or produce an error. + // * Applications are allowed to cache lookup results based on the + // URL, or have them precompiled into a binary to avoid any + // lookup. Therefore, binary compatibility needs to be preserved + // on changes to types. (Use versioned type names to manage + // breaking changes.) + // + // Note: this functionality is not currently available in the official + // protobuf release, and it is not used for type URLs beginning with + // type.googleapis.com. + // + // Schemes other than `http`, `https` (or the empty scheme) might be + // used with implementation specific semantics. + // + string type_url = 1; + + // Must be a valid serialized protocol buffer of the above specified type. + bytes value = 2; +} \ No newline at end of file diff --git a/spawn_activators/activator_api/priv/protos/google/api/auth.proto b/spawn_activators/activator_api/priv/protos/google/api/auth.proto new file mode 100644 index 000000000..cdee0a97c --- /dev/null +++ b/spawn_activators/activator_api/priv/protos/google/api/auth.proto @@ -0,0 +1,236 @@ +// Copyright 2015 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package google.api; + +option go_package = "google.golang.org/genproto/googleapis/api/serviceconfig;serviceconfig"; +option java_multiple_files = true; +option java_outer_classname = "AuthProto"; +option java_package = "com.google.api"; +option objc_class_prefix = "GAPI"; + +// `Authentication` defines the authentication configuration for API methods +// provided by an API service. +// +// Example: +// +// name: calendar.googleapis.com +// authentication: +// providers: +// - id: google_calendar_auth +// jwks_uri: https://www.googleapis.com/oauth2/v1/certs +// issuer: https://securetoken.google.com +// rules: +// - selector: "*" +// requirements: +// provider_id: google_calendar_auth +// - selector: google.calendar.Delegate +// oauth: +// canonical_scopes: https://www.googleapis.com/auth/calendar.read +message Authentication { + // A list of authentication rules that apply to individual API methods. + // + // **NOTE:** All service configuration rules follow "last one wins" order. + repeated AuthenticationRule rules = 3; + + // Defines a set of authentication providers that a service supports. + repeated AuthProvider providers = 4; +} + +// Authentication rules for the service. +// +// By default, if a method has any authentication requirements, every request +// must include a valid credential matching one of the requirements. +// It's an error to include more than one kind of credential in a single +// request. +// +// If a method doesn't have any auth requirements, request credentials will be +// ignored. +message AuthenticationRule { + // Selects the methods to which this rule applies. + // + // Refer to [selector][google.api.DocumentationRule.selector] for syntax details. + string selector = 1; + + // The requirements for OAuth credentials. + OAuthRequirements oauth = 2; + + // If true, the service accepts API keys without any other credential. + // This flag only applies to HTTP and gRPC requests. + bool allow_without_credential = 5; + + // Requirements for additional authentication providers. + repeated AuthRequirement requirements = 7; +} + +// Specifies a location to extract JWT from an API request. +message JwtLocation { + oneof in { + // Specifies HTTP header name to extract JWT token. + string header = 1; + + // Specifies URL query parameter name to extract JWT token. + string query = 2; + + // Specifies cookie name to extract JWT token. + string cookie = 4; + } + + // The value prefix. The value format is "value_prefix{token}" + // Only applies to "in" header type. Must be empty for "in" query type. + // If not empty, the header value has to match (case sensitive) this prefix. + // If not matched, JWT will not be extracted. If matched, JWT will be + // extracted after the prefix is removed. + // + // For example, for "Authorization: Bearer {JWT}", + // value_prefix="Bearer " with a space at the end. + string value_prefix = 3; +} + +// Configuration for an authentication provider, including support for +// [JSON Web Token +// (JWT)](https://tools.ietf.org/html/draft-ietf-oauth-json-web-token-32). +message AuthProvider { + // The unique identifier of the auth provider. It will be referred to by + // `AuthRequirement.provider_id`. + // + // Example: "bookstore_auth". + string id = 1; + + // Identifies the principal that issued the JWT. See + // https://tools.ietf.org/html/draft-ietf-oauth-json-web-token-32#section-4.1.1 + // Usually a URL or an email address. + // + // Example: https://securetoken.google.com + // Example: 1234567-compute@developer.gserviceaccount.com + string issuer = 2; + + // URL of the provider's public key set to validate signature of the JWT. See + // [OpenID + // Discovery](https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderMetadata). + // Optional if the key set document: + // - can be retrieved from + // [OpenID + // Discovery](https://openid.net/specs/openid-connect-discovery-1_0.html) + // of the issuer. + // - can be inferred from the email domain of the issuer (e.g. a Google + // service account). + // + // Example: https://www.googleapis.com/oauth2/v1/certs + string jwks_uri = 3; + + // The list of JWT + // [audiences](https://tools.ietf.org/html/draft-ietf-oauth-json-web-token-32#section-4.1.3). + // that are allowed to access. A JWT containing any of these audiences will + // be accepted. When this setting is absent, JWTs with audiences: + // - "https://[service.name]/[google.protobuf.Api.name]" + // - "https://[service.name]/" + // will be accepted. + // For example, if no audiences are in the setting, LibraryService API will + // accept JWTs with the following audiences: + // - + // https://library-example.googleapis.com/google.example.library.v1.LibraryService + // - https://library-example.googleapis.com/ + // + // Example: + // + // audiences: bookstore_android.apps.googleusercontent.com, + // bookstore_web.apps.googleusercontent.com + string audiences = 4; + + // Redirect URL if JWT token is required but not present or is expired. + // Implement authorizationUrl of securityDefinitions in OpenAPI spec. + string authorization_url = 5; + + // Defines the locations to extract the JWT. For now it is only used by the + // Cloud Endpoints to store the OpenAPI extension [x-google-jwt-locations] + // (https://cloud.google.com/endpoints/docs/openapi/openapi-extensions#x-google-jwt-locations) + // + // JWT locations can be one of HTTP headers, URL query parameters or + // cookies. The rule is that the first match wins. + // + // If not specified, default to use following 3 locations: + // 1) Authorization: Bearer + // 2) x-goog-iap-jwt-assertion + // 3) access_token query parameter + // + // Default locations can be specified as followings: + // jwt_locations: + // - header: Authorization + // value_prefix: "Bearer " + // - header: x-goog-iap-jwt-assertion + // - query: access_token + repeated JwtLocation jwt_locations = 6; +} + +// OAuth scopes are a way to define data and permissions on data. For example, +// there are scopes defined for "Read-only access to Google Calendar" and +// "Access to Cloud Platform". Users can consent to a scope for an application, +// giving it permission to access that data on their behalf. +// +// OAuth scope specifications should be fairly coarse grained; a user will need +// to see and understand the text description of what your scope means. +// +// In most cases: use one or at most two OAuth scopes for an entire family of +// products. If your product has multiple APIs, you should probably be sharing +// the OAuth scope across all of those APIs. +// +// When you need finer grained OAuth consent screens: talk with your product +// management about how developers will use them in practice. +// +// Please note that even though each of the canonical scopes is enough for a +// request to be accepted and passed to the backend, a request can still fail +// due to the backend requiring additional scopes or permissions. +message OAuthRequirements { + // The list of publicly documented OAuth scopes that are allowed access. An + // OAuth token containing any of these scopes will be accepted. + // + // Example: + // + // canonical_scopes: https://www.googleapis.com/auth/calendar, + // https://www.googleapis.com/auth/calendar.read + string canonical_scopes = 1; +} + +// User-defined authentication requirements, including support for +// [JSON Web Token +// (JWT)](https://tools.ietf.org/html/draft-ietf-oauth-json-web-token-32). +message AuthRequirement { + // [id][google.api.AuthProvider.id] from authentication provider. + // + // Example: + // + // provider_id: bookstore_auth + string provider_id = 1; + + // NOTE: This will be deprecated soon, once AuthProvider.audiences is + // implemented and accepted in all the runtime components. + // + // The list of JWT + // [audiences](https://tools.ietf.org/html/draft-ietf-oauth-json-web-token-32#section-4.1.3). + // that are allowed to access. A JWT containing any of these audiences will + // be accepted. When this setting is absent, only JWTs with audience + // "https://[Service_name][google.api.Service.name]/[API_name][google.protobuf.Api.name]" + // will be accepted. For example, if no audiences are in the setting, + // LibraryService API will only accept JWTs with the following audience + // "https://library-example.googleapis.com/google.example.library.v1.LibraryService". + // + // Example: + // + // audiences: bookstore_android.apps.googleusercontent.com, + // bookstore_web.apps.googleusercontent.com + string audiences = 2; +} \ No newline at end of file diff --git a/spawn_activators/activator_api/priv/protos/google/api/descriptor.proto b/spawn_activators/activator_api/priv/protos/google/api/descriptor.proto new file mode 100644 index 000000000..3b3867543 --- /dev/null +++ b/spawn_activators/activator_api/priv/protos/google/api/descriptor.proto @@ -0,0 +1,975 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// Author: kenton@google.com (Kenton Varda) +// Based on original Protocol Buffers design by +// Sanjay Ghemawat, Jeff Dean, and others. +// +// The messages in this file describe the definitions found in .proto files. +// A valid .proto file can be translated directly to a FileDescriptorProto +// without any other information (e.g. without reading its imports). + +syntax = "proto2"; + +package google.protobuf; + +option go_package = "google.golang.org/protobuf/types/descriptorpb"; +option java_package = "com.google.protobuf"; +option java_outer_classname = "DescriptorProtos"; +option csharp_namespace = "Google.Protobuf.Reflection"; +option objc_class_prefix = "GPB"; +option cc_enable_arenas = true; + +// descriptor.proto must be optimized for speed because reflection-based +// algorithms don't work during bootstrapping. +option optimize_for = SPEED; + +// The protocol compiler can output a FileDescriptorSet containing the .proto +// files it parses. +message FileDescriptorSet { + repeated FileDescriptorProto file = 1; +} + +// Describes a complete .proto file. +message FileDescriptorProto { + optional string name = 1; // file name, relative to root of source tree + optional string package = 2; // e.g. "foo", "foo.bar", etc. + + // Names of files imported by this file. + repeated string dependency = 3; + // Indexes of the public imported files in the dependency list above. + repeated int32 public_dependency = 10; + // Indexes of the weak imported files in the dependency list. + // For Google-internal migration only. Do not use. + repeated int32 weak_dependency = 11; + + // All top-level definitions in this file. + repeated DescriptorProto message_type = 4; + repeated EnumDescriptorProto enum_type = 5; + repeated ServiceDescriptorProto service = 6; + repeated FieldDescriptorProto extension = 7; + + optional FileOptions options = 8; + + // This field contains optional information about the original source code. + // You may safely remove this entire field without harming runtime + // functionality of the descriptors -- the information is needed only by + // development tools. + optional SourceCodeInfo source_code_info = 9; + + // The syntax of the proto file. + // The supported values are "proto2", "proto3", and "editions". + // + // If `edition` is present, this value must be "editions". + optional string syntax = 12; + + // The edition of the proto file, which is an opaque string. + optional string edition = 13; +} + +// Describes a message type. +message DescriptorProto { + optional string name = 1; + + repeated FieldDescriptorProto field = 2; + repeated FieldDescriptorProto extension = 6; + + repeated DescriptorProto nested_type = 3; + repeated EnumDescriptorProto enum_type = 4; + + message ExtensionRange { + optional int32 start = 1; // Inclusive. + optional int32 end = 2; // Exclusive. + + optional ExtensionRangeOptions options = 3; + } + repeated ExtensionRange extension_range = 5; + + repeated OneofDescriptorProto oneof_decl = 8; + + optional MessageOptions options = 7; + + // Range of reserved tag numbers. Reserved tag numbers may not be used by + // fields or extension ranges in the same message. Reserved ranges may + // not overlap. + message ReservedRange { + optional int32 start = 1; // Inclusive. + optional int32 end = 2; // Exclusive. + } + repeated ReservedRange reserved_range = 9; + // Reserved field names, which may not be used by fields in the same message. + // A given name may only be reserved once. + repeated string reserved_name = 10; +} + +message ExtensionRangeOptions { + // The parser stores options it doesn't recognize here. See above. + repeated UninterpretedOption uninterpreted_option = 999; + + // Clients can define custom options in extensions of this message. See above. + extensions 1000 to max; +} + +// Describes a field within a message. +message FieldDescriptorProto { + enum Type { + // 0 is reserved for errors. + // Order is weird for historical reasons. + TYPE_DOUBLE = 1; + TYPE_FLOAT = 2; + // Not ZigZag encoded. Negative numbers take 10 bytes. Use TYPE_SINT64 if + // negative values are likely. + TYPE_INT64 = 3; + TYPE_UINT64 = 4; + // Not ZigZag encoded. Negative numbers take 10 bytes. Use TYPE_SINT32 if + // negative values are likely. + TYPE_INT32 = 5; + TYPE_FIXED64 = 6; + TYPE_FIXED32 = 7; + TYPE_BOOL = 8; + TYPE_STRING = 9; + // Tag-delimited aggregate. + // Group type is deprecated and not supported in proto3. However, Proto3 + // implementations should still be able to parse the group wire format and + // treat group fields as unknown fields. + TYPE_GROUP = 10; + TYPE_MESSAGE = 11; // Length-delimited aggregate. + + // New in version 2. + TYPE_BYTES = 12; + TYPE_UINT32 = 13; + TYPE_ENUM = 14; + TYPE_SFIXED32 = 15; + TYPE_SFIXED64 = 16; + TYPE_SINT32 = 17; // Uses ZigZag encoding. + TYPE_SINT64 = 18; // Uses ZigZag encoding. + } + + enum Label { + // 0 is reserved for errors + LABEL_OPTIONAL = 1; + LABEL_REQUIRED = 2; + LABEL_REPEATED = 3; + } + + optional string name = 1; + optional int32 number = 3; + optional Label label = 4; + + // If type_name is set, this need not be set. If both this and type_name + // are set, this must be one of TYPE_ENUM, TYPE_MESSAGE or TYPE_GROUP. + optional Type type = 5; + + // For message and enum types, this is the name of the type. If the name + // starts with a '.', it is fully-qualified. Otherwise, C++-like scoping + // rules are used to find the type (i.e. first the nested types within this + // message are searched, then within the parent, on up to the root + // namespace). + optional string type_name = 6; + + // For extensions, this is the name of the type being extended. It is + // resolved in the same manner as type_name. + optional string extendee = 2; + + // For numeric types, contains the original text representation of the value. + // For booleans, "true" or "false". + // For strings, contains the default text contents (not escaped in any way). + // For bytes, contains the C escaped value. All bytes >= 128 are escaped. + optional string default_value = 7; + + // If set, gives the index of a oneof in the containing type's oneof_decl + // list. This field is a member of that oneof. + optional int32 oneof_index = 9; + + // JSON name of this field. The value is set by protocol compiler. If the + // user has set a "json_name" option on this field, that option's value + // will be used. Otherwise, it's deduced from the field's name by converting + // it to camelCase. + optional string json_name = 10; + + optional FieldOptions options = 8; + + // If true, this is a proto3 "optional". When a proto3 field is optional, it + // tracks presence regardless of field type. + // + // When proto3_optional is true, this field must be belong to a oneof to + // signal to old proto3 clients that presence is tracked for this field. This + // oneof is known as a "synthetic" oneof, and this field must be its sole + // member (each proto3 optional field gets its own synthetic oneof). Synthetic + // oneofs exist in the descriptor only, and do not generate any API. Synthetic + // oneofs must be ordered after all "real" oneofs. + // + // For message fields, proto3_optional doesn't create any semantic change, + // since non-repeated message fields always track presence. However it still + // indicates the semantic detail of whether the user wrote "optional" or not. + // This can be useful for round-tripping the .proto file. For consistency we + // give message fields a synthetic oneof also, even though it is not required + // to track presence. This is especially important because the parser can't + // tell if a field is a message or an enum, so it must always create a + // synthetic oneof. + // + // Proto2 optional fields do not set this flag, because they already indicate + // optional with `LABEL_OPTIONAL`. + optional bool proto3_optional = 17; +} + +// Describes a oneof. +message OneofDescriptorProto { + optional string name = 1; + optional OneofOptions options = 2; +} + +// Describes an enum type. +message EnumDescriptorProto { + optional string name = 1; + + repeated EnumValueDescriptorProto value = 2; + + optional EnumOptions options = 3; + + // Range of reserved numeric values. Reserved values may not be used by + // entries in the same enum. Reserved ranges may not overlap. + // + // Note that this is distinct from DescriptorProto.ReservedRange in that it + // is inclusive such that it can appropriately represent the entire int32 + // domain. + message EnumReservedRange { + optional int32 start = 1; // Inclusive. + optional int32 end = 2; // Inclusive. + } + + // Range of reserved numeric values. Reserved numeric values may not be used + // by enum values in the same enum declaration. Reserved ranges may not + // overlap. + repeated EnumReservedRange reserved_range = 4; + + // Reserved enum value names, which may not be reused. A given name may only + // be reserved once. + repeated string reserved_name = 5; +} + +// Describes a value within an enum. +message EnumValueDescriptorProto { + optional string name = 1; + optional int32 number = 2; + + optional EnumValueOptions options = 3; +} + +// Describes a service. +message ServiceDescriptorProto { + optional string name = 1; + repeated MethodDescriptorProto method = 2; + + optional ServiceOptions options = 3; +} + +// Describes a method of a service. +message MethodDescriptorProto { + optional string name = 1; + + // Input and output type names. These are resolved in the same way as + // FieldDescriptorProto.type_name, but must refer to a message type. + optional string input_type = 2; + optional string output_type = 3; + + optional MethodOptions options = 4; + + // Identifies if client streams multiple client messages + optional bool client_streaming = 5 [default = false]; + // Identifies if server streams multiple server messages + optional bool server_streaming = 6 [default = false]; +} + +// =================================================================== +// Options + +// Each of the definitions above may have "options" attached. These are +// just annotations which may cause code to be generated slightly differently +// or may contain hints for code that manipulates protocol messages. +// +// Clients may define custom options as extensions of the *Options messages. +// These extensions may not yet be known at parsing time, so the parser cannot +// store the values in them. Instead it stores them in a field in the *Options +// message called uninterpreted_option. This field must have the same name +// across all *Options messages. We then use this field to populate the +// extensions when we build a descriptor, at which point all protos have been +// parsed and so all extensions are known. +// +// Extension numbers for custom options may be chosen as follows: +// * For options which will only be used within a single application or +// organization, or for experimental options, use field numbers 50000 +// through 99999. It is up to you to ensure that you do not use the +// same number for multiple options. +// * For options which will be published and used publicly by multiple +// independent entities, e-mail protobuf-global-extension-registry@google.com +// to reserve extension numbers. Simply provide your project name (e.g. +// Objective-C plugin) and your project website (if available) -- there's no +// need to explain how you intend to use them. Usually you only need one +// extension number. You can declare multiple options with only one extension +// number by putting them in a sub-message. See the Custom Options section of +// the docs for examples: +// https://developers.google.com/protocol-buffers/docs/proto#options +// If this turns out to be popular, a web service will be set up +// to automatically assign option numbers. + +message FileOptions { + + // Sets the Java package where classes generated from this .proto will be + // placed. By default, the proto package is used, but this is often + // inappropriate because proto packages do not normally start with backwards + // domain names. + optional string java_package = 1; + + // Controls the name of the wrapper Java class generated for the .proto file. + // That class will always contain the .proto file's getDescriptor() method as + // well as any top-level extensions defined in the .proto file. + // If java_multiple_files is disabled, then all the other classes from the + // .proto file will be nested inside the single wrapper outer class. + optional string java_outer_classname = 8; + + // If enabled, then the Java code generator will generate a separate .java + // file for each top-level message, enum, and service defined in the .proto + // file. Thus, these types will *not* be nested inside the wrapper class + // named by java_outer_classname. However, the wrapper class will still be + // generated to contain the file's getDescriptor() method as well as any + // top-level extensions defined in the file. + optional bool java_multiple_files = 10 [default = false]; + + // This option does nothing. + optional bool java_generate_equals_and_hash = 20 [deprecated=true]; + + // If set true, then the Java2 code generator will generate code that + // throws an exception whenever an attempt is made to assign a non-UTF-8 + // byte sequence to a string field. + // Message reflection will do the same. + // However, an extension field still accepts non-UTF-8 byte sequences. + // This option has no effect on when used with the lite runtime. + optional bool java_string_check_utf8 = 27 [default = false]; + + // Generated classes can be optimized for speed or code size. + enum OptimizeMode { + SPEED = 1; // Generate complete code for parsing, serialization, + // etc. + CODE_SIZE = 2; // Use ReflectionOps to implement these methods. + LITE_RUNTIME = 3; // Generate code using MessageLite and the lite runtime. + } + optional OptimizeMode optimize_for = 9 [default = SPEED]; + + // Sets the Go package where structs generated from this .proto will be + // placed. If omitted, the Go package will be derived from the following: + // - The basename of the package import path, if provided. + // - Otherwise, the package statement in the .proto file, if present. + // - Otherwise, the basename of the .proto file, without extension. + optional string go_package = 11; + + // Should generic services be generated in each language? "Generic" services + // are not specific to any particular RPC system. They are generated by the + // main code generators in each language (without additional plugins). + // Generic services were the only kind of service generation supported by + // early versions of google.protobuf. + // + // Generic services are now considered deprecated in favor of using plugins + // that generate code specific to your particular RPC system. Therefore, + // these default to false. Old code which depends on generic services should + // explicitly set them to true. + optional bool cc_generic_services = 16 [default = false]; + optional bool java_generic_services = 17 [default = false]; + optional bool py_generic_services = 18 [default = false]; + optional bool php_generic_services = 42 [default = false]; + + // Is this file deprecated? + // Depending on the target platform, this can emit Deprecated annotations + // for everything in the file, or it will be completely ignored; in the very + // least, this is a formalization for deprecating files. + optional bool deprecated = 23 [default = false]; + + // Enables the use of arenas for the proto messages in this file. This applies + // only to generated classes for C++. + optional bool cc_enable_arenas = 31 [default = true]; + + // Sets the objective c class prefix which is prepended to all objective c + // generated classes from this .proto. There is no default. + optional string objc_class_prefix = 36; + + // Namespace for generated classes; defaults to the package. + optional string csharp_namespace = 37; + + // By default Swift generators will take the proto package and CamelCase it + // replacing '.' with underscore and use that to prefix the types/symbols + // defined. When this options is provided, they will use this value instead + // to prefix the types/symbols defined. + optional string swift_prefix = 39; + + // Sets the php class prefix which is prepended to all php generated classes + // from this .proto. Default is empty. + optional string php_class_prefix = 40; + + // Use this option to change the namespace of php generated classes. Default + // is empty. When this option is empty, the package name will be used for + // determining the namespace. + optional string php_namespace = 41; + + // Use this option to change the namespace of php generated metadata classes. + // Default is empty. When this option is empty, the proto file name will be + // used for determining the namespace. + optional string php_metadata_namespace = 44; + + // Use this option to change the package of ruby generated classes. Default + // is empty. When this option is not set, the package name will be used for + // determining the ruby package. + optional string ruby_package = 45; + + // The parser stores options it doesn't recognize here. + // See the documentation for the "Options" section above. + repeated UninterpretedOption uninterpreted_option = 999; + + // Clients can define custom options in extensions of this message. + // See the documentation for the "Options" section above. + extensions 1000 to max; + + reserved 38; +} + +message MessageOptions { + // Set true to use the old proto1 MessageSet wire format for extensions. + // This is provided for backwards-compatibility with the MessageSet wire + // format. You should not use this for any other reason: It's less + // efficient, has fewer features, and is more complicated. + // + // The message must be defined exactly as follows: + // message Foo { + // option message_set_wire_format = true; + // extensions 4 to max; + // } + // Note that the message cannot have any defined fields; MessageSets only + // have extensions. + // + // All extensions of your type must be singular messages; e.g. they cannot + // be int32s, enums, or repeated messages. + // + // Because this is an option, the above two restrictions are not enforced by + // the protocol compiler. + optional bool message_set_wire_format = 1 [default = false]; + + // Disables the generation of the standard "descriptor()" accessor, which can + // conflict with a field of the same name. This is meant to make migration + // from proto1 easier; new code should avoid fields named "descriptor". + optional bool no_standard_descriptor_accessor = 2 [default = false]; + + // Is this message deprecated? + // Depending on the target platform, this can emit Deprecated annotations + // for the message, or it will be completely ignored; in the very least, + // this is a formalization for deprecating messages. + optional bool deprecated = 3 [default = false]; + + reserved 4, 5, 6; + + // NOTE: Do not set the option in .proto files. Always use the maps syntax + // instead. The option should only be implicitly set by the proto compiler + // parser. + // + // Whether the message is an automatically generated map entry type for the + // maps field. + // + // For maps fields: + // map map_field = 1; + // The parsed descriptor looks like: + // message MapFieldEntry { + // option map_entry = true; + // optional KeyType key = 1; + // optional ValueType value = 2; + // } + // repeated MapFieldEntry map_field = 1; + // + // Implementations may choose not to generate the map_entry=true message, but + // use a native map in the target language to hold the keys and values. + // The reflection APIs in such implementations still need to work as + // if the field is a repeated message field. + optional bool map_entry = 7; + + reserved 8; // javalite_serializable + reserved 9; // javanano_as_lite + + // Enable the legacy handling of JSON field name conflicts. This lowercases + // and strips underscored from the fields before comparison in proto3 only. + // The new behavior takes `json_name` into account and applies to proto2 as + // well. + // + // This should only be used as a temporary measure against broken builds due + // to the change in behavior for JSON field name conflicts. + // + // TODO(b/261750190) This is legacy behavior we plan to remove once downstream + // teams have had time to migrate. + optional bool deprecated_legacy_json_field_conflicts = 11 [deprecated = true]; + + // The parser stores options it doesn't recognize here. See above. + repeated UninterpretedOption uninterpreted_option = 999; + + // Clients can define custom options in extensions of this message. See above. + extensions 1000 to max; +} + +message FieldOptions { + // The ctype option instructs the C++ code generator to use a different + // representation of the field than it normally would. See the specific + // options below. This option is not yet implemented in the open source + // release -- sorry, we'll try to include it in a future version! + optional CType ctype = 1 [default = STRING]; + enum CType { + // Default mode. + STRING = 0; + + CORD = 1; + + STRING_PIECE = 2; + } + // The packed option can be enabled for repeated primitive fields to enable + // a more efficient representation on the wire. Rather than repeatedly + // writing the tag and type for each element, the entire array is encoded as + // a single length-delimited blob. In proto3, only explicit setting it to + // false will avoid using packed encoding. + optional bool packed = 2; + + // The jstype option determines the JavaScript type used for values of the + // field. The option is permitted only for 64 bit integral and fixed types + // (int64, uint64, sint64, fixed64, sfixed64). A field with jstype JS_STRING + // is represented as JavaScript string, which avoids loss of precision that + // can happen when a large value is converted to a floating point JavaScript. + // Specifying JS_NUMBER for the jstype causes the generated JavaScript code to + // use the JavaScript "number" type. The behavior of the default option + // JS_NORMAL is implementation dependent. + // + // This option is an enum to permit additional types to be added, e.g. + // goog.math.Integer. + optional JSType jstype = 6 [default = JS_NORMAL]; + enum JSType { + // Use the default type. + JS_NORMAL = 0; + + // Use JavaScript strings. + JS_STRING = 1; + + // Use JavaScript numbers. + JS_NUMBER = 2; + } + + // Should this field be parsed lazily? Lazy applies only to message-type + // fields. It means that when the outer message is initially parsed, the + // inner message's contents will not be parsed but instead stored in encoded + // form. The inner message will actually be parsed when it is first accessed. + // + // This is only a hint. Implementations are free to choose whether to use + // eager or lazy parsing regardless of the value of this option. However, + // setting this option true suggests that the protocol author believes that + // using lazy parsing on this field is worth the additional bookkeeping + // overhead typically needed to implement it. + // + // This option does not affect the public interface of any generated code; + // all method signatures remain the same. Furthermore, thread-safety of the + // interface is not affected by this option; const methods remain safe to + // call from multiple threads concurrently, while non-const methods continue + // to require exclusive access. + // + // Note that implementations may choose not to check required fields within + // a lazy sub-message. That is, calling IsInitialized() on the outer message + // may return true even if the inner message has missing required fields. + // This is necessary because otherwise the inner message would have to be + // parsed in order to perform the check, defeating the purpose of lazy + // parsing. An implementation which chooses not to check required fields + // must be consistent about it. That is, for any particular sub-message, the + // implementation must either *always* check its required fields, or *never* + // check its required fields, regardless of whether or not the message has + // been parsed. + // + // As of May 2022, lazy verifies the contents of the byte stream during + // parsing. An invalid byte stream will cause the overall parsing to fail. + optional bool lazy = 5 [default = false]; + + // unverified_lazy does no correctness checks on the byte stream. This should + // only be used where lazy with verification is prohibitive for performance + // reasons. + optional bool unverified_lazy = 15 [default = false]; + + // Is this field deprecated? + // Depending on the target platform, this can emit Deprecated annotations + // for accessors, or it will be completely ignored; in the very least, this + // is a formalization for deprecating fields. + optional bool deprecated = 3 [default = false]; + + // For Google-internal migration only. Do not use. + optional bool weak = 10 [default = false]; + + // Indicate that the field value should not be printed out when using debug + // formats, e.g. when the field contains sensitive credentials. + optional bool debug_redact = 16 [default = false]; + + // If set to RETENTION_SOURCE, the option will be omitted from the binary. + // Note: as of January 2023, support for this is in progress and does not yet + // have an effect (b/264593489). + enum OptionRetention { + RETENTION_UNKNOWN = 0; + RETENTION_RUNTIME = 1; + RETENTION_SOURCE = 2; + } + + optional OptionRetention retention = 17; + + // This indicates the types of entities that the field may apply to when used + // as an option. If it is unset, then the field may be freely used as an + // option on any kind of entity. Note: as of January 2023, support for this is + // in progress and does not yet have an effect (b/264593489). + enum OptionTargetType { + TARGET_TYPE_UNKNOWN = 0; + TARGET_TYPE_FILE = 1; + TARGET_TYPE_EXTENSION_RANGE = 2; + TARGET_TYPE_MESSAGE = 3; + TARGET_TYPE_FIELD = 4; + TARGET_TYPE_ONEOF = 5; + TARGET_TYPE_ENUM = 6; + TARGET_TYPE_ENUM_ENTRY = 7; + TARGET_TYPE_SERVICE = 8; + TARGET_TYPE_METHOD = 9; + } + + optional OptionTargetType target = 18; + + // The parser stores options it doesn't recognize here. See above. + repeated UninterpretedOption uninterpreted_option = 999; + + // Clients can define custom options in extensions of this message. See above. + extensions 1000 to max; + + reserved 4; // removed jtype +} + +message OneofOptions { + // The parser stores options it doesn't recognize here. See above. + repeated UninterpretedOption uninterpreted_option = 999; + + // Clients can define custom options in extensions of this message. See above. + extensions 1000 to max; +} + +message EnumOptions { + + // Set this option to true to allow mapping different tag names to the same + // value. + optional bool allow_alias = 2; + + // Is this enum deprecated? + // Depending on the target platform, this can emit Deprecated annotations + // for the enum, or it will be completely ignored; in the very least, this + // is a formalization for deprecating enums. + optional bool deprecated = 3 [default = false]; + + reserved 5; // javanano_as_lite + + // Enable the legacy handling of JSON field name conflicts. This lowercases + // and strips underscored from the fields before comparison in proto3 only. + // The new behavior takes `json_name` into account and applies to proto2 as + // well. + // TODO(b/261750190) Remove this legacy behavior once downstream teams have + // had time to migrate. + optional bool deprecated_legacy_json_field_conflicts = 6 [deprecated = true]; + + // The parser stores options it doesn't recognize here. See above. + repeated UninterpretedOption uninterpreted_option = 999; + + // Clients can define custom options in extensions of this message. See above. + extensions 1000 to max; +} + +message EnumValueOptions { + // Is this enum value deprecated? + // Depending on the target platform, this can emit Deprecated annotations + // for the enum value, or it will be completely ignored; in the very least, + // this is a formalization for deprecating enum values. + optional bool deprecated = 1 [default = false]; + + // The parser stores options it doesn't recognize here. See above. + repeated UninterpretedOption uninterpreted_option = 999; + + // Clients can define custom options in extensions of this message. See above. + extensions 1000 to max; +} + +message ServiceOptions { + + // Note: Field numbers 1 through 32 are reserved for Google's internal RPC + // framework. We apologize for hoarding these numbers to ourselves, but + // we were already using them long before we decided to release Protocol + // Buffers. + + // Is this service deprecated? + // Depending on the target platform, this can emit Deprecated annotations + // for the service, or it will be completely ignored; in the very least, + // this is a formalization for deprecating services. + optional bool deprecated = 33 [default = false]; + + // The parser stores options it doesn't recognize here. See above. + repeated UninterpretedOption uninterpreted_option = 999; + + // Clients can define custom options in extensions of this message. See above. + extensions 1000 to max; +} + +message MethodOptions { + + // Note: Field numbers 1 through 32 are reserved for Google's internal RPC + // framework. We apologize for hoarding these numbers to ourselves, but + // we were already using them long before we decided to release Protocol + // Buffers. + + // Is this method deprecated? + // Depending on the target platform, this can emit Deprecated annotations + // for the method, or it will be completely ignored; in the very least, + // this is a formalization for deprecating methods. + optional bool deprecated = 33 [default = false]; + + // Is this method side-effect-free (or safe in HTTP parlance), or idempotent, + // or neither? HTTP based RPC implementation may choose GET verb for safe + // methods, and PUT verb for idempotent methods instead of the default POST. + enum IdempotencyLevel { + IDEMPOTENCY_UNKNOWN = 0; + NO_SIDE_EFFECTS = 1; // implies idempotent + IDEMPOTENT = 2; // idempotent, but may have side effects + } + optional IdempotencyLevel idempotency_level = 34 + [default = IDEMPOTENCY_UNKNOWN]; + + // The parser stores options it doesn't recognize here. See above. + repeated UninterpretedOption uninterpreted_option = 999; + + // Clients can define custom options in extensions of this message. See above. + extensions 1000 to max; +} + +// A message representing a option the parser does not recognize. This only +// appears in options protos created by the compiler::Parser class. +// DescriptorPool resolves these when building Descriptor objects. Therefore, +// options protos in descriptor objects (e.g. returned by Descriptor::options(), +// or produced by Descriptor::CopyTo()) will never have UninterpretedOptions +// in them. +message UninterpretedOption { + // The name of the uninterpreted option. Each string represents a segment in + // a dot-separated name. is_extension is true iff a segment represents an + // extension (denoted with parentheses in options specs in .proto files). + // E.g.,{ ["foo", false], ["bar.baz", true], ["moo", false] } represents + // "foo.(bar.baz).moo". + message NamePart { + required string name_part = 1; + required bool is_extension = 2; + } + repeated NamePart name = 2; + + // The value of the uninterpreted option, in whatever type the tokenizer + // identified it as during parsing. Exactly one of these should be set. + optional string identifier_value = 3; + optional uint64 positive_int_value = 4; + optional int64 negative_int_value = 5; + optional double double_value = 6; + optional bytes string_value = 7; + optional string aggregate_value = 8; +} + +// =================================================================== +// Optional source code info + +// Encapsulates information about the original source file from which a +// FileDescriptorProto was generated. +message SourceCodeInfo { + // A Location identifies a piece of source code in a .proto file which + // corresponds to a particular definition. This information is intended + // to be useful to IDEs, code indexers, documentation generators, and similar + // tools. + // + // For example, say we have a file like: + // message Foo { + // optional string foo = 1; + // } + // Let's look at just the field definition: + // optional string foo = 1; + // ^ ^^ ^^ ^ ^^^ + // a bc de f ghi + // We have the following locations: + // span path represents + // [a,i) [ 4, 0, 2, 0 ] The whole field definition. + // [a,b) [ 4, 0, 2, 0, 4 ] The label (optional). + // [c,d) [ 4, 0, 2, 0, 5 ] The type (string). + // [e,f) [ 4, 0, 2, 0, 1 ] The name (foo). + // [g,h) [ 4, 0, 2, 0, 3 ] The number (1). + // + // Notes: + // - A location may refer to a repeated field itself (i.e. not to any + // particular index within it). This is used whenever a set of elements are + // logically enclosed in a single code segment. For example, an entire + // extend block (possibly containing multiple extension definitions) will + // have an outer location whose path refers to the "extensions" repeated + // field without an index. + // - Multiple locations may have the same path. This happens when a single + // logical declaration is spread out across multiple places. The most + // obvious example is the "extend" block again -- there may be multiple + // extend blocks in the same scope, each of which will have the same path. + // - A location's span is not always a subset of its parent's span. For + // example, the "extendee" of an extension declaration appears at the + // beginning of the "extend" block and is shared by all extensions within + // the block. + // - Just because a location's span is a subset of some other location's span + // does not mean that it is a descendant. For example, a "group" defines + // both a type and a field in a single declaration. Thus, the locations + // corresponding to the type and field and their components will overlap. + // - Code which tries to interpret locations should probably be designed to + // ignore those that it doesn't understand, as more types of locations could + // be recorded in the future. + repeated Location location = 1; + message Location { + // Identifies which part of the FileDescriptorProto was defined at this + // location. + // + // Each element is a field number or an index. They form a path from + // the root FileDescriptorProto to the place where the definition occurs. + // For example, this path: + // [ 4, 3, 2, 7, 1 ] + // refers to: + // file.message_type(3) // 4, 3 + // .field(7) // 2, 7 + // .name() // 1 + // This is because FileDescriptorProto.message_type has field number 4: + // repeated DescriptorProto message_type = 4; + // and DescriptorProto.field has field number 2: + // repeated FieldDescriptorProto field = 2; + // and FieldDescriptorProto.name has field number 1: + // optional string name = 1; + // + // Thus, the above path gives the location of a field name. If we removed + // the last element: + // [ 4, 3, 2, 7 ] + // this path refers to the whole field declaration (from the beginning + // of the label to the terminating semicolon). + repeated int32 path = 1 [packed = true]; + + // Always has exactly three or four elements: start line, start column, + // end line (optional, otherwise assumed same as start line), end column. + // These are packed into a single field for efficiency. Note that line + // and column numbers are zero-based -- typically you will want to add + // 1 to each before displaying to a user. + repeated int32 span = 2 [packed = true]; + + // If this SourceCodeInfo represents a complete declaration, these are any + // comments appearing before and after the declaration which appear to be + // attached to the declaration. + // + // A series of line comments appearing on consecutive lines, with no other + // tokens appearing on those lines, will be treated as a single comment. + // + // leading_detached_comments will keep paragraphs of comments that appear + // before (but not connected to) the current element. Each paragraph, + // separated by empty lines, will be one comment element in the repeated + // field. + // + // Only the comment content is provided; comment markers (e.g. //) are + // stripped out. For block comments, leading whitespace and an asterisk + // will be stripped from the beginning of each line other than the first. + // Newlines are included in the output. + // + // Examples: + // + // optional int32 foo = 1; // Comment attached to foo. + // // Comment attached to bar. + // optional int32 bar = 2; + // + // optional string baz = 3; + // // Comment attached to baz. + // // Another line attached to baz. + // + // // Comment attached to moo. + // // + // // Another line attached to moo. + // optional double moo = 4; + // + // // Detached comment for corge. This is not leading or trailing comments + // // to moo or corge because there are blank lines separating it from + // // both. + // + // // Detached comment for corge paragraph 2. + // + // optional string corge = 5; + // /* Block comment attached + // * to corge. Leading asterisks + // * will be removed. */ + // /* Block comment attached to + // * grault. */ + // optional int32 grault = 6; + // + // // ignored detached comments. + optional string leading_comments = 3; + optional string trailing_comments = 4; + repeated string leading_detached_comments = 6; + } +} + +// Describes the relationship between generated code and its original source +// file. A GeneratedCodeInfo message is associated with only one generated +// source file, but may contain references to different source .proto files. +message GeneratedCodeInfo { + // An Annotation connects some span of text in generated code to an element + // of its generating .proto file. + repeated Annotation annotation = 1; + message Annotation { + // Identifies the element in the original source .proto file. This field + // is formatted the same as SourceCodeInfo.Location.path. + repeated int32 path = 1 [packed = true]; + + // Identifies the filesystem path to the original source .proto. + optional string source_file = 2; + + // Identifies the starting offset in bytes in the generated code + // that relates to the identified object. + optional int32 begin = 3; + + // Identifies the ending offset in bytes in the generated code that + // relates to the identified object. The end offset should be one past + // the last relevant byte (so the length of the text = end - begin). + optional int32 end = 4; + + // Represents the identified object's effect on the element in the original + // .proto file. + enum Semantic { + // There is no effect or the effect is indescribable. + NONE = 0; + // The element is set or otherwise mutated. + SET = 1; + // An alias to the element is returned. + ALIAS = 2; + } + optional Semantic semantic = 5; + } +} diff --git a/spawn_activators/activator_api/priv/protos/google/api/http.proto b/spawn_activators/activator_api/priv/protos/google/api/http.proto new file mode 100644 index 000000000..1fe371ce4 --- /dev/null +++ b/spawn_activators/activator_api/priv/protos/google/api/http.proto @@ -0,0 +1,250 @@ +// service Messaging { +// rpc UpdateMessage(UpdateMessageRequest) returns (Message) { +// option (google.api.http) = { +// patch: "/v1/messages/{message_id}" +// body: "message" +// }; +// } +// } +// message UpdateMessageRequest { +// string message_id = 1; // mapped to the URL +// Message message = 2; // mapped to the body +// } +// +// The following HTTP JSON to RPC mapping is enabled, where the +// representation of the JSON in the request body is determined by +// protos JSON encoding: +// +// HTTP | gRPC +// -----|----- +// `PATCH /v1/messages/123456 { "text": "Hi!" }` | `UpdateMessage(message_id: +// "123456" message { text: "Hi!" })` +// +// The special name `*` can be used in the body mapping to define that +// every field not bound by the path template should be mapped to the +// request body. This enables the following alternative definition of +// the update method: +// +// service Messaging { +// rpc UpdateMessage(Message) returns (Message) { +// option (google.api.http) = { +// patch: "/v1/messages/{message_id}" +// body: "*" +// }; +// } +// } +// message Message { +// string message_id = 1; +// string text = 2; +// } +// +// +// The following HTTP JSON to RPC mapping is enabled: +// +// HTTP | gRPC +// -----|----- +// `PATCH /v1/messages/123456 { "text": "Hi!" }` | `UpdateMessage(message_id: +// "123456" text: "Hi!")` +// +// Note that when using `*` in the body mapping, it is not possible to +// have HTTP parameters, as all fields not bound by the path end in +// the body. This makes this option more rarely used in practice when +// defining REST APIs. The common usage of `*` is in custom methods +// which don't use the URL at all for transferring data. +// +// It is possible to define multiple HTTP methods for one RPC by using +// the `additional_bindings` option. Example: +// +// service Messaging { +// rpc GetMessage(GetMessageRequest) returns (Message) { +// option (google.api.http) = { +// get: "/v1/messages/{message_id}" +// additional_bindings { +// get: "/v1/users/{user_id}/messages/{message_id}" +// } +// }; +// } +// } +// message GetMessageRequest { +// string message_id = 1; +// string user_id = 2; +// } +// +// This enables the following two alternative HTTP JSON to RPC mappings: +// +// HTTP | gRPC +// -----|----- +// `GET /v1/messages/123456` | `GetMessage(message_id: "123456")` +// `GET /v1/users/me/messages/123456` | `GetMessage(user_id: "me" message_id: +// "123456")` +// +// ## Rules for HTTP mapping +// +// 1. Leaf request fields (recursive expansion nested messages in the request +// message) are classified into three categories: +// - Fields referred by the path template. They are passed via the URL path. +// - Fields referred by the [HttpRule.body][google.api.HttpRule.body]. They are passed via the HTTP +// request body. +// - All other fields are passed via the URL query parameters, and the +// parameter name is the field path in the request message. A repeated +// field can be represented as multiple query parameters under the same +// name. +// 2. If [HttpRule.body][google.api.HttpRule.body] is "*", there is no URL query parameter, all fields +// are passed via URL path and HTTP request body. +// 3. If [HttpRule.body][google.api.HttpRule.body] is omitted, there is no HTTP request body, all +// fields are passed via URL path and URL query parameters. +// +// ### Path template syntax +// +// Template = "/" Segments [ Verb ] ; +// Segments = Segment { "/" Segment } ; +// Segment = "*" | "**" | LITERAL | Variable ; +// Variable = "{" FieldPath [ "=" Segments ] "}" ; +// FieldPath = IDENT { "." IDENT } ; +// Verb = ":" LITERAL ; +// +// The syntax `*` matches a single URL path segment. The syntax `**` matches +// zero or more URL path segments, which must be the last part of the URL path +// except the `Verb`. +// +// The syntax `Variable` matches part of the URL path as specified by its +// template. A variable template must not contain other variables. If a variable +// matches a single path segment, its template may be omitted, e.g. `{var}` +// is equivalent to `{var=*}`. +// +// The syntax `LITERAL` matches literal text in the URL path. If the `LITERAL` +// contains any reserved character, such characters should be percent-encoded +// before the matching. +// +// If a variable contains exactly one path segment, such as `"{var}"` or +// `"{var=*}"`, when such a variable is expanded into a URL path on the client +// side, all characters except `[-_.~0-9a-zA-Z]` are percent-encoded. The +// server side does the reverse decoding. Such variables show up in the +// [Discovery +// Document](https://developers.google.com/discovery/v1/reference/apis) as +// `{var}`. +// +// If a variable contains multiple path segments, such as `"{var=foo/*}"` +// or `"{var=**}"`, when such a variable is expanded into a URL path on the +// client side, all characters except `[-_.~/0-9a-zA-Z]` are percent-encoded. +// The server side does the reverse decoding, except "%2F" and "%2f" are left +// unchanged. Such variables show up in the +// [Discovery +// Document](https://developers.google.com/discovery/v1/reference/apis) as +// `{+var}`. +// +// ## Using gRPC API Service Configuration +// +// gRPC API Service Configuration (service config) is a configuration language +// for configuring a gRPC service to become a user-facing product. The +// service config is simply the YAML representation of the `google.api.Service` +// proto message. +// +// As an alternative to annotating your proto file, you can configure gRPC +// transcoding in your service config YAML files. You do this by specifying a +// `HttpRule` that maps the gRPC method to a REST endpoint, achieving the same +// effect as the proto annotation. This can be particularly useful if you +// have a proto that is reused in multiple services. Note that any transcoding +// specified in the service config will override any matching transcoding +// configuration in the proto. +// +// Example: +// +// http: +// rules: +// # Selects a gRPC method and applies HttpRule to it. +// - selector: example.v1.Messaging.GetMessage +// get: /v1/messages/{message_id}/{sub.subfield} +// +// ## Special notes +// +// When gRPC Transcoding is used to map a gRPC to JSON REST endpoints, the +// proto to JSON conversion must follow the [proto3 +// specification](https://developers.google.com/protocol-buffers/docs/proto3#json). +// +// While the single segment variable follows the semantics of +// [RFC 6570](https://tools.ietf.org/html/rfc6570) Section 3.2.2 Simple String +// Expansion, the multi segment variable **does not** follow RFC 6570 Section +// 3.2.3 Reserved Expansion. The reason is that the Reserved Expansion +// does not expand special characters like `?` and `#`, which would lead +// to invalid URLs. As the result, gRPC Transcoding uses a custom encoding +// for multi segment variables. +// +// The path variables **must not** refer to any repeated or mapped field, +// because client libraries are not capable of handling such variable expansion. +// +// The path variables **must not** capture the leading "/" character. The reason +// is that the most common use case "{var}" does not capture the leading "/" +// character. For consistency, all path variables must share the same behavior. +// +// Repeated message fields must not be mapped to URL query parameters, because +// no client library can support such complicated mapping. +// +// If an API needs to use a JSON array for request or response body, it can map +// the request or response body to a repeated field. However, some gRPC +// Transcoding implementations may not support this feature. +syntax = "proto3"; + +message HttpRule { + // Selects a method to which this rule applies. + // + // Refer to [selector][google.api.DocumentationRule.selector] for syntax details. + string selector = 1; + + // Determines the URL pattern is matched by this rules. This pattern can be + // used with any of the {get|put|post|delete|patch} methods. A custom method + // can be defined using the 'custom' field. + oneof pattern { + // Maps to HTTP GET. Used for listing and getting information about + // resources. + string get = 2; + + // Maps to HTTP PUT. Used for replacing a resource. + string put = 3; + + // Maps to HTTP POST. Used for creating a resource or performing an action. + string post = 4; + + // Maps to HTTP DELETE. Used for deleting a resource. + string delete = 5; + + // Maps to HTTP PATCH. Used for updating a resource. + string patch = 6; + + // The custom pattern is used for specifying an HTTP method that is not + // included in the `pattern` field, such as HEAD, or "*" to leave the + // HTTP method unspecified for this rule. The wild-card rule is useful + // for services that provide content to Web (HTML) clients. + CustomHttpPattern custom = 8; + } + + // The name of the request field whose value is mapped to the HTTP request + // body, or `*` for mapping all request fields not captured by the path + // pattern to the HTTP body, or omitted for not having any HTTP request body. + // + // NOTE: the referred field must be present at the top-level of the request + // message type. + string body = 7; + + // Optional. The name of the response field whose value is mapped to the HTTP + // response body. When omitted, the entire response message will be used + // as the HTTP response body. + // + // NOTE: The referred field must be present at the top-level of the response + // message type. + string response_body = 12; + + // Additional HTTP bindings for the selector. Nested bindings must + // not contain an `additional_bindings` field themselves (that is, + // the nesting may only be one level deep). + repeated HttpRule additional_bindings = 11; + } + + // A custom pattern is used for defining custom HTTP verb. + message CustomHttpPattern { + // The name of this custom HTTP verb. + string kind = 1; + + // The path matched by this custom verb. + string path = 2; + } \ No newline at end of file diff --git a/spawn_activators/activator_api/priv/protos/google/api/httpbody.proto b/spawn_activators/activator_api/priv/protos/google/api/httpbody.proto new file mode 100644 index 000000000..9ca36d112 --- /dev/null +++ b/spawn_activators/activator_api/priv/protos/google/api/httpbody.proto @@ -0,0 +1,81 @@ +// Copyright 2015 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package google.api; + +import "any.proto"; + +option cc_enable_arenas = true; +option go_package = "google.golang.org/genproto/googleapis/api/httpbody;httpbody"; +option java_multiple_files = true; +option java_outer_classname = "HttpBodyProto"; +option java_package = "com.google.api"; +option objc_class_prefix = "GAPI"; + +// Message that represents an arbitrary HTTP body. It should only be used for +// payload formats that can't be represented as JSON, such as raw binary or +// an HTML page. +// +// +// This message can be used both in streaming and non-streaming API methods in +// the request as well as the response. +// +// It can be used as a top-level request field, which is convenient if one +// wants to extract parameters from either the URL or HTTP template into the +// request fields and also want access to the raw HTTP body. +// +// Example: +// +// message GetResourceRequest { +// // A unique request id. +// string request_id = 1; +// +// // The raw HTTP body is bound to this field. +// google.api.HttpBody http_body = 2; +// +// } +// +// service ResourceService { +// rpc GetResource(GetResourceRequest) +// returns (google.api.HttpBody); +// rpc UpdateResource(google.api.HttpBody) +// returns (google.protobuf.Empty); +// +// } +// +// Example with streaming methods: +// +// service CaldavService { +// rpc GetCalendar(stream google.api.HttpBody) +// returns (stream google.api.HttpBody); +// rpc UpdateCalendar(stream google.api.HttpBody) +// returns (stream google.api.HttpBody); +// +// } +// +// Use of this type only changes how the request and response bodies are +// handled, all other features will continue to work unchanged. +message HttpBody { + // The HTTP Content-Type header value specifying the content type of the body. + string content_type = 1; + + // The HTTP request/response body as raw binary. + bytes data = 2; + + // Application specific response metadata. Must be set in the first response + // for streaming APIs. + repeated google.protobuf.Any extensions = 3; +} \ No newline at end of file diff --git a/spawn_activators/activator_api/priv/protos/google/api/source_info.proto b/spawn_activators/activator_api/priv/protos/google/api/source_info.proto new file mode 100644 index 000000000..a7a10e7fe --- /dev/null +++ b/spawn_activators/activator_api/priv/protos/google/api/source_info.proto @@ -0,0 +1,31 @@ +// Copyright 2015 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package google.api; + +import "any.proto"; + +option go_package = "google.golang.org/genproto/googleapis/api/serviceconfig;serviceconfig"; +option java_multiple_files = true; +option java_outer_classname = "SourceInfoProto"; +option java_package = "com.google.api"; +option objc_class_prefix = "GAPI"; + +// Source information used to create a Service Config +message SourceInfo { + // All files used during config generation. + repeated google.protobuf.Any source_files = 1; +} \ No newline at end of file diff --git a/spawn_activators/activator_api/priv/protos/service.proto b/spawn_activators/activator_api/priv/protos/service.proto new file mode 100644 index 000000000..a9fc2bda2 --- /dev/null +++ b/spawn_activators/activator_api/priv/protos/service.proto @@ -0,0 +1,26 @@ +syntax = "proto3"; + +import "google/api/annotations.proto"; + +package io.eigr.spawn.example; + +option java_multiple_files = true; +option java_package = "io.eigr.spawn.example"; +option java_outer_classname = "ExampleProtos"; + +message MyState { + int32 value = 1; +} + +message MyBusinessMessage { + int32 value = 1; +} + +service TestService { + rpc Sum (MyBusinessMessage) returns (MyBusinessMessage) { + option (google.api.http) = { + post: "/v1/sum" + body: "*" + }; + } +} \ No newline at end of file diff --git a/spawn_activators/activator_api/priv/templates/grpc_endpoint.ex.eex b/spawn_activators/activator_api/priv/templates/grpc_endpoint.ex.eex new file mode 100644 index 000000000..59f5c5fcc --- /dev/null +++ b/spawn_activators/activator_api/priv/templates/grpc_endpoint.ex.eex @@ -0,0 +1,15 @@ +defmodule ActivatorAPI.Server.Grpc.ProxyEndpoint do + @moduledoc false + use GRPC.Endpoint + + intercept(GRPC.Logger.Server) + + services = [ + #MassProxy.Reflection.Service, +<%= Enum.map service_names, fn(service_name) -> %> + <%= service_name %>, +<% end %> + ] + + run(services) +end \ No newline at end of file diff --git a/spawn_activators/activator_api/priv/templates/grpc_service.ex.eex b/spawn_activators/activator_api/priv/templates/grpc_service.ex.eex new file mode 100644 index 000000000..2dc1acc58 --- /dev/null +++ b/spawn_activators/activator_api/priv/templates/grpc_service.ex.eex @@ -0,0 +1,35 @@ +defmodule <%= mod_name %>.ProxyService do + @moduledoc """ + This <%= mod_name %>.ProxyService module only passes + incoming gRPC requests to the respective Actors Actions. + """ + @moduledoc since: "0.5.1" + use GRPC.Server, service: <%= name %> + + alias ActivatorAPI.Api.RouterDispatcher, as: Dispatcher +<%= Enum.map methods, fn(method) -> %> + @spec <%= method %>(<%= Map.get(input_types, method) %>.t(), GRPC.Server.Stream.t()) :: <%= Map.get(output_types, method) %>.t() + def <%= method %>(message, stream) do + opts = [ + service_name: "<%= service_name %>", + original_method: "<%= Map.get(original_methods, method) %>", + actor_name: "<%= dispatcher_actor_name %>", + action: "<%= dispatcher_actor_action %>", + parent_actor: "<%= dispatcher_actor_parent %>", + system_name: "<%= dispatcher_system_name %>", + invocation_type: "<%= dispatcher_option_invocation_type %>", + request_type: "<%= Map.get(request_types, method) %>", + input_type: <%= Map.get(input_types, method) %>, + output_type: <%= Map.get(output_types, method) %>, + pooled: "<%= dispatcher_option_pooled %>", + timeout: "<%= dispatcher_option_timeout %>", + async: "<%= dispatcher_option_async %>", + stream_out_from_channel: "<%= dispatcher_option_output_channel %>", + authentication_kind: "<%= dispatcher_option_authentication_kind %>", + authentication_secret: "<%= dispatcher_option_authentication_secret %>" + ] + + Dispatcher.dispatch(message, stream, opts) + end +<% end %> +end \ No newline at end of file diff --git a/spawn_activators/activator_api/test/activator_grpc_test.exs b/spawn_activators/activator_api/test/activator_grpc_test.exs new file mode 100644 index 000000000..63281f436 --- /dev/null +++ b/spawn_activators/activator_api/test/activator_grpc_test.exs @@ -0,0 +1,4 @@ +defmodule ActivatorAPITest do + use ExUnit.Case + doctest ActivatorAPI +end diff --git a/spawn_activators/activator_api/test/test_helper.exs b/spawn_activators/activator_api/test/test_helper.exs new file mode 100644 index 000000000..869559e70 --- /dev/null +++ b/spawn_activators/activator_api/test/test_helper.exs @@ -0,0 +1 @@ +ExUnit.start() diff --git a/spawn_activators/activator_kafka/.env b/spawn_activators/activator_kafka/.env new file mode 100644 index 000000000..1e0e2451f --- /dev/null +++ b/spawn_activators/activator_kafka/.env @@ -0,0 +1,4 @@ +PROXY_APP_NAME=spawn-activator-kafka +PROXY_HTTP_PORT=9004 +PROXY_CLUSTER_STRATEGY=gossip +PROXY_CLUSTER_POLLING=3000 diff --git a/spawn_activators/activator_kafka/.formatter.exs b/spawn_activators/activator_kafka/.formatter.exs new file mode 100644 index 000000000..d2cda26ed --- /dev/null +++ b/spawn_activators/activator_kafka/.formatter.exs @@ -0,0 +1,4 @@ +# Used by "mix format" +[ + inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"] +] diff --git a/spawn_activators/activator_kafka/.gitignore b/spawn_activators/activator_kafka/.gitignore new file mode 100644 index 000000000..4de1c8519 --- /dev/null +++ b/spawn_activators/activator_kafka/.gitignore @@ -0,0 +1,26 @@ +# The directory Mix will write compiled artifacts to. +/_build/ + +# If you run "mix test --cover", coverage assets end up here. +/cover/ + +# The directory Mix downloads your dependencies sources to. +/deps/ + +# Where third-party dependencies like ExDoc output generated docs. +/doc/ + +# Ignore .fetch files in case you like to edit your project deps locally. +/.fetch + +# If the VM crashes, it generates a dump, let's ignore it too. +erl_crash.dump + +# Also ignore archive artifacts (built via "mix archive.build"). +*.ez + +# Ignore package tarball (built via "mix hex.build"). +activator_kafka-*.tar + +# Temporary files, for example, from tests. +/tmp/ diff --git a/spawn_activators/activator_kafka/README.md b/spawn_activators/activator_kafka/README.md new file mode 100644 index 000000000..8db9c6900 --- /dev/null +++ b/spawn_activators/activator_kafka/README.md @@ -0,0 +1,21 @@ +# ActivatorKafka + +**TODO: Add description** + +## Installation + +If [available in Hex](https://hex.pm/docs/publish), the package can be installed +by adding `activator_kafka` to your list of dependencies in `mix.exs`: + +```elixir +def deps do + [ + {:activator_kafka, "~> 0.1.0"} + ] +end +``` + +Documentation can be generated with [ExDoc](https://github.com/elixir-lang/ex_doc) +and published on [HexDocs](https://hexdocs.pm). Once published, the docs can +be found at . + diff --git a/spawn_activators/activator_kafka/lib/activator_kafka.ex b/spawn_activators/activator_kafka/lib/activator_kafka.ex new file mode 100644 index 000000000..a85b654ea --- /dev/null +++ b/spawn_activators/activator_kafka/lib/activator_kafka.ex @@ -0,0 +1,18 @@ +defmodule ActivatorKafka do + @moduledoc """ + Documentation for `ActivatorKafka`. + """ + + @doc """ + Hello world. + + ## Examples + + iex> ActivatorKafka.hello() + :world + + """ + def hello do + :world + end +end diff --git a/spawn_activators/activator_kafka/lib/activator_kafka/application.ex b/spawn_activators/activator_kafka/lib/activator_kafka/application.ex new file mode 100644 index 000000000..29e72d098 --- /dev/null +++ b/spawn_activators/activator_kafka/lib/activator_kafka/application.ex @@ -0,0 +1,21 @@ +defmodule ActivatorKafka.Application do + @moduledoc false + + use Application + + alias Actors.Config.PersistentTermConfig, as: Config + import Activator, only: [get_http_port: 1] + + @impl true + def start(_type, _args) do + Config.load() + + children = [ + Activator.Supervisor.child_spec([]), + {Bandit, plug: ActivatorKafka.Router, scheme: :http, port: get_http_port()} + ] + + opts = [strategy: :one_for_one, name: ActivatorKafka.Supervisor] + Supervisor.start_link(children, opts) + end +end diff --git a/spawn_activators/activator_kafka/lib/activator_kafka/router/router.ex b/spawn_activators/activator_kafka/lib/activator_kafka/router/router.ex new file mode 100644 index 000000000..276c1e9c0 --- /dev/null +++ b/spawn_activators/activator_kafka/lib/activator_kafka/router/router.ex @@ -0,0 +1,15 @@ +defmodule ActivatorKafka.Router do + use Plug.Router + + plug(Plug.Logger) + + plug(:match) + plug(Plug.Parsers, parsers: [:json], json_decoder: Jason) + plug(:dispatch) + + forward("/health", to: ActivatorKafka.Routes.Health) + + match _ do + send_resp(conn, 404, "Not found!") + end +end diff --git a/spawn_activators/activator_kafka/lib/activator_kafka/routes/base.ex b/spawn_activators/activator_kafka/lib/activator_kafka/routes/base.ex new file mode 100644 index 000000000..078b45f73 --- /dev/null +++ b/spawn_activators/activator_kafka/lib/activator_kafka/routes/base.ex @@ -0,0 +1,40 @@ +defmodule ActivatorKafka.Routes.Base do + defmacro __using__([]) do + quote do + use Plug.Router + + plug(Plug.Logger) + + plug(:match) + + plug(Plug.Parsers, + parsers: [:json], + json_decoder: Jason + ) + + plug(:dispatch) + + def send!(conn, code, data, content_type) + when is_integer(code) and content_type == "application/json" do + conn + |> Plug.Conn.put_resp_content_type(content_type) + |> send_resp(code, Jason.encode!(data)) + end + + def send!(conn, code, data, content_type) when is_atom(code) do + code = + case code do + :ok -> 200 + :not_found -> 404 + :malformed_data -> 400 + :non_authenticated -> 401 + :forbidden_access -> 403 + :server_error -> 500 + :error -> 504 + end + + send!(conn, code, data, content_type) + end + end + end +end diff --git a/spawn_activators/activator_kafka/lib/activator_kafka/routes/health.ex b/spawn_activators/activator_kafka/lib/activator_kafka/routes/health.ex new file mode 100644 index 000000000..055ee22c3 --- /dev/null +++ b/spawn_activators/activator_kafka/lib/activator_kafka/routes/health.ex @@ -0,0 +1,17 @@ +defmodule ActivatorKafka.Routes.Health do + use ActivatorKafka.Routes.Base + + @content_type "application/json" + + get "/" do + send!(conn, 200, %{status: "up"}, @content_type) + end + + get "/liveness" do + send!(conn, 200, %{status: "up"}, @content_type) + end + + get "/readiness" do + send!(conn, 200, %{status: "up"}, @content_type) + end +end diff --git a/spawn_activators/activator_kafka/mix.exs b/spawn_activators/activator_kafka/mix.exs new file mode 100644 index 000000000..e8ac98833 --- /dev/null +++ b/spawn_activators/activator_kafka/mix.exs @@ -0,0 +1,55 @@ +defmodule ActivatorKafka.MixProject do + use Mix.Project + + @app :activator_kafka + @version "0.0.0-local.dev" + + def project do + [ + app: @app, + version: @version, + build_path: "../activator/_build", + config_path: "../../config/config.exs", + deps_path: "../activator/deps", + lockfile: "../activator/mix.lock", + elixir: "~> 1.15", + start_permanent: Mix.env() == :prod, + deps: deps(), + releases: releases() + ] + end + + # Run "mix help compile.app" to learn about applications. + def application do + [ + extra_applications: [:logger], + mod: {ActivatorKafka.Application, []} + ] + end + + # Run "mix help deps" to learn about dependencies. + defp deps do + [ + {:activator, path: "../activator"}, + {:spawn, path: "../../"}, + {:broadway_kafka, "~> 0.4.1"}, + {:bakeware, "~> 0.2"}, + {:bandit, "~> 1.5"}, + {:nimble_options, "~> 0.5.2", override: true} + ] + end + + defp releases do + [ + activator_kafka: [ + include_executables_for: [:unix], + applications: [activator_kafka: :permanent], + steps: [ + :assemble, + &Bakeware.assemble/1 + ], + bakeware: [compression_level: 19] + ] + ] + end +end diff --git a/spawn_activators/activator_kafka/test/activator_kafka_test.exs b/spawn_activators/activator_kafka/test/activator_kafka_test.exs new file mode 100644 index 000000000..10725a33f --- /dev/null +++ b/spawn_activators/activator_kafka/test/activator_kafka_test.exs @@ -0,0 +1,8 @@ +defmodule ActivatorKafkaTest do + use ExUnit.Case + doctest ActivatorKafka + + test "greets the world" do + assert ActivatorKafka.hello() == :world + end +end diff --git a/spawn_activators/activator_kafka/test/test_helper.exs b/spawn_activators/activator_kafka/test/test_helper.exs new file mode 100644 index 000000000..869559e70 --- /dev/null +++ b/spawn_activators/activator_kafka/test/test_helper.exs @@ -0,0 +1 @@ +ExUnit.start() diff --git a/spawn_activators/activator_pubsub/.env b/spawn_activators/activator_pubsub/.env new file mode 100644 index 000000000..da9523735 --- /dev/null +++ b/spawn_activators/activator_pubsub/.env @@ -0,0 +1,4 @@ +PROXY_APP_NAME=spawn-activator-pubsub +PROXY_HTTP_PORT=9005 +PROXY_CLUSTER_STRATEGY=gossip +PROXY_CLUSTER_POLLING=3000 diff --git a/spawn_activators/activator_pubsub/.formatter.exs b/spawn_activators/activator_pubsub/.formatter.exs new file mode 100644 index 000000000..d2cda26ed --- /dev/null +++ b/spawn_activators/activator_pubsub/.formatter.exs @@ -0,0 +1,4 @@ +# Used by "mix format" +[ + inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"] +] diff --git a/spawn_activators/activator_pubsub/.gitignore b/spawn_activators/activator_pubsub/.gitignore new file mode 100644 index 000000000..727028f4c --- /dev/null +++ b/spawn_activators/activator_pubsub/.gitignore @@ -0,0 +1,26 @@ +# The directory Mix will write compiled artifacts to. +/_build/ + +# If you run "mix test --cover", coverage assets end up here. +/cover/ + +# The directory Mix downloads your dependencies sources to. +/deps/ + +# Where third-party dependencies like ExDoc output generated docs. +/doc/ + +# Ignore .fetch files in case you like to edit your project deps locally. +/.fetch + +# If the VM crashes, it generates a dump, let's ignore it too. +erl_crash.dump + +# Also ignore archive artifacts (built via "mix archive.build"). +*.ez + +# Ignore package tarball (built via "mix hex.build"). +activator_pubsub-*.tar + +# Temporary files, for example, from tests. +/tmp/ diff --git a/spawn_activators/activator_pubsub/README.md b/spawn_activators/activator_pubsub/README.md new file mode 100644 index 000000000..cd02aa80a --- /dev/null +++ b/spawn_activators/activator_pubsub/README.md @@ -0,0 +1,21 @@ +# ActivatorPubSub + +**TODO: Add description** + +## Installation + +If [available in Hex](https://hex.pm/docs/publish), the package can be installed +by adding `activator_pubsub` to your list of dependencies in `mix.exs`: + +```elixir +def deps do + [ + {:activator_pubsub, "~> 0.1.0"} + ] +end +``` + +Documentation can be generated with [ExDoc](https://github.com/elixir-lang/ex_doc) +and published on [HexDocs](https://hexdocs.pm). Once published, the docs can +be found at . + diff --git a/spawn_activators/activator_pubsub/lib/activator_pubsub.ex b/spawn_activators/activator_pubsub/lib/activator_pubsub.ex new file mode 100644 index 000000000..cb0251c5d --- /dev/null +++ b/spawn_activators/activator_pubsub/lib/activator_pubsub.ex @@ -0,0 +1,5 @@ +defmodule ActivatorPubSub do + @moduledoc """ + Documentation for `ActivatorPubSub`. + """ +end diff --git a/spawn_activators/activator_pubsub/lib/activator_pubsub/application.ex b/spawn_activators/activator_pubsub/lib/activator_pubsub/application.ex new file mode 100644 index 000000000..4bf0b5215 --- /dev/null +++ b/spawn_activators/activator_pubsub/lib/activator_pubsub/application.ex @@ -0,0 +1,21 @@ +defmodule ActivatorPubSub.Application do + @moduledoc false + + use Application + + alias Actors.Config.PersistentTermConfig, as: Config + import Activator, only: [get_http_port: 1] + + @impl true + def start(_type, _args) do + Config.load() + + children = [ + Activator.Supervisor.child_spec([]), + {Bandit, plug: ActivatorPubSub.Router, scheme: :http, port: get_http_port()} + ] + + opts = [strategy: :one_for_one, name: ActivatorPubSub.Supervisor] + Supervisor.start_link(children, opts) + end +end diff --git a/spawn_activators/activator_pubsub/lib/activator_pubsub/router/router.ex b/spawn_activators/activator_pubsub/lib/activator_pubsub/router/router.ex new file mode 100644 index 000000000..3ddebc85d --- /dev/null +++ b/spawn_activators/activator_pubsub/lib/activator_pubsub/router/router.ex @@ -0,0 +1,15 @@ +defmodule ActivatorPubSub.Router do + use Plug.Router + + plug(Plug.Logger) + + plug(:match) + plug(Plug.Parsers, parsers: [:json], json_decoder: Jason) + plug(:dispatch) + + forward("/health", to: ActivatorPubSub.Routes.Health) + + match _ do + send_resp(conn, 404, "Not found!") + end +end diff --git a/spawn_activators/activator_pubsub/lib/activator_pubsub/routes/base.ex b/spawn_activators/activator_pubsub/lib/activator_pubsub/routes/base.ex new file mode 100644 index 000000000..be080c111 --- /dev/null +++ b/spawn_activators/activator_pubsub/lib/activator_pubsub/routes/base.ex @@ -0,0 +1,40 @@ +defmodule ActivatorPubSub.Routes.Base do + defmacro __using__([]) do + quote do + use Plug.Router + + plug(Plug.Logger) + + plug(:match) + + plug(Plug.Parsers, + parsers: [:json], + json_decoder: Jason + ) + + plug(:dispatch) + + def send!(conn, code, data, content_type) + when is_integer(code) and content_type == "application/json" do + conn + |> Plug.Conn.put_resp_content_type(content_type) + |> send_resp(code, Jason.encode!(data)) + end + + def send!(conn, code, data, content_type) when is_atom(code) do + code = + case code do + :ok -> 200 + :not_found -> 404 + :malformed_data -> 400 + :non_authenticated -> 401 + :forbidden_access -> 403 + :server_error -> 500 + :error -> 504 + end + + send!(conn, code, data, content_type) + end + end + end +end diff --git a/spawn_activators/activator_pubsub/lib/activator_pubsub/routes/health.ex b/spawn_activators/activator_pubsub/lib/activator_pubsub/routes/health.ex new file mode 100644 index 000000000..ca9380c96 --- /dev/null +++ b/spawn_activators/activator_pubsub/lib/activator_pubsub/routes/health.ex @@ -0,0 +1,17 @@ +defmodule ActivatorPubSub.Routes.Health do + use ActivatorPubSub.Routes.Base + + @content_type "application/json" + + get "/" do + send!(conn, 200, %{status: "up"}, @content_type) + end + + get "/liveness" do + send!(conn, 200, %{status: "up"}, @content_type) + end + + get "/readiness" do + send!(conn, 200, %{status: "up"}, @content_type) + end +end diff --git a/spawn_activators/activator_pubsub/mix.exs b/spawn_activators/activator_pubsub/mix.exs new file mode 100644 index 000000000..e4ec94b4b --- /dev/null +++ b/spawn_activators/activator_pubsub/mix.exs @@ -0,0 +1,55 @@ +defmodule ActivatorPubSub.MixProject do + use Mix.Project + + @app :activator_pubsub + @version "0.0.0-local.dev" + + def project do + [ + app: @app, + version: @version, + build_path: "../activator/_build", + config_path: "../../config/config.exs", + deps_path: "../activator/deps", + lockfile: "../activator/mix.lock", + elixir: "~> 1.15", + start_permanent: Mix.env() == :prod, + deps: deps(), + releases: releases() + ] + end + + # Run "mix help compile.app" to learn about applications. + def application do + [ + extra_applications: [:logger], + mod: {ActivatorPubSub.Application, []} + ] + end + + # Run "mix help deps" to learn about dependencies. + defp deps do + [ + {:activator, path: "../activator"}, + {:spawn, path: "../../"}, + {:bakeware, "~> 0.2"}, + {:bandit, "~> 1.5"} + # {:broadway_cloud_pub_sub, "~> 0.7"}, + # {:goth, "~> 1.0"} + ] + end + + defp releases do + [ + activator_pubsub: [ + include_executables_for: [:unix], + applications: [activator_pubsub: :permanent], + steps: [ + :assemble, + &Bakeware.assemble/1 + ], + bakeware: [compression_level: 19] + ] + ] + end +end diff --git a/spawn_activators/activator_pubsub/test/activator_pubsub_test.exs b/spawn_activators/activator_pubsub/test/activator_pubsub_test.exs new file mode 100644 index 000000000..ab261e47b --- /dev/null +++ b/spawn_activators/activator_pubsub/test/activator_pubsub_test.exs @@ -0,0 +1,4 @@ +defmodule ActivatorPubSubTest do + use ExUnit.Case + doctest ActivatorPubSub +end diff --git a/spawn_activators/activator_pubsub/test/test_helper.exs b/spawn_activators/activator_pubsub/test/test_helper.exs new file mode 100644 index 000000000..869559e70 --- /dev/null +++ b/spawn_activators/activator_pubsub/test/test_helper.exs @@ -0,0 +1 @@ +ExUnit.start() diff --git a/spawn_activators/activator_rabbitmq/.env b/spawn_activators/activator_rabbitmq/.env new file mode 100644 index 000000000..f08484f98 --- /dev/null +++ b/spawn_activators/activator_rabbitmq/.env @@ -0,0 +1,18 @@ +PROXY_APP_NAME=spawn-activator-rabbitmq +PROXY_HTTP_PORT=9006 +PROXY_CLUSTER_STRATEGY=gossip +PROXY_CLUSTER_POLLING=3000 + +ACTIVATOR_ACTOR_SYSTEM=spawn-system +ACTIVATOR_TARGET_ACTORS=%{actor: "joe",action: "setLanguage"},%{actor: "robert", action: "setLanguage"} +ACTIVATOR_ACTOR_CONCURRENCY=1 +ACTIVATOR_SOURCE_TYPE=rabbitmq +ACTIVATOR_SOURCE_QUEUE_NAME=test +ACTIVATOR_SOURCE_USERNAME=guest +ACTIVATOR_SOURCE_PASSWORD=guest +ACTIVATOR_SOURCE_CONCURRENCY=1 +ACTIVATOR_SOURCE_PREFETCH_COUNT=50 +ACTIVATOR_SOURCE_USE_RATE_LIMITING=true +ACTIVATOR_SOURCE_RATE_LIMITING_INTERVAL=1_000 +ACTIVATOR_SOURCE_RATE_LIMITING_ALLOWED_MESSAGES_PER_TIME=100 + diff --git a/spawn_activators/activator_rabbitmq/.formatter.exs b/spawn_activators/activator_rabbitmq/.formatter.exs new file mode 100644 index 000000000..d2cda26ed --- /dev/null +++ b/spawn_activators/activator_rabbitmq/.formatter.exs @@ -0,0 +1,4 @@ +# Used by "mix format" +[ + inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"] +] diff --git a/spawn_activators/activator_rabbitmq/.gitignore b/spawn_activators/activator_rabbitmq/.gitignore new file mode 100644 index 000000000..a21410501 --- /dev/null +++ b/spawn_activators/activator_rabbitmq/.gitignore @@ -0,0 +1,26 @@ +# The directory Mix will write compiled artifacts to. +/_build/ + +# If you run "mix test --cover", coverage assets end up here. +/cover/ + +# The directory Mix downloads your dependencies sources to. +/deps/ + +# Where third-party dependencies like ExDoc output generated docs. +/doc/ + +# Ignore .fetch files in case you like to edit your project deps locally. +/.fetch + +# If the VM crashes, it generates a dump, let's ignore it too. +erl_crash.dump + +# Also ignore archive artifacts (built via "mix archive.build"). +*.ez + +# Ignore package tarball (built via "mix hex.build"). +activator_rabbitmq-*.tar + +# Temporary files, for example, from tests. +/tmp/ diff --git a/spawn_activators/activator_rabbitmq/README.md b/spawn_activators/activator_rabbitmq/README.md new file mode 100644 index 000000000..262bfb962 --- /dev/null +++ b/spawn_activators/activator_rabbitmq/README.md @@ -0,0 +1,21 @@ +# ActivatorRabbitMQ + +**TODO: Add description** + +## Installation + +If [available in Hex](https://hex.pm/docs/publish), the package can be installed +by adding `activator_rabbitmq` to your list of dependencies in `mix.exs`: + +```elixir +def deps do + [ + {:activator_rabbitmq, "~> 0.1.0"} + ] +end +``` + +Documentation can be generated with [ExDoc](https://github.com/elixir-lang/ex_doc) +and published on [HexDocs](https://hexdocs.pm). Once published, the docs can +be found at . + diff --git a/spawn_activators/activator_rabbitmq/config.json b/spawn_activators/activator_rabbitmq/config.json new file mode 100644 index 000000000..d86209c07 --- /dev/null +++ b/spawn_activators/activator_rabbitmq/config.json @@ -0,0 +1,50 @@ +{ + "settings": { + "actorConcurrency": 1, + "sourceConcurrency": 1, + "maxDemand": 2, + "prefetchCount": 50, + "useRateLimiting": true, + "rateLimitingInterval": 1, + "rateLimitingAllowedMessages": 100 + }, + "bindings": [ + { + "name": "test-queue-config", + "source": { + "queue": "test.queue" + }, + "sinks": [ + { + "name": "robert-sink", + "to": { + "actor": { + "type": "unnamed", + "action": "setLanguage", + "actorId": { + "parent": "abs_actor", + "systemFrom": { + "strategy": { + "type": "metadata", // or fixed + "value": "app_id" + }, + "suffixWith": "-actor", + "prefixWith": "some-" + }, + "nameFrom": { + "strategy": { + "type": "metadata", // or fixed + "value": "user_id" // valid entries are: [app_id, user_id, message_id, correlation_id, or any key present on amqp headers key] + }, + "suffixWith": "-actor", + "prefixWith": "some-" + } + } + } + } + } + ] + }, + {} + ] +} \ No newline at end of file diff --git a/spawn_activators/activator_rabbitmq/deployment.yaml b/spawn_activators/activator_rabbitmq/deployment.yaml new file mode 100644 index 000000000..f3a2b98ba --- /dev/null +++ b/spawn_activators/activator_rabbitmq/deployment.yaml @@ -0,0 +1,103 @@ +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: rabbitmq-activator + namespace: default + labels: + k8s-app: rabbitmq-activator +spec: + replicas: 1 + selector: + matchLabels: + k8s-app: rabbitmq-activator + template: + metadata: + labels: + k8s-app: rabbitmq-activator + spec: + containers: + - image: eigr/spawn-activator-rabbitmq:2.0.0-RC9 + name: spawn-activator + env: + - name: MIX_ENV + value: prod + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: POD_IP + valueFrom: + fieldRef: + fieldPath: status.podIP + - name: SPAWN_INTERNAL_NATS_AUTH_USER + valueFrom: + secretKeyRef: + name: invocation-connection-ref + key: username + - name: SPAWN_INTERNAL_NATS_AUTH_PASS + valueFrom: + secretKeyRef: + name: invocation-connection-ref + key: password + - name: SPAWN_INTERNAL_NATS_HOSTS + valueFrom: + secretKeyRef: + name: invocation-connection-ref + key: url + - name: SPAWN_INTERNAL_NATS_AUTH + valueFrom: + secretKeyRef: + name: invocation-connection-ref + key: authEnabled + optional: true + - name: SPAWN_INTERNAL_NATS_TLS + valueFrom: + secretKeyRef: + name: invocation-connection-ref + key: tlsEnabled + optional: true + - name: SPAWN_ACTIVATOR_SOURCE_PROVIDER_HOST + value: broker.default.svc.cluster.local + - name: SPAWN_ACTIVATOR_SOURCE_PROVIDER_PORT + value: "5672" + - name: SPAWN_ACTIVATOR_SOURCE_PROVIDER_AUTH_USER + valueFrom: + secretKeyRef: + name: rabbitmq-connection-secret + key: username + - name: SPAWN_ACTIVATOR_SOURCE_PROVIDER_AUTH_PASS + valueFrom: + secretKeyRef: + name: rabbitmq-connection-secret + key: password + envFrom: + - secretRef: + name: system-secret + resources: + limits: + cpu: 1 + requests: + cpu: 50m + memory: 128Mi + volumeMounts: + - name: settings-volume + mountPath: /opt/activator/data/config.json + subPath: config.json + - mountPath: /app/.cache/bakeware/ + name: bakeware-cache + securityContext: + allowPrivilegeEscalation: false + readOnlyRootFilesystem: true + runAsNonRoot: true + runAsUser: 65534 + volumes: + - name: settings-cm + configMap: + name: rabbitmq-activator-settings-cm + - name: bakeware-cache + emptyDir: {} diff --git a/spawn_activators/activator_rabbitmq/lib/activator_rabbitmq.ex b/spawn_activators/activator_rabbitmq/lib/activator_rabbitmq.ex new file mode 100644 index 000000000..982dbc8a4 --- /dev/null +++ b/spawn_activators/activator_rabbitmq/lib/activator_rabbitmq.ex @@ -0,0 +1,5 @@ +defmodule ActivatorRabbitMQ do + @moduledoc """ + Documentation for `ActivatorRabbitMQ`. + """ +end diff --git a/spawn_activators/activator_rabbitmq/lib/activator_rabbitmq/application.ex b/spawn_activators/activator_rabbitmq/lib/activator_rabbitmq/application.ex new file mode 100644 index 000000000..0f220ef72 --- /dev/null +++ b/spawn_activators/activator_rabbitmq/lib/activator_rabbitmq/application.ex @@ -0,0 +1,21 @@ +defmodule ActivatorRabbitMQ.Application do + @moduledoc false + + use Application + require Logger + + alias Actors.Config.PersistentTermConfig, as: Config + alias ActivatorRabbitmq.Supervisor, as: RabbitMQConsumerSupervisor + + @impl true + def start(_type, _args) do + Config.load() + + children = [ + {RabbitMQConsumerSupervisor, []} + ] + + opts = [strategy: :one_for_one, name: ActivatorRabbitMQ.Supervisor] + Supervisor.start_link(children, opts) + end +end diff --git a/spawn_activators/activator_rabbitmq/lib/activator_rabbitmq/router/router.ex b/spawn_activators/activator_rabbitmq/lib/activator_rabbitmq/router/router.ex new file mode 100644 index 000000000..5ec02a8d7 --- /dev/null +++ b/spawn_activators/activator_rabbitmq/lib/activator_rabbitmq/router/router.ex @@ -0,0 +1,15 @@ +defmodule ActivatorRabbitMQ.Router do + use Plug.Router + + plug(Plug.Logger) + + plug(:match) + plug(Plug.Parsers, parsers: [:json], json_decoder: Jason) + plug(:dispatch) + + forward("/health", to: ActivatorRabbitMQ.Routes.Health) + + match _ do + send_resp(conn, 404, "Not found!") + end +end diff --git a/spawn_activators/activator_rabbitmq/lib/activator_rabbitmq/routes/base.ex b/spawn_activators/activator_rabbitmq/lib/activator_rabbitmq/routes/base.ex new file mode 100644 index 000000000..f9a576b7b --- /dev/null +++ b/spawn_activators/activator_rabbitmq/lib/activator_rabbitmq/routes/base.ex @@ -0,0 +1,40 @@ +defmodule ActivatorRabbitMQ.Routes.Base do + defmacro __using__([]) do + quote do + use Plug.Router + + plug(Plug.Logger) + + plug(:match) + + plug(Plug.Parsers, + parsers: [:json], + json_decoder: Jason + ) + + plug(:dispatch) + + def send!(conn, code, data, content_type) + when is_integer(code) and content_type == "application/json" do + conn + |> Plug.Conn.put_resp_content_type(content_type) + |> send_resp(code, Jason.encode!(data)) + end + + def send!(conn, code, data, content_type) when is_atom(code) do + code = + case code do + :ok -> 200 + :not_found -> 404 + :malformed_data -> 400 + :non_authenticated -> 401 + :forbidden_access -> 403 + :server_error -> 500 + :error -> 504 + end + + send!(conn, code, data, content_type) + end + end + end +end diff --git a/spawn_activators/activator_rabbitmq/lib/activator_rabbitmq/routes/health.ex b/spawn_activators/activator_rabbitmq/lib/activator_rabbitmq/routes/health.ex new file mode 100644 index 000000000..a5b14526b --- /dev/null +++ b/spawn_activators/activator_rabbitmq/lib/activator_rabbitmq/routes/health.ex @@ -0,0 +1,17 @@ +defmodule ActivatorRabbitMQ.Routes.Health do + use ActivatorRabbitMQ.Routes.Base + + @content_type "application/json" + + get "/" do + send!(conn, 200, %{status: "up"}, @content_type) + end + + get "/liveness" do + send!(conn, 200, %{status: "up"}, @content_type) + end + + get "/readiness" do + send!(conn, 200, %{status: "up"}, @content_type) + end +end diff --git a/spawn_activators/activator_rabbitmq/lib/activator_rabbitmq/sources/rabbitmq.ex b/spawn_activators/activator_rabbitmq/lib/activator_rabbitmq/sources/rabbitmq.ex new file mode 100644 index 000000000..752f2b3d3 --- /dev/null +++ b/spawn_activators/activator_rabbitmq/lib/activator_rabbitmq/sources/rabbitmq.ex @@ -0,0 +1,110 @@ +defmodule ActivatorRabbitMQ.Sources.RabbitMQ do + @moduledoc """ + RabbitMQ Broadway Producer + """ + use Broadway + require Logger + + alias Activator.Dispatcher.DefaultDispatcher, as: Dispatcher + + alias Broadway.Message + + @spec start_link(keyword) :: :ignore | {:error, any} | {:ok, pid} + def start_link(opts), do: start_source(opts) + + @impl true + def handle_message(_, message, context) do + message + |> Message.update_data(fn data -> + Dispatcher.dispatch(data, context) + end) + rescue + e -> + msg = + Message.update_data(message, fn data -> + {e, __STACKTRACE__, data} + end) + + Message.put_batcher(msg, :dispatch_err) + end + + def handle_batch(:dispatch_err, messages, _, _) do + Enum.map(messages, fn msg -> + {error, stack_trace, data} = msg.data + + Logger.error( + "Error on process Message: #{inspect(data)}. Error #{Exception.format(:error, error, stack_trace)}" + ) + end) + + messages + end + + defp start_source(opts) do + encoder = Keyword.get(opts, :encoder, Activator.Encoder.CloudEvent) + actor_concurrency = Keyword.get(opts, :actor_concurrency, 1) + actor_system = Keyword.fetch!(opts, :actor_system) + kind = Keyword.fetch!(opts, :kind) + + Broadway.start_link(__MODULE__, + name: __MODULE__, + context: [ + encoder: encoder, + system: actor_system, + kind: kind + ], + producer: get_producer_settings(opts), + processors: [ + default: [ + concurrency: actor_concurrency + ] + ], + batchers: [ + dispatch_err: [batch_size: 10, concurrency: 2, batch_timeout: 1500] + ] + ) + end + + defp get_producer_settings(opts) do + queue = Keyword.fetch!(opts, :source_queue) + username = Keyword.fetch!(opts, :username) + password = Keyword.fetch!(opts, :password) + provider_host = Keyword.get(opts, :provider_host, "localhost") + provider_port = Keyword.get(opts, :provider_port, 5672) + source_concurrency = Keyword.get(opts, :source_concurrency, 1) + qos_prefetch_count = Keyword.get(opts, :prefetch_count, 50) + + producer = [ + module: + {BroadwayRabbitMQ.Producer, + queue: queue, + connection: [ + host: provider_host, + port: provider_port, + username: username, + password: password + ], + on_failure: :reject_and_requeue_once, + qos: [ + prefetch_count: qos_prefetch_count + ]}, + concurrency: source_concurrency + ] + + case Keyword.get(producer, :use_rate_limiting, false) do + true -> + interval = Keyword.get(opts, :rate_limiting_interval, 1_000) + allowed_messages = Keyword.fetch!(opts, :rate_limiting_allowed_messages) + + rate_limiting = [ + interval: interval, + allowed_messages: allowed_messages + ] + + Keyword.merge(producer, rate_limiting) + + false -> + producer + end + end +end diff --git a/spawn_activators/activator_rabbitmq/lib/activator_rabbitmq/sources/source_supervisor.ex b/spawn_activators/activator_rabbitmq/lib/activator_rabbitmq/sources/source_supervisor.ex new file mode 100644 index 000000000..0868e7a12 --- /dev/null +++ b/spawn_activators/activator_rabbitmq/lib/activator_rabbitmq/sources/source_supervisor.ex @@ -0,0 +1,49 @@ +defmodule ActivatorRabbitmq.Sources.SourceSupervisor do + use Supervisor + + def child_spec(config) do + name = Map.get(config, :name, "rabbitmq-activator-#{inspect(:rand.uniform(1000))}") + + %{ + id: Atom.to_string(name), + start: {__MODULE__, :start_link, [config]} + } + end + + def start_link(config) do + name = Map.get(config, :name, "rabbitmq-activator-#{inspect(:rand.uniform(1000))}") + Supervisor.start_link(__MODULE__, config, name: Atom.to_string(name)) + end + + @impl true + def init(config) do + children = [ + {ActivatorRabbitMQ.Sources.RabbitMQ, [config]} + ] + + Supervisor.init(children, strategy: :one_for_one) + end + + defp make_opts(_config) do + [ + encoder: Activator.Encoder.CloudEvent, + actor_system: "spawn-system", + actor_concurrency: 1, + username: "guest", + password: "guest", + source_queue: "test", + source_concurrency: 1, + prefetch_count: 50, + provider_host: "localhost", + provider_port: 5672, + provider_url: nil, + use_rate_limiting: true, + rate_limiting_interval: 1, + rate_limiting_allowed_messages: 100, + targets: [ + # %{actor: "joe", action: "setLanguage"}, + %{actor: "robert", action: "setLanguage"} + ] + ] + end +end diff --git a/spawn_activators/activator_rabbitmq/lib/activator_rabbitmq/supervisor.ex b/spawn_activators/activator_rabbitmq/lib/activator_rabbitmq/supervisor.ex new file mode 100644 index 000000000..17550710e --- /dev/null +++ b/spawn_activators/activator_rabbitmq/lib/activator_rabbitmq/supervisor.ex @@ -0,0 +1,48 @@ +defmodule ActivatorRabbitmq.Supervisor do + use Supervisor + + import Activator, only: [get_http_port: 1, read_config_from_file: 1] + import Spawn.Utils.Common, only: [supervisor_process_logger: 1] + + @impl true + def init(opts) do + config_file_path = Keyword.get(opts, :config_file_path, "/opt/activator/data/config.json") + + listeners = + case read_config_from_file(config_file_path) do + {:ok, configs} when is_list(configs) -> + Enum.map(configs, fn cfg -> build_listener(cfg, opts) end) + + {:error, reason} -> + Logger.warning( + "Unable to initialize any listener. Failed to read configuration. Details #{inspect(reason)}" + ) + + raise ErlangError, + "Unable to initialize any listener. Failed to read configuration. Details #{inspect(reason)}" + end + + children = + [ + supervisor_process_logger(__MODULE__), + Activator.Supervisor.child_spec(opts), + {Bandit, plug: ActivatorRabbitMQ.Router, scheme: :http, port: get_http_port()} + ] ++ listeners + + Supervisor.init(children, strategy: :one_for_one) + end + + defp build_listener(cfg, opts) do + config = Map.merge(cfg, Enum.into(opts, %{})) + ActivatorRabbitmq.Sources.SourceSupervisor.child_spec(config) + end + + def start_link(opts) do + Supervisor.start_link( + __MODULE__, + opts, + shutdown: 120_000, + strategy: :one_for_one + ) + end +end diff --git a/spawn_activators/activator_rabbitmq/mix.exs b/spawn_activators/activator_rabbitmq/mix.exs new file mode 100644 index 000000000..14232e4c9 --- /dev/null +++ b/spawn_activators/activator_rabbitmq/mix.exs @@ -0,0 +1,55 @@ +defmodule ActivatorRabbitMQ.MixProject do + use Mix.Project + + @app :activator_rabbitmq + @version "0.0.0-local.dev" + + def project do + [ + app: @app, + version: @version, + build_path: "../activator/_build", + config_path: "../../config/config.exs", + deps_path: "../activator/deps", + lockfile: "../activator/mix.lock", + elixir: "~> 1.15", + start_permanent: Mix.env() == :prod, + deps: deps(), + releases: releases() + ] + end + + # Run "mix help compile.app" to learn about applications. + def application do + [ + extra_applications: [:logger], + mod: {ActivatorRabbitMQ.Application, []} + ] + end + + # Run "mix help deps" to learn about dependencies. + defp deps do + [ + {:activator, path: "../activator"}, + {:spawn, path: "../../"}, + {:bakeware, "~> 0.2"}, + {:bandit, "~> 1.5"}, + {:broadway_rabbitmq, "~> 0.7"}, + {:nimble_options, "~> 0.5.2", override: true} + ] + end + + defp releases do + [ + activator_rabbitmq: [ + include_executables_for: [:unix], + applications: [activator_rabbitmq: :permanent], + steps: [ + :assemble, + &Bakeware.assemble/1 + ], + bakeware: [compression_level: 19] + ] + ] + end +end diff --git a/spawn_activators/activator_rabbitmq/settings-cm.yaml b/spawn_activators/activator_rabbitmq/settings-cm.yaml new file mode 100644 index 000000000..f83a43418 --- /dev/null +++ b/spawn_activators/activator_rabbitmq/settings-cm.yaml @@ -0,0 +1,56 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: rabbitmq-activator-settings-cm + namespace: default +data: + config.json: |- + { + "settings": { + "actorConcurrency": 1, + "sourceConcurrency": 1, + "maxDemand": 2, + "prefetchCount": 50, + "useRateLimiting": true, + "rateLimitingInterval": 1, + "rateLimitingAllowedMessages": 100 + }, + "bindings": [ + { + "name": "test-queue-config", + "source": { + "queue": "test.queue" + }, + "sinks": [ + { + "name": "robert-sink", + "to": { + "actor": { + "type": "unnamed", + "action": "setLanguage", + "actorId": { + "parent": "abs_actor", + "systemFrom": { + "strategy": { + "type": "metadata", // or fixed + "value": "app_id" + }, + "prefixWith": "some-" + "suffixWith": "-system", + }, + "nameFrom": { + "strategy": { + "type": "metadata", // or fixed + "value": "user_id" // valid entries are: [app_id, user_id, message_id, correlation_id, or any key present on amqp headers key] + }, + "prefixWith": "some-" + "suffixWith": "-actor", + } + } + } + } + } + ] + } + ] + } diff --git a/spawn_activators/activator_rabbitmq/test/activator_rabbitmq_test.exs b/spawn_activators/activator_rabbitmq/test/activator_rabbitmq_test.exs new file mode 100644 index 000000000..9a7c4bcb3 --- /dev/null +++ b/spawn_activators/activator_rabbitmq/test/activator_rabbitmq_test.exs @@ -0,0 +1,4 @@ +defmodule ActivatorRabbitMQTest do + use ExUnit.Case + doctest ActivatorRabbitMQ +end diff --git a/spawn_activators/activator_rabbitmq/test/test_helper.exs b/spawn_activators/activator_rabbitmq/test/test_helper.exs new file mode 100644 index 000000000..869559e70 --- /dev/null +++ b/spawn_activators/activator_rabbitmq/test/test_helper.exs @@ -0,0 +1 @@ +ExUnit.start() diff --git a/spawn_activators/activator_simple/Cargo.lock b/spawn_activators/activator_simple/Cargo.lock new file mode 100644 index 000000000..081307709 --- /dev/null +++ b/spawn_activators/activator_simple/Cargo.lock @@ -0,0 +1,1549 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +dependencies = [ + "async-nats", + "prost", + "prost-types", + "protobuf-codegen", + "tokio", +] +name = "activator-simple" +version = "0.1.0" + +[[package]] +checksum = "67fc08ce920c31afb70f013dcce1bfc3a3195de6a228474e45e1f145b36f8d04" +dependencies = [ + "memchr", +] +name = "aho-corasick" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "1.0.1" + +[[package]] +checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8" +name = "anyhow" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "1.0.71" + +[[package]] +checksum = "8257238e2a3629ee5618502a75d1b91f8017c24638c75349fc8d2d80cf1f7c4c" +dependencies = [ + "base64", + "bytes", + "futures", + "http", + "itoa", + "memchr", + "nkeys", + "nuid", + "once_cell", + "rand", + "regex", + "ring", + "rustls-native-certs", + "rustls-pemfile", + "rustls-webpki", + "serde", + "serde_json", + "serde_nanos", + "serde_repr", + "thiserror", + "time", + "tokio", + "tokio-retry", + "tokio-rustls", + "tracing", + "url", +] +name = "async-nats" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "0.31.0" + +[[package]] +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +name = "autocfg" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "1.1.1" + +[[package]] +checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a" +name = "base64" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "0.21.0" + +[[package]] +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" +name = "base64ct" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "1.6.0" + +[[package]] +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +name = "bitflags" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "1.3.2" + +[[package]] +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] +name = "block-buffer" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "0.10.4" + +[[package]] +checksum = "9b1ce199063694f33ffb7dd4e0ee620741495c32833cde5aa08f02a0bf96f0c8" +name = "bumpalo" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "3.12.1" + +[[package]] +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" +name = "byteorder" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "1.4.3" + +[[package]] +checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" +dependencies = [ + "serde", +] +name = "bytes" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "1.4.0" + +[[package]] +checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" +name = "cc" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "1.0.79" + +[[package]] +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +name = "cfg-if" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "1.0.0" + +[[package]] +checksum = "28c122c3980598d243d63d9a704629a2d748d101f278052ff068be5a4423ab6f" +name = "const-oid" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "0.9.5" + +[[package]] +checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" +dependencies = [ + "core-foundation-sys", + "libc", +] +name = "core-foundation" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "0.9.3" + +[[package]] +checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" +name = "core-foundation-sys" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "0.8.4" + +[[package]] +checksum = "3e4c1eaa2012c47becbbad2ab175484c2a84d1185b566fb2cc5b8707343dfe58" +dependencies = [ + "libc", +] +name = "cpufeatures" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "0.2.7" + +[[package]] +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] +name = "crypto-common" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "0.1.6" + +[[package]] +checksum = "f711ade317dd348950a9910f81c5947e3d8907ebd2b83f76203ff1807e6a2bc2" +dependencies = [ + "cfg-if", + "cpufeatures", + "curve25519-dalek-derive", + "digest", + "fiat-crypto", + "platforms", + "rustc_version", + "subtle", +] +name = "curve25519-dalek" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "4.0.0" + +[[package]] +checksum = "83fdaf97f4804dcebfa5862639bc9ce4121e82140bec2a987ac5140294865b5b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.15", +] +name = "curve25519-dalek-derive" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "0.1.0" + +[[package]] +checksum = "23d8666cb01533c39dde32bcbab8e227b4ed6679b2c925eba05feabea39508fb" +name = "data-encoding" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "2.3.3" + +[[package]] +checksum = "fffa369a668c8af7dbf8b5e56c9f744fbd399949ed171606040001947de40b1c" +dependencies = [ + "const-oid", + "pem-rfc7468", + "zeroize", +] +name = "der" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "0.7.8" + +[[package]] +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] +name = "digest" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "0.10.7" + +[[package]] +checksum = "60f6d271ca33075c88028be6f04d502853d63a5ece419d269c15315d4fc1cf1d" +dependencies = [ + "signature", +] +name = "ed25519" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "2.2.2" + +[[package]] +checksum = "7277392b266383ef8396db7fdeb1e77b6c52fed775f5df15bb24f35b72156980" +dependencies = [ + "curve25519-dalek", + "ed25519", + "sha2", + "signature", +] +name = "ed25519-dalek" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "2.0.0" + +[[package]] +checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" +name = "either" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "1.8.1" + +[[package]] +checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" +dependencies = [ + "errno-dragonfly", + "libc", + "windows-sys 0.48.0", +] +name = "errno" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "0.3.1" + +[[package]] +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] +name = "errno-dragonfly" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "0.1.2" + +[[package]] +checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" +dependencies = [ + "instant", +] +name = "fastrand" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "1.9.0" + +[[package]] +checksum = "e825f6987101665dea6ec934c09ec6d721de7bc1bf92248e1d5810c8cd636b77" +name = "fiat-crypto" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "0.1.20" + +[[package]] +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +name = "fnv" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "1.0.7" + +[[package]] +checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" +dependencies = [ + "percent-encoding", +] +name = "form_urlencoded" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "1.1.0" + +[[package]] +checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] +name = "futures" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "0.3.28" + +[[package]] +checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" +dependencies = [ + "futures-core", + "futures-sink", +] +name = "futures-channel" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "0.3.28" + +[[package]] +checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" +name = "futures-core" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "0.3.28" + +[[package]] +checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" +name = "futures-io" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "0.3.28" + +[[package]] +checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.15", +] +name = "futures-macro" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "0.3.28" + +[[package]] +checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" +name = "futures-sink" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "0.3.28" + +[[package]] +checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" +name = "futures-task" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "0.3.28" + +[[package]] +checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] +name = "futures-util" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "0.3.28" + +[[package]] +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] +name = "generic-array" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "0.14.7" + +[[package]] +checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] +name = "getrandom" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "0.2.9" + +[[package]] +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +name = "hashbrown" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "0.12.3" + +[[package]] +checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" +name = "hermit-abi" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "0.3.1" + +[[package]] +checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" +dependencies = [ + "bytes", + "fnv", + "itoa", +] +name = "http" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "0.2.9" + +[[package]] +checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] +name = "idna" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "0.3.0" + +[[package]] +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown", +] +name = "indexmap" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "1.9.3" + +[[package]] +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if", +] +name = "instant" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "0.1.12" + +[[package]] +checksum = "9c66c74d2ae7e79a5a8f7ac924adbe38ee42a859c6539ad869eb51f0b52dc220" +dependencies = [ + "hermit-abi", + "libc", + "windows-sys 0.48.0", +] +name = "io-lifetimes" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "1.0.10" + +[[package]] +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] +name = "itertools" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "0.10.5" + +[[package]] +checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" +name = "itoa" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "1.0.6" + +[[package]] +checksum = "445dde2150c55e483f3d8416706b97ec8e8237c307e5b7b4b8dd15e6af2a0730" +dependencies = [ + "wasm-bindgen", +] +name = "js-sys" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "0.3.61" + +[[package]] +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +name = "lazy_static" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "1.4.0" + +[[package]] +checksum = "6a987beff54b60ffa6d51982e1aa1146bc42f19bd26be28b0586f252fccf5317" +name = "libc" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "0.2.142" + +[[package]] +checksum = "ece97ea872ece730aed82664c424eb4c8291e1ff2480247ccf7409044bc6479f" +name = "linux-raw-sys" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "0.3.7" + +[[package]] +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +dependencies = [ + "cfg-if", +] +name = "log" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "0.4.17" + +[[package]] +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +name = "memchr" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "2.5.0" + +[[package]] +checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9" +dependencies = [ + "libc", + "log", + "wasi", + "windows-sys 0.45.0", +] +name = "mio" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "0.8.6" + +[[package]] +checksum = "aad178aad32087b19042ee36dfd450b73f5f934fbfb058b59b198684dfec4c47" +dependencies = [ + "byteorder", + "data-encoding", + "ed25519", + "ed25519-dalek", + "getrandom", + "log", + "rand", + "signatory", +] +name = "nkeys" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "0.3.2" + +[[package]] +checksum = "20c1bb65186718d348306bf1afdeb20d9ab45b2ab80fb793c0fdcf59ffbb4f38" +dependencies = [ + "lazy_static", + "rand", +] +name = "nuid" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "0.3.2" + +[[package]] +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +dependencies = [ + "hermit-abi", + "libc", +] +name = "num_cpus" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "1.16.0" + +[[package]] +checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" +name = "once_cell" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "1.17.1" + +[[package]] +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" +name = "openssl-probe" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "0.1.5" + +[[package]] +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] +name = "pem-rfc7468" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "0.7.0" + +[[package]] +checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" +name = "percent-encoding" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "2.2.0" + +[[package]] +checksum = "ad29a609b6bcd67fee905812e544992d216af9d755757c05ed2d0e15a74c6ecc" +dependencies = [ + "pin-project-internal", +] +name = "pin-project" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "1.0.12" + +[[package]] +checksum = "069bdb1e05adc7a8990dce9cc75370895fbe4e3d58b9b73bf1aee56359344a55" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] +name = "pin-project-internal" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "1.0.12" + +[[package]] +checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" +name = "pin-project-lite" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "0.2.9" + +[[package]] +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +name = "pin-utils" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "0.1.0" + +[[package]] +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] +name = "pkcs8" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "0.10.2" + +[[package]] +checksum = "e3d7ddaed09e0eb771a79ab0fd64609ba0afb0a8366421957936ad14cbd13630" +name = "platforms" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "3.0.2" + +[[package]] +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +name = "ppv-lite86" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "0.2.17" + +[[package]] +checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435" +dependencies = [ + "unicode-ident", +] +name = "proc-macro2" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "1.0.56" + +[[package]] +checksum = "0b82eaa1d779e9a4bc1c3217db8ffbeabaae1dca241bf70183242128d48681cd" +dependencies = [ + "bytes", + "prost-derive", +] +name = "prost" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "0.11.9" + +[[package]] +checksum = "e5d2d8d10f3c6ded6da8b05b5fb3b8a5082514344d56c9f871412d29b4e075b4" +dependencies = [ + "anyhow", + "itertools", + "proc-macro2", + "quote", + "syn 1.0.109", +] +name = "prost-derive" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "0.11.9" + +[[package]] +checksum = "213622a1460818959ac1181aaeb2dc9c7f63df720db7d788b3e24eacd1983e13" +dependencies = [ + "prost", +] +name = "prost-types" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "0.11.9" + +[[package]] +checksum = "b55bad9126f378a853655831eb7363b7b01b81d19f8cb1218861086ca4a1a61e" +dependencies = [ + "once_cell", + "protobuf-support", + "thiserror", +] +name = "protobuf" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "3.2.0" + +[[package]] +checksum = "0dd418ac3c91caa4032d37cb80ff0d44e2ebe637b2fb243b6234bf89cdac4901" +dependencies = [ + "anyhow", + "once_cell", + "protobuf", + "protobuf-parse", + "regex", + "tempfile", + "thiserror", +] +name = "protobuf-codegen" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "3.2.0" + +[[package]] +checksum = "9d39b14605eaa1f6a340aec7f320b34064feb26c93aec35d6a9a2272a8ddfa49" +dependencies = [ + "anyhow", + "indexmap", + "log", + "protobuf", + "protobuf-support", + "tempfile", + "thiserror", + "which", +] +name = "protobuf-parse" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "3.2.0" + +[[package]] +checksum = "a5d4d7b8601c814cfb36bcebb79f0e61e45e1e93640cf778837833bbed05c372" +dependencies = [ + "thiserror", +] +name = "protobuf-support" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "3.2.0" + +[[package]] +checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc" +dependencies = [ + "proc-macro2", +] +name = "quote" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "1.0.26" + +[[package]] +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] +name = "rand" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "0.8.5" + +[[package]] +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] +name = "rand_chacha" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "0.3.1" + +[[package]] +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] +name = "rand_core" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "0.6.4" + +[[package]] +checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +dependencies = [ + "bitflags", +] +name = "redox_syscall" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "0.3.5" + +[[package]] +checksum = "af83e617f331cc6ae2da5443c602dfa5af81e517212d9d611a5b3ba1777b5370" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] +name = "regex" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "1.8.1" + +[[package]] +checksum = "a5996294f19bd3aae0453a862ad728f60e6600695733dd5df01da90c54363a3c" +name = "regex-syntax" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "0.7.1" + +[[package]] +checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" +dependencies = [ + "cc", + "libc", + "once_cell", + "spin", + "untrusted", + "web-sys", + "winapi", +] +name = "ring" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "0.16.20" + +[[package]] +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] +name = "rustc_version" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "0.4.0" + +[[package]] +checksum = "acf8729d8542766f1b2cf77eb034d52f40d375bb8b615d0b147089946e16613d" +dependencies = [ + "bitflags", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys", + "windows-sys 0.48.0", +] +name = "rustix" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "0.37.19" + +[[package]] +checksum = "1d1feddffcfcc0b33f5c6ce9a29e341e4cd59c3f78e7ee45f4a40c038b1d6cbb" +dependencies = [ + "log", + "ring", + "rustls-webpki", + "sct", +] +name = "rustls" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "0.21.6" + +[[package]] +checksum = "0167bac7a9f490495f3c33013e7722b53cb087ecbe082fb0c6387c96f634ea50" +dependencies = [ + "openssl-probe", + "rustls-pemfile", + "schannel", + "security-framework", +] +name = "rustls-native-certs" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "0.6.2" + +[[package]] +checksum = "d194b56d58803a43635bdc398cd17e383d6f71f9182b9a192c127ca42494a59b" +dependencies = [ + "base64", +] +name = "rustls-pemfile" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "1.0.2" + +[[package]] +checksum = "7d93931baf2d282fff8d3a532bbfd7653f734643161b87e3e01e59a04439bf0d" +dependencies = [ + "ring", + "untrusted", +] +name = "rustls-webpki" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "0.101.4" + +[[package]] +checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" +name = "ryu" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "1.0.13" + +[[package]] +checksum = "713cfb06c7059f3588fb8044c0fad1d09e3c01d225e25b9220dbfdcf16dbb1b3" +dependencies = [ + "windows-sys 0.42.0", +] +name = "schannel" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "0.1.21" + +[[package]] +checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" +dependencies = [ + "ring", + "untrusted", +] +name = "sct" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "0.7.0" + +[[package]] +checksum = "a332be01508d814fed64bf28f798a146d73792121129962fdf335bb3c49a4254" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] +name = "security-framework" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "2.8.2" + +[[package]] +checksum = "31c9bb296072e961fcbd8853511dd39c2d8be2deb1e17c6860b1d30732b323b4" +dependencies = [ + "core-foundation-sys", + "libc", +] +name = "security-framework-sys" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "2.8.0" + +[[package]] +checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918" +name = "semver" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "1.0.18" + +[[package]] +checksum = "71b2f6e1ab5c2b98c05f0f35b236b22e8df7ead6ffbf51d7808da7f8817e7ab6" +dependencies = [ + "serde_derive", +] +name = "serde" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "1.0.162" + +[[package]] +checksum = "a2a0814352fd64b58489904a44ea8d90cb1a91dcb6b4f5ebabc32c8318e93cb6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.15", +] +name = "serde_derive" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "1.0.162" + +[[package]] +checksum = "057d394a50403bcac12672b2b18fb387ab6d289d957dab67dd201875391e52f1" +dependencies = [ + "itoa", + "ryu", + "serde", +] +name = "serde_json" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "1.0.96" + +[[package]] +checksum = "8ae801b7733ca8d6a2b580debe99f67f36826a0f5b8a36055dc6bc40f8d6bc71" +dependencies = [ + "serde", +] +name = "serde_nanos" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "0.1.3" + +[[package]] +checksum = "bcec881020c684085e55a25f7fd888954d56609ef363479dc5a1305eb0d40cab" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.15", +] +name = "serde_repr" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "0.1.12" + +[[package]] +checksum = "479fb9d862239e610720565ca91403019f2f00410f1864c5aa7479b950a76ed8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] +name = "sha2" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "0.10.7" + +[[package]] +checksum = "c1e303f8205714074f6068773f0e29527e0453937fe837c9717d066635b65f31" +dependencies = [ + "pkcs8", + "rand_core", + "signature", + "zeroize", +] +name = "signatory" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "0.27.1" + +[[package]] +checksum = "8fe458c98333f9c8152221191a77e2a44e8325d0193484af2e9421a53019e57d" +dependencies = [ + "digest", +] +name = "signature" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "2.0.0" + +[[package]] +checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" +dependencies = [ + "autocfg", +] +name = "slab" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "0.4.8" + +[[package]] +checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" +dependencies = [ + "libc", + "winapi", +] +name = "socket2" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "0.4.9" + +[[package]] +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" +name = "spin" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "0.5.2" + +[[package]] +checksum = "9d1e996ef02c474957d681f1b05213dfb0abab947b446a62d37770b23500184a" +dependencies = [ + "base64ct", + "der", +] +name = "spki" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "0.7.2" + +[[package]] +checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" +name = "subtle" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "2.5.0" + +[[package]] +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] +name = "syn" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "1.0.109" + +[[package]] +checksum = "a34fcf3e8b60f57e6a14301a2e916d323af98b0ea63c599441eec8558660c822" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] +name = "syn" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "2.0.15" + +[[package]] +checksum = "b9fbec84f381d5795b08656e4912bec604d162bff9291d6189a78f4c8ab87998" +dependencies = [ + "cfg-if", + "fastrand", + "redox_syscall", + "rustix", + "windows-sys 0.45.0", +] +name = "tempfile" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "3.5.0" + +[[package]] +checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac" +dependencies = [ + "thiserror-impl", +] +name = "thiserror" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "1.0.40" + +[[package]] +checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.15", +] +name = "thiserror-impl" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "1.0.40" + +[[package]] +checksum = "8f3403384eaacbca9923fa06940178ac13e4edb725486d70e8e15881d0c836cc" +dependencies = [ + "itoa", + "serde", + "time-core", + "time-macros", +] +name = "time" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "0.3.21" + +[[package]] +checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb" +name = "time-core" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "0.1.1" + +[[package]] +checksum = "372950940a5f07bf38dbe211d7283c9e6d7327df53794992d293e534c733d09b" +dependencies = [ + "time-core", +] +name = "time-macros" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "0.2.9" + +[[package]] +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] +name = "tinyvec" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "1.6.0" + +[[package]] +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" +name = "tinyvec_macros" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "0.1.1" + +[[package]] +checksum = "c3c786bf8134e5a3a166db9b29ab8f48134739014a3eca7bc6bfa95d673b136f" +dependencies = [ + "autocfg", + "bytes", + "libc", + "mio", + "num_cpus", + "pin-project-lite", + "socket2", + "tokio-macros", + "windows-sys 0.48.0", +] +name = "tokio" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "1.28.0" + +[[package]] +checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.15", +] +name = "tokio-macros" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "2.1.0" + +[[package]] +checksum = "7f57eb36ecbe0fc510036adff84824dd3c24bb781e21bfa67b69d556aa85214f" +dependencies = [ + "pin-project", + "rand", + "tokio", +] +name = "tokio-retry" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "0.3.0" + +[[package]] +checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" +dependencies = [ + "rustls", + "tokio", +] +name = "tokio-rustls" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "0.24.1" + +[[package]] +checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" +dependencies = [ + "cfg-if", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] +name = "tracing" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "0.1.37" + +[[package]] +checksum = "0f57e3ca2a01450b1a921183a9c9cbfda207fd822cef4ccb00a65402cbba7a74" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.15", +] +name = "tracing-attributes" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "0.1.24" + +[[package]] +checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" +dependencies = [ + "once_cell", +] +name = "tracing-core" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "0.1.30" + +[[package]] +checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" +name = "typenum" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "1.16.0" + +[[package]] +checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" +name = "unicode-bidi" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "0.3.13" + +[[package]] +checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" +name = "unicode-ident" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "1.0.8" + +[[package]] +checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +dependencies = [ + "tinyvec", +] +name = "unicode-normalization" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "0.1.22" + +[[package]] +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" +name = "untrusted" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "0.7.1" + +[[package]] +checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] +name = "url" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "2.3.1" + +[[package]] +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +name = "version_check" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "0.9.4" + +[[package]] +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +name = "wasi" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "0.11.0+wasi-snapshot-preview1" + +[[package]] +checksum = "31f8dcbc21f30d9b8f2ea926ecb58f6b91192c17e9d33594b3df58b2007ca53b" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] +name = "wasm-bindgen" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "0.2.84" + +[[package]] +checksum = "95ce90fd5bcc06af55a641a86428ee4229e44e07033963a2290a8e241607ccb9" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn 1.0.109", + "wasm-bindgen-shared", +] +name = "wasm-bindgen-backend" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "0.2.84" + +[[package]] +checksum = "4c21f77c0bedc37fd5dc21f897894a5ca01e7bb159884559461862ae90c0b4c5" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] +name = "wasm-bindgen-macro" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "0.2.84" + +[[package]] +checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] +name = "wasm-bindgen-macro-support" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "0.2.84" + +[[package]] +checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d" +name = "wasm-bindgen-shared" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "0.2.84" + +[[package]] +checksum = "e33b99f4b23ba3eec1a53ac264e35a755f00e966e0065077d6027c0f575b0b97" +dependencies = [ + "js-sys", + "wasm-bindgen", +] +name = "web-sys" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "0.3.61" + +[[package]] +checksum = "2441c784c52b289a054b7201fc93253e288f094e2f4be9058343127c4226a269" +dependencies = [ + "either", + "libc", + "once_cell", +] +name = "which" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "4.4.0" + +[[package]] +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] +name = "winapi" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "0.3.9" + +[[package]] +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +name = "winapi-i686-pc-windows-gnu" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "0.4.0" + +[[package]] +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +name = "winapi-x86_64-pc-windows-gnu" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "0.4.0" + +[[package]] +checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] +name = "windows-sys" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "0.42.0" + +[[package]] +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] +name = "windows-sys" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "0.45.0" + +[[package]] +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.0", +] +name = "windows-sys" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "0.48.0" + +[[package]] +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] +name = "windows-targets" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "0.42.2" + +[[package]] +checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" +dependencies = [ + "windows_aarch64_gnullvm 0.48.0", + "windows_aarch64_msvc 0.48.0", + "windows_i686_gnu 0.48.0", + "windows_i686_msvc 0.48.0", + "windows_x86_64_gnu 0.48.0", + "windows_x86_64_gnullvm 0.48.0", + "windows_x86_64_msvc 0.48.0", +] +name = "windows-targets" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "0.48.0" + +[[package]] +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" +name = "windows_aarch64_gnullvm" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "0.42.2" + +[[package]] +checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" +name = "windows_aarch64_gnullvm" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "0.48.0" + +[[package]] +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" +name = "windows_aarch64_msvc" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "0.42.2" + +[[package]] +checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" +name = "windows_aarch64_msvc" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "0.48.0" + +[[package]] +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" +name = "windows_i686_gnu" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "0.42.2" + +[[package]] +checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" +name = "windows_i686_gnu" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "0.48.0" + +[[package]] +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" +name = "windows_i686_msvc" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "0.42.2" + +[[package]] +checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" +name = "windows_i686_msvc" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "0.48.0" + +[[package]] +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" +name = "windows_x86_64_gnu" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "0.42.2" + +[[package]] +checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" +name = "windows_x86_64_gnu" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "0.48.0" + +[[package]] +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" +name = "windows_x86_64_gnullvm" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "0.42.2" + +[[package]] +checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" +name = "windows_x86_64_gnullvm" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "0.48.0" + +[[package]] +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" +name = "windows_x86_64_msvc" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "0.42.2" + +[[package]] +checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" +name = "windows_x86_64_msvc" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "0.48.0" + +[[package]] +checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9" +name = "zeroize" +source = "registry+https://github.com/rust-lang/crates.io-index" +version = "1.6.0" diff --git a/spawn_activators/activator_simple/Cargo.toml b/spawn_activators/activator_simple/Cargo.toml new file mode 100644 index 000000000..57eef4b8b --- /dev/null +++ b/spawn_activators/activator_simple/Cargo.toml @@ -0,0 +1,13 @@ +[package] +edition = "2021" +name = "activator-simple" +version = "2.0.0-RC9" + +[build-dependencies] +protobuf-codegen = "3.0.0" + +[dependencies] +async-nats = "0.31.0" +prost = "0.11.9" +prost-types = "0.11.9" +tokio = {version = "1", features = ["macros", "rt-multi-thread"]} diff --git a/spawn_activators/activator_simple/README.md b/spawn_activators/activator_simple/README.md new file mode 100644 index 000000000..9918c15a1 --- /dev/null +++ b/spawn_activators/activator_simple/README.md @@ -0,0 +1,9 @@ +## Usage + +Its as simple as it gets + +```BASH +./activator-simple system actor_name action_name +``` + +This will invoke a async action to a specified actor \ No newline at end of file diff --git a/spawn_activators/activator_simple/src/main.rs b/spawn_activators/activator_simple/src/main.rs new file mode 100644 index 000000000..a3636fa35 --- /dev/null +++ b/spawn_activators/activator_simple/src/main.rs @@ -0,0 +1,64 @@ +use async_nats; +mod protocol_pb; + +use prost::bytes::Bytes; +use prost::Message; +use protocol_pb::{Actor, ActorId, ActorSystem, InvocationRequest}; +use std::env; + +#[tokio::main] +async fn main() -> Result<(), async_nats::Error> { + let [_, system, actor_name, action_name]: [String; 4] = env::args() + .collect::>() + .into_iter() + .take(4) + .collect::>() + .try_into() + .unwrap(); + + let host = env::var("SPAWN_INTERNAL_NATS_HOSTS").unwrap_or("nats://0.0.0.0:4222".to_string()); + + println!("Trying to connect to host {:?}", host); + + let nc = async_nats::connect(host).await?; + + let request: InvocationRequest = InvocationRequest { + actor: Some(Actor { + id: Some(ActorId { + name: actor_name, + system: system.clone(), + ..Default::default() + }), + ..Default::default() + }), + system: Some(ActorSystem { + name: system.clone(), + ..Default::default() + }), + r#async: true, + action_name, + ..Default::default() + }; + + let mut buffer = Vec::new(); + request.encode(&mut buffer).unwrap(); + + let bytes = Bytes::from(buffer); + + let topic: String = format!("spawn.{:}.actors.actions", system.clone()).into(); + println!("Topic -> {:?}", topic); + + let headers = async_nats::HeaderMap::new(); + + match nc.request_with_headers(topic, headers, bytes).await { + Ok(response) => { + println!("Requested -> {:?}", request); + println!("Got response -> {:?}", response.payload); + } + Err(err) => { + println!("Request Error -> {:?}", err); + } + } + + Ok(()) +} diff --git a/spawn_activators/activator_simple/src/protocol_pb.rs b/spawn_activators/activator_simple/src/protocol_pb.rs new file mode 100644 index 000000000..cac622ea9 --- /dev/null +++ b/spawn_activators/activator_simple/src/protocol_pb.rs @@ -0,0 +1,592 @@ +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct Registry { + #[prost(map = "string, message", tag = "1")] + pub actors: ::std::collections::HashMap<::prost::alloc::string::String, Actor>, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ActorSystem { + #[prost(string, tag = "1")] + pub name: ::prost::alloc::string::String, + #[prost(message, optional, tag = "2")] + pub registry: ::core::option::Option, +} +/// A strategy for save state. +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ActorSnapshotStrategy { + #[prost(oneof = "actor_snapshot_strategy::Strategy", tags = "1")] + pub strategy: ::core::option::Option, +} +/// Nested message and enum types in `ActorSnapshotStrategy`. +pub mod actor_snapshot_strategy { + #[allow(clippy::derive_partial_eq_without_eq)] + #[derive(Clone, PartialEq, ::prost::Oneof)] + pub enum Strategy { + /// the timeout strategy. + #[prost(message, tag = "1")] + Timeout(super::TimeoutStrategy), + } +} +/// A strategy which a user function's entity is passivated. +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ActorDeactivationStrategy { + #[prost(oneof = "actor_deactivation_strategy::Strategy", tags = "1")] + pub strategy: ::core::option::Option, +} +/// Nested message and enum types in `ActorDeactivationStrategy`. +pub mod actor_deactivation_strategy { + #[allow(clippy::derive_partial_eq_without_eq)] + #[derive(Clone, PartialEq, ::prost::Oneof)] + pub enum Strategy { + /// the timeout strategy. + #[prost(message, tag = "1")] + Timeout(super::TimeoutStrategy), + } +} +/// A strategy based on a timeout. +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct TimeoutStrategy { + /// The timeout in millis + #[prost(int64, tag = "1")] + pub timeout: i64, +} +/// A action represents an action that the user can perform on an Actor. +/// Commands in supporting languages are represented by functions or methods. +/// An Actor action has nothing to do with the semantics of Commands in a CQRS/EventSourced system. +/// It just represents an action that supporting languages can invoke. +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct Action { + /// The name of the function or method in the supporting language that has been registered in Ator. + #[prost(string, tag = "1")] + pub name: ::prost::alloc::string::String, +} +/// A FixedTimerAction is similar to a regular Action, its main differences are that it is scheduled to run at regular intervals +/// and only takes the actor's state as an argument. +/// Timer Commands are good for executing loops that manipulate the actor's own state. +/// In Elixir or other languages in BEAM it would be similar to invoking Process.send_after(self(), atom, msg, timeout) +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct FixedTimerAction { + /// The time to wait until the action is triggered + #[prost(int32, tag = "1")] + pub seconds: i32, + /// See Action description Above + #[prost(message, optional, tag = "2")] + pub action: ::core::option::Option, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ActorState { + #[prost(map = "string, string", tag = "1")] + pub tags: + ::std::collections::HashMap<::prost::alloc::string::String, ::prost::alloc::string::String>, + #[prost(message, optional, tag = "2")] + pub state: ::core::option::Option<::prost_types::Any>, +} +/// TODO doc here +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct Metadata { + /// A channel group represents a way to send actions to various actors + /// that belong to a certain semantic group. + #[prost(string, tag = "1")] + pub channel_group: ::prost::alloc::string::String, + #[prost(map = "string, string", tag = "2")] + pub tags: + ::std::collections::HashMap<::prost::alloc::string::String, ::prost::alloc::string::String>, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ActorSettings { + /// Indicates the type of Actor to be configured. + #[prost(enumeration = "Kind", tag = "1")] + pub kind: i32, + /// Indicates whether an actor's state should be persisted in a definitive store. + #[prost(bool, tag = "2")] + pub stateful: bool, + /// Snapshot strategy + #[prost(message, optional, tag = "3")] + pub snapshot_strategy: ::core::option::Option, + /// Deactivate strategy + #[prost(message, optional, tag = "4")] + pub deactivation_strategy: ::core::option::Option, + /// When kind is POOLED this is used to define minimun actor instances + #[prost(int32, tag = "5")] + pub min_pool_size: i32, + /// When kind is POOLED this is used to define maximum actor instances + #[prost(int32, tag = "6")] + pub max_pool_size: i32, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ActorId { + /// The name of a Actor Entity. + #[prost(string, tag = "1")] + pub name: ::prost::alloc::string::String, + /// Name of a ActorSystem + #[prost(string, tag = "2")] + pub system: ::prost::alloc::string::String, + /// When the Actor is of the Named type, + /// the name of the parent Actor must be informed here. + #[prost(string, tag = "3")] + pub parent: ::prost::alloc::string::String, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct Actor { + /// Actor Identification + #[prost(message, optional, tag = "1")] + pub id: ::core::option::Option, + /// A Actor state. + #[prost(message, optional, tag = "2")] + pub state: ::core::option::Option, + /// Actor metadata + #[prost(message, optional, tag = "6")] + pub metadata: ::core::option::Option, + /// Actor settings. + #[prost(message, optional, tag = "3")] + pub settings: ::core::option::Option, + /// The actions registered for an actor + #[prost(message, repeated, tag = "4")] + pub actions: ::prost::alloc::vec::Vec, + /// The registered timer actions for an actor. + #[prost(message, repeated, tag = "5")] + pub timer_actions: ::prost::alloc::vec::Vec, +} +/// The type that defines the runtime characteristics of the Actor. +/// Regardless of the type of actor it is important that +/// all actors are registered during the proxy and host initialization phase. +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] +#[repr(i32)] +pub enum Kind { + /// When no type is informed, the default to be assumed will be the Named pattern. + UnknowKind = 0, + /// NAMED actors are used to create children of this based actor at runtime + Named = 1, + /// UNNAMED actors as the name suggests have only one real instance of themselves running + /// during their entire lifecycle. That is, they are the opposite of the NAMED type Actors. + Unnamed = 2, + /// Pooled Actors are similar to Unnamed actors, but unlike them, + /// their identifying name will always be the one registered at the system initialization stage. + /// The great advantage of Pooled actors is that they have multiple instances of themselves + /// acting as a request service pool. + /// Pooled actors are also stateless actors, that is, they will not have their + /// in-memory state persisted via Statesstore. This is done to avoid problems + /// with the correctness of the stored state. + /// Pooled Actors are generally used for tasks where the Actor Model would perform worse + /// than other concurrency models and for tasks that do not require state concerns. + /// Integration flows, data caching, proxies are good examples of use cases + /// for this type of Actor. + Pooled = 3, + /// Reserved for future use + Proxy = 4, +} +impl Kind { + /// String value of the enum field names used in the ProtoBuf definition. + /// + /// The values are not transformed in any way and thus are considered stable + /// (if the ProtoBuf definition does not change) and safe for programmatic use. + pub fn as_str_name(&self) -> &'static str { + match self { + Kind::UnknowKind => "UNKNOW_KIND", + Kind::Named => "NAMED", + Kind::Unnamed => "UNNAMED", + Kind::Pooled => "POOLED", + Kind::Proxy => "PROXY", + } + } + /// Creates an enum from field names used in the ProtoBuf definition. + pub fn from_str_name(value: &str) -> ::core::option::Option { + match value { + "UNKNOW_KIND" => Some(Self::UnknowKind), + "NAMED" => Some(Self::Named), + "UNNAMED" => Some(Self::Unnamed), + "POOLED" => Some(Self::Pooled), + "PROXY" => Some(Self::Proxy), + _ => None, + } + } +} +/// Context is where current and/or updated state is stored +/// to be transmitted to/from proxy and user function +/// +/// Params: +/// * state: Actor state passed back and forth between proxy and user function. +/// * metadata: Meta information that comes in invocations +/// * tags: Meta information stored in the actor +/// * caller: ActorId of who is calling target actor +/// * self: ActorId of itself +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct Context { + #[prost(message, optional, tag = "1")] + pub state: ::core::option::Option<::prost_types::Any>, + #[prost(map = "string, string", tag = "4")] + pub metadata: + ::std::collections::HashMap<::prost::alloc::string::String, ::prost::alloc::string::String>, + #[prost(map = "string, string", tag = "5")] + pub tags: + ::std::collections::HashMap<::prost::alloc::string::String, ::prost::alloc::string::String>, + /// Who is calling target actor + #[prost(message, optional, tag = "2")] + pub caller: ::core::option::Option, + /// The target actor itself + #[prost(message, optional, tag = "3")] + pub self_: ::core::option::Option, +} +/// Noop is used when the input or output value of a function or method +/// does not matter to the caller of a Workflow or when the user just wants to receive +/// the Context in the request, that is, +/// he does not care about the input value only with the state. +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct Noop {} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct RegistrationRequest { + #[prost(message, optional, tag = "1")] + pub service_info: ::core::option::Option, + #[prost(message, optional, tag = "2")] + pub actor_system: ::core::option::Option, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct RegistrationResponse { + #[prost(message, optional, tag = "1")] + pub status: ::core::option::Option, + #[prost(message, optional, tag = "2")] + pub proxy_info: ::core::option::Option, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ServiceInfo { + /// The name of the actor system, eg, "my-actor-system". + #[prost(string, tag = "1")] + pub service_name: ::prost::alloc::string::String, + /// The version of the service. + #[prost(string, tag = "2")] + pub service_version: ::prost::alloc::string::String, + /// A description of the runtime for the service. Can be anything, but examples might be: + /// - node v10.15.2 + /// - OpenJDK Runtime Environment 1.8.0_192-b12 + #[prost(string, tag = "3")] + pub service_runtime: ::prost::alloc::string::String, + /// If using a support library, the name of that library, eg "spawn-jvm" + #[prost(string, tag = "4")] + pub support_library_name: ::prost::alloc::string::String, + /// The version of the support library being used. + #[prost(string, tag = "5")] + pub support_library_version: ::prost::alloc::string::String, + /// Spawn protocol major version accepted by the support library. + #[prost(int32, tag = "6")] + pub protocol_major_version: i32, + /// Spawn protocol minor version accepted by the support library. + #[prost(int32, tag = "7")] + pub protocol_minor_version: i32, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct SpawnRequest { + #[prost(message, repeated, tag = "1")] + pub actors: ::prost::alloc::vec::Vec, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct SpawnResponse { + #[prost(message, optional, tag = "1")] + pub status: ::core::option::Option, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ProxyInfo { + #[prost(int32, tag = "1")] + pub protocol_major_version: i32, + #[prost(int32, tag = "2")] + pub protocol_minor_version: i32, + #[prost(string, tag = "3")] + pub proxy_name: ::prost::alloc::string::String, + #[prost(string, tag = "4")] + pub proxy_version: ::prost::alloc::string::String, +} +/// When a Host Function is invoked it returns the updated state and return value to the call. +/// It can also return a number of side effects to other Actors as a result of its computation. +/// These side effects will be forwarded to the respective Actors asynchronously and should not affect the Host Function's response to its caller. +/// Internally side effects is just a special kind of InvocationRequest. +/// Useful for handle handle `recipient list` and `Composed Message Processor` patterns: +/// +/// +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct SideEffect { + #[prost(message, optional, tag = "1")] + pub request: ::core::option::Option, +} +/// Broadcast a message to many Actors +/// Useful for handle `recipient list`, `publish-subscribe channel`, and `scatter-gatther` patterns: +/// +/// +/// +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct Broadcast { + /// Channel of target Actors + #[prost(string, tag = "1")] + pub channel_group: ::prost::alloc::string::String, + /// Action. Only Actors that have this action will run successfully + #[prost(string, tag = "2")] + pub action_name: ::prost::alloc::string::String, + /// Payload + #[prost(oneof = "broadcast::Payload", tags = "3, 4")] + pub payload: ::core::option::Option, +} +/// Nested message and enum types in `Broadcast`. +pub mod broadcast { + /// Payload + #[allow(clippy::derive_partial_eq_without_eq)] + #[derive(Clone, PartialEq, ::prost::Oneof)] + pub enum Payload { + #[prost(message, tag = "3")] + Value(::prost_types::Any), + #[prost(message, tag = "4")] + Noop(super::Noop), + } +} +/// Sends the output of a action of an Actor to the input of another action of an Actor +/// Useful for handle `pipes` pattern: +/// +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct Pipe { + /// Target Actor + #[prost(string, tag = "1")] + pub actor: ::prost::alloc::string::String, + /// Action. + #[prost(string, tag = "2")] + pub action_name: ::prost::alloc::string::String, +} +/// Sends the input of a action of an Actor to the input of another action of an Actor +/// Useful for handle `content-basead router` pattern +/// +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct Forward { + /// Target Actor + #[prost(string, tag = "1")] + pub actor: ::prost::alloc::string::String, + /// Action. + #[prost(string, tag = "2")] + pub action_name: ::prost::alloc::string::String, +} +/// Container for archicetural message patterns +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct Workflow { + #[prost(message, optional, tag = "2")] + pub broadcast: ::core::option::Option, + #[prost(message, repeated, tag = "1")] + pub effects: ::prost::alloc::vec::Vec, + #[prost(oneof = "workflow::Routing", tags = "3, 4")] + pub routing: ::core::option::Option, +} +/// Nested message and enum types in `Workflow`. +pub mod workflow { + #[allow(clippy::derive_partial_eq_without_eq)] + #[derive(Clone, PartialEq, ::prost::Oneof)] + pub enum Routing { + #[prost(message, tag = "3")] + Pipe(super::Pipe), + #[prost(message, tag = "4")] + Forward(super::Forward), + } +} +/// The user function when it wants to send a message to an Actor uses the InvocationRequest message type. +/// +/// Params: +/// * system: See ActorSystem message. +/// * actor: The target Actor, i.e. the one that the user function is calling to perform some computation. +/// * caller: The caller Actor +/// * action_name: The function or method on the target Actor that will receive this request +/// and perform some useful computation with the sent data. +/// * value: This is the value sent by the user function to be computed by the request's target Actor action. +/// * async: Indicates whether the action should be processed synchronously, where a response should be sent back to the user function, +/// or whether the action should be processed asynchronously, i.e. no response sent to the caller and no waiting. +/// * metadata: Meta information or headers +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct InvocationRequest { + #[prost(message, optional, tag = "1")] + pub system: ::core::option::Option, + #[prost(message, optional, tag = "2")] + pub actor: ::core::option::Option, + #[prost(string, tag = "3")] + pub action_name: ::prost::alloc::string::String, + #[prost(bool, tag = "5")] + pub r#async: bool, + #[prost(message, optional, tag = "6")] + pub caller: ::core::option::Option, + #[prost(map = "string, string", tag = "8")] + pub metadata: + ::std::collections::HashMap<::prost::alloc::string::String, ::prost::alloc::string::String>, + #[prost(int64, tag = "9")] + pub scheduled_to: i64, + #[prost(bool, tag = "10")] + pub pooled: bool, + #[prost(oneof = "invocation_request::Payload", tags = "4, 7")] + pub payload: ::core::option::Option, +} +/// Nested message and enum types in `InvocationRequest`. +pub mod invocation_request { + #[allow(clippy::derive_partial_eq_without_eq)] + #[derive(Clone, PartialEq, ::prost::Oneof)] + pub enum Payload { + #[prost(message, tag = "4")] + Value(::prost_types::Any), + #[prost(message, tag = "7")] + Noop(super::Noop), + } +} +/// ActorInvocation is a translation message between a local invocation made via InvocationRequest +/// and the real Actor that intends to respond to this invocation and that can be located anywhere in the cluster. +/// +/// Params: +/// * actor: The ActorId handling the InvocationRequest request, also called the target Actor. +/// * action_name: The function or method on the target Actor that will receive this request +/// and perform some useful computation with the sent data. +/// * current_context: The current Context with current state value of the target Actor. +/// That is, the same as found via matching in %Actor{name: target_actor, state: %ActorState{state: value} = actor_state}. +/// In this case, the Context type will contain in the value attribute the same `value` as the matching above. +/// * payload: The value to be passed to the function or method corresponding to action_name. +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ActorInvocation { + #[prost(message, optional, tag = "1")] + pub actor: ::core::option::Option, + #[prost(string, tag = "2")] + pub action_name: ::prost::alloc::string::String, + #[prost(message, optional, tag = "3")] + pub current_context: ::core::option::Option, + #[prost(message, optional, tag = "6")] + pub caller: ::core::option::Option, + #[prost(oneof = "actor_invocation::Payload", tags = "4, 5")] + pub payload: ::core::option::Option, +} +/// Nested message and enum types in `ActorInvocation`. +pub mod actor_invocation { + #[allow(clippy::derive_partial_eq_without_eq)] + #[derive(Clone, PartialEq, ::prost::Oneof)] + pub enum Payload { + #[prost(message, tag = "4")] + Value(::prost_types::Any), + #[prost(message, tag = "5")] + Noop(super::Noop), + } +} +/// The user function's response after executing the action originated by the local proxy request via ActorInvocation. +/// +/// Params: +/// actor_name: The name of the Actor handling the InvocationRequest request, also called the target Actor. +/// actor_system: The name of ActorSystem registered in Registration step. +/// updated_context: The Context with updated state value of the target Actor after user function has processed a request. +/// value: The value that the original request proxy will forward in response to the InvocationRequest type request. +/// This is the final response from the point of view of the user who invoked the Actor call and its subsequent processing. +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ActorInvocationResponse { + #[prost(string, tag = "1")] + pub actor_name: ::prost::alloc::string::String, + #[prost(string, tag = "2")] + pub actor_system: ::prost::alloc::string::String, + #[prost(message, optional, tag = "3")] + pub updated_context: ::core::option::Option, + #[prost(message, optional, tag = "5")] + pub workflow: ::core::option::Option, + #[prost(oneof = "actor_invocation_response::Payload", tags = "4, 6")] + pub payload: ::core::option::Option, +} +/// Nested message and enum types in `ActorInvocationResponse`. +pub mod actor_invocation_response { + #[allow(clippy::derive_partial_eq_without_eq)] + #[derive(Clone, PartialEq, ::prost::Oneof)] + pub enum Payload { + #[prost(message, tag = "4")] + Value(::prost_types::Any), + #[prost(message, tag = "6")] + Noop(super::Noop), + } +} +/// InvocationResponse is the response that the proxy that received the InvocationRequest request will forward to the request's original user function. +/// +/// Params: +/// status: Status of request. Could be one of [UNKNOWN, OK, ACTOR_NOT_FOUND, ERROR]. +/// system: The original ActorSystem of the InvocationRequest request. +/// actor: The target Actor originally sent in the InvocationRequest message. +/// value: The value resulting from the request processing that the target Actor made. +/// This value must be passed by the user function to the one who requested the initial request in InvocationRequest. +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct InvocationResponse { + #[prost(message, optional, tag = "1")] + pub status: ::core::option::Option, + #[prost(message, optional, tag = "2")] + pub system: ::core::option::Option, + #[prost(message, optional, tag = "3")] + pub actor: ::core::option::Option, + #[prost(oneof = "invocation_response::Payload", tags = "4, 5")] + pub payload: ::core::option::Option, +} +/// Nested message and enum types in `InvocationResponse`. +pub mod invocation_response { + #[allow(clippy::derive_partial_eq_without_eq)] + #[derive(Clone, PartialEq, ::prost::Oneof)] + pub enum Payload { + #[prost(message, tag = "4")] + Value(::prost_types::Any), + #[prost(message, tag = "5")] + Noop(super::Noop), + } +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct RequestStatus { + #[prost(enumeration = "Status", tag = "1")] + pub status: i32, + #[prost(string, tag = "2")] + pub message: ::prost::alloc::string::String, +} +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] +#[repr(i32)] +pub enum Status { + Unknown = 0, + Ok = 1, + ActorNotFound = 2, + Error = 3, +} +impl Status { + /// String value of the enum field names used in the ProtoBuf definition. + /// + /// The values are not transformed in any way and thus are considered stable + /// (if the ProtoBuf definition does not change) and safe for programmatic use. + pub fn as_str_name(&self) -> &'static str { + match self { + Status::Unknown => "UNKNOWN", + Status::Ok => "OK", + Status::ActorNotFound => "ACTOR_NOT_FOUND", + Status::Error => "ERROR", + } + } + /// Creates an enum from field names used in the ProtoBuf definition. + pub fn from_str_name(value: &str) -> ::core::option::Option { + match value { + "UNKNOWN" => Some(Self::Unknown), + "OK" => Some(Self::Ok), + "ACTOR_NOT_FOUND" => Some(Self::ActorNotFound), + "ERROR" => Some(Self::Error), + _ => None, + } + } +} diff --git a/spawn_activators/activator_sqs/.env b/spawn_activators/activator_sqs/.env new file mode 100644 index 000000000..f35606c57 --- /dev/null +++ b/spawn_activators/activator_sqs/.env @@ -0,0 +1,4 @@ +PROXY_APP_NAME=spawn-activator-sqs +PROXY_HTTP_PORT=9007 +PROXY_CLUSTER_STRATEGY=gossip +PROXY_CLUSTER_POLLING=3000 diff --git a/spawn_activators/activator_sqs/.formatter.exs b/spawn_activators/activator_sqs/.formatter.exs new file mode 100644 index 000000000..d2cda26ed --- /dev/null +++ b/spawn_activators/activator_sqs/.formatter.exs @@ -0,0 +1,4 @@ +# Used by "mix format" +[ + inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"] +] diff --git a/spawn_activators/activator_sqs/.gitignore b/spawn_activators/activator_sqs/.gitignore new file mode 100644 index 000000000..2abad3243 --- /dev/null +++ b/spawn_activators/activator_sqs/.gitignore @@ -0,0 +1,26 @@ +# The directory Mix will write compiled artifacts to. +/_build/ + +# If you run "mix test --cover", coverage assets end up here. +/cover/ + +# The directory Mix downloads your dependencies sources to. +/deps/ + +# Where third-party dependencies like ExDoc output generated docs. +/doc/ + +# Ignore .fetch files in case you like to edit your project deps locally. +/.fetch + +# If the VM crashes, it generates a dump, let's ignore it too. +erl_crash.dump + +# Also ignore archive artifacts (built via "mix archive.build"). +*.ez + +# Ignore package tarball (built via "mix hex.build"). +activator_sqs-*.tar + +# Temporary files, for example, from tests. +/tmp/ diff --git a/spawn_activators/activator_sqs/README.md b/spawn_activators/activator_sqs/README.md new file mode 100644 index 000000000..abd6d2d1c --- /dev/null +++ b/spawn_activators/activator_sqs/README.md @@ -0,0 +1,21 @@ +# ActivatorSQS + +**TODO: Add description** + +## Installation + +If [available in Hex](https://hex.pm/docs/publish), the package can be installed +by adding `activator_sqs` to your list of dependencies in `mix.exs`: + +```elixir +def deps do + [ + {:activator_sqs, "~> 0.1.0"} + ] +end +``` + +Documentation can be generated with [ExDoc](https://github.com/elixir-lang/ex_doc) +and published on [HexDocs](https://hexdocs.pm). Once published, the docs can +be found at . + diff --git a/spawn_activators/activator_sqs/lib/activator_sqs.ex b/spawn_activators/activator_sqs/lib/activator_sqs.ex new file mode 100644 index 000000000..5aa457f7d --- /dev/null +++ b/spawn_activators/activator_sqs/lib/activator_sqs.ex @@ -0,0 +1,18 @@ +defmodule ActivatorSQS do + @moduledoc """ + Documentation for `ActivatorSQS`. + """ + + @doc """ + Hello world. + + ## Examples + + iex> ActivatorSQS.hello() + :world + + """ + def hello do + :world + end +end diff --git a/spawn_activators/activator_sqs/lib/activator_sqs/application.ex b/spawn_activators/activator_sqs/lib/activator_sqs/application.ex new file mode 100644 index 000000000..a653cbc16 --- /dev/null +++ b/spawn_activators/activator_sqs/lib/activator_sqs/application.ex @@ -0,0 +1,21 @@ +defmodule ActivatorSQS.Application do + @moduledoc false + + use Application + + alias Actors.Config.PersistentTermConfig, as: Config + import Activator, only: [get_http_port: 1] + + @impl true + def start(_type, _args) do + Config.load() + + children = [ + Activator.Supervisor.child_spec([]), + {Bandit, plug: ActivatorSQS.Router, scheme: :http, port: get_http_port()} + ] + + opts = [strategy: :one_for_one, name: ActivatorSQS.Supervisor] + Supervisor.start_link(children, opts) + end +end diff --git a/spawn_activators/activator_sqs/lib/activator_sqs/router/router.ex b/spawn_activators/activator_sqs/lib/activator_sqs/router/router.ex new file mode 100644 index 000000000..27ad23139 --- /dev/null +++ b/spawn_activators/activator_sqs/lib/activator_sqs/router/router.ex @@ -0,0 +1,15 @@ +defmodule ActivatorSQS.Router do + use Plug.Router + + plug(Plug.Logger) + + plug(:match) + plug(Plug.Parsers, parsers: [:json], json_decoder: Jason) + plug(:dispatch) + + forward("/health", to: ActivatorSQS.Routes.Health) + + match _ do + send_resp(conn, 404, "Not found!") + end +end diff --git a/spawn_activators/activator_sqs/lib/activator_sqs/routes/base.ex b/spawn_activators/activator_sqs/lib/activator_sqs/routes/base.ex new file mode 100644 index 000000000..3662a234f --- /dev/null +++ b/spawn_activators/activator_sqs/lib/activator_sqs/routes/base.ex @@ -0,0 +1,40 @@ +defmodule ActivatorSQS.Routes.Base do + defmacro __using__([]) do + quote do + use Plug.Router + + plug(Plug.Logger) + + plug(:match) + + plug(Plug.Parsers, + parsers: [:json], + json_decoder: Jason + ) + + plug(:dispatch) + + def send!(conn, code, data, content_type) + when is_integer(code) and content_type == "application/json" do + conn + |> Plug.Conn.put_resp_content_type(content_type) + |> send_resp(code, Jason.encode!(data)) + end + + def send!(conn, code, data, content_type) when is_atom(code) do + code = + case code do + :ok -> 200 + :not_found -> 404 + :malformed_data -> 400 + :non_authenticated -> 401 + :forbidden_access -> 403 + :server_error -> 500 + :error -> 504 + end + + send!(conn, code, data, content_type) + end + end + end +end diff --git a/spawn_activators/activator_sqs/lib/activator_sqs/routes/health.ex b/spawn_activators/activator_sqs/lib/activator_sqs/routes/health.ex new file mode 100644 index 000000000..c71fc9c3f --- /dev/null +++ b/spawn_activators/activator_sqs/lib/activator_sqs/routes/health.ex @@ -0,0 +1,17 @@ +defmodule ActivatorSQS.Routes.Health do + use ActivatorSQS.Routes.Base + + @content_type "application/json" + + get "/" do + send!(conn, 200, %{status: "up"}, @content_type) + end + + get "/liveness" do + send!(conn, 200, %{status: "up"}, @content_type) + end + + get "/readiness" do + send!(conn, 200, %{status: "up"}, @content_type) + end +end diff --git a/spawn_activators/activator_sqs/mix.exs b/spawn_activators/activator_sqs/mix.exs new file mode 100644 index 000000000..0fd4eda9a --- /dev/null +++ b/spawn_activators/activator_sqs/mix.exs @@ -0,0 +1,55 @@ +defmodule ActivatorSQS.MixProject do + use Mix.Project + + @app :activator_sqs + @version "0.0.0-local.dev" + + def project do + [ + app: @app, + version: @version, + build_path: "../activator/_build", + config_path: "../../config/config.exs", + deps_path: "../activator/deps", + lockfile: "../activator/mix.lock", + elixir: "~> 1.15", + start_permanent: Mix.env() == :prod, + deps: deps(), + releases: releases() + ] + end + + # Run "mix help compile.app" to learn about applications. + def application do + [ + extra_applications: [:logger], + mod: {ActivatorSQS.Application, []} + ] + end + + # Run "mix help deps" to learn about dependencies. + defp deps do + [ + {:activator, path: "../activator"}, + {:spawn, path: "../../"}, + {:bakeware, "~> 0.2"}, + {:bandit, "~> 1.5"}, + {:broadway_sqs, "~> 0.7"}, + {:nimble_options, "~> 0.5.2", override: true} + ] + end + + defp releases do + [ + activator_sqs: [ + include_executables_for: [:unix], + applications: [activator_sqs: :permanent], + steps: [ + :assemble, + &Bakeware.assemble/1 + ], + bakeware: [compression_level: 19] + ] + ] + end +end diff --git a/spawn_activators/activator_sqs/test/activator_sqs_test.exs b/spawn_activators/activator_sqs/test/activator_sqs_test.exs new file mode 100644 index 000000000..a2aad193a --- /dev/null +++ b/spawn_activators/activator_sqs/test/activator_sqs_test.exs @@ -0,0 +1,8 @@ +defmodule ActivatorSQSTest do + use ExUnit.Case + doctest ActivatorSQS + + test "greets the world" do + assert ActivatorSQS.hello() == :world + end +end diff --git a/spawn_activators/activator_sqs/test/test_helper.exs b/spawn_activators/activator_sqs/test/test_helper.exs new file mode 100644 index 000000000..869559e70 --- /dev/null +++ b/spawn_activators/activator_sqs/test/test_helper.exs @@ -0,0 +1 @@ +ExUnit.start() diff --git a/spawn_operator/spawn_operator/lib/spawn_operator/k8s/proxy/deployment.ex b/spawn_operator/spawn_operator/lib/spawn_operator/k8s/proxy/deployment.ex index fb797f5ef..04fd265eb 100644 --- a/spawn_operator/spawn_operator/lib/spawn_operator/k8s/proxy/deployment.ex +++ b/spawn_operator/spawn_operator/lib/spawn_operator/k8s/proxy/deployment.ex @@ -461,7 +461,7 @@ defmodule SpawnOperator.K8s.Proxy.Deployment do spec, %{"pullSecretRef" => secret_name} = _host_params ) do - Map.merge(spec, %{"imagePullSecrets" => %{"name" => secret_name}}) + Map.merge(spec, %{"imagePullSecrets" => [%{"name" => secret_name}]}) end defp maybe_put_image_pull_secrets(spec, _), do: spec diff --git a/spawn_operator/spawn_operator/manifest.yaml b/spawn_operator/spawn_operator/manifest.yaml index fe242f747..e2dd6c95d 100644 --- a/spawn_operator/spawn_operator/manifest.yaml +++ b/spawn_operator/spawn_operator/manifest.yaml @@ -36,7 +36,7 @@ spec: ports: - containerPort: 9090 #image: ghcr.io/eigr/spawn-operator:2.0.0-RC9 - image: docker.io/eigr/spawn-operator:2.0.0-RC9 + image: eliasdarruda/operator:2.0.0-RC10 resources: limits: cpu: 200m @@ -119,226 +119,397 @@ kind: CustomResourceDefinition apiVersion: apiextensions.k8s.io/v1 --- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition metadata: name: actorhosts.spawn-eigr.io - labels: {} spec: - scope: Cluster + conversion: + strategy: None group: spawn-eigr.io names: kind: ActorHost + listKind: ActorHostList plural: actorhosts shortNames: - - ac - - ah - - actor - - actors - - hosts + - ac + - ah + - actor + - actors + - hosts singular: actorhost + scope: Cluster versions: - - name: v1 - deprecated: false - schema: - openAPIV3Schema: - type: object - description: | - Defines an ActorHost application. Example: + - additionalPrinterColumns: + - description: SDK used by the ActorHost + jsonPath: .spec.host.sdk + name: SDK + type: string + - description: Embedded Proxy used by the ActorHost + jsonPath: .spec.host.embedded + name: Embedded + type: string + - description: Docker image used for the ActorHost + jsonPath: .spec.host.image + name: Image + type: string + - description: Minimum number of replicas for the ActorHost + jsonPath: .spec.autoscaler.min + name: Min Replicas + type: integer + - description: Maximum number of replicas for the ActorHost + jsonPath: .spec.autoscaler.max + name: Max Replicas + type: integer + name: v1 + schema: + openAPIV3Schema: + description: | + Defines an ActorHost application. Example: - --- - apiVersion: spawn-eigr.io/v1 - kind: ActorHost - metadata: - name: my-java-app - spec: + --- + apiVersion: spawn-eigr.io/v1 + kind: ActorHost + metadata: + name: my-java-app + spec: + host: + image: ghcr.io/eigr/spawn-springboot-examples:latest + sdk: java + ports: + - containerPort: 80 + properties: + spec: + properties: + affinity: + type: object + autoscaler: + properties: + averageCpuUtilizationPercentage: + type: integer + averageMemoryUtilizationValue: + type: integer + max: + type: integer + min: + type: integer + type: object host: - image: ghcr.io/eigr/spawn-springboot-examples:latest - sdk: java - ports: - - containerPort: 80 - required: - - spec - properties: - status: - type: object - properties: - conditions: - type: array - items: + properties: + embedded: + type: boolean + env: + items: + properties: + name: + type: string + value: + type: string + valueFrom: + properties: + fieldRef: + properties: + fieldPath: + type: string + type: object + secretKeyRef: + properties: + name: + type: string + key: + type: string + type: object + type: object + type: object + type: array + image: + type: string + ports: + items: + properties: + containerPort: + type: integer + name: + type: string + type: object + type: array + sdk: + enum: + - dart + - elixir + - go + - java + - python + - rust + - springboot + - nodejs + type: string + pullSecretRef: + type: string + taskActors: + items: + properties: + actorName: + type: string + topology: + properties: + nodeSelector: + additionalProperties: + type: string + type: object + tolerations: + items: + properties: + effect: + type: string + key: + type: string + operator: + type: string + type: object + type: array + type: object + workerPool: + properties: + bootTimeout: + type: integer + callTimeout: + type: integer + idleShutdownAfter: + type: integer + max: + type: integer + maxConcurrency: + type: integer + min: + type: integer + oneOff: + enum: + - "true" + - "false" + type: string + type: object + type: object + type: array + volumeMounts: + items: + properties: + mountPath: + type: string + name: + type: string + type: object + type: array + required: + - image + type: object + replicas: + type: integer + topology: + properties: + nodeSelector: + additionalProperties: + type: string type: object - properties: - message: - type: string - status: - type: string - enum: - - 'True' - - 'False' - type: - type: string - lastHeartbeatTime: - type: string - format: date-time - lastTransitionTime: - type: string - format: date-time - observedGeneration: - type: integer - spec: - properties: - affinity: - type: object - autoscaler: - properties: - averageCpuUtilizationPercentage: - type: integer - averageMemoryUtilizationValue: - type: integer - max: - type: integer - min: - type: integer - type: object - host: + tolerations: + items: + properties: + effect: + type: string + key: + type: string + operator: + type: string + type: object + type: array + type: object + volumes: + items: properties: - embedded: - type: boolean - env: - items: - properties: - name: - type: string - value: - type: string - valueFrom: + configMap: + properties: + items: + items: + properties: + key: + type: string + path: + type: string + type: object + type: array + name: + type: string + type: object + downwardAPI: + properties: + items: + items: properties: fieldRef: properties: fieldPath: type: string type: object + path: + type: string + resourceFieldRef: + properties: + containerName: + type: string + divisor: + type: string + resource: + type: string + type: object type: object - type: object - type: array - image: - type: string - ports: - items: - properties: - containerPort: - type: integer - name: - type: string - type: object - type: array - sdk: - enum: - - dart - - elixir - - go - - java - - python - - rust - - springboot - - nodejs + type: array + type: object + emptyDir: + properties: + medium: + enum: + - "" + - Memory + type: string + sizeLimit: + type: string + type: object + hostPath: + properties: + path: + type: string + type: + enum: + - "" + - DirectoryOrCreate + - Directory + - FileOrCreate + - File + - Socket + - CharDevice + - BlockDevice + type: string + type: object + name: type: string - taskActors: - items: - properties: - actorName: - type: string - topology: + persistentVolumeClaim: + properties: + claimName: + type: string + readOnly: + type: boolean + type: object + projected: + properties: + sources: + items: properties: - nodeSelector: - additionalProperties: - type: string + configMap: + properties: + items: + items: + properties: + key: + type: string + path: + type: string + type: object + type: array + name: + type: string + type: object + secret: + properties: + items: + items: + properties: + key: + type: string + path: + type: string + type: object + type: array + name: + type: string type: object - tolerations: - items: - properties: - effect: - type: string - key: - type: string - operator: - type: string - type: object - type: array type: object - workerPool: + type: array + type: object + secret: + properties: + items: + items: properties: - bootTimeout: - type: integer - callTimeout: - type: integer - idleShutdownAfter: - type: integer - max: - type: integer - maxConcurrency: - type: integer - min: - type: integer - oneOff: - enum: - - 'true' - - 'false' + key: + type: string + path: type: string type: object - type: object - type: array - required: - - image + type: array + secretName: + type: string + type: object type: object - replicas: - type: integer - topology: + type: array + type: object + status: + properties: + conditions: + items: properties: - nodeSelector: - additionalProperties: - type: string - type: object - tolerations: - items: - properties: - effect: - type: string - key: - type: string - operator: - type: string - type: object - type: array + lastHeartbeatTime: + format: date-time + type: string + lastTransitionTime: + format: date-time + type: string + message: + type: string + status: + enum: + - "True" + - "False" + type: string + type: + type: string type: object - type: object - additionalPrinterColumns: - - name: SDK - type: string - description: SDK used by the ActorHost - jsonPath: .spec.host.sdk - - name: Embedded - type: string - description: Embedded Proxy used by the ActorHost - jsonPath: .spec.host.embedded - - name: Image - type: string - description: Docker image used for the ActorHost - jsonPath: .spec.host.image - - name: Min Replicas - type: integer - description: Minimum number of replicas for the ActorHost - jsonPath: .spec.autoscaler.min - - name: Max Replicas - type: integer - description: Maximum number of replicas for the ActorHost - jsonPath: .spec.autoscaler.max - storage: true - subresources: - status: {} - served: true - deprecationWarning: -kind: CustomResourceDefinition -apiVersion: apiextensions.k8s.io/v1 + type: array + observedGeneration: + type: integer + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: ActorHost + listKind: ActorHostList + plural: actorhosts + shortNames: + - ac + - ah + - actor + - actors + - hosts + singular: actorhost + conditions: + - lastTransitionTime: "2025-01-19T22:17:17Z" + message: no conflicts found + reason: NoConflicts + status: "True" + type: NamesAccepted + - lastTransitionTime: "2025-01-19T22:17:17Z" + message: the initial names have been accepted + reason: InitialNamesAccepted + status: "True" + type: Established + storedVersions: + - v1 --- metadata: diff --git a/spawn_operator/spawn_operator/mix.lock b/spawn_operator/spawn_operator/mix.lock index 65b68050b..70b929b51 100644 --- a/spawn_operator/spawn_operator/mix.lock +++ b/spawn_operator/spawn_operator/mix.lock @@ -53,7 +53,6 @@ "mime": {:hex, :mime, "2.0.6", "8f18486773d9b15f95f4f4f1e39b710045fa1de891fada4516559967276e4dc2", [:mix], [], "hexpm", "c9945363a6b26d747389aac3643f8e0e09d30499a138ad64fe8fd1d13d9b153e"}, "mint": {:hex, :mint, "1.7.1", "113fdb2b2f3b59e47c7955971854641c61f378549d73e829e1768de90fc1abf1", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1 or ~> 0.2.0 or ~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "fceba0a4d0f24301ddee3024ae116df1c3f4bb7a563a731f45fdfeb9d39a231b"}, "mint_web_socket": {:hex, :mint_web_socket, "1.0.3", "aab42fff792a74649916236d0b01f560a0b3f03ca5dea693c230d1c44736b50e", [:mix], [{:mint, ">= 1.4.1 and < 2.0.0-0", [hex: :mint, repo: "hexpm", optional: false]}], "hexpm", "ca3810ca44cc8532e3dce499cc17f958596695d226bb578b2fbb88c09b5954b0"}, - "mnesiac": {:hex, :mnesiac, "0.3.14", "5ea3f1f3e615073629d0822bcf2297be73149beee2d1f7e482c1943894f59b53", [:mix], [{:libcluster, "~> 3.3", [hex: :libcluster, repo: "hexpm", optional: true]}], "hexpm", "e51b38bf983b9320aba56d5dce79dbf50cbff07f7495e70b89eb45461b8d32fa"}, "myxql": {:hex, :myxql, "0.7.1", "7c7b75aa82227cd2bc9b7fbd4de774fb19a1cdb309c219f411f82ca8860f8e01", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:geo, "~> 3.4", [hex: :geo, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "a491cdff53353a09b5850ac2d472816ebe19f76c30b0d36a43317a67c9004936"}, "nebulex": {:hex, :nebulex, "2.6.1", "58c1924fa9f4e844c3470c20e6351b311a556652de29ed3b05fd2e5d817c6fef", [:mix], [{:decorator, "~> 1.4", [hex: :decorator, repo: "hexpm", optional: true]}, {:shards, "~> 1.1", [hex: :shards, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "177949fef2dc34a0055d7140b6bc94f6904225e2b5bbed6266ea9679522d23c6"}, "nimble_options": {:hex, :nimble_options, "1.1.0", "3b31a57ede9cb1502071fade751ab0c7b8dbe75a9a4c2b5bbb0943a690b63172", [:mix], [], "hexpm", "8bbbb3941af3ca9acc7835f5655ea062111c9c27bcac53e004460dfd19008a99"}, diff --git a/spawn_proxy/proxy/mix.exs b/spawn_proxy/proxy/mix.exs index 2d146b696..542f3ebfb 100644 --- a/spawn_proxy/proxy/mix.exs +++ b/spawn_proxy/proxy/mix.exs @@ -34,12 +34,8 @@ defmodule Proxy.MixProject do defp deps do [ {:spawn, path: "../../"}, - {:spawn_statestores_mariadb, - path: "../../spawn_statestores/statestores_mariadb", optional: false}, {:spawn_statestores_postgres, path: "../../spawn_statestores/statestores_postgres", optional: false}, - {:spawn_statestores_native, - path: "../../spawn_statestores/statestores_native", optional: false}, {:bakeware, "~> 0.2"}, {:bandit, "~> 1.5"}, {:observer_cli, "~> 1.7"}, diff --git a/spawn_sdk/spawn_sdk/lib/system/spawn_system.ex b/spawn_sdk/spawn_sdk/lib/system/spawn_system.ex index aa1d7930d..60e821fb6 100644 --- a/spawn_sdk/spawn_sdk/lib/system/spawn_system.ex +++ b/spawn_sdk/spawn_sdk/lib/system/spawn_system.ex @@ -75,7 +75,7 @@ defmodule SpawnSdk.System.SpawnSystem do @impl SpawnSdk.System def spawn_actor(actor_name, spawn_actor_opts) do - opts = [revision: Keyword.get(spawn_actor_opts, :revision, 0)] + opts = [revision: Keyword.get(spawn_actor_opts, :revision, 0), interface: SpawnSdk.Interface] system = Keyword.get(spawn_actor_opts, :system, nil) parent = get_parent_actor_name(spawn_actor_opts) @@ -109,7 +109,7 @@ defmodule SpawnSdk.System.SpawnSystem do raise "You have to specify an action" end - opts = [] + opts = [interface: SpawnSdk.Interface] payload = parse_payload(payload) req = %InvocationRequest{ @@ -428,6 +428,7 @@ defmodule SpawnSdk.System.SpawnSystem do ) do Enum.map(effects, fn %SpawnSdk.Flow.SideEffect{} = effect -> payload = parse_payload(effect.payload) + metadata = effect.metadata || %{} %Spawn.SideEffect{ request: %InvocationRequest{ @@ -440,7 +441,7 @@ defmodule SpawnSdk.System.SpawnSystem do register_ref: effect.register_ref, async: true, caller: %ActorId{name: caller_name, system: system}, - metadata: effect.metadata, + metadata: metadata |> Map.put("fail_backoff", "true"), scheduled_to: effect.scheduled_to } } diff --git a/spawn_statestores/statestore_controller/lib/statestore_controller/supervisor.ex b/spawn_statestores/statestore_controller/lib/statestore_controller/supervisor.ex index cbc6d6154..8f51a4056 100644 --- a/spawn_statestores/statestore_controller/lib/statestore_controller/supervisor.ex +++ b/spawn_statestores/statestore_controller/lib/statestore_controller/supervisor.ex @@ -1,8 +1,7 @@ defmodule StatestoreController.Supervisor do @moduledoc false - import Statestores.Util, - only: [load_lookup_adapter: 0, load_snapshot_adapter: 0] + import Statestores.Util, only: [load_repo: 0] def start_link(args) do Supervisor.start_link(__MODULE__, args, name: __MODULE__) @@ -17,27 +16,17 @@ defmodule StatestoreController.Supervisor do @impl true def init(args) do - lookup_adapter = load_lookup_adapter() - snapshot_adapter = load_snapshot_adapter() - Statestores.Migrator.migrate(snapshot_adapter) - Statestores.Migrator.migrate(lookup_adapter) + repo = load_repo() + + Statestores.Migrator.migrate(repo) children = [ Statestores.Vault, - snapshot_adapter, - lookup_adapter + repo, + {StatestoreController.CDC.CdcSupervisor, [args]} ] - |> maybe_add_cdc(snapshot_adapter, args) Supervisor.init(children, strategy: :one_for_one) end - - defp maybe_add_cdc(children, snapshot_adapter, args) - when is_atom(snapshot_adapter) and - snapshot_adapter in [Statestores.Adapters.PostgresSnapshotAdapter] do - children ++ [{StatestoreController.CDC.CdcSupervisor, [args]}] - end - - defp maybe_add_cdc(_children, _snapshot_adapter, _args), do: nil end diff --git a/spawn_statestores/statestore_controller/test/support/data_case.ex b/spawn_statestores/statestore_controller/test/support/data_case.ex index 98e14ef3f..a48910bc9 100644 --- a/spawn_statestores/statestore_controller/test/support/data_case.ex +++ b/spawn_statestores/statestore_controller/test/support/data_case.ex @@ -5,7 +5,7 @@ defmodule Statestores.DataCase do using do quote do - use Statestores.SandboxHelper, repos: [Statestores.Util.load_snapshot_adapter()] + use Statestores.SandboxHelper, repos: [Statestores.Util.load_repo()] import Statestores.DataCase end diff --git a/spawn_statestores/statestores/lib/statestores/adapters/lookup_behaviour.ex b/spawn_statestores/statestores/lib/statestores/adapters/lookup_behaviour.ex index e1c149c45..ca41379e9 100644 --- a/spawn_statestores/statestores/lib/statestores/adapters/lookup_behaviour.ex +++ b/spawn_statestores/statestores/lib/statestores/adapters/lookup_behaviour.ex @@ -35,11 +35,9 @@ defmodule Statestores.Adapters.LookupBehaviour do quote do alias Statestores.Adapters.LookupBehaviour import Ecto.Query, only: [from: 2] - import Statestores.Util, only: [init_config: 1, generate_key: 1] + import Statestores.Util, only: [generate_key: 1] @behaviour Statestores.Adapters.LookupBehaviour - - def init(_type, config), do: init_config(config) end end end diff --git a/spawn_statestores/statestores/lib/statestores/adapters/projection_behaviour.ex b/spawn_statestores/statestores/lib/statestores/adapters/projection_behaviour.ex index 8162ce6de..356909c0f 100644 --- a/spawn_statestores/statestores/lib/statestores/adapters/projection_behaviour.ex +++ b/spawn_statestores/statestores/lib/statestores/adapters/projection_behaviour.ex @@ -22,11 +22,9 @@ defmodule Statestores.Adapters.ProjectionBehaviour do defmacro __using__(_opts) do quote do alias Statestores.Adapters.ProjectionBehaviour - import Statestores.Util, only: [init_config: 1, generate_key: 1] + import Statestores.Util, only: [generate_key: 1] @behaviour Statestores.Adapters.ProjectionBehaviour - - def init(_type, config), do: init_config(config) end end end diff --git a/spawn_statestores/statestores/lib/statestores/adapters/snapshot_behaviour.ex b/spawn_statestores/statestores/lib/statestores/adapters/snapshot_behaviour.ex index f617f5cd1..03fe62728 100644 --- a/spawn_statestores/statestores/lib/statestores/adapters/snapshot_behaviour.ex +++ b/spawn_statestores/statestores/lib/statestores/adapters/snapshot_behaviour.ex @@ -4,7 +4,7 @@ defmodule Statestores.Adapters.SnapshotBehaviour do """ alias Statestores.Schemas.Snapshot - @type id :: String.t() + @type actor :: String.t() @type revision :: integer() @@ -16,13 +16,13 @@ defmodule Statestores.Adapters.SnapshotBehaviour do @type time_end :: String.t() - @callback get_by_key(id()) :: snapshot() + @callback get_by_key(actor()) :: snapshot() - @callback get_by_key_and_revision(id(), revision()) :: snapshot() + @callback get_by_key_and_revision(actor(), revision()) :: snapshot() - @callback get_all_snapshots_by_key(id()) :: snapshots() + @callback get_all_snapshots_by_key(actor()) :: snapshots() - @callback get_snapshots_by_interval(id(), time_start(), time_end()) :: snapshots() + @callback get_snapshots_by_interval(actor(), time_start(), time_end()) :: snapshots() @callback save(snapshot()) :: {:error, any} | {:ok, snapshot()} @@ -31,11 +31,9 @@ defmodule Statestores.Adapters.SnapshotBehaviour do defmacro __using__(_opts) do quote do alias Statestores.Adapters.SnapshotBehaviour - import Statestores.Util, only: [init_config: 1, generate_key: 1] + import Statestores.Util, only: [generate_key: 1] @behaviour Statestores.Adapters.SnapshotBehaviour - - def init(_type, config), do: init_config(config) end end end diff --git a/spawn_statestores/statestores/lib/statestores/schemas/snapshot.ex b/spawn_statestores/statestores/lib/statestores/schemas/snapshot.ex index 1aea6ee8e..4f08e2ddf 100644 --- a/spawn_statestores/statestores/lib/statestores/schemas/snapshot.ex +++ b/spawn_statestores/statestores/lib/statestores/schemas/snapshot.ex @@ -10,8 +10,8 @@ defmodule Statestores.Schemas.Snapshot do @primary_key false schema "snapshots" do - field(:id, :integer, primary_key: true) - field(:actor, :string) + field(:id, :integer) + field(:actor, :string, primary_key: true) field(:system, :string) field(:status, :string) field(:node, :string) @@ -26,7 +26,7 @@ defmodule Statestores.Schemas.Snapshot do def changeset(event, attrs \\ %{}) do event |> cast(attrs, [:id, :actor, :system, :status, :node, :revision, :tags, :data_type, :data]) - |> validate_required([:id, :actor, :system, :status, :node, :revision, :tags, :data_type]) + |> validate_required([:actor, :system, :status, :node, :revision, :tags, :data_type]) |> case do %{valid?: false, changes: changes} = changeset when changes == %{} -> # If the changeset is invalid and has no changes, it is diff --git a/spawn_statestores/statestores/lib/statestores/supervisor.ex b/spawn_statestores/statestores/lib/statestores/supervisor.ex index 289efd802..86084ca12 100644 --- a/spawn_statestores/statestores/lib/statestores/supervisor.ex +++ b/spawn_statestores/statestores/lib/statestores/supervisor.ex @@ -6,9 +6,7 @@ defmodule Statestores.Supervisor do import Statestores.Util, only: [ - load_lookup_adapter: 0, - load_snapshot_adapter: 0, - load_projection_adapter: 0, + load_repo: 0, supervisor_process_logger: 1 ] @@ -25,30 +23,24 @@ defmodule Statestores.Supervisor do @impl true def init(_args) do - lookup_adapter = load_lookup_adapter() - snapshot_adapter = load_snapshot_adapter() - projection_adapter = load_projection_adapter() + repo = load_repo() case System.get_env("MIX_ENV") do env when env in ["dev", "test"] -> - Statestores.Migrator.migrate(snapshot_adapter) - Statestores.Migrator.migrate(lookup_adapter) + Statestores.Migrator.migrate(repo) _ -> # TODO: migrate via job in production (future release) - Statestores.Migrator.migrate(snapshot_adapter) - Statestores.Migrator.migrate(lookup_adapter) + Statestores.Migrator.migrate(repo) end children = [ supervisor_process_logger(__MODULE__), Statestores.Vault, - snapshot_adapter, - lookup_adapter, - projection_adapter + repo ] - |> maybe_add_native_children(snapshot_adapter) + |> maybe_add_native_children(repo) Supervisor.init(children, strategy: :one_for_one) end diff --git a/spawn_statestores/statestores/lib/statestores/util.ex b/spawn_statestores/statestores/lib/statestores/util.ex index 9f365bae0..00af9a167 100644 --- a/spawn_statestores/statestores/lib/statestores/util.ex +++ b/spawn_statestores/statestores/lib/statestores/util.ex @@ -57,6 +57,14 @@ defmodule Statestores.Util do } end + @spec load_repo :: adapter() + def load_repo() do + type = + String.to_existing_atom(System.get_env("PROXY_DATABASE_TYPE", get_default_database_type())) + + load_repo_adapter(type) + end + @spec load_lookup_adapter :: adapter() def load_lookup_adapter() do case Application.fetch_env(@otp_app, :database_lookup_adapter) do @@ -195,8 +203,8 @@ defmodule Statestores.Util do load_snapshot_adapter().default_port() end - @spec generate_key(any()) :: integer() - def generate_key(id), do: :erlang.phash2({id.name, id.system}) + @spec generate_key(any()) :: String.t() + def generate_key(%{name: name}), do: name def get_statestore_key do key = @@ -223,6 +231,13 @@ defmodule Statestores.Util do |> String.downcase() end + # Repo adapter + defp load_repo_adapter(:mariadb), do: Statestores.MariadbRepo + + defp load_repo_adapter(:postgres), do: Statestores.PostgresRepo + + defp load_repo_adapter(:native), do: Statestores.Adapters.NativeSnapshotAdapter + # Lookup Adapters defp load_lookup_adapter_by_type(:mariadb), do: Statestores.Adapters.MariaDBLookupAdapter diff --git a/spawn_statestores/statestores/priv/postgres_repo/migrations/20220521162410_create_snapshots_table.exs b/spawn_statestores/statestores/priv/postgres_repo/migrations/20220521162410_create_snapshots_table.exs new file mode 100644 index 000000000..5c0983fcb --- /dev/null +++ b/spawn_statestores/statestores/priv/postgres_repo/migrations/20220521162410_create_snapshots_table.exs @@ -0,0 +1,24 @@ +defmodule Statestores.PostgresRepo.Migrations.CreateSnapshotsTable do + use Ecto.Migration + + def up do + create_if_not_exists table(:snapshots, primary_key: false) do + add(:id, :bigint, primary_key: true) + add(:actor, :string) + add(:system, :string) + add(:status, :string) + add(:node, :string) + add(:revision, :integer, default: 0) + add(:tags, :map) + add(:data_type, :string) + add(:data, :binary) + timestamps(type: :utc_datetime_usec) + end + + create_if_not_exists(index(:snapshots, [:status])) + end + + def down do + drop(table(:snapshots)) + end +end diff --git a/spawn_statestores/statestores/priv/postgres_repo/migrations/20220521162411_create_lookups_table.exs b/spawn_statestores/statestores/priv/postgres_repo/migrations/20220521162411_create_lookups_table.exs new file mode 100644 index 000000000..30d34b5aa --- /dev/null +++ b/spawn_statestores/statestores/priv/postgres_repo/migrations/20220521162411_create_lookups_table.exs @@ -0,0 +1,21 @@ +defmodule Statestores.PostgresRepo.Migrations.CreateLookupsTable do + use Ecto.Migration + + def up do + create_if_not_exists table(:lookups, primary_key: false) do + add :id, :bigint, primary_key: true + add :node, :string, primary_key: true + add :actor, :string + add :system, :string + add :data, :binary + timestamps([type: :utc_datetime_usec]) + end + + create_if_not_exists unique_index(:lookups, [:id, :node]) + create_if_not_exists index(:lookups, [:node]) + end + + def down do + drop table(:lookups) + end +end diff --git a/spawn_statestores/statestores/priv/postgres_repo/migrations/20220521162412_create_historical_snapshots_table.exs b/spawn_statestores/statestores/priv/postgres_repo/migrations/20220521162412_create_historical_snapshots_table.exs new file mode 100644 index 000000000..1735d0e63 --- /dev/null +++ b/spawn_statestores/statestores/priv/postgres_repo/migrations/20220521162412_create_historical_snapshots_table.exs @@ -0,0 +1,27 @@ +defmodule Statestores.PostgresRepo.Migrations.CreateHistoricalSnapshotsTable do + use Ecto.Migration + + def up do + create_if_not_exists table(:historical_snapshots, primary_key: false) do + add(:historical_id, :bigserial, primary_key: true) + add(:actor_id, :bigint) + add(:actor, :string) + add(:system, :string) + add(:status, :string) + add(:node, :string) + add(:revision, :integer) + add(:tags, :map) + add(:data_type, :string) + add(:data, :binary) + add(:valid_from, :utc_datetime_usec) + add(:valid_to, :utc_datetime_usec) + timestamps(type: :utc_datetime_usec) + end + + create_if_not_exists(index(:historical_snapshots, [:status])) + end + + def down do + drop(table(:historical_snapshots)) + end +end diff --git a/spawn_statestores/statestores/priv/postgres_repo/migrations/20250314013201_change_snapshots_primary_key_to_actor.exs b/spawn_statestores/statestores/priv/postgres_repo/migrations/20250314013201_change_snapshots_primary_key_to_actor.exs new file mode 100644 index 000000000..847f4da05 --- /dev/null +++ b/spawn_statestores/statestores/priv/postgres_repo/migrations/20250314013201_change_snapshots_primary_key_to_actor.exs @@ -0,0 +1,28 @@ +defmodule Statestores.PostgresRepo.Migrations.ChangePrimaryKeyToActorAndDropId do + use Ecto.Migration + @disable_ddl_transaction true + + def up do + # Ensure actor can be a primary key (optional safety check) + execute """ + DO $$ + BEGIN + IF EXISTS ( + SELECT actor + FROM snapshots + WHERE actor IS NULL + OR actor IN (SELECT actor FROM snapshots GROUP BY actor HAVING COUNT(*) > 1) + ) THEN + RAISE EXCEPTION 'Cannot set actor as primary key: column contains NULL or duplicate values'; + END IF; + END + $$; + """ + + create_if_not_exists index(:snapshots, [:actor], unique: true, concurrently: true) + end + + def down do + drop_if_exists index(:snapshots, [:actor], unique: true) + end +end diff --git a/spawn_statestores/statestores/test/support/data_case.ex b/spawn_statestores/statestores/test/support/data_case.ex index 98e14ef3f..a48910bc9 100644 --- a/spawn_statestores/statestores/test/support/data_case.ex +++ b/spawn_statestores/statestores/test/support/data_case.ex @@ -5,7 +5,7 @@ defmodule Statestores.DataCase do using do quote do - use Statestores.SandboxHelper, repos: [Statestores.Util.load_snapshot_adapter()] + use Statestores.SandboxHelper, repos: [Statestores.Util.load_repo()] import Statestores.DataCase end diff --git a/spawn_statestores/statestores_postgres/lib/statestores/adapters/postgres_lookup_adapter.ex b/spawn_statestores/statestores_postgres/lib/statestores/adapters/postgres_lookup_adapter.ex index 9cb967b5d..5126e2db3 100644 --- a/spawn_statestores/statestores_postgres/lib/statestores/adapters/postgres_lookup_adapter.ex +++ b/spawn_statestores/statestores_postgres/lib/statestores/adapters/postgres_lookup_adapter.ex @@ -4,36 +4,35 @@ defmodule Statestores.Adapters.PostgresLookupAdapter do """ use Statestores.Adapters.LookupBehaviour - use Ecto.Repo, - otp_app: :spawn_statestores, - adapter: Ecto.Adapters.Postgres + import Ecto.Query alias Statestores.Schemas.{Lookup, ValueObjectSchema} + alias Statestores.PostgresRepo @impl true def clean(node) do node = Atom.to_string(node) - res = delete_all(from(l in Lookup, where: l.node == ^node)) + res = PostgresRepo.delete_all(from(l in Lookup, where: l.node == ^node)) {:ok, res} end @impl true def get_all_by_node(node) do node = Atom.to_string(node) - res = all(from(l in Lookup, where: l.node == ^node)) + res = PostgresRepo.all(from(l in Lookup, where: l.node == ^node)) {:ok, res} end @impl true def get_by_id(id) do key = generate_key(id) - {:ok, all(from(l in Lookup, where: l.id == ^key))} + {:ok, PostgresRepo.all(from(l in Lookup, where: l.id == ^key))} end @impl true def get_by_id_node(id, node) do node = Atom.to_string(node) - res = all(from(l in Lookup, where: l.id == ^id and l.node == ^node)) + res = PostgresRepo.all(from(l in Lookup, where: l.id == ^id and l.node == ^node)) {:ok, res} end @@ -52,7 +51,7 @@ defmodule Statestores.Adapters.PostgresLookupAdapter do %Lookup{} |> Lookup.changeset(ValueObjectSchema.to_map(event)) - |> insert_or_update( + |> PostgresRepo.insert_or_update( on_conflict: [ set: [ actor: actor, diff --git a/spawn_statestores/statestores_postgres/lib/statestores/adapters/postgres_projection_adapter.ex b/spawn_statestores/statestores_postgres/lib/statestores/adapters/postgres_projection_adapter.ex index 809083799..84860eed5 100644 --- a/spawn_statestores/statestores_postgres/lib/statestores/adapters/postgres_projection_adapter.ex +++ b/spawn_statestores/statestores_postgres/lib/statestores/adapters/postgres_projection_adapter.ex @@ -4,11 +4,8 @@ defmodule Statestores.Adapters.PostgresProjectionAdapter do """ use Statestores.Adapters.ProjectionBehaviour - use Ecto.Repo, - otp_app: :spawn_statestores, - adapter: Ecto.Adapters.Postgres - alias Ecto.Adapters.SQL + alias Statestores.PostgresRepo @type_map %{ :TYPE_INT32 => "INTEGER", @@ -38,7 +35,7 @@ defmodule Statestores.Adapters.PostgresProjectionAdapter do """ @impl true def create_or_update_table(protobuf_module, table_name) do - repo = __MODULE__ + repo = PostgresRepo descriptor = protobuf_module.descriptor() fields = descriptor.field @@ -171,7 +168,7 @@ defmodule Statestores.Adapters.PostgresProjectionAdapter do """ @impl true def query(protobuf_module, query, params, opts) do - repo = __MODULE__ + repo = PostgresRepo case validate_params(query, params) do {:error, message} -> @@ -295,6 +292,10 @@ defmodule Statestores.Adapters.PostgresProjectionAdapter do DateTime.to_iso8601(value) end + defp to_proto_decoded(%Decimal{} = value) do + Decimal.to_float(value) + end + defp to_proto_decoded(value) when is_atom(value) do Atom.to_string(value) end @@ -323,7 +324,7 @@ defmodule Statestores.Adapters.PostgresProjectionAdapter do """ @impl true def upsert(protobuf_module, table_name, data) do - repo = __MODULE__ + repo = PostgresRepo descriptor = protobuf_module.descriptor() fields = descriptor.field diff --git a/spawn_statestores/statestores_postgres/lib/statestores/adapters/postgres_snapshot_adapter.ex b/spawn_statestores/statestores_postgres/lib/statestores/adapters/postgres_snapshot_adapter.ex index eb7610d42..21fa372c4 100644 --- a/spawn_statestores/statestores_postgres/lib/statestores/adapters/postgres_snapshot_adapter.ex +++ b/spawn_statestores/statestores_postgres/lib/statestores/adapters/postgres_snapshot_adapter.ex @@ -4,160 +4,63 @@ defmodule Statestores.Adapters.PostgresSnapshotAdapter do """ use Statestores.Adapters.SnapshotBehaviour - use Ecto.Repo, - otp_app: :spawn_statestores, - adapter: Ecto.Adapters.Postgres + import Bitwise - alias Statestores.Schemas.{Snapshot, HistoricalSnapshot} + alias Statestores.Schemas.Snapshot + alias Statestores.PostgresRepo - def get_by_key(id), do: get_by(Snapshot, id: id) + def get_by_key(actor), do: PostgresRepo.get_by(Snapshot, actor: actor) - def get_by_key_and_revision(id, revision) do - query = """ - SELECT * - FROM historical_snapshots - WHERE actor_id = #{id} - AND revision = #{revision} - ORDER BY inserted_at DESC, updated_at DESC - """ - - %Postgrex.Result{rows: rows} = - Ecto.Adapters.SQL.query!(Statestores.Adapters.PostgresSnapshotAdapter, query) - - List.first(rows) - |> to_snapshot(:historical) - |> case do - nil -> get_by_key(id) - response -> response - end + def get_by_key_and_revision(_actor, _revision) do end - def get_all_snapshots_by_key(id) do - query = "SELECT * FROM historical_snapshots WHERE actor_id = #{id}" - - %Postgrex.Result{rows: rows} = - Ecto.Adapters.SQL.query!(Statestores.Adapters.PostgresSnapshotAdapter, query) - - rows - |> Enum.map(&to_snapshot(&1, :historical)) + def get_all_snapshots_by_key(_actor) do end - def get_snapshots_by_interval(id, time_start, time_end) do - query = """ - SELECT * - FROM historical_snapshots - WHERE actor_id = #{id} - AND valid_from <= '#{time_end}' - AND valid_to >= '#{time_start}' - ORDER BY inserted_at ASC, updated_at ASC - """ - - %Postgrex.Result{rows: rows} = - Ecto.Adapters.SQL.query!(Statestores.Adapters.PostgresSnapshotAdapter, query) - - rows - |> Enum.map(&to_snapshot(:historical, &1)) + def get_snapshots_by_interval(_actor, _time_start, _time_end) do end def save(%Snapshot{} = actual_snapshot) do - __MODULE__.transaction(fn -> - case get_by_key(actual_snapshot.id) do - nil -> - # Insert the new snapshot as there's no previous one - insert_new_snapshot(actual_snapshot) - - previous_snapshot -> - # Move the previous snapshot to historical_snapshots - move_to_historical_snapshots(previous_snapshot) - - # Update the current snapshot with the new data - update_snapshot(previous_snapshot, actual_snapshot) - end - end) - end - - defp insert_new_snapshot(%Snapshot{} = snapshot) do - snapshot - |> Map.put(:revision, 1) - |> __MODULE__.insert() - end - - defp move_to_historical_snapshots(%Snapshot{} = previous_snapshot) do - %HistoricalSnapshot{ - actor_id: previous_snapshot.id, - actor: previous_snapshot.actor, - system: previous_snapshot.system, - status: previous_snapshot.status, - node: previous_snapshot.node, - revision: previous_snapshot.revision, - tags: previous_snapshot.tags, - data_type: previous_snapshot.data_type, - data: previous_snapshot.data, - valid_from: previous_snapshot.inserted_at, - valid_to: previous_snapshot.updated_at - } - |> __MODULE__.insert() - end - - defp update_snapshot(%Snapshot{} = previous_snapshot, %Snapshot{} = actual_snapshot) do - changeset = - actual_snapshot - |> Map.put(:revision, previous_snapshot.revision + 1) - |> build_snapshot_changeset(previous_snapshot) - - __MODULE__.update(changeset) - end - - def default_port, do: "5432" - - defp build_snapshot_changeset(%Snapshot{} = snapshot, %Snapshot{} = previous_snapshot) do - previous_snapshot + %Snapshot{} |> Snapshot.changeset(%{ - id: snapshot.id, - actor: snapshot.actor, - system: snapshot.system, - status: snapshot.status, - node: snapshot.node, - revision: snapshot.revision || 0, - tags: snapshot.tags, - data_type: snapshot.data_type, - data: snapshot.data + id: random_64bit(), + actor: actual_snapshot.actor, + system: actual_snapshot.system, + status: actual_snapshot.status, + node: actual_snapshot.node, + revision: actual_snapshot.revision || 0, + tags: actual_snapshot.tags, + data_type: actual_snapshot.data_type, + data: actual_snapshot.data }) + |> PostgresRepo.insert_or_update( + on_conflict: [ + set: [ + system: actual_snapshot.system, + status: actual_snapshot.status, + node: actual_snapshot.node, + revision: (actual_snapshot.revision || 0) + 1, + tags: actual_snapshot.tags, + data_type: actual_snapshot.data_type, + data: actual_snapshot.data, + updated_at: DateTime.utc_now() + ] + ], + conflict_target: [:actor] + ) end - defp to_snapshot(row, :current) do - data = Statestores.Vault.decrypt!(Enum.at(row, 8)) - - %Snapshot{ - id: Enum.at(row, 0), - actor: Enum.at(row, 1), - system: Enum.at(row, 2), - status: Enum.at(row, 3), - node: Enum.at(row, 4), - revision: Enum.at(row, 5), - tags: Enum.at(row, 6), - data_type: Enum.at(row, 7), - data: data, - inserted_at: Enum.at(row, 9), - updated_at: Enum.at(row, 10) - } - end + def default_port, do: "5432" - defp to_snapshot(row, :historical) do - data = Statestores.Vault.decrypt!(Enum.at(row, 9)) + defp random_64bit do + # Generate a random unsigned 63-bit integer + rand_unsigned = trunc(:rand.uniform() * (1 <<< 63)) - %Snapshot{ - id: Enum.at(row, 1), - actor: Enum.at(row, 2), - system: Enum.at(row, 3), - status: Enum.at(row, 4), - node: Enum.at(row, 5), - revision: Enum.at(row, 6), - tags: Enum.at(row, 7), - data_type: Enum.at(row, 8), - data: data, - inserted_at: Enum.at(row, 12), - updated_at: Enum.at(row, 13) - } + # Randomly choose whether to make it negative or positive + if :rand.uniform() < 0.5 do + rand_unsigned + else + -rand_unsigned + end end end diff --git a/spawn_statestores/statestores_postgres/lib/statestores/postgres_repo.ex b/spawn_statestores/statestores_postgres/lib/statestores/postgres_repo.ex new file mode 100644 index 000000000..4e97f64b4 --- /dev/null +++ b/spawn_statestores/statestores_postgres/lib/statestores/postgres_repo.ex @@ -0,0 +1,9 @@ +defmodule Statestores.PostgresRepo do + use Ecto.Repo, + otp_app: :spawn_statestores, + adapter: Ecto.Adapters.Postgres + + import Statestores.Util, only: [init_config: 1] + + def init(_type, config), do: init_config(config) +end diff --git a/spawn_statestores/statestores_postgres/test/postgres_projection_test.exs b/spawn_statestores/statestores_postgres/test/postgres_projection_test.exs index 5879c7be2..4859d342b 100644 --- a/spawn_statestores/statestores_postgres/test/postgres_projection_test.exs +++ b/spawn_statestores/statestores_postgres/test/postgres_projection_test.exs @@ -4,10 +4,10 @@ defmodule StatestoresPostgres.PostgresProjectionTest do alias Statestores.Manager.StateManager alias Test.TestMessage - import Statestores.Util, only: [load_projection_adapter: 0] + import Statestores.Util, only: [load_repo: 0] setup do - repo = load_projection_adapter() + repo = load_repo() table_name = "test_messages" data = %TestMessage{ diff --git a/spawn_statestores/statestores_postgres/test/repo_test.exs b/spawn_statestores/statestores_postgres/test/repo_test.exs index e8f3630e0..9693e6d1b 100644 --- a/spawn_statestores/statestores_postgres/test/repo_test.exs +++ b/spawn_statestores/statestores_postgres/test/repo_test.exs @@ -1,7 +1,8 @@ defmodule StatestoresPostgresTest.RepoTest do use Statestores.DataCase alias Statestores.Schemas.Snapshot - import Statestores.Util, only: [load_snapshot_adapter: 0, generate_key: 1] + alias Statestores.Adapters.PostgresSnapshotAdapter + import Statestores.Util, only: [generate_key: 1] setup do %{system: "test-system"} @@ -13,7 +14,6 @@ defmodule StatestoresPostgresTest.RepoTest do actor = "mike" id = %{name: actor, system: system} key = generate_key(id) - repo = load_snapshot_adapter() event = %Snapshot{ id: key, @@ -27,10 +27,10 @@ defmodule StatestoresPostgresTest.RepoTest do data: "Hello Joe" } - _result = repo.save(event) + _result = PostgresSnapshotAdapter.save(event) # Ensure no historical snapshot is created - historical_events = repo.get_all_snapshots_by_key(key) + historical_events = PostgresSnapshotAdapter.get_all_snapshots_by_key(key) assert length(historical_events) == 0 end @@ -40,7 +40,6 @@ defmodule StatestoresPostgresTest.RepoTest do actor = "mike" id = %{name: actor, system: system} key = generate_key(id) - repo = load_snapshot_adapter() event = %Snapshot{ id: key, @@ -53,8 +52,8 @@ defmodule StatestoresPostgresTest.RepoTest do data: "Hello Joe" } - _result = repo.save(event) - actor_state = repo.get_by_key(key) + _result = PostgresSnapshotAdapter.save(event) + actor_state = PostgresSnapshotAdapter.get_by_key(key) # IO.inspect(actor_state, label: "First event") # Ensure the initial state is correct @@ -64,8 +63,8 @@ defmodule StatestoresPostgresTest.RepoTest do # Simulate an update after some time Process.sleep(1000) updated_event = %{event | data: "new joe"} - _result = repo.save(updated_event) - actor_state2 = repo.get_by_key(key) + _result = PostgresSnapshotAdapter.save(updated_event) + actor_state2 = PostgresSnapshotAdapter.get_by_key(key) # IO.inspect(actor_state2, label: "Updated event") # Validate that the current_snapshots table was updated @@ -74,7 +73,7 @@ defmodule StatestoresPostgresTest.RepoTest do assert actor_state.updated_at != actor_state2.updated_at # Validate that a record was added to historical_snapshots - historical_events = repo.get_all_snapshots_by_key(key) + historical_events = PostgresSnapshotAdapter.get_all_snapshots_by_key(key) assert length(historical_events) == 1 historical_event = List.first(historical_events) @@ -89,7 +88,6 @@ defmodule StatestoresPostgresTest.RepoTest do actor = "mike" id = %{name: actor, system: system} key = generate_key(id) - repo = load_snapshot_adapter() # Insert initial snapshot event = %Snapshot{ @@ -104,32 +102,32 @@ defmodule StatestoresPostgresTest.RepoTest do data: "Initial" } - _result = repo.save(event) + _result = PostgresSnapshotAdapter.save(event) # First update Process.sleep(1000) first_update = %{event | data: "First Update"} - _result = repo.save(first_update) + _result = PostgresSnapshotAdapter.save(first_update) # Second update Process.sleep(1000) second_update = %{first_update | data: "Second Update"} - _result = repo.save(second_update) + _result = PostgresSnapshotAdapter.save(second_update) # Third update Process.sleep(1000) third_update = %{second_update | data: "Third Update"} - _result = repo.save(third_update) + _result = PostgresSnapshotAdapter.save(third_update) # Validate current snapshot # TODO: The implementation should include the snapshot table when using get_all_snapshots... # This needs to be implemented in the future and this test should be changed to reflect the implementation - final_state = repo.get_by_key(key) + final_state = PostgresSnapshotAdapter.get_by_key(key) assert final_state.data == "Third Update" assert final_state.revision == 4 # Validate historical snapshots - historical_events = repo.get_all_snapshots_by_key(key) + historical_events = PostgresSnapshotAdapter.get_all_snapshots_by_key(key) assert length(historical_events) == 3 # Check the data and revision for each historical snapshot @@ -149,7 +147,6 @@ defmodule StatestoresPostgresTest.RepoTest do actor = "mike" id = %{name: actor, system: system} key = generate_key(id) - repo = load_snapshot_adapter() event = %Snapshot{ id: key, @@ -163,14 +160,14 @@ defmodule StatestoresPostgresTest.RepoTest do data: "Hello Joe" } - _result = repo.save(event) - actor_state = repo.get_by_key(key) + _result = PostgresSnapshotAdapter.save(event) + actor_state = PostgresSnapshotAdapter.get_by_key(key) assert actor_state.data == "Hello Joe" assert actor_state.revision == 1 # Validate that no historical record was created on the first insert - historical_events = repo.get_all_snapshots_by_key(key) + historical_events = PostgresSnapshotAdapter.get_all_snapshots_by_key(key) assert length(historical_events) == 0 end @@ -180,7 +177,6 @@ defmodule StatestoresPostgresTest.RepoTest do actor = "mike" id = %{name: actor, system: system} key = generate_key(id) - repo = load_snapshot_adapter() event = %Snapshot{ id: key, @@ -194,14 +190,14 @@ defmodule StatestoresPostgresTest.RepoTest do data: "Hello Joe" } - _result = repo.save(event) + _result = PostgresSnapshotAdapter.save(event) Process.sleep(1000) updated_event = %{event | data: "Hello Joe"} - _result = repo.save(updated_event) + _result = PostgresSnapshotAdapter.save(updated_event) # Retrieve the historical snapshot by revision - historical_event = repo.get_by_key_and_revision(key, 1) + historical_event = PostgresSnapshotAdapter.get_by_key_and_revision(key, 1) assert historical_event.data == "Hello Joe" assert historical_event.revision == 1 diff --git a/spawn_statestores/statestores_postgres/test/support/data_case.ex b/spawn_statestores/statestores_postgres/test/support/data_case.ex index c60cd65e7..a48910bc9 100644 --- a/spawn_statestores/statestores_postgres/test/support/data_case.ex +++ b/spawn_statestores/statestores_postgres/test/support/data_case.ex @@ -5,11 +5,7 @@ defmodule Statestores.DataCase do using do quote do - use Statestores.SandboxHelper, - repos: [ - Statestores.Util.load_snapshot_adapter(), - Statestores.Util.load_projection_adapter() - ] + use Statestores.SandboxHelper, repos: [Statestores.Util.load_repo()] import Statestores.DataCase end diff --git a/test/support/data_case.ex b/test/support/data_case.ex index ce8675fc1..ecf47c908 100644 --- a/test/support/data_case.ex +++ b/test/support/data_case.ex @@ -6,7 +6,7 @@ defmodule Actors.DataCase do using do quote do if Statestores.Util.load_snapshot_adapter() != Statestores.Adapters.NativeSnapshotAdapter do - use Statestores.SandboxHelper, repos: [Statestores.Util.load_snapshot_adapter()] + use Statestores.SandboxHelper, repos: [Statestores.Util.load_repo()] end use Actors.MockTest diff --git a/test/support/factory.ex b/test/support/factory.ex index 0f5ed1842..3b18f8ff6 100644 --- a/test/support/factory.ex +++ b/test/support/factory.ex @@ -116,13 +116,9 @@ defmodule Actors.FactoryTest do def build_actor_state(attrs \\ []) do state = - if is_nil(attrs[:state]) do - any_pack!(%Actors.Protos.StateTest{name: "example_state_name_#{inspect(make_ref())}"}) - else - any_pack!(attrs[:state]) - end + any_pack!(%Actors.Protos.StateTest{name: "example_state_name_#{inspect(make_ref())}"}) - %ActorState{state: state} + %ActorState{state: Any.new(attrs[:state] || state)} end def build_actor_deactivation_strategy(attrs \\ []) do