1 Component definition

A component is a deployable unit that encapsulates code, configuration, resources, and service endpoints. Components can define interfaces (contracts) and provide implementations.

A component definition is split into two files:

  1. Interface (*.h.kumori): Declares the component’s public contract, including services, configuration, and resources.
  2. Implementation (*.kumori): Provides the concrete implementation of the component, including code, scaling parameters, and resource requirements.

Both files must have the same component name and be in the same package.

1.1 Interface

The interface of a component might define channels, configuration and resources a component publicly exposes.

A component’s interface is declared following its type alias definition:

alias ComponentInterface struct {
    srv?         Links
    config?      struct open[]
    resource?    struct open[Resource]
}

In plain words, the previous definition accepts a component interface composed of an optional set of component Links under the srv keyword, an optional set of configuration properties that accept any definition under the config keyword and an optional set of resource declarations, whose type is restricted to Resources type.

A component interface is defined with the component keyword inside a .h.kumori file.

component NAME { ... }

Given the previous restrictions, this would be a full example of a component interface:

// <your-component>.h.kumori
package component

import "kumori"

component MyComponent {
    srv {
        server {
            server1 "tcp"
            server2 "http"
            server3 {
                protocol "tcp"
                port 3333
            }
            server4 {
                protocol "http"
                port 3334
            }
        }

        client {
            client1 "tcp"
            client2 "http"
        }

        duplex {
            duplex1 {
                protocol "tcp"
                port 6666
            }
        }
    }

    config {
        value: string
        anotherValue: number
    }

    resource {
        volumeOne: kumori.Volume
        secret: kumori.Secret
        cert: kumori.Certificate
    }
}

1.1.2 Resources

The Resource type alias defines the resources that a component can declare in its interface. A resource can be a volume, a certificate authority (CA), a certificate, a secret, a domain or a port. For a deep understanding of resources, please refer to the Resources documentation.

package kumori

library

alias Resource      Volume | CA | Certificate | Secret | Domain | Port

type CA             string
type Certificate    string
type Secret         string
type Domain         string
type Port           string

alias Volume        Registered | InlineVolume | DeprecatedVolume
alias InlineVolume  NonReplicated | Persisted | Volatile 

type Registered     string
type NonReplicated  StorageSized
type Persisted      StorageSized
type Volatile       StorageSized

1.2 Implementation

The implementation of a component actually defines the component itself: with all its code (container) fields, scaling parameters, probes…

A component is defined with the component keyword inside a .kumori file.

component NAME { ... }

After the interface has been defined, a component must be declared with the same identifier as the one used in the interface. For example, if in <your-component>.h.kumori you defined one component as component MyComponent, then your <your-component>.kumori file should define the same component with MyComponent name.

A component is defined following its type alias definition:

alias component struct {
    srv?         Links
    config?      struct open[]
    resource?    struct open[Resource]

    size struct {
        bandwidth       BandwidthSized
        minbandwidth?   BandwidthSized
        mincpu          CPUSized
    }

    code? struct open[Code]
    init? []struct {
        image       Image
        cmd?        []string
        entrypoint? []string
        
        size struct {
            memory  RAMSized
            cpu     CPUSized
            mincpu? CPUSized
        }

        env? struct open[Env]
        fs?  struct open[File]
    }

    probe? struct open[Probes]
}

As you might have noticed, at the beginning of a component implementation type definition, there are three fields exactly the same as in the ComponentInterface type alias. You could declare everything inside a Component implementation rather than having it separated in its interface since all the fields are merged while a component is being processed. However, when a component is defined with an implementation, it is mandatory to declare its interface as well (even if it is empty).

A component allows for multiple fields.

1.2.1 Init

The init field accepts an array of structs with a certain structure (which is actually the same as Code). The difference with the code field is that init containers do not accept an identifier.

Inside this field you can declare as many containers as you want. This example illustrates how a component with two init containers would look like:

//
package component

[...]

init [{
        image "docker.io/..."
        entrypoint ["/bin/entrypoint"]

        size {
            memory 100M
            cpu 100m
            mincpu 100m
        }
        
        fs {
            "/kumori/shared" {
                volume "shared"
            }
            "/bin/entrypoint" {
                data io.Open("init_entrypoint.sh")
                mode 0o755
            }
        }
    },
    {
        image "docker.io/..."

        size {
            memory 50M
            cpu 50m
        }
        
        env {
            "CONFIG_PATH" "/kumori/config/config.yaml"
        }
    }]

[...]

1.2.2 Code

The code field defines containers for your component and accepts one or more structs with the following structure:

alias Code struct {
    image       Image
    cmd?        []string
    entrypoint? []string
    
    size struct {
        memory  RAMSized
        cpu     CPUSized
        mincpu? CPUSized
    }

    env? struct open[Env]
    fs?  struct open[File]
}

alias Image string | struct { hub: struct { name: string, secret?: string }, tag: string }
  • The field image must contain the container image to be used. The format of the image should comply with OCI Image Specification. Additionally, you can specify the image as a struct containing the hub and tag fields. The hub field is itself a struct that contains the name of the image and an optional secret field to reference a secret defined in the component’s resource field. The tag field specifies the tag of the image.
  • The field cmd defines the command to be executed when the container starts.
  • The field entrypoint defines the entrypoint of the container.
  • The field size defines the resource requests for the container. Inside this field, you must define the memory and cpu fields, which are both mandatory. You can also define the mincpu field, which is optional.
  • The field env defines the environment variables for the container. See Environment variables for more details.
  • The field fs defines the file mounts for the container. See File mounts for more details.

An example of a component with a code container would be: ```// .kumori package component

[…]

code { Redis { image { hub { name “my-custom-registry.com” secret “my-dockerhub-secret” } tag “redis:alpine” } entrypoint [“sh”] cmd [“/bin/entrypoint”]

    size {
        memory 1000M
        cpu 250m            
        mincpu 100m
    }

    env {
        ROOT_USERNAME interface.config.redisrootusername
        GLOBAL_PASSWORD {
            secret "redisglobalpassword"
        }
    }

    fs {
        "/data/" {
            volume "data"
        }

        "/bin/entrypoint" {
            data io.Open("entrypoint.sh")
            mode 0o755
        }
    }
}

}


You can define multiple containers inside the `code` field by adding more structs with different identifiers.

### Environment variables
You can define environment variables for both `code` and `init` containers using the `env` field. This field accepts a struct with any number of fields, where each field name is the environment variable name and its value is the environment variable value.

Any environment variable is defined following the following type alias:

alias Env string | bool | number | struct { secret: string }


Therefore, the following environment variables would be valid examples:

[…] env { DEBUG true PORT 8080 ROOT_USERNAME interface.config.rootUser CONFIG io.Open(“my_config.cnf”)

API_KEY { 
    // Must be a secret defined in the component's resource field
    secret: "my_secret_name"
}

}


### File mounts
You can define file mounts for both `code` and `init` containers using the `fs` field. This field accepts a struct with any number of fields, restricted to the `File` type alias defined below:

```kumori
alias File  string | FSMap | struct { volume: string }
alias FSMap FSData | FSSecret | FSPort | FSDomain | FSCertificate | FSCa

alias AllowedFormat "text" | "json" | "yaml" | "flatdict"

alias FSData        struct { data: string, mode?: number, format?: AllowedFormat }
alias FSSecret      struct { secret: string, mode?: number, format?: AllowedFormat }
alias FSPort        struct { port: string, mode?: number, format?: AllowedFormat }
alias FSDomain      struct { domain: string, mode?: number, format?: AllowedFormat }
alias FSCertificate struct { certificate: string, mode?: number, format?: AllowedFormat }
alias FSCa          struct { ca: string, mode?: number, format?: AllowedFormat }

This definition allows you to specify file mounts in three different ways:

  1. By directly specifying the content of the mount:
fs {
    "/bin/entrypoint/" io.Open("entrypoint.sh")
    "/your/config/" interface.config.configData
}
  1. By specifying the content, with the optional mode and format fields. The allowed keys are: data, secret, port, domain, certificate and ca.

[!IMPORTANT] data is used to specify raw data, while the other keys are used to reference resources defined in the component’s resource field.

fs {
    "/bin/entrypoint/" {
        data io.Open("entrypoint.sh")
        format "text"
    }

    "/your/config/" {
        // Must be a secret defined in the component's resource field
        secret "your-secret-name"
        mode 0o644
    }

    "/another/path" {
        // Must be a port defined in the component's resource field
        certificate "your-port-name"
        mode 0o755
    }
}

[!NOTE] When specified, the mode field accepts values in octal format. If not specified, the default mode is 0o644.

When specified, the format field accepts one of the following values: text, json, yaml or flatdict. If not specified, the default format is text.

  1. By mounting a volume defined in the component’s resource field:
fs {
    "/kumori/data" {
        // Must be a volume defined in the component's resource field
        volume "data_volume"
    }
}

1.2.3 Probes

The probe field accepts a struct with any of the following fields: liveness, readiness and pmetrics. The definition of the Probes type alias guides how to declare each probe:

alias Probes struct {
    liveness?  LivenessProbeAttributes
    readiness? ReadinessProbeAttributes
    pmetrics?  PrometheusMetricsProbeAttributes
}

alias LivenessProbeAttributes struct {
    protocol            ProbeProtocol
    startupGraceWindow? StartupGraceWindow
    frequency?          any
    timeout?            number
}

alias ReadinessProbeAttributes struct {
    protocol    ProbeProtocol
    frequency?  any
    timeout?    number
}

alias PrometheusMetricsProbeAttributes struct {
    protocol HTTPOnlyProbeProtocol
}

alias ProbeProtocol struct {
    http? HTTPProbeProtocol
    tcp?  TCPProbeProtocol
    exec? ExecProbeProtocol
}

alias HTTPOnlyProbeProtocol struct {
    http HTTPProbeProtocol
}

alias HTTPProbeProtocol struct {
    port number
    path string
}

alias TCPProbeProtocol struct {
    port number
}

alias ExecProbeProtocol struct {
    path string
}

alias StartupGraceWindow struct {
    unit     "ms" | "attempt"
    duration number
    probe    bool
}

Probes can only be declared for existing containers inside the code field, using their identifiers. For example, if you have a container named Redis inside the code field, you can declare probes for it as follows:

probe {
    Redis {
        liveness {
            protocol {
                tcp {
                    port 6379
                }
            }
            frequency "10s"
            timeout 5
        }

        readiness {
            protocol {
                tcp {
                    port 6379
                }
            }
            frequency "5s"
            timeout 3
        }

        pmetrics {
            protocol {
                http {
                    port 9121
                    path "/metrics"
                }
            }
        }
    }
}

The Component Examples section contains several complete component definitions showcasing different configurations and use cases.