# Dynamic Configuration

> Octelium documentation. Canonical page: <https://octelium.com/docs/octelium/latest/management/core/service/dynamic-config>.

## Overview

A *Service* has its configuration which includes the information about its upstream as well as the application-layer specific configurations (e.g. HTTP-based configurations such as manipulating headers and injecting the upstream credentials) for various widely used L7 protocols and supported by Octelium (read more about *Service* modes [here](https://octelium.com/docs/octelium/latest/management/core/service/overview.md#mode)). Here is an example:

```yaml
kind: Service
metadata:
  name: svc1
spec:
  mode: HTTP
  config:
    upstream:
      url: https://api.example.com
    http:
      auth:
        bearer:
          fromSecret: apikey1
```

The above configuration is the `default` configuration which is used globally for all the *Service* requests by default. However, in many cases, you might want to make this configuration dynamic depending on the request's context. For instance, you might want to route to a certain upstream depending on the identity of the *User* or the L7 specific context (e.g. HTTP paths, headers, request body, etc...). For such cases, Octelium provides you a very flexible way to apply dynamic configuration where you can define multiple configurations and choose one of them on a per-request basis via rules that are defined exactly the same way as *Policy* rules (read more about *Policies* and access control [here](https://octelium.com/docs/octelium/latest/management/core/policy.md)).

Here is an example where we can provide smart routing based on the *User* identity to route to additional upstreams. We now have 2 additional configs (i.e. `config-1` and `config-2`) to the `default` configuration, each having a different credential representing maybe a different account and/or privileges for the upstream. If no rules match or if there are no rules, then the `default` config is used as a fallback config.

```yaml
kind: Service
metadata:
  name: svc1
spec:
  mode: HTTP
  # This is our typical static configuration used by default if no rules match
  config:
    upstream:
      url: https://api.example.com
    http:
      auth:
        bearer:
          fromSecret: apikey0
  dynamicConfig:
    # we define a list of named configs that could override the global config
    #!mark
    configs:
    - name: config-1
      upstream:
        url: https://api-01.example.com
      http:
        auth:
          bearer:
            fromSecret: apikey1
    - name: config-2
      upstream:
        url: https://api-02.example.com
      http:
        auth:
          bearer:
            fromSecret: apikey2
    #!mark
    rules:
      # Use the Config `config-1` for Users belonging to the Group `group-1`
      - condition:
          match: '"group-1" in ctx.user.spec.groups'
        configName: config-1
      # Use the Config `config-2` for Users belonging to the Group `group-2`
      - condition:
          match: '"group-2" in ctx.user.spec.groups'
        configName: config-2
```

As shown in the example above, the `dynamicConfig` has 2 lists: one for the named dynamic configs represented by the `configs` list and another, `rules`, for the rules that are checked upon each request to choose one of these configs when matched. Each rule has a condition that can be defined in the exact same way as *Policy* rule conditions (read more [here](https://octelium.com/docs/octelium/latest/management/core/policy.md#condition)). A rule matches if its condition is met and in such case its corresponding named config, represented by the `configName` field, is chosen to be the configuration for that particular request overriding the default *Service* configuration.

Dynamic configuration is not restricted to a certain *Service* mode. The above example can be generalized to any other mode where you can override the default configuration based on whatever dynamic rules you wish to define. For example, you can use it for the `POSTGRES` and `MYSQL` modes to route to specific upstreams and/or use specific usernames and passwords as well as databases depending on the identity and/or context of the request. For the case of SSH, you can route to different usernames and passwords/private keys depending on the identity or the request's *User*. You can explore more mode-specific examples here:

- [HTTP](https://octelium.com/docs/octelium/latest/management/core/service/http.md#dynamic-configuration)
- [DNS](https://octelium.com/docs/octelium/latest/management/core/service/dns.md#dynamic-configuration)
- [SSH](https://octelium.com/docs/octelium/latest/management/core/service/ssh.md#dynamic-configuration)
- [PostgreSQL](https://octelium.com/docs/octelium/latest/management/core/service/postgres.md#dynamic-configuration)
- [MySQL](https://octelium.com/docs/octelium/latest/management/core/service/mysql.md#dynamic-configuration)
- [TCP](https://octelium.com/docs/octelium/latest/management/core/service/tcp.md#dynamic-configuration)
- [UDP](https://octelium.com/docs/octelium/latest/management/core/service/udp.md#dynamic-configuration)

Also, you are not really required to define a `default` configuration when you have one or more named configurations unless with working with certain situations (e.g. buffering request body in `HTTP`-based *Services* (read more [here](https://octelium.com/docs/octelium/latest/management/core/service/http.md#request-body))). Here is an example of a *Service* that acts as an API gateway:

```yaml
kind: Service
metadata:
  name: my-api
spec:
  mode: HTTP
  dynamicConfig:
    configs:
    - name: v1
      upstream:
        url: https://apiv1.example.com
      http:
        auth:
          bearer:
            fromSecret: apikey1
        path:
          removePrefix: /v1
    - name: v2
      upstream:
        url: https://apiv2.example.com
      http:
        auth:
          bearer:
            fromSecret: apikey2
        path:
          removePrefix: /v2
    rules:
      # Use the Config `config-1` for Users belonging to the Group `group-1`
      - condition:
          match: ctx.request.http.path.startsWith("/v1")
        configName: v1
      # Use the Config `config-2` for Users belonging to the Group `group-2`
      - condition:
          match: ctx.request.http.path.startsWith("/v2")
        configName: v2
      # Fallback to v1 when nothing matches
      - condition:
          matchAny: true
        configName: v1
```

However, in such case, you need to be careful that your rules cover all the possible conditions.

## Evaluated Configs

In addition to defining multiple named dynamic configs and choosing among them via rule conditions, you can also use CEL and OPA expressions to dynamically generate *Service* configs on a per-request basis without having to choose a defined named dynamic config. Here is an example that shows 2 evaluated configs, the first by a CEL expression, and the second by an OP expression as follows:

```yaml
kind: Service
metadata:
  name: my-svc
spec:
  mode: HTTP
  port: 80
  dynamicConfig:
    rules:
      - condition:
          match: ctx.user.spec.email.endsWith("@example.com")
        eval: |
          {
            "upstream": {
              "url": "https://" + ctx.user.metadata.uid + ".example.com"
            }
          }
      - condition:
          match: ctx.user.spec.email.endsWith("@another.example.com")
        opa: |
          package octelium.eval

          result := {
            "upstream": {
              "url": sprintf("https://%s.example.com", [input.ctx.service.metadata.name])
            }
          }
```

As you can see in the above example, we now do not have a list of named dynamic configs, instead, the config is dynamically generated on a per-request basis if the corresponding condition matches.

## Inheritance

In some cases, you might have multiple configurations that share one or more fields and sub-fields making the process of writing dynamic configurations mundane and even error-prone for more complex configurations. For example, here is an example for a `POSTGRES` *Service* (read more [here](https://octelium.com/docs/octelium/latest/management/core/service/postgres.md)) where you force the *User* to use a certain database based on the identity while keeping everything else in the config the same (i.e. same URL, same user and password). Therefore, without Inheritance, your *Service* looks as follows:

```yaml
kind: Service
metadata:
  name: postgres
spec:
  mode: POSTGRES
  dynamicConfig:
    configs:
    - name: db1
      upstream:
        url: postgres://address-to-pg
      postgres:
        user: postgres
        database: db1
        auth:
          password:
            fromSecret: pg-password
    - name: db2
      # !mark
      parent: db1
      postgres:
        database: db2
    rules:
    - condition:
        match: '"prod" in ctx.user.spec.groups'
      configName: db2
    - condition:
        matchAny: true
      configName: db1
```

Now with *Service* config inheritance, your *Service* can become much shorter and easier to maintain and read as follows:

```yaml
kind: Service
metadata:
  name: postgres
spec:
  mode: POSTGRES
  dynamicConfig:
    configs:
    - name: db1
      upstream:
        url: postgres://address-to-pg
      postgres:
        user: postgres
        database: my-db1
        auth:
          password:
            fromSecret: pg-password
    - name: db2
      # !mark
      parent: db1
      postgres:
        # We're overriding the `my-db1` database with `my-db2`
        # while inheriting everything else from `db1` config
        database: db2
    rules:
    - condition:
        match: '"prod" in ctx.user.spec.groups'
      configName: db1
    - condition:
        matchAny: true
      configName: db2
```

You can use the default configuration whose name is reserved as `default` as a parent for your dynamic configs. Here is an example:

```yaml
kind: Service
metadata:
  name: postgres
spec:
  mode: POSTGRES
  config:
    name: db1
    upstream:
      url: postgres://address-to-pg
    postgres:
      user: postgres
      database: my-db1
      auth:
        password:
          fromSecret: pg-password
  dynamicConfig:
    configs:
    - name: db1
      # !mark
      parent: default
      postgres:
        database: db1
    - name: db2
      # !mark
      parent: default
      postgres:
        database: db2
```

As shown above, you actually use the `default` as well as any other dynamic config as templates to be used as parents for other child configs. *Service* configuration inheritance can help you to actually create very complex dynamic configuration setups without causing degradation of maintainability and readability at scale. Note that Octelium currently only supports one-level inheritance. In other words, you cannot currently have configs whose parent also inherits from another config. Also note that the `default` config cannot have parents.
