jobs:
build:
- docker:
- - image: cimg/base:stable
+ machine:
+ image: 'ubuntu-2004:202010-01'
steps:
- checkout
- setup-bazel
+ - restore_cache:
+ keys:
+ - bazel-cache-{{ .Branch }}
- run:
name: "Build"
command: "bazel build //:packages"
+ - save_cache:
+ paths:
+ - ~/.cache/bazel
+ key: bazel-cache-{{ .Branch }}
+ - run:
+ name: "Move artifacts"
+ command: |
+ mkdir ~/project/artifacts
+ mv ~/project/bazel-bin/packages* ~/project/artifacts
- store_artifacts:
- path: ~/project/bazel-bin/packages*
+ path: ~/project/artifacts
workflows:
build-workflow:
.vscode\r
ratelimiter/target\r
target/\r
-**/local*
\ No newline at end of file
+**/local*\r
+.ijwb\r
+.idea
\ No newline at end of file
--- /dev/null
+gazelle:
+ bazel run //:gazelle -- update-repos -from_file=go.mod -to_macro=//bazel:deps.bzl%go_dependencies
\ No newline at end of file
--- /dev/null
+"""Rule for simple expansion of template files. This performs a simple
+search over the template file for the keys in substitutions,
+and replaces them with the corresponding values.
+
+Typical usage:
+ load("//tools/build_rules:expand_template.bzl", "expand_template")
+ expand_template(
+ name = "ExpandMyTemplate",
+ template = "my.template",
+ out = "my.txt",
+ substitutions = {
+ "$VAR1": "foo",
+ "$VAR2": "bar",
+ }
+ )
+
+Args:
+ name: The name of the rule.
+ template: The template file to expand
+ out: The destination of the expanded file
+ substitutions: A dictionary mapping strings to their substitutions
+ is_executable: A boolean indicating whether the output file should be executable
+"""
+
+def expand_template_impl(ctx):
+ ctx.actions.expand_template(
+ template = ctx.file.template,
+ output = ctx.outputs.out,
+ substitutions = {
+ k: ctx.expand_location(v, ctx.attr.data)
+ for k, v in ctx.attr.substitutions.items()
+ },
+ is_executable = ctx.attr.is_executable,
+ )
+
+expand_template = rule(
+ implementation = expand_template_impl,
+ attrs = {
+ "template": attr.label(mandatory = True, allow_single_file = True),
+ "substitutions": attr.string_dict(mandatory = True),
+ "out": attr.output(mandatory = True),
+ "is_executable": attr.bool(default = False, mandatory = False),
+ "data": attr.label_list(allow_files = True),
+ },
+)
\ No newline at end of file
--- /dev/null
+load("@rules_proto//proto:defs.bzl", "proto_library")
+load("@io_bazel_rules_go//go:def.bzl", "go_library")
+load("@io_bazel_rules_go//proto:def.bzl", "go_proto_library")
+
+proto_library(
+ name = "nova_ratelimit_v1_proto",
+ srcs = ["nova.management.v1alpha.proto"],
+ visibility = ["//visibility:public"],
+)
+
+go_proto_library(
+ name = "nova_ratelimit_v1_go_proto",
+ compilers = ["@io_bazel_rules_go//proto:go_grpc"],
+ importpath = "github.com/discordnova/nova/common/management",
+ proto = ":nova_ratelimit_v1_proto",
+ visibility = ["//visibility:public"],
+)
+
+go_library(
+ name = "management",
+ embed = [":nova_ratelimit_v1_go_proto"],
+ importpath = "github.com/discordnova/nova/common/management",
+ visibility = ["//visibility:public"],
+)
--- /dev/null
+syntax = "proto3";
+package nova.ratelimit.v1;
+
+message Empty {}
+
+// Represents the status of a shard
+enum ShardStatus {
+ DISCONNECTED = 0;
+ RUNNING = 1;
+ RECONNECTING = 2;
+}
+
+// represents the state of a nova shard
+message ShardStatusResponse {
+ // Status of the shard in the cluster
+ ShardStatus status = 1;
+ // Index of the discord shard
+ int64 identifier = 2;
+ // If the cluster have a node assigned
+ string cluster = 3;
+ // the websocket latency of the shard
+ int64 latency = 4;
+}
+
+message ShardStatusRequest {
+ // the id of the shard
+ int64 identifier = 1;
+}
+
+// represents the status of a cluster
+// (an instance of the gateway which holds multiple shards)
+message ClusterStatusResponse {
+ // the unique id of the cluster
+ string id = 1;
+ // the node the cluster is running on
+ string node = 2;
+ // the average latency of the cluster
+ int64 average_latency = 3;
+ // list of all the shards on the cluster
+ repeated ShardStatusResponse shards = 4;
+}
+
+message ClusterStatusRequest {
+ string id = 1;
+}
+
+// Represents the status of all the nova clusters & shards
+message GlobalClusterStatusResponse {
+ int64 size = 1;
+ repeated ClusterStatusResponse shards = 2;
+}
+
+// used by the cli to interact with the nova manager
+service ManagementService {
+ rpc GetGlobalClusterStatus (Empty) returns (GlobalClusterStatusResponse);
+ rpc GetClusterStatus (ClusterStatusRequest) returns (ClusterStatusResponse);
+ rpc GetShardStatus (ShardStatusRequest) returns (ShardStatusResponse);
+}
\ No newline at end of file
--- /dev/null
+package common
+
+const (
+ VERSION = "$VERSION"
+)
--- /dev/null
+package cmd
+
+import (
+ "context"
+ "errors"
+ "github.com/discordnova/nova/common/management"
+ "github.com/discordnova/nova/novactl/lib"
+ "github.com/olekukonko/tablewriter"
+ "github.com/rs/zerolog/log"
+ "github.com/spf13/cobra"
+ "os"
+ "strconv"
+ "time"
+)
+
+var ClusterCommand = createClusterCommand()
+
+var (
+ ServerUrl *string = nil
+ Ctx = context.Background()
+)
+
+func createClusterCommand() *cobra.Command {
+ command := &cobra.Command{
+ Use: "cluster",
+ Short: "Commands to interact with the nova cluster",
+ Aliases: []string{"c"},
+ TraverseChildren: true,
+ }
+ // shard sub command
+ shard := cobra.Command{
+ Use: "shard [shard]",
+ Short: "Returns information about a specific shard",
+ Run: shardCommand,
+ Args: func(cmd *cobra.Command, args []string) error {
+ if len(args) != 1 {
+ return errors.New("one shard id must be specified")
+ } else {
+ if _, err := strconv.Atoi(args[0]); err != nil {
+ return errors.New("the shard id must be a string")
+ }
+ }
+ return nil
+ },
+ TraverseChildren: true,
+ }
+ // info sub command
+ info := cobra.Command{
+ Use: "info",
+ Short: "Gets the status of the cluster",
+ Aliases: []string{"i"},
+ Run: infoCommand,
+ TraverseChildren: true,
+ }
+
+ ServerUrl = command.Flags().StringP("server", "s", "localhost:8053", "")
+
+ command.AddCommand(&shard)
+ command.AddCommand(&info)
+
+ return command
+}
+
+func shardCommand(command *cobra.Command, args []string) {
+ id, err := strconv.ParseInt(args[0], 10, 64)
+ if err != nil {
+ log.Err(err).Msg("Failed to parse the shard id")
+ os.Exit(1)
+ }
+
+ log.Info().Msgf("Starting connection with server %s", *ServerUrl)
+ conn, err := lib.NewConn(*ServerUrl)
+
+ if err != nil {
+ log.Err(err).Msg("Failed to connect to the manager")
+ return
+ }
+ ctx, _ := context.WithTimeout(Ctx, time.Second*10)
+
+ manager := *conn
+ data, err := manager.GetShardStatus(ctx, &management.ShardStatusRequest{
+ Identifier: id,
+ })
+
+ if err != nil {
+ log.Err(err).Msg("Failed to get the status of the shard")
+ return
+ }
+
+ table := tablewriter.NewWriter(os.Stdout)
+ table.SetHeader([]string{"Status", "Id", "Cluster", "Latency"})
+ table.Append([]string{string(data.Status), string(data.Identifier), data.Cluster, string(data.Latency)})
+ table.Render()
+}
+
+func infoCommand(command *cobra.Command, args []string) {}
--- /dev/null
+package lib
+
+import (
+ "google.golang.org/grpc"
+
+ "github.com/discordnova/nova/common/management"
+)
+
+// NewConn creates a connection to the manager
+func NewConn(host string) (*management.ManagementServiceClient, error) {
+ lis, err := grpc.Dial(host, grpc.WithInsecure())
+ if err != nil {
+ return nil, err
+ }
+
+ conn := management.NewManagementServiceClient(lis)
+
+ return &conn, nil
+}