TypeScript Quickstart

Quickstart for Developing Config Functions

Developer Quickstart

This quickstart will get you started developing a config function with the TypeScript SDK, using an existing kpt-functions package. Config functions are any functions which implement the KptFunc interface documented in the TS SDK API.

Prerequisites

System Requirements

Currently supported platforms: amd64 Linux/Mac

Setting Up Your Local Environment

  • Install node
  • Install docker
  • Install kpt and add it to your $PATH

Explore the Demo Functions Package

  1. Get the demo-functions package:

    git clone --depth 1 https://github.com/GoogleContainerTools/kpt-functions-sdk.git
    

    All subsequent commands are run from the demo-functions directory:

    cd kpt-functions-sdk/ts/demo-functions
    
  2. Install all dependencies:

    npm install
    
  3. Run the following in a separate terminal to continuously build your function as you make changes:

    npm run watch
    
  4. Explore the label_namespace transformer function. This function takes a given label_name and label_value to add the appropriate label to Namespace objects using the SDK’s addLabel function.

import { addLabel, Configs } from 'kpt-functions';
import { isNamespace } from './gen/io.k8s.api.core.v1';

export const LABEL_NAME = 'label_name';
export const LABEL_VALUE = 'label_value';

export async function labelNamespace(configs: Configs) {
  const labelName = configs.getFunctionConfigValueOrThrow(LABEL_NAME);
  const labelValue = configs.getFunctionConfigValueOrThrow(LABEL_VALUE);
  configs.get(isNamespace).forEach(n => addLabel(n, labelName, labelValue));
}
  1. Run the label_namespace function on the example-configs directory:

    export CONFIGS=../../example-configs
    
    kpt fn source $CONFIGS |
      node dist/label_namespace_run.js -d label_name=color -d label_value=orange |
      kpt fn sink $CONFIGS
    

    As the name suggests, this function added the given label to all Namespace objects in the example-configs directory:

    git diff $CONFIGS
    
  2. Try modifying the function in src/label_namespace.ts to perform other operations and rerun the function on example-configs to see the changes.

  3. Explore validation functions like validate-rolebinding. Instead of transforming configuration, this function searches RoleBindings and returns a results field containing details about invalid subject names.

import { Configs, kubernetesObjectResult } from 'kpt-functions';
import { isRoleBinding } from './gen/io.k8s.api.rbac.v1';

const SUBJECT_NAME = 'subject_name';

export async function validateRolebinding(configs: Configs) {
  // Get the subject name parameter.
  const subjectName = configs.getFunctionConfigValueOrThrow(SUBJECT_NAME);

  // Iterate over all RoleBinding objects in the input
  // Filter those that have a subject we're looking for.
  const results = configs
    .get(isRoleBinding)
    .filter(
      (rb) =>
        rb && rb.subjects && rb.subjects.find((s) => s.name === subjectName)
    )
    .map((rb) =>
      kubernetesObjectResult(`Found banned subject '${subjectName}'`, rb)
    );

  configs.addResults(...results);
}
  1. Run validate-rolebinding on example-configs.

    kpt fn source $CONFIGS |
      node dist/validate_rolebinding_run.js -d subject_name=alice@foo-corp.com |
      kpt fn sink $CONFIGS
    git diff $CONFIGS
    
  2. Explore generator functions like expand-team-cr. This function generates a per-environment Namespace and RoleBinding object for each custom resource (CR) of type Team.

import { Configs } from 'kpt-functions';
import { isTeam, Team } from './gen/dev.cft.anthos.v1alpha1';
import { Namespace } from './gen/io.k8s.api.core.v1';
import { RoleBinding, Subject } from './gen/io.k8s.api.rbac.v1';

const ENVIRONMENTS = ['dev', 'prod'];

export async function expandTeamCr(configs: Configs) {
  // For each 'Team' custom resource in the input:
  // 1. Generate a per-enviroment Namespace.
  // 2. Generate RoleBindings in each Namespace.
  configs.get(isTeam).forEach((team) => {
    const name = team.metadata.name;

    ENVIRONMENTS.forEach((suffix) => {
      const ns = `${name}-${suffix}`;
      configs.insert(Namespace.named(ns));
      configs.insert(...createRoleBindings(team, ns));
    });
  });
}

function createRoleBindings(team: Team, namespace: string): RoleBinding[] {
  return (team.spec.roles || []).map((item) => {
    return new RoleBinding({
      metadata: {
        name: item.role,
        namespace,
      },
      subjects: roleSubjects(item),
      roleRef: {
        kind: 'ClusterRole',
        name: item.role,
        apiGroup: 'rbac.authorization.k8s.io',
      },
    });
  });
}

function roleSubjects(item: Team.Spec.Item): Subject[] {
  const userSubjects: Subject[] = (item.users || []).map(
    (user) =>
      new Subject({
        kind: 'User',
        name: user,
      })
  );
  const groupSubjects: Subject[] = (item.groups || []).map(
    (group) =>
      new Subject({
        kind: 'Group',
        name: group,
      })
  );
  return userSubjects.concat(groupSubjects);
}
  1. Run expand-team-cr on example-configs.

    kpt fn source $CONFIGS |
      node dist/expand_team_cr_run.js |
      kpt fn sink $CONFIGS
    git diff $CONFIGS
    

Next Steps


Last modified August 18, 2020: Deduplicate kpt function docs (#923) (8a9a3587)