Package and Test the Image

Build a custom Krkn base and a standalone scenario image, then verify its metadata and runtime behavior.

Starting point: the plugin methods are implemented. Result: a locally tested image that clients can discover through its labels.

Build the custom Krkn base

Dockerfile.base overlays the local Python package on the published runtime:

FROM quay.io/krkn-chaos/krkn:latest

COPY --chown=krkn:krkn __init__.py \
    /home/krkn/kraken/krkn/scenario_plugins/dev_demo/__init__.py
COPY --chown=krkn:krkn dev_demo_scenario_plugin.py \
    /home/krkn/kraken/krkn/scenario_plugins/dev_demo/dev_demo_scenario_plugin.py
podman build \
  -f "$KRKN_DEV_PLUGIN_DIR/Dockerfile.base" \
  -t "$KRKN_BASE_IMAGE" \
  "$KRKN_DEV_PLUGIN_DIR"

This build context is the plugin repository. Using Krkn’s upstream container Dockerfile unchanged would clone Krkn from GitHub during the build and omit the local plugin. If you change the core engine or krkn-lib, build a runtime that includes those changes explicitly.

Inspect the scenario contract

The image has two user inputs in krknctl-input.json:

[
  {
    "name": "wait",
    "short_description": "Wait (seconds)",
    "description": "Seconds to wait before listing pods in the namespace (0 to 60).",
    "variable": "WAIT_SECONDS",
    "type": "number",
    "required": "false"
  },
  {
    "name": "namespace",
    "short_description": "Namespace",
    "description": "Kubernetes namespace whose pod names will be logged.",
    "variable": "NAMESPACE",
    "type": "string",
    "required": "true",
    "validator": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$",
    "validation_message": "namespace must be a Kubernetes namespace name"
  }
]

name identifies the input presented by a client; variable is the environment variable injected into the container. The string values "true" and "false" match krknctl’s field descriptor format. The plugin still enforces its own integer range: a generic number field alone does not restrict input to an integer from 0 to 60.

Dockerfile.template embeds the JSON in krknctl.input_fields, together with the other labels included in the companion image:

LABEL krknctl.is_a_scenario="true"
LABEL krknctl.privileged="false"
LABEL krknctl.has_rollback="false"
LABEL krknctl.kubeconfig_path="/home/krkn/.kube/config"
LABEL krknctl.title="Dev Demo"
LABEL krknctl.description="Waits, then logs pod names from one Kubernetes namespace."
LABEL krknctl.input_fields='$KRKNCTL_INPUT'

The same Dockerfile copies the startup script with an explicit executable bit:

COPY --chmod=0755 --chown=krkn:krkn run.sh /home/krkn/run.sh
ENTRYPOINT ["/bin/bash", "/home/krkn/run.sh"]

This permission is required because the operator invokes /home/krkn/run.sh directly. A local entrypoint that passes the script to bash can conceal a missing executable bit.

Follow the runtime configuration

env.sh supplies defaults of 2 seconds and namespace default for direct container execution. The console still requires the namespace input declared in the metadata.

scenario.yaml.template contains:

wait: ${WAIT_SECONDS}
namespace: ${NAMESPACE}

run.sh changes to /home/krkn/kraken and uses an explicit substitution list:

envsubst '$WAIT_SECONDS $NAMESPACE' \
  < scenarios/scenario.yaml.template > scenarios/dev_demo.yaml

It derives config/dev_demo_config.yaml from the base configuration, sets kraken.kubeconfig_path from KUBECONFIG, and selects only dev_demo_scenarios. For this focused demo, telemetry export, event backup, reports, resiliency scoring, and the inter-scenario stabilization delay are disabled. These are demo settings, not a requirement for custom plugins.

Finally it hands control to Krkn while preserving the process exit status:

exec ./containers/entrypoint.sh --config=config/dev_demo_config.yaml

Build and inspect the scenario image

Compact the JSON, expand only its build-time placeholder, and select the custom base image in the generated Dockerfile. The runtime placeholders in the YAML and shell files remain untouched.

export KRKNCTL_INPUT="$(jq -c . "$KRKN_DEV_PLUGIN_DIR/krknctl-input.json")"
envsubst '${KRKNCTL_INPUT}' \
  < "$KRKN_DEV_PLUGIN_DIR/Dockerfile.template" |
  sed "1s|^FROM .*|FROM $KRKN_BASE_IMAGE|" \
  > "$KRKN_DEV_PLUGIN_DIR/Dockerfile"

podman build \
  -f "$KRKN_DEV_PLUGIN_DIR/Dockerfile" \
  -t "$KRKN_SCENARIO_IMAGE" \
  "$KRKN_DEV_PLUGIN_DIR"

podman image inspect "$KRKN_SCENARIO_IMAGE" |
  jq '.[0].Config.Labels["krknctl.input_fields"] | fromjson'
podman run --rm --user 1001:1001 --entrypoint /bin/bash \
  "$KRKN_SCENARIO_IMAGE" -c 'test -x /home/krkn/run.sh'

Run against an existing Kind cluster

Set KRKN_TEST_CLUSTER to a running cluster you supply. This test does not create or register an operator target.

Kind’s host kubeconfig normally uses a loopback address. A container on the kind network needs the internal API address instead. Save that kubeconfig under your home directory so Podman Machine on macOS can mount it. The temporary directory is private; the mounted file is readable by container UID 1001.

export KRKN_TEST_CLUSTER=kind
export KRKN_TEST_NAMESPACE=default

(
  set -e
  mkdir -p "$HOME/.kube"
  KRKN_TEST_CONFIG_DIR="$(mktemp -d "$HOME/.kube/krkn-dev-demo.XXXXXX")"
  trap 'rm -rf "$KRKN_TEST_CONFIG_DIR"' EXIT
  kind get kubeconfig --internal --name "$KRKN_TEST_CLUSTER" \
    > "$KRKN_TEST_CONFIG_DIR/config"
  chmod 644 "$KRKN_TEST_CONFIG_DIR/config"

  podman run --rm --user 1001:1001 --network kind \
    -e WAIT_SECONDS=2 \
    -e NAMESPACE="$KRKN_TEST_NAMESPACE" \
    -e KUBECONFIG=/home/krkn/.kube/config \
    -v "$KRKN_TEST_CONFIG_DIR/config:/home/krkn/.kube/config:ro" \
    --entrypoint /bin/bash \
    "$KRKN_SCENARIO_IMAGE" -c 'exec /home/krkn/run.sh'
)

On a Linux host with SELinux enforcing, use :ro,Z for this dedicated temporary kubeconfig mount if a label denial prevents access.

For a non-Kind target, use a kubeconfig whose API endpoint is reachable from the container. Replace kind get kubeconfig ... with kubectl --kubeconfig "$KUBECONFIG" config view --raw --flatten --minify and omit --network kind. Flattening embeds certificate files; an external credential plugin still needs to be available in the image.

Check both outcomes

For an existing namespace, expect container exit status 0 and a log block:

##### DEVELOPMENT SCENARIO #####
Namespace: default
Pods:
  (none)
##### END DEVELOPMENT SCENARIO #####

Pod names replace (none) when the namespace contains pods. Set KRKN_TEST_NAMESPACE=dev-demo-missing-namespace to a namespace you have confirmed does not exist and repeat the test. Expect a nonzero exit status and a concise missing-namespace error. Do not create that namespace as part of this failure-path test.

Next: Publish and run with Krkn Operator.