edition = "2023";

// If you make any changes make sure you run: make regenerate-proto

package gitlab.agent.kubernetes_api.rpc;

option go_package = "gitlab.com/gitlab-org/cluster-integration/gitlab-agent/v18/internal/module/kubernetes_api/rpc";
option features.field_presence = IMPLICIT;

import "internal/tool/grpctool/grpctool.proto";
import "internal/module/kubernetes_api/kubernetes_api.proto";
import "buf/validate/validate.proto";

service KubernetesApi {
  // MakeRequest allows to make a HTTP request to Kubernetes API.
  rpc MakeRequest (stream grpctool.HttpRequest) returns (stream grpctool.HttpResponse) {
  }
  // WatchGraph streams back a graph representation of Kubernetes objects.
  rpc WatchGraph(WatchGraphRequest) returns (stream WatchGraphResponse) {
  }
  // WatchGraphWithRoots is the same as WatchGraph but is guaranteed to support roots filtering.
  rpc WatchGraphWithRoots(WatchGraphRequest) returns (stream WatchGraphResponse) {
  }
}

message Roots {
  repeated string object_selector_expressions = 1 [
    json_name = "object_selector_expressions",
    (buf.validate.field).repeated.min_items = 1,
    (buf.validate.field).repeated.items.string.min_bytes = 1
  ];
  repeated string ignore_arc_direction = 2 [
    json_name = "ignore_arc_direction",
    (buf.validate.field).repeated.items.string.(is_arc_type) = true
  ];
}

message Query {
  message IncludeObject {
    string label_selector = 1 [json_name = "label_selector"]; // can be empty
    string field_selector = 2 [json_name = "field_selector"]; // can be empty
    string object_selector_expression = 3 [
      json_name = "object_selector_expression",
      (buf.validate.field).string.max_bytes = 1024
    ]; // can be empty
    string json_path = 4 [json_name = "json_path"]; // can be empty
  }

  message Include {
    string resource_selector_expression = 1 [
      json_name = "resource_selector_expression",
      (buf.validate.field).string.min_bytes = 1,
      (buf.validate.field).string.max_bytes = 1024
    ];
    IncludeObject object = 2 [json_name = "object"]; // optional
  }

  message Exclude {
    string resource_selector_expression = 1 [
      json_name = "resource_selector_expression",
      (buf.validate.field).string.min_bytes = 1,
      (buf.validate.field).string.max_bytes = 1024
    ];
  }

  oneof query {
    option (buf.validate.oneof).required = true;

    Include include = 1 [json_name = "include"];
    Exclude exclude = 2 [json_name = "exclude"];
  }
}

message Namespaces {
  option (buf.validate.message).cel = {
    id: "names_xor_labels_fields",
    message: "either names or selectors can be use, not both",
    expression: "!(this.names.size() > 0 && (this.label_selector != '' || this.field_selector != ''))"
  };

  // empty array is ok, but empty strings are not allowed
  repeated string names = 1 [json_name = "names", (buf.validate.field).repeated.items.string.min_bytes = 1];
  string label_selector = 2 [json_name = "label_selector"]; // can be empty
  string field_selector = 3 [json_name = "field_selector"]; // can be empty
  string object_selector_expression = 4 [
    json_name = "object_selector_expression",
    (buf.validate.field).string.max_bytes = 1024
  ]; // can be empty
}

message WatchGraphWebSocketRequest {
  repeated Query queries = 1 [json_name = "queries", (buf.validate.field).repeated.min_items = 1];
  Namespaces namespaces = 2 [json_name = "namespaces"]; // optional
  Roots roots = 3 [json_name = "roots"]; // optional
}

message WatchGraphRequest {
  // Optional impersonation configuration.
  ImpersonationConfig imp_config = 1;

  repeated Query queries = 2 [(buf.validate.field).repeated.min_items = 1];
  Namespaces namespaces = 3; // optional
  Roots roots = 4; // optional
}

message Action {
  message Vertex {
    string group = 1 [json_name = "g"]; // can be empty
    string version = 2 [json_name = "v", (buf.validate.field).string.min_bytes = 1];
    string resource = 3 [json_name = "r", (buf.validate.field).string.min_bytes = 1];
    string namespace = 4 [json_name = "ns"]; // can be empty
    string name = 5 [json_name = "n", (buf.validate.field).string.min_bytes = 1];
  }

  // Must be marshaled to JSON manually, so no json_name options.
  message SetVertex {
    Vertex vertex = 1 [(buf.validate.field).required = true];

    // cannot use oneof with "repeated". TODO validate at most one is set with CEL.

    bytes object = 2; // raw JSON to avoid proto unmarshaling and JSON marshaling overhead
    bytes json_path = 3; // array of raw JSON to avoid proto unmarshaling and JSON marshaling overhead
    bytes helm_release = 4; // raw JSON to avoid proto unmarshaling and JSON marshaling overhead
  }

  message DeleteVertex {
    Vertex vertex = 1 [json_name = "vx", (buf.validate.field).required = true];
  }

  // Must be marshaled to JSON manually, so no json_name options.
  message SetArc {
    Vertex source = 1 [(buf.validate.field).required = true];
    Vertex destination = 2 [(buf.validate.field).required = true];
    string type = 3 [(buf.validate.field).string.(is_arc_type) = true];
    bytes attributes = 4; // raw JSON to avoid proto unmarshaling and JSON marshaling overhead
  }

  message DeleteArc {
    Vertex source = 1 [json_name = "s", (buf.validate.field).required = true];
    Vertex destination = 2 [json_name = "d", (buf.validate.field).required = true];
    string type = 3 [json_name = "t", (buf.validate.field).string.(is_arc_type) = true];
  }

  oneof action {
    option (buf.validate.oneof).required = true;

    SetVertex set_vertex = 1 [json_name = "svx"];
    DeleteVertex delete_vertex = 2 [json_name = "dvx"];

    SetArc set_arc = 3 [json_name = "sarc"];
    DeleteArc delete_arc = 4 [json_name = "darc"];
  }
}

// Must be marshaled to JSON manually, so no json_name options.
message Warning {
  string type = 1 [
    (buf.validate.field).string.in = "INFORMER_SYNC_FAILED",
    (buf.validate.field).string.in = "DISCOVERY_FAILED",
    (buf.validate.field).string.in = "NAMESPACE_LIST_FAILED",
    (buf.validate.field).string.in = "OBJECT_PROCESSING_FAILED",
    (buf.validate.field).string.in = "INTERNAL_ERROR"
  ];
  string message = 2 [(buf.validate.field).string.min_bytes = 1];
  bytes attributes = 3; // raw JSON to avoid proto unmarshaling and JSON marshaling overhead
}

// Must be marshaled to JSON manually, so no json_name options.
message WatchGraphResponse {
  repeated Action actions = 1;
  repeated Warning warnings = 2;
}

// HeaderExtra is passed in grpctool.HttpRequest.extra.
// Message must not be moved or renamed since its used in an Any WKT.
message HeaderExtra {
  // Optional impersonation configuration.
  kubernetes_api.ImpersonationConfig imp_config = 1;
}

// See https://github.com/bufbuild/protovalidate/blob/main/docs/predefined-constraints.md.
extend buf.validate.StringRules {
  bool is_arc_type = 193001 [(buf.validate.predefined).cel = {
    id: "string.is_arc_type"
    expression: "!rule || this in ['or', 'r', 't']" // is_arc_type==false or is_arc_type==true and in the list
    message: "invalid arc type"
  }];
}
