npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@kubevious/kubik

v1.0.33

Published

Kubik - domain specific validation language

Downloads

498

Readme

Codefresh build status

Kubik

Kubik (pronounced [ku:bik]) is a Kubernetes-centric validation and query language for detecting and preventing errors and violations of compliance, security, and cloud-native best practices. It was designed from the ground up to allow graph-like queries across manifests. Kubik uses JavaScript-like syntax for expressing custom rules.

We are maintaining a community-driven rules library which is a great way to get familiar with Kubik and implement your own custom rules.

The Kubevious CLI command-line tool interprets Kubik rules towards manifests in the file system as well as live manifests already configured in the Kubernetes cluster.

Concepts

Kubik rule consists of two primary parts: target and rule scripts. The target script declares on which manifests the validation should be executed. The rule script validates each manifest that matches the target script.

Target:

Api('apps')
  .Kind('Deployment')

Rule:

for(const container of config.spec?.template?.spec?.containers ?? [])
{
    for(const envFrom of container.envFrom ?? [])
    {
        if (envFrom.configMapRef)
        {
            const configMap = ApiVersion('v1')
                                .Kind('ConfigMap')
                                .name(envFrom.configMapRef.name)
                                 .single()
            if (!configMap)
            {
                error(`Could not find ConfigMap ${envFrom.configMapRef.name}`)
            }
        }
    }
}

Target Script

Querying K8s Manifests

Query Kubernetes objects by ApiVersion and Kind:

ApiVersion('apps/v1')
  .Kind('Deployment')

Query Kubernetes object by ApiGroup and Kind:

Api('autoscaling')
  .Kind('HorizontalPodAutoscaler')

Query Clustered objects:

Api('rbac.authorization.k8s.io')
  .Kind('ClusterRoleBinding')
  .isClusterScope(true)

Filtering query results by object name:

ApiVersion('v1')
  .Kind('Service')
  .name('backend')

Filtering query results by label:

ApiVersion('v1')
  .Kind('Service')
  .label('foo', 'bar')

Filtering query results by multiple labels:

ApiVersion('v1')
  .Kind('Service')
  .label({ foo: 'bar', stage: 'prod' })

Limiting the query within a namespace:

ApiVersion('v1')
  .Kind('Service')
  .namespace('pepsi')

Union

Queries can be combined:

Union(
  Api('rbac.authorization.k8s.io')
    .Kind("ClusterRoleBinding")
    .isClusterScope(true),
  Api('rbac.authorization.k8s.io')
    .Kind("RoleBinding")
)

Shortcut

Kubik comes with Shortcut queries to simplify writing common Kubernetes rules:

| Shortcut | Description | | ------------------------- | ------------------------------------------------------------ | | Shortcut('PodSpec') | Returns PodSpecs across Deployments, StatefulSets, DaemonSet, Jobs, and CronJobs | | Shortcut('ContainerSpec') | Returns ContainerSpecs across Deployments, StatefulSets, DaemonSet, Jobs, and CronJobs. Also includes init containers. | | Shortcut('Secret', name) | Returns the K8s Secret object or the Secret object produced by the corresponding bitnami SealedSecret. |

Filter

Queries can be filtered:

Filter(
  Api('batch')
    .Kind("Job")
).Criteria(item => {
  if (item.config.metadata?.ownerReferences) {
    return false;
  }
  return true;
})

Transform

Query results can be transformed into another object:

Transform(
  Api('batch')
    .Kind("CronJob")
).To(item => ({
  synthetic: true,
  apiVersion: 'v1',
  kind: 'PodSpec',
  metadata: {
    ...item.config.spec?.template?.metadata ?? {},
    name: `CronJob-${item.config.metadata?.name}`
  },
  spec: item.config.spec?.jobTemplate?.spec?.template?.spec
}))

TransformMany

Query results can be transformed into multiple other objects:

TransformMany(
  Shortcut("PodSpec")
).To((item) => {
  const results = [];
  for (const cont of item?.config.spec?.containers ?? []) {
    results.push({
      synthetic: true,
      apiVersion: "v1",
      kind: "ContainerSpec",
      metadata: {
        ...(item.config.spec?.template?.metadata ?? {}),
        name: `${item.config.metadata?.name}-Cont-${cont.name}`,
      },
      spec: cont,
    });
  }
  for (const cont of item?.config.spec?.initContainers ?? []) {
    results.push({
      synthetic: true,
      apiVersion: "v1",
      kind: "ContainerSpec",
      metadata: {
        ...(item.config.spec?.template?.metadata ?? {}),
        name: `${item.config.metadata?.name}-InitCont-${cont.name}`,
      },
      spec: cont,
    });
  }
  return results;
});

Rule Script

The rule script is executed for every item returned from the target script. The purpose of the rule script is to validate whether the item is good or not. In case of violations, it should trigger errors or warnings:

error(`Something is wrong with the object ${item.name}!`);
warning(`We prefer not configuring ${config.kind} in this way...`);

The global scope of the rule script contains:

| Variable | Description | | -------- | ------------------------------------------------------------ | | config | The raw Kubernetes object | | item | A wrapper around the config object. It provides quick access to:- item.name- item.namespace- item.labels- item.annotations | | helpers | Contains common utility functions. See the reference here | | cache | Scoped cache object. Learn more here |

Sample rule script:

if (helpers.parseImage(config.spec.image).tag === 'latest') {
  error(`Latest Image Tags not allowed: ${config.spec.image}`);
}

Nested queries

Additional queries can be made from the rule script following the target script syntax and by adding .many(), .single(), or .count() suffix.

In the example below we can loop through HPAs:

for(const hpa of Api('autoscaling')
                  .Kind('HorizontalPodAutoscaler')
                  .many())
{
  // do something
}

or query the ConfigMap by name:

const configMap = ApiVersion('v1')
                  .Kind('ConfigMap')
                  .name('mysql-configs')
                  .single();
if (!configMap) {
  error('Could not find the ConfigMap');
}

Query Scope

Nested queryes by default are scoped to the namespace of the target item. To query objects from a specific namespace, the .namespace('...') query filter should be used:

const gatewayItem = Api('gateway.networking.k8s.io')
                      .Kind('Gateway')
                      .name('my-gateway')
                      .namespace('from-another-namespace')
                      .single();

When querying Clustered objects the .isClusterScope(true) filter should always be set:

Api('rbac.authorization.k8s.io')
  .Kind('ClusterRole')
  .isClusterScope(true)

It is also possible to query Namespaced objects globally by settings .allNamespaces() filter should always be set:

Api('rbac.authorization.k8s.io')
  .Kind('Role')
  .allNamespaces()

Cache Script

Cache scripts are optional. They are called only once per Namespace or once per Cluster scope. Cache scripts share a global cache object which is unique per Namespace. It is useful to perform queries and building lookup queries.

Following cache scripts builds a Pod Label dictionary once per Namespace:

cache.apps = helpers.newLabelLookupDict();
for(const app of Shortcut("PodSpec")
                   .many())
{
  cache.apps.add(app, app.config.metadata?.labels);
}

It is then accessible to items in the rule scripts:

const apps = cache.apps.resolveSelector(config.spec.selector)
if (apps.length === 0)
{
  error(`Could not find Applications for Service`);
}

Here is a good example for cache script usage: https://github.com/kubevious/rules-library/blob/main/k8s/service/service-selector-ref.yaml

Global Helpers

Scripts have access to following helper functions:

Parsing image string into image and tag

parseImage(fullImage: string) : { image: string, tag: string };

Helper to perform label selector lookup

newLabelLookupDict() : { 
  add(item, labels),  
  resolveSelector(selector) : items[],
  matchesSelector(selector) : boolean
}

Helper to perform name lookup

newNameLookupDict(items) : { 
  resolve(name) : Item | null,
  contains(name) : boolean
}

Labels dictionary to string

labelsToString(labels) : string