Skip to content

Commit

Permalink
add an initial getting started guide and some placeholder documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
jlarfors committed Mar 13, 2024
1 parent 75e5735 commit c2ee447
Show file tree
Hide file tree
Showing 19 changed files with 534 additions and 241 deletions.
6 changes: 5 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@

CI_CMD := go run ./cmd/ci/*.go
CI_CMD := go run ./cmd/ci/ci.go

.PHONY: generate
generate:
$(CI_CMD) -generate

.PHONY: pr
pr:
Expand Down
96 changes: 5 additions & 91 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,105 +47,19 @@ Below is a high-level overview of the different Horizon components. For a more d
> [!NOTE]
> All communication is handled via NATS. As such, the dotted lines between components do not actually exist but are subject-based pub/subs via NATS.
Your end users will typically interact with Horizon via HTTP servers (portals) or command line tools that the platform team build. More on [portals](#platform---portals).
Your end users will typically interact with Horizon via HTTP servers (portals) or command line tools that the platform team build. More on [portals](./docs/architecture.md#platform---portals).

For provisioning "resources" (e.g. cloud infrastructure, Git repositories, artifact respositories) the platform team will define objects and a controller to handle reconciliation (taking an object specifiation and moving the object towards the desired state). More on [controllers](#platform---controllers).
For provisioning "resources" (e.g. cloud infrastructure, Git repositories, artifact respositories) the platform team will define objects and a controller to handle reconciliation (taking an object specifiation and moving the object towards the desired state). More on [controllers](./docs/architecture.md#platform---controllers).

For running operations across different nodes, actors can be called via the broker and actors are selected based on their labels. More on [actors](#platform---actors).
For running operations across different nodes, actors can be called via the broker and actors are selected based on their labels. More on [actors](./docs/architecture.md#platform---actors).

## Architecture

This section describes the different components of Horizon to build a platform.

### Core

The "core" consists of a [NATS](https://nats.io/) server and some internal services.

#### Core - NATS

Horizon requires a NATS server. Horizon makes heavy use of NATS: basic subject based pub/sub, accounts for multitenancy, streams and consumers for controllers and the Key-Value store for storing the objects (a NATS KV is actually just a glorified stream in the end).

You do not need to know NATS to get started with Horizon, but if you want to get serious with Horizon you should learn enough about NATS to debug any issues.
Horizon does not try to hide away the NATS abstractions.
Therefore if anything goes wrong, you can always connect to NATS directly for debugging.
Or if you create lots of data in Horizon and decide to migrate away, your data is readily available in NATS.

#### Core - Store

The `store` is a service that handles all server-side operations for objects in the NATS KV.

No other service is expected to interface directly with the NATS KV (except for controllers that create NATS consumers for the underlying KV stream).

The store provides basic CRUD operations (`create`, `get`, `list`, `update` and `delete`), as well as `validate` and `apply`.
The noteworthy operation is the `apply` because this works like Kubernetes' [server-side apply](https://kubernetes.io/docs/reference/using-api/server-side-apply/).

As objects in the store will be mutated by users and controllers, the server-side apply controls which fields are *owned* by the different entities.
Reading the Kubernetes documentation will give you a greater understanding of how this works.
Note that Horizon does not support client-side apply (of course you could write your own and use a store `update` operation if you really wanted to...).

The store defers all validation requests to the controller for an object.

#### Core - Gateway

The `gateway` is the single HTTP endpoint of a Horizon deployment.
It is the entrypoint (or gateway) into Horizon for end users.

It handles authentication (OIDC) and authorization (RBAC).
Portals are used to extend the HTTP-based UI and the gateway uses an HTTP-to-NATS proxy for serving requests to the portal.
After all, portals are just HTTP servers connected via NATS and all requests go via the gateway.
Portal HTTP handlers are expected to user server-side rendering using libraries like [htmx](https://htmx.org/).

The gateway is not very complex, and much of it can be re-used so building your own gateway is justifiable if you want complete control.

#### Core - Broker

The `broker` is a service that handles actor run requests.

Upon receiving a run request, it advertises the request to all actors and forwards the request on to the first actor that responds.
Actors can choose whether to accept a request based on label selectors or any other filtering technique you want to use.

### Platform

The "platform" layer contains all the components that the platform team will develop to make Horizon actually do something!

#### Platform - Portals

Portals are how the Horizon web UI is extended.

Portals are just HTTP servers connected to NATS and the gateway proxies HTTP requests over NATS to portals.

The goal is to have a single user-facing HTTP endpoing (i.e. the gateway) and as many portals as you need, all accessible under that one endpoint.

Typically your portal HTTP servers will render HTML and use a library like [htmx](https://htmx.org/) to modify the HTML on the client-side.
As the portals are just HTTP servers you can develop whatever you want, as long as it is HTTP-based (like JSON REST APIs).

#### Platform - Controllers

Controllers are similar to Kubernetes controllers.
A controller requires you to define an object that it controls, and will perform validation and reconciliation of that object.

`Reconcilers` take an object specifiation and move the object towards the desired state.
This is handled by a reconcile loop, which you can implement.
Under the hood, a controller creates a [NATS consumer](https://docs.nats.io/nats-concepts/jetstream/consumers) that gets notified about objects in the NATS KV store.

`Validators` validate objects as they are added to the KV store.
This is similar to Kubernetes' [Admission Controllers](https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/), but instead of config languages you write code to validate objects.
By default, NATS uses [CUE](https://cuelang.org/) to validate objects and you can write custom validate functions as well.

#### Platform - Actors

Actors enable you to write synchronous actions that operate on objects.
Actions do not require any persistence, but can interact with any persistence layer (like the NATS KV store).

Actors provide a broker mechanism for selecting an appropriate instance of an actor to run the action on.
For example, when scheduling a container you want it to run on a specific node.
Actors allow you to define an action such as `RunContainer` and the broker will ensure (based on label selection) that the relevant actors run the action.

Unless you are doing things that are node-dependent (like running containers, or executing CLIs that require specific tooling), you might not need actors at all.
Check the [architecture](./docs/architecture.md) document for some more information on the different components.

## Getting started

TODO.
Check the [getting started](./docs/gettingstarted.md) section.

## Examples

Expand Down
11 changes: 5 additions & 6 deletions cmd/ci/ci.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ const (
)

func main() {
var build, lint, test, pr bool
flag.BoolVar(&build, "build", false, "build the website locally")
var generate, lint, test, pr bool
flag.BoolVar(&generate, "generate", false, "generate code")
flag.BoolVar(&lint, "lint", false, "lint the code")
flag.BoolVar(&test, "test", false, "run the tests")
flag.BoolVar(&pr, "pr", false, "run the pull request checks")
Expand All @@ -50,16 +50,15 @@ func main() {
)
defer stop()

if generate {
Generate(ctx)
}
if lint {
Lint(ctx)
}
if test {
Test(ctx)
}
if build {
panic("build: not implemented")
// _ = KoBuild(ctx, WithKoLocal())
}
if pr {
PullRequest(ctx)
}
Expand Down
12 changes: 1 addition & 11 deletions cmd/horizon/main.go → cmd/horizon/horizon.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,22 +24,12 @@ func run() error {
ctx,
server.WithDevMode(),
server.WithAuthOptions(auth.WithAdminGroups("admin")),
// server.WithGatewayOptions(
// gateway.WithOIDCConfig(
// gateway.OIDCConfig{
// Issuer: "http://localhost:9998/",
// ClientID: "web",
// ClientSecret: "secret",
// RedirectURL: "http://localhost:9999/auth/callback",
// },
// ),
// ),
)
if err != nil {
return err
}
defer s.Close()
slog.Info("horizon server started", "services", s.Services())
slog.Info("horizon server started")

<-ctx.Done()
// Stop listening for interrupts so that a second interrupt will force
Expand Down
89 changes: 89 additions & 0 deletions docs/architecture.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
# Architecture

This section describes the different components of Horizon to build a platform.

## Core

The "core" consists of a [NATS](https://nats.io/) server and some internal services.

### Core - NATS

Horizon requires a NATS server. Horizon makes heavy use of NATS: basic subject based pub/sub, accounts for multitenancy, streams and consumers for controllers and the Key-Value store for storing the objects (a NATS KV is actually just a glorified stream in the end).

You do not need to know NATS to get started with Horizon, but if you want to get serious with Horizon you should learn enough about NATS to debug any issues.
Horizon does not try to hide away the NATS abstractions.
Therefore if anything goes wrong, you can always connect to NATS directly for debugging.
Or if you create lots of data in Horizon and decide to migrate away, your data is readily available in NATS.

### Core - Store

The `store` is a service that handles all server-side operations for objects in the NATS KV.

No other service is expected to interface directly with the NATS KV (except for controllers that create NATS consumers for the underlying KV stream).

The store provides basic CRUD operations (`create`, `get`, `list`, `update` and `delete`), as well as `validate` and `apply`.
The noteworthy operation is the `apply` because this works like Kubernetes' [server-side apply](https://kubernetes.io/docs/reference/using-api/server-side-apply/).

As objects in the store will be mutated by users and controllers, the server-side apply controls which fields are *owned* by the different entities.
Reading the Kubernetes documentation will give you a greater understanding of how this works.
Note that Horizon does not support client-side apply (of course you could write your own and use a store `update` operation if you really wanted to...).

The store defers all validation requests to the controller for an object.

### Core - Gateway

The `gateway` is the single HTTP endpoint of a Horizon deployment.
It is the entrypoint (or gateway) into Horizon for end users.

It handles authentication (OIDC) and authorization (RBAC).
Portals are used to extend the HTTP-based UI and the gateway uses an HTTP-to-NATS proxy for serving requests to the portal.
After all, portals are just HTTP servers connected via NATS and all requests go via the gateway.
Portal HTTP handlers are expected to user server-side rendering using libraries like [htmx](https://htmx.org/).

The gateway is not very complex, and much of it can be re-used so building your own gateway is justifiable if you want complete control.

### Core - Broker

The `broker` is a service that handles actor run requests.

Upon receiving a run request, it advertises the request to all actors and forwards the request on to the first actor that responds.
Actors can choose whether to accept a request based on label selectors or any other filtering technique you want to use.

## Platform

The "platform" layer contains all the components that the platform team will develop to make Horizon actually do something!

### Platform - Portals

Portals are how the Horizon web UI is extended.

Portals are just HTTP servers connected to NATS and the gateway proxies HTTP requests over NATS to portals.

The goal is to have a single user-facing HTTP endpoing (i.e. the gateway) and as many portals as you need, all accessible under that one endpoint.

Typically your portal HTTP servers will render HTML and use a library like [htmx](https://htmx.org/) to modify the HTML on the client-side.
As the portals are just HTTP servers you can develop whatever you want, as long as it is HTTP-based (like JSON REST APIs).

### Platform - Controllers

Controllers are similar to Kubernetes controllers.
A controller requires you to define an object that it controls, and will perform validation and reconciliation of that object.

`Reconcilers` take an object specifiation and move the object towards the desired state.
This is handled by a reconcile loop, which you can implement.
Under the hood, a controller creates a [NATS consumer](https://docs.nats.io/nats-concepts/jetstream/consumers) that gets notified about objects in the NATS KV store.

`Validators` validate objects as they are added to the KV store.
This is similar to Kubernetes' [Admission Controllers](https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/), but instead of config languages you write code to validate objects.
By default, NATS uses [CUE](https://cuelang.org/) to validate objects and you can write custom validate functions as well.

### Platform - Actors

Actors enable you to write synchronous actions that operate on objects.
Actions do not require any persistence, but can interact with any persistence layer (like the NATS KV store).

Actors provide a broker mechanism for selecting an appropriate instance of an actor to run the action on.
For example, when scheduling a container you want it to run on a specific node.
Actors allow you to define an action such as `RunContainer` and the broker will ensure (based on label selection) that the relevant actors run the action.

Unless you are doing things that are node-dependent (like running containers, or executing CLIs that require specific tooling), you might not need actors at all.
4 changes: 4 additions & 0 deletions docs/controllers.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Controllers

TODO: tell more intricate details about controllers and writing them.
How do they work under the hood (i.e. NATS consumers), how can they be configured, etc.
Loading

0 comments on commit c2ee447

Please sign in to comment.