Skip to main content

· 4 min read

Today, I'm excited to announce the release of Kosko 4.1. This release is a significant milestone for Kosko.

Plugin System

The first major feature is the plugin system introduced in Kosko 4.0. It allows you to modify manifests, or run extra validations. Currently there are two official plugins, and we are going to introduce them in this post.

Set Metadata

The first one is @kosko/plugin-set-metadata. This plugin can modify namespace, name, labels and annotations of manifests. For example, you can set a namespace for all manifests.

[[plugins]]
name = "@kosko/plugin-set-metadata"
config.namespace.value = "dev"

You can check here for more details.

Lint

The other one is @kosko/plugin-lint. Prior to Kosko 4, Kosko only validates manifests against Kubernetes OpenAPI schema, which only contains data types, formats, required fields, etc. However, it's not enough to find all potential issues in manifests.

@kosko/plugin-lint can run extra validations on manifests. For example:

You can enable the recommended rules by setting the following configuration.

[[plugins]]
name = "@kosko/plugin-lint"
config.extends = ["@kosko/plugin-lint/presets/recommended"]

Please check here for more details. More lint rules will be added in the future. Feel free to open an issue if you have any ideas.

Streamlined Error Report

Error report was hugely improved in Kosko 3.0. However, it might be too verbose sometimes. In Kosko 4.1, error report system was redesigned to be more streamlined.

Manifest information such as API version, kind, namespace, name, and index were previously displayed in multiple lines. Now, they are displayed in a single line.

components/nginx.ts - apps/v1/Deployment dev/nginx [0]

✖ /spec/replicas must be integer

✖ no-missing-namespace: Namespace "dev" does not exist or is not allowed.

components/nginx.ts - v1/Service nginx [1]

✖ unique-service-port-name: Service contains multiple ports with the same name "http"

error - Found 3 errors in total
error - Generate failed

The other improvement is the new warning severity. This severity is used for issues that are not critical, but should be fixed. When a warning is found, the error report will display a warning message, but the manifest generation will still be successful.

In the following example, missing readiness probe is displayed as a warning.

components/nginx.ts - apps/v1/Deployment nginx [0]

⚠ require-probe: Container "nginx" must define a readiness probe.

warn - Found 1 warning in total
info - Components are valid

Breaking Changes

Minimum Node.js Version

Support for Node.js 14 was dropped. The minimum supported Node.js version is now 18.

Kosko CLI

kosko init command was removed from the CLI because it's only used at the first time. Now, you can use the new create-kosko package to create a new project.

npm create kosko@latest example

@kosko/generate

info

This section only applies to users who use @kosko/generate programmatically. You can safely ignore this section if you only use the CLI.

Error handling was hugely changed in @kosko/generate 5.0. To support the new error report system, a new type Issue ws introduced. This type is used in Manifest.issues, providing more information about issues of manifests.

However, this means errors are no longer thrown when a validation error occurs. You can enable the previous behavior by setting throwOnError to true.

Another change about the error handling is that Ajv errors are automatically transformed into issues. Now you can access Ajv errors in Manifest.issues. You can disable this behavior by setting keepAjvErrors to true.

Next, the component property was replaced by metadata property in ResolveError class. The metadata property was added to Manifest type as well. This property contains API version, kind, namespace, and name of a manifest.

Lastly, path and index properties were replaced by the position property in Manifest, ResolveErrorOptions, and ResolveError type.

@kosko/aggregate-error

info

This section only applies to users who use the @kosko/aggregate-error package. You can safely ignore this section if you only use the CLI.

This package was removed because AggregateError is now built-in in Node.js 15.0.0 and later. If you are using @kosko/aggregate-error package, please remove it from your dependencies and just use the global AggregateError class.

@kosko/require

info

This section only applies to users who use the @kosko/require package. It's a low-level package, so you probably don't use it at all.

The resolve function is renamed to resolvePath because the introduction of the new resolveModule function. The resolvePath function only supports path resolution, while the resolveModule function supports both path and module resolution.

Changelog

· 4 min read

Kosko 3.0 has been released a few weeks ago. There are many improvements and some breaking changes in this version.

Refined Error Report

In the previous version, when you run kosko generate or kosko validate command, only the first error is reported. Error messages are cramped and there is a lot of useless information.

error - data/spec/replicas must be integer, data/spec must be null, data/spec must match exactly one schema in oneOf (path: "/home/tommy/Projects/kosko-old/examples/getting-started/components/nginx.js", index: [0], kind: "apps/v1/Deployment", name: "nginx")
ValidationError: data/spec/replicas must be integer, data/spec must be null, data/spec must match exactly one schema in oneOf
- path: "/home/tommy/Projects/kosko-old/examples/getting-started/components/nginx.js"
- index: [0]
- kind: "apps/v1/Deployment"
- name: "nginx"
at Object.validate (.../node_modules/.pnpm/@[email protected]/node_modules/@kubernetes-models/validate/dist/validate.js:9:21)
at IoK8sApiAppsV1Deployment.validate (.../node_modules/.pnpm/@[email protected]/node_modules/@kubernetes-models/base/dist/model.js:44:20)
at resolve (.../packages/generate/dist/resolve.js:63:29)
at resolve (.../packages/generate/dist/resolve.js:50:34)
at generate (.../packages/generate/dist/generate.js:39:56)
at async generateHandler (.../packages/cli/dist/commands/generate/index.js:69:20)
at async Object.handler (.../packages/cli/dist/commands/generate/index.js:108:24)

In Kosko 3.0 and kubernetes-models 4.0, you can get all errors in a single run. This means you don't need to modify your code and rerun kosko generate command anymore.

Furthermore, error messages become cleaner and more human-readable now. They are grouped by component paths, and error stacks are only displayed for non-validation errors.

components/mysql.js - 1 error

✖ ResolveError: Validation error
Index: [0]
Kind: apps/v1/Deployment
Name: mysql

/spec/template/spec/containers/0/env/0/value must be string

components/nginx.js - 1 error

✖ ResolveError: Validation error
Index: [0]
Kind: apps/v1/Deployment
Name: nginx

/spec/replicas must be integer
/spec/template/spec/containers/0/ports/0/containerPort must be integer

error - Generate failed (Total 2 errors)

If you want to stop immediately whenever an error occurred, run kosko generate with --bail option.

kosko generate --bail

Or enable bail option in kosko.toml.

kosko.toml
bail: true

Improved Nullable Type Error

Besides Kosko, kubernetes-models was also updated to provide better error information for nullable types.

Let's say we have a nullable type as below.

interface IPod {
spec?: IPodSpec;
}

When a value in spec is invalid, it throws a validation error like this.

data/spec must have required property 'containers',
data/spec must be null,
data/spec must match exactly one schema in oneOf

Only the first line in this error message is helpful. Other lines are very confusing and might make developers think they should set spec as null. Such error messages are removed in this version.

data/spec must have required property 'containers'

ESM Loader Support

ESM loaders are finally supported in Kosko 3.0. You can use ESM loaders by adding loaders option to kosko.toml.

kosko.toml
loaders = ["ts-node/esm"]

Or running kosko generate with --loader option.

kosko generate --loader ts-node/esm

See ECMAScript modules for more information about how to use TypeScript ESM loader.

Faster Kustomize Build

In @kosko/kustomize 0.2, when you call loadKustomize function without kustomize CLI installed on your computer. loadKustomize will always try to call kustomize first then kubectl kustomize, which is slow if your components contain a lot of loadKustomize calls.

In @kosko/kustomize 1.0, successful kustomize CLI invocations are cached, so loadKustomize doesn't have to retry kustomize CLI every time.

Iterable Support

Kosko 3.0 supports the iterable protocol. You can use Set, Map, or generator functions in components now.

// Set
export default new Set([new Deployment(), new Service()]);

// Generator function
function* gen() {
yield new Deployment();
yield new Service();
}

See Iterable and Async Iterable for more info.

Breaking Changes

  • Drop support for Node.js 12. The minimum supported Node.js version is 14.18.0 now.

  • @kosko/generate - All errors thrown in generate and resolve functions are wrapped in GenerateError or ResolveError for better access to context.

  • @kosko/generate - ValidationError is renamed as ResolveError.

  • @kosko/generate - Multiple errors are wrapped in AggregateError.

  • @kosko/generate - ResolveError.message no longer contains context information. You can access context from stack or direct access properties in the error value.

  • @kosko/generate - fast-glob is replaced with a homemade glob function based on micromatch. The behavior will be slightly different. Please submit an issue if you encounter any unexpected problems.

  • @kosko/yaml - Value type in Manifest has been changed from any to unknown. You might need to modify transform function to fix type errors. For example:

    // Before
    manifest.metadata.namespace = "foo";

    // After (Preferred)
    import { Pod } from "kubernetes-models/v1/Pod";

    if (Pod.is(manifest)) {
    manifest.metadata.namespace = "foo";
    }

    // After (Another way)
    import { IObjectMeta } from "@kubernetes-models/apimachinery/apis/meta/v1/ObjectMeta";
    (manifest.metadata as IObjectMeta).namespace = "foo";
  • readonly attribute is removed from the return types of many packages.

Changelog

· 4 min read

kubernetes-models 3.0 has been released last month. To upgrade to the latest kubernetes-models, run:

npm install kubernetes-models@latest

If you have installed any CRD packages, don't forget to upgrade them as well.

Smaller Package Size

The package size of kubernetes-models 3.0 is reduced by 34% compressed, or 20% unpacked (compared to 2.0.2).

VersionUnpackedCompressed
2.0.25.9MB749KB
3.0.14.7MB494KB
Diff-20.33%-34.04%

Several changes made the package size smaller in 3.0.

First, the following alias files are removed in 3.0. These alias files were introduced a few years ago. Removing these files can reduce the number of files and the size of the export map.

Breaking Change

If you are using any of these import patterns, please rewrite import paths after upgrading to 3.0.

  • kubernetes-models/api/core/v1/Pod
  • kubernetes-models/api/apps/v1/Deployment
  • kubernetes-models/apiextensions-apiserver/pkg/apis/apiextensions/v1/CustomResourceDefinition

Next, type definition files are used to be stored in _definitions/<id>.js. In 3.0, they are moved to <apiVersion>/<kind>.js. This can also reduce the number of alias files, and import paths can be shorter, too.

Last, a new type TypeMeta has been added to @kubernetes-models/base package. Interfaces with apiVersion and kind will extend TypeMeta type now. This can remove doc comments in each type.

// Before
export interface IIoK8sApiCoreV1Pod {
/**
* APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
*/
apiVersion: "v1";
/**
* Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
*/
kind: "Pod";
}

// After
export interface IPod extends TypeMeta {
apiVersion: "v1";
kind: "Pod";
}

Better Import Suggestions

Type definition files are used to be stored in _definitions/<id>.js, which are supposed to be hidden files. But sometimes IDEs get confused and show them in import suggestions. In 3.0, they are moved to more proper locations, which should provide a better IDE experience.

Before:

After:

API Machinery Package

A new package @kubernetes-models/apimachinery has been released with kubernetes-models 3.0. This package includes types defined in kubernetes/apimachinery only. All CRD packages have been migrated to import this package instead of the whole kubernetes-models. This can reduce the size of dependencies of CRD packages.

Breaking Change

If you are using API machinery files, please rewrite import paths as below.

// Before
import { IObjectMeta } from "kubernetes-models/apimachinery/pkg/apis/meta/v1/ObjectMeta";

// After
import { IObjectMeta } from "@kubernetes-models/apimachinery/pkg/apis/meta/v1/ObjectMeta";

Type Guard

All classes with apiVersion and kind now come with a new static method is, which can be used to narrow down object types. Please note that this function does NOT validate the object itself. It just checks whether apiVersion and kind match or not.

import { Pod } from "kubernetes-models/v1/Pod";

const thing = { apiVersion: "v1", kind: "Pod" };

if (Pod.is(thing)) {
// thing is an `IPod`.
} else {
// thing is something else
}

Type guards can also be used in the transform function of @kosko/yaml.

import { loadFile } from "@kosko/yaml";
import { Service } from "kubernetes-models/v1/Service";

loadFile("manifest.yaml", {
transform(manifest) {
// Set all service type as "ClusterIP"
if (Service.is(manifest)) {
manifest.spec.type = "ClusterIP";
}

return manifest;
}
});

You can see TypeScript documentation for more information about narrowing and type predicates.

· One min read

Kosko now supports Kustomize files. With the new @kosko/kustomize package, you can load your existing Kustomize files into Kosko, and it also supports Kubernetes OpenAPI schema validation.

@kosko/kustomize uses the kustomize build or kubectl kustomize command to generate Kubernetes manifests and the @kosko/yaml package to load Kubernetes YAML.

First, you have to install either kustomize or kubectl CLI, then install the @kosko/kustomize package.

npm install @kosko/kustomize

Next, use the loadKustomize function to load Kustomize files.

const { loadKustomize } = require("@kosko/kustomize");

loadKustomize({
path: "./dir"
});

See loading Kustomize for more details.

· 2 min read

Last month, ECMAScript modules (ESM) support was shipped in Kosko 1.1. Today, you can use ESM not only in Node.js, but also in browsers. Currently only the following packages are browser-ready. Please make sure to update these packages before using Kosko in a browser.

  • @kosko/env - 2.0.0
  • @kosko/generate - 1.2.0

Programmatic API

Kosko can be used in browsers via the programmatic API. In the following example, first we use dynamic import to load environment variables. Then, use the resolve function to resolve and validate components. And finally, print the resolved manifests with the print function.

import env, { createAsyncLoaderReducers } from "@kosko/env";
import { resolve, print, PrintFormat } from "@kosko/generate";

// Load environment variables with dynamic import
env.setReducers((reducers) => [
...reducers,
...createAsyncLoaderReducers({
global: () =>
import("./environments/dev/index.js").then((mod) => mod.default),
component: (name) =>
import(`./environments/dev/${name}.js`).then((mod) => mod.default)
})
]);

(async () => {
// Resolve and validate components
const manifests = await resolve(
import("./components/nginx.js").then((mod) => mod.default)
);

// Print resolved manifests
print(
{ manifests },
{
format: PrintFormat.YAML,
writer: { write: (data) => console.log(data) }
}
);
})();

See using in browser for more details.

More Examples

You can try Kosko in the playground, or check the following examples.

Breaking Changes

@kosko/env

The following APIs were changed in this release.

  • Environment class → Environment interface
  • SyncEnvironment class → createNodeCJSEnvironment function
  • AsyncEnvironment class → createNodeESMEnvironment function

You don't have to change anything, unless you initialize these classes manually.

// Before
const { Environment } = require("@kosko/env");
const env = new Environment(process.cwd());

// After
const { createNodeCJSEnvironment } = require("@kosko/env");
const env = createNodeCJSEnvironment({ cwd: process.cwd() });

· One min read

Most popular apps are available as Helm charts. It was not easy to use them in Kosko directly. The all new @kosko/helm package can help you load Helm charts, and of course, it also supports Kubernetes OpenAPI schema validation.

@kosko/helm uses the helm template command to render chart templates and the @kosko/yaml package to load Kubernetes YAML.

First, you have to install the Helm CLI, then install the @kosko/helm package.

npm install @kosko/helm

Next, use the loadChart function to load Helm charts.

const { loadChart } = require("@kosko/helm");

loadChart({
name: "prom",
chart: "prometheus",
repo: "https://prometheus-community.github.io/helm-charts",
version: "13.6.0",
values: {
server: {
persistentVolume: {
enabled: true
}
}
}
});

See loading Helm chart for more details.

· 2 min read

Kosko comes with ECMAScript modules (ESM) support since v1.1. You can write components and environments in native ESM files. The following packages are updated for ESM support. Please make sure to update these packages before using ESM.

  • @kosko/cli - 1.2.0
  • @kosko/env - 1.1.0
  • @kosko/generate - 1.1.0
  • kosko - 1.1.0
  • @kosko/migrate - 2.0.0
  • @kosko/require - 2.0.0
  • @kosko/yaml - 1.0.0

The biggest difference between CommonJS and ESM is that @kosko/env is asynchronous in ESM. This is because import() is asynchronous. But don't freak out, it won't break your current code at all. @kosko/env is still synchronous in CommonJS.

When retrieving environments in ESM, you MUST add await as the example below.

import env from "@kosko/env";

const globalParams = await env.global();
const componentParams = await env.component("demo");

Instead of module.exports, export components or environments with export default.

import { Deployment } from "kubernetes-models/apps/v1/Deployment";
import { Service } from "kubernetes-models/v1/Service";

export default [new Deployment({}), new Service({})];

For more details about how to enable ESM or programmatical usage, please check here.

Breaking Changes

@kosko/yaml

loadString and getResourceModule functions become asynchronous because of ESM support.

@kosko/migrate

migrate and migrateString functions become asynchronous because of the breaking changes of @kosko/yaml package.

@kosko/require

resolve function no longer allows resolve.AsyncOpts as options, please use ResolveOptions instead.

kubernetes-models

kubernetes-models is also updated for ESM support.

  • @kubernetes-models/base - 1.2.0
  • @kubernetes-models/cert-manager - 1.1.0
  • @kubernetes-models/contour - 1.1.0
  • @kubernetes-models/crd-generate - 1.2.0
  • @kubernetes-models/gke - 1.1.0
  • kubernetes-models - 1.1.0
  • @kubernetes-models/openapi-generate - 1.2.0
  • @kubernetes-models/prometheus-operator - 1.1.0
  • @kubernetes-models/read-input - 1.1.0
  • @kubernetes-models/sealed-secrets - 1.1.0
  • @kubernetes-models/string-util - 1.1.0
  • @kubernetes-models/validate - 1.1.0

· 2 min read

It has been a long time since the last stable release v0.9. Recently, I decide to implement some features that I always want to have at work. Hope these new features can also help you.

Nested Manifests

In v1.0, arrays and functions in components are flattened. This is useful for sharing manifests across components.

For instance, a database in Kubernetes is typically composed by a Deployment and a Service. To include a database in a component, before v1.0, you have to flatten these two manifests in the component by yourself. After v1.0, they are flattened automatically.

In this way, a database can be used as a single resource, which can be used everywhere in your components.

function createDatabase() {
return [new Deployment(), new Service()];
}

// Before v1.0
module.exports = [new Deployment(), ...createDatabase()];

// After v1.0
module.exports = [new Deployment(), createDatabase()];

More Information in ValidationError

There was only path and index in ValidationError before v1.0. Sometimes it might be difficult to find where the error is. In v1.0, apiVersion, kind, namespace and name are added to ValidationError. The following is an example of the new error message.

ValidationError: data.metadata.annotations['dependencies'] should be string
- path: ".../components/config-api"
- index: [0]
- kind: "apps/v1/Deployment"
- name: "config-api"
at resolveComponent (.../node_modules/@kosko/generate/src/generate.ts:81:15)
at resolveComponent (.../node_modules/@kosko/generate/src/generate.ts:59:28)
at Object.generate (.../node_modules/@kosko/generate/src/generate.ts:134:30)
at generateHandler (.../node_modules/@kosko/cli/src/commands/generate/index.ts:156:18)
at handler (.../node_modules/@kosko/cli/src/commands/generate/index.ts:200:20)
at Object.run (.../node_modules/@kosko/cli/src/index.ts:12:3)

Loading Kubernetes YAML

If you have been using Kubernetes for a while, you may have a lot of existing Kubernetes YAML files just like me. Instead of migrating YAML into JavaScript, try use the new package @kosko/yaml.

@kosko/yaml reads YAML files and transforms data into kubernetes-models classes, so your manifests are validated against Kubernetes OpenAPI schema.

const { loadFile } = require("@kosko/yaml");

module.exports = loadFile("manifest.yaml");

This package works better with "nested manifests", so please don't forget to upgrade to Kosko v1.0 first.

For more details, please check here.

Breaking Changes

  • Drop support for Node.js 8.
  • @kosko/generate
    • The type of Manifest.index and ValidationError.index is changed from number to number[].
    • The type of Manifest.data is changed from any to unknown.