# Runner (Admission) Controller

The Runner Controller module enables external job admission control for GitLab CI/CD pipelines when routing jobs.
It allows users to build custom controllers that intercept job execution requests during dequeuing and make
admission decisions before jobs are dispatched to runners.

Use cases include:
- Custom admission policies (e.g., user allowlists, image allowlists, resource quotas)
- Capacity management
- Compliance and security enforcement
- Cost control and resource allocation

## Architecture

Runner Controllers connect to the Job Router using gRPC and maintain a bidirectional streaming
connection for receiving job admission requests and sending decisions.

```
GitLab Instance → Job Router (KAS) → Runner Controller
                        ↓
                  AdmitJob RPC (bidirectional stream)
```

The admission flow:
1. Controller authenticates with the Job Router using a runner controller agent token.
2. Controller registers itself with the Job Router for presence tracking.
3. Controller opens a bidirectional stream via `AdmitJob`.
4. Job Router sends job details and runner information when a job needs admission to be run on a specific runner.
5. Controller evaluates the job request against custom policies.
6. Controller sends admission decision (admit or reject with reason) back to the Job Router.
7. Job Router proceeds with job execution or reports rejection.

## Authentication

Runner Controllers authenticate with the Job Router using gRPC metadata headers:

| Header | Value |
|--------|-------|
| `authorization` | `Bearer <agent-token>` |
| `gitlab-agent-type` | `runnerc` |

These headers must be included with every RPC call using gRPC's `PerRPCCredentials` mechanism.

## gRPC Services

### `RunnerControllerService`

Defined in [`internal/module/runner_controller/rpc/runner_controller.proto`](/internal/module/runner_controller/rpc/runner_controller.proto).

The main service for job admission control.

**RPC:** `AdmitJob(stream AdmitJobResponse) returns (stream AdmitJobRequest)`

This is a bidirectional stream where the Job Router sends `AdmitJobRequest` messages and the
controller responds with `AdmitJobResponse` messages.

#### `AdmitJobRequest`

Contains job-related fields (project ID, name, full path, namespace IDs, organization ID),
user information, container image details, and runner ID.

See the proto file for the complete field definitions.

#### `AdmitJobResponse`

Must contain exactly one of `admitted` (job proceeds) or `rejected` (with a required `reason`
string).

See the proto file for the complete field definitions.

### AgentRegistrar

Defined in [`internal/module/agent_registrar/runnerc_rpc/rpc.proto`](/internal/module/agent_registrar/runnerc_rpc/rpc.proto).

Service for controller registration and presence tracking. **Strongly recommended** for all
Runner Controller implementations to enable monitoring, debugging, and operational visibility.

**RPC:** `Register(RegisterRequest) returns (RegisterResponse)` (idempotent)
**RPC:** `Unregister(UnregisterRequest) returns (UnregisterResponse)` (idempotent)

Both RPCs accept a `Meta` message containing controller metadata and an `instance_id` that
uniquely identifies the controller process instance. See the proto file for the complete
field definitions.

### `Meta` (Controller Metadata)

Defined in [`pkg/entity/runnerc/runnerc.proto`](/pkg/entity/runnerc/runnerc.proto).

Contains version information, git reference, custom telemetry data, and architecture.

See the proto file for the complete field definitions.

## Protocol Flow

1. **Connection**: Establish gRPC connection to the Job Router with TLS and token authentication
2. **Registration**: Call `AgentRegistrar.Register` to announce presence; re-register periodically (recommended: every 3 minutes) to maintain presence
3. **Admission Loop**:
   - Call `RunnerControllerService.AdmitJob` to open bidirectional stream
   - Wait for `AdmitJobRequest` from the Job Router (blocking)
   - Evaluate job and send `AdmitJobResponse`
   - Close send side of stream
   - Wait for server to close the stream (EOF)
   - Repeat for next job
4. **Shutdown**: Call `AgentRegistrar.Unregister` before terminating

## Generating Client Code

You can generate client code from the protobuf definitions using any method you prefer,
including vendoring the proto files manually into your project.

The following example uses [buf](https://buf.build/) to generate Go client code.

Create `buf.gen.yaml`:

```yaml
version: v2

managed:
  enabled: true

  disable:
    - module: buf.build/bufbuild/protovalidate

  override:
    - file_option: go_package
      value: internal/rpc

inputs:
  - git_repo: https://gitlab.com/gitlab-org/cluster-integration/gitlab-agent.git
    branch: master

plugins:
  - local: ["go", "run", "google.golang.org/protobuf/cmd/protoc-gen-go@v1.36.10"]
    out: .
  - local: ["go", "run", "google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.5.1"]
    out: .
```

Generate the code:

```bash
buf generate
```

## Example Implementation

For a complete reference implementation, see the
[runner-controller-example](https://gitlab.com/gitlab-org/cluster-integration/runner-controller-example)
repository.
