This guide provides coding standards, development workflows, and common commands for AI agents and developers contributing to the Kiali project. It complements the existing CONTRIBUTING.md, STYLE_GUIDE.adoc, and CODE_OF_CONDUCT.md.
- Quick Reference
- Code Quality Standards
- Building and Testing
- Development Workflows
- Cluster-Specific Development Workflows
- Operator Development
- File Protection Rules
- Common Patterns and Best Practices
- Troubleshooting
- Quick Command Reference
# Build everything
make build-ui build test
# Run Kiali locally with hot-reload
make build-ui
make run-backend # In one terminal
make run-frontend # In another terminal
# Build and push dev images to cluster (example for minikube with profile "ci")
make CLUSTER_TYPE=minikube MINIKUBE_PROFILE=ci build-ui build test cluster-push
# Deploy to cluster (ensure CLUSTER_TYPE is set - see cluster-specific sections below)
make CLUSTER_TYPE=minikube MINIKUBE_PROFILE=minikube cluster-push # Example for minikube
make CLUSTER_TYPE=minikube MINIKUBE_PROFILE=minikube operator-create # Example for minikube
make CLUSTER_TYPE=minikube MINIKUBE_PROFILE=minikube kiali-create # Example for minikube
# Format and lint
make format lint
# Run tests
make test # Unit tests
make cypress-gui # Frontend integration tests (GUI)
make cypress-run # Frontend integration tests (headless)The Kiali project consists of multiple repositories that should be cloned together:
kiali_sources/
├── kiali/ # Main server and UI repo
├── kiali-operator/ # Operator repo (link as kiali/operator)
└── helm-charts/ # Helm charts repo
Setup commands:
mkdir kiali_sources && cd kiali_sources
git clone https://github.com/kiali/kiali.git
git clone https://github.com/kiali/kiali-operator.git
git clone https://github.com/kiali/helm-charts.git
ln -s $PWD/kiali-operator kiali/operator- Use
anyinstead ofinterface{}- Always preferanyfor empty interfaces - No end-of-line spaces - Remove all trailing whitespace from any line you add or modify
- Sort struct fields alphabetically - When adding or modifying Go structs, sort field names alphabetically
- Sort YAML keys alphabetically - When adding or modifying YAML schemas or files, sort keys alphabetically
- Meaningful comments - Write comments that explain "why", not "what". Focus on the purpose and usage of abstractions.
Organize imports in three groups separated by blank lines:
import (
// Standard library imports
"errors"
"fmt"
"time"
// Third-party imports
"k8s.io/client-go/tools/clientcmd/api"
// Kiali imports
"github.com/kiali/kiali/log"
)- Use
gofmtfor formatting (automatically applied bymake format) - Use
golangci-lintfor linting (run viamake lint) - Install linting tools:
make lint-install - The project requires a specific Go version defined in
go.mod- check withmake go-check
Files:
- Most files:
PascalCase(e.g.,ServiceList.ts) - General purpose files:
camelCase(e.g.,routes.ts)
Variables and Functions:
- Generally:
camelCase - Redux actions:
PascalCase(e.g.,GraphActions) - Global constants:
UPPER_SNAKE_CASE(e.g.,TIMER_REQUEST_PER_SECOND_MIN) - Local constants:
camelCase
Enums:
enum DisplayMode {
LARGE, // Values in UPPER_SNAKE_CASE
SMALL
}Use consistent naming:
- Handler methods:
handle+ event name (e.g.,handleClick,handleChange) - Props:
on+ event name (e.g.,onSelect,onChange) - Use present tense
- Avoid clashing with native events
Example:
<Item
onClick={() => handleClick(item.name)}
onSelect={() => handleSelect(item.id)}
/>Prefer arrow functions:
createItem = () => {
return (
<ul>
{props.items.map((item, index) => (
<Item key={item.key} onClick={() => doSomethingWith(item.name, index)} />
))}
</ul>
);
}Type-safe Redux:
- Use
typesafe-actionslibrary - Separate
ReduxPropsfrom component props
type ReduxProps = {
// Redux props only, alphabetically sorted
};
type MyComponentProps = ReduxProps & {
// Component-specific props, alphabetically sorted
};
class MyComponent extends React.Component<MyComponentProps> {
// ...
}URL Consistency:
- Store page state in Redux
- Make pages bookmarkable via URL parameters
- On construction: URL params override Redux state
- After construction: Update URL to reflect Redux state changes
Always use the t function for translatable strings:
import { t } from 'utils/I18nUtils'; // NOT from 'i18next'!
title = t('Traffic Graph');For components that don't re-render on language change, add language to Redux props.
# Build UI (must be done before building backend)
make build-ui
# Build backend
make build
# Build for specific architecture
make build-linux-multi-arch
# Clean builds
make clean # Clean backend build artifacts
make clean-ui # Clean UI build artifacts
make clean-all # Clean everything including _output dir# Run all backend tests
make test
# Run specific tests with flags
make -e GO_TEST_FLAGS="-race -v -run=\"TestName\"" test
# Run integration tests
make test-integration
# Run controller integration tests
make test-integration-controller# Build UI and run tests
make build-ui-test
# Run Cypress tests in GUI mode
make cypress-gui
# Run Cypress tests headlessly
make cypress-run
# Run performance tests
make perf-tests-gui
make perf-tests-runCypress Environment Variables:
These variables can be set when running tests outside of the local cluster or to customize test behavior:
export CYPRESS_BASE_URL=<value> # defaults to http://localhost:3000
export CYPRESS_USERNAME=<value> # defaults to jenkins, opt. kubeadmin
export CYPRESS_PASSWD=<value> # no defaults
export CYPRESS_AUTH_PROVIDER=<value> # defaults to my_htpasswd_provider
export CYPRESS_ALLOW_INSECURE_KIALI_API=<true|false> # Useful when running tests locally against an insecure endpoint like crc
export CYPRESS_STERN=<true|false> # defaults to falseTest Requirements:
- Istio installed
- Kiali deployed
- Bookinfo demo app deployed
- Error rates demo app deployed
Install demo apps:
./hack/istio/install-testing-demos.sh -c kubectl# Start Kiali backend (in terminal 1)
make run-backend
# Start frontend dev server (in terminal 2)
make -e KIALI_PROXY_URL=http://localhost:20001/kiali yarn-start
# Run Cypress tests (in terminal 3)
make cypress-guiThe hack/run-integration-tests.sh script is the main entrypoint for setting up clusters and running end-to-end integration tests. It can create a KinD cluster, install Istio, deploy demo apps, deploy Kiali, and run the test suite — all in one command.
kindanddockerinstalledmake build(kiali binary required forlocalandofflinesuites)make build-ui(Cypress must be installed for all frontend suites)
# Full run: setup cluster + run tests
hack/run-integration-tests.sh --test-suite <suite>
# Setup only (create cluster, install Istio, deploy apps, skip tests)
hack/run-integration-tests.sh --test-suite <suite> --setup-only true
# Tests only (skip setup, run against existing cluster)
hack/run-integration-tests.sh --test-suite <suite> --tests-only true| Suite | Description |
|---|---|
backend |
Go backend integration tests (default) |
backend-external-controlplane |
Backend tests with external control plane |
frontend |
All frontend Cypress tests |
frontend-core-1 |
Frontend core test group 1 |
frontend-core-2 |
Frontend core test group 2 |
frontend-core-optional |
CRD validation and Perses tests |
frontend-ambient |
Frontend tests with Istio ambient mode |
frontend-primary-remote |
Frontend multicluster primary-remote tests |
frontend-multi-primary |
Frontend multicluster multi-primary tests |
frontend-multi-mesh |
Frontend multi-mesh tests |
frontend-external-kiali |
Frontend tests with external Kiali |
frontend-tempo |
Frontend tracing tests with Tempo |
local |
Runs Kiali locally (not in-cluster) with smoke Cypress tests |
offline |
Runs Kiali in offline mode with must-gather data |
The local suite is useful for testing local code changes without building container images. It:
- Creates a KinD cluster with Istio (via Sail) but does not deploy Kiali in-cluster
- Installs demo applications (bookinfo, error rates, etc.)
- Runs the Kiali binary directly from
$GOPATH/bin/kiali - Executes the
cypress:run:smoketest suite against the locally-running Kiali
# Step 1: Build the kiali binary and frontend
make build-ui build
# Step 2: Setup cluster only (takes ~5 minutes)
hack/run-integration-tests.sh --test-suite local --setup-only true
# Step 3: Run tests against existing cluster (repeatable after code changes)
make build # Rebuild after code changes
hack/run-integration-tests.sh --test-suite local --tests-only trueAfter setting up a cluster with --setup-only true, you can start Kiali locally and run specific tests interactively. This is the recommended workflow for writing and debugging individual e2e tests.
Step 1: Start Kiali locally
# Start Kiali binary locally (uses anonymous auth, port-forwards to in-cluster services)
$(go env GOPATH)/bin/kiali \
-c hack/ci-yaml/ci-test-config-no-cache.yaml run \
--cluster-name-overrides kind-ci=cluster-default \
--port-forward-tracing --enable-tracing \
--port-forward-prom --port-forward-grafana --no-browserKiali will be available at http://localhost:20001.
Step 2: Run specific tests
Tests use Gherkin .feature files with @tags for grouping. Tests are in frontend/cypress/integration/featureFiles/ with step definitions in frontend/cypress/integration/common/.
cd frontend
# Run a specific test tag
yarn cypress run -e TAGS="@smoke"
yarn cypress run -e TAGS="@core-1"
yarn cypress run -e TAGS="@core-2"
# Run a specific feature file
npx cypress run --spec "cypress/integration/featureFiles/services.feature"
# Run a specific feature file with a specific tag
npx cypress run --spec "cypress/integration/featureFiles/services.feature" -e TAGS="@smoke"
# Open Cypress GUI to pick tests interactively
yarn cypress open -e TAGS="@core-1"
# Use make targets
make cypress-gui # Opens GUI with core tests
make cypress-run # Headless core tests
make cypress-selected # Runs @selected tagged tests (tag a scenario with @selected for debugging)Available test tags:
| Tag | Description | Suite |
|---|---|---|
@smoke |
Quick smoke tests (~30 scenarios) | local |
@core-1 |
Core UI tests group 1 (~130 scenarios) | frontend-core-1 |
@core-2 |
Core UI tests group 2 (~155 scenarios) | frontend-core-2 |
@crd-validation |
CRD validation tests | frontend-core-optional |
@perses |
Perses dashboard tests | frontend-core-optional |
@multi-cluster |
Primary-remote multicluster tests | frontend-primary-remote |
@multi-primary |
Multi-primary multicluster tests | frontend-multi-primary |
@multi-mesh |
Multi-mesh tests | frontend-multi-mesh |
@ambient |
Ambient mesh tests | frontend-ambient |
@tracing |
Distributed tracing (Tempo) tests | frontend-tempo |
@offline |
Offline mode tests | offline |
@selected |
Manual selection for debugging | N/A |
Tip: To debug a single scenario, add the @selected tag to it in the .feature file, then run make cypress-selected.
AI agents can connect to the actual Cypress-controlled Chrome browser via the Chrome DevTools Protocol (CDP) to inspect the test runner and the application under test.
Setup: .mcp.json
The project includes a .mcp.json at the repo root with a cypress-debugger MCP server that connects to the Cypress Chrome browser on port 9222:
{
"mcpServers": {
"cypress-debugger": {
"type": "stdio",
"command": "npx",
"args": ["@playwright/mcp@latest", "--cdp-endpoint", "http://127.0.0.1:9222"]
}
}
}This is already checked into the repository — no manual setup is needed. The cypress-debugger MCP tools (e.g., mcp__cypress-debugger__browser_snapshot, mcp__cypress-debugger__browser_click) become available automatically when Claude Code loads the project.
Prerequisites:
Before using the debug browser, ensure:
- Chrome is installed —
--browser chromerequires a Chrome/Chromium binary on the system - A cluster with Istio and demo apps is running — check with
kind get clusters(look forci) - Kiali is running locally — check with
curl -s http://localhost:20001/kiali/api
If the cluster doesn't exist yet, set it up:
make build-ui build
hack/run-integration-tests.sh --test-suite local --setup-only trueIf Kiali isn't running, start it:
$(go env GOPATH)/bin/kiali \
-c hack/ci-yaml/ci-test-config-no-cache.yaml run \
--cluster-name-overrides kind-ci=cluster-default \
--port-forward-tracing --enable-tracing \
--port-forward-prom --port-forward-grafana --no-browserStep 1: Run Cypress with Chrome on a fixed CDP port
cd frontend
# Run a specific test with Chrome, keep the browser open after completion
CYPRESS_BASE_URL=http://localhost:20001 \
CYPRESS_REMOTE_DEBUGGING_PORT=9222 \
npx cypress run \
--browser chrome \
--headed \
--no-exit \
-e TAGS="@smoke" \
--spec "cypress/integration/featureFiles/kiali_about.feature"Key flags:
CYPRESS_REMOTE_DEBUGGING_PORT=9222— exposes the Chrome browser on a fixed CDP port so thecypress-debuggerMCP can connect--browser chrome— uses Chrome instead of Electron (required for CDP)--headed— shows the browser window--no-exit— keeps the browser open after tests finish so you can inspect the state
Step 2: Use the cypress-debugger MCP to inspect the browser
Once Cypress is running with the Chrome browser on port 9222, the cypress-debugger MCP tools (mcp__cypress-debugger__browser_snapshot, mcp__cypress-debugger__browser_click, etc.) connect directly to the Cypress Chrome instance. This lets AI agents:
- Inspect the Cypress test runner — see test results, passed/failed steps, and error messages
- Inspect the app under test — the Kiali UI is rendered inside the Cypress runner; snapshots show the actual DOM state
- Debug failing assertions — read the Cypress step definition (in
frontend/cypress/integration/common/*.ts), understand what DOM selector it uses (e.g.,td[data-label="Details"],data-test="namespace-dropdown"), and usebrowser_evaluateto run the same query against the live page - Re-run tests — press
Ctrl+Rviabrowser_press_keyto re-run the current spec (see below) - Verify the CDP endpoint is reachable:
curl -s http://127.0.0.1:9222/json/list
Re-running tests from MCP:
To re-run a Cypress spec via the MCP tools, press Ctrl+R:
browser_press_key({ key: "Control+r" })
This reloads the Cypress runner page which triggers a full re-run of the current spec. To verify a re-run occurred, take a browser_snapshot and check that iframe element ref prefixes changed (e.g., f1e* → f3e*) and timestamps in the Kiali UI updated to the current time.
Note: Pressing just
rviabrowser_press_keydoes not work because focus isn't on the Cypress runner. Clicking the spec file link and usingbrowser_navigateto the same URL also do not trigger re-runs. UseCtrl+Rorbrowser_evaluatewithwindow.location.reload().
Important:
Ctrl+Rdoes NOT pick up code changes. Cypress caches compiled step definitions and feature files. If you modify a.tsstep definition or.featurefile, you must kill the Cypress process and restart it for the changes to take effect.Ctrl+Ronly re-runs the previously compiled spec. To restart cleanly:
- Kill all Cypress and associated Chrome processes:
pkill -9 -f cypress; pkill -9 -f "chrome.*9222"- Wait 2-3 seconds for ports to be released
- Verify port 9222 is free:
curl -s http://127.0.0.1:9222/json/listshould fail- Re-launch Cypress with the same command
- After the new browser opens, use
browser_closethenbrowser_navigateto reconnect the MCP to the new browser instance (the MCP caches its connection to the old browser)
Snapshot depth tips:
The browser_snapshot tool accepts an optional depth parameter to control output size:
depth: 3-4— quick overview of page structure and test runner controlsdepth: 5-6— detailed view including the Kiali app inside the iframe- No depth limit (default) — full DOM tree; can be very large (50KB+), useful for detailed inspection
Understanding the snapshot structure:
The Cypress runner page has two main areas:
- Test runner panel (left side) — element refs like
e13,e17, etc. Contains the spec name, pass/fail stats, test steps, and timing - App under test iframe (right side) — element refs like
f1e3,f1e7, etc. (prefixed withf<N>e). Contains the actual Kiali UI being tested. Use these refs to interact with the Kiali app directly
Reading test runner state via browser_evaluate:
Snapshots can be noisy. Use browser_evaluate for quick, targeted checks:
// Check pass/fail/pending counts
() => { const stats = document.querySelectorAll('[aria-label="Stats"] li'); return Array.from(stats).map(s => s.textContent?.trim()); }
// → ["Passed:1", "Failed:--", "Pending:--"]
// Check last N Cypress command log entries (most useful for debugging)
() => { const items = document.querySelectorAll('.command-wrapper'); return Array.from(items).slice(-10).map(el => el.textContent?.trim()).join('\n'); }
// Check for error messages
() => { const el = document.querySelector('.runnable-err-message'); return el ? el.textContent : 'no error'; }
// Inspect the Kiali app DOM inside the iframe
() => { const iframe = document.querySelector('iframe'); if (!iframe?.contentDocument) return 'no iframe'; return iframe.contentDocument.querySelector('#target-panel-control-plane')?.textContent?.substring(0, 200); }Understanding Cypress test structure:
- Feature files:
frontend/cypress/integration/featureFiles/*.feature(Gherkin BDD with@tags) - Step definitions:
frontend/cypress/integration/common/*.ts(TypeScript implementing each Given/When/Then) - Key selectors used in tests:
cy.getBySel('namespace-dropdown')→[data-test="namespace-dropdown"]cy.get('th[data-label="Name"]')→ table column headerscy.get('td[data-label="Details"]')→ table column cellsgetColWithRowText(rowText, colName)→ finds a cell by row content and column namea[href$="..."]→ link assertions using endsWith
Use the gh CLI to identify failures from CI, then reproduce locally.
Step 1: Find the failed CI job
# List recent failed Kiali CI runs
gh run list --repo kiali/kiali --status failure --limit 10 \
--json databaseId,name,headBranch,createdAt \
--jq '.[] | select(.name == "Kiali CI") | {id: .databaseId, branch: .headBranch, date: .createdAt}'
# Or given a specific run URL (https://github.com/kiali/kiali/actions/runs/<RUN_ID>):
gh run view <RUN_ID> --repo kiali/kiali --json jobs \
--jq '.jobs[] | select(.conclusion == "failure") | {name: .name, id: .databaseId}'Step 2: Extract failure details from logs
# Get the failing test names and error messages
gh run view <RUN_ID> --repo kiali/kiali --log --job <JOB_ID> 2>&1 \
| grep -E "(failing|AssertionError|CypressError|Error:)" | head -20
# Get context around failures (test name + error)
gh run view <RUN_ID> --repo kiali/kiali --log --job <JOB_ID> 2>&1 \
| grep -B5 "AssertionError" | head -40Step 3: Download failure screenshots
Cypress takes screenshots on failure and uploads them as artifacts.
# Download all cypress screenshots from the run
gh run download <RUN_ID> --repo kiali/kiali --dir /tmp/ci-artifacts --pattern "*cypress*"
# View the screenshots to understand the failure visually
# Screenshots are at: /tmp/ci-artifacts/cypress-screenshots-*/The screenshots show the Cypress test runner (left) with the failing step highlighted, and the Kiali UI (right) showing the actual state at the time of failure.
Step 4: Identify the test suite and feature file
Map the failed job name to a test suite:
| CI Job Name Pattern | Test Suite | Feature Files |
|---|---|---|
Run frontend core 1 integration tests |
frontend-core-1 |
@core-1 tagged scenarios |
Run frontend core 2 integration tests |
frontend-core-2 |
@core-2 tagged scenarios |
Run Ambient frontend integration tests |
frontend-ambient |
@ambient, @waypoint scenarios |
Run frontend multicluster multi-primary |
frontend-multi-primary |
@multi-primary scenarios |
Run frontend multicluster primary-remote |
frontend-primary-remote |
@multi-cluster scenarios |
Run frontend local and offline mode |
local / offline |
@smoke / @offline scenarios |
Run backend integration tests |
backend |
Go tests in tests/integration/ |
Step 5: Reproduce locally
# Checkout the failing branch
git checkout <branch-name>
# Build
make build-ui build
# Setup cluster for the correct suite (if not already running)
hack/run-integration-tests.sh --test-suite <suite> --setup-only true
# Start Kiali (if not already running)
$(go env GOPATH)/bin/kiali \
-c hack/ci-yaml/ci-test-config-no-cache.yaml run \
--cluster-name-overrides kind-ci=cluster-default \
--port-forward-tracing --enable-tracing \
--port-forward-prom --port-forward-grafana --no-browser &
# Run just the failing test with debug browser
cd frontend
CYPRESS_BASE_URL=http://localhost:20001 \
CYPRESS_REMOTE_DEBUGGING_PORT=9222 \
npx cypress run --browser chrome --headed --no-exit \
--spec "cypress/integration/featureFiles/<failing-feature>.feature"Step 6: Debug with Playwright MCP
With the Cypress browser on port 9222 and --no-exit keeping it open, use cypress-debugger MCP tools to inspect the Cypress Chrome browser, examine the DOM state, and understand why the assertion failed. Use Ctrl+R to re-run the test after making fixes. See Debugging Cypress Tests with Playwright MCP for full details.
AI agents can write new Cypress e2e tests, run them against a local cluster, and iterate until they pass.
Test file structure:
Tests use Gherkin BDD (.feature files) paired with TypeScript step definitions:
frontend/cypress/integration/
├── featureFiles/ # Gherkin .feature files (scenarios with @tags)
│ ├── services.feature
│ ├── app_details.feature
│ └── ...
└── common/ # TypeScript step definitions (shared across features)
├── table.ts # Reusable table assertions (getColWithRowText, colExists, etc.)
├── navigation.ts # Page navigation steps ("user is at the X page")
├── transition.ts # Loading state helpers (ensureKialiFinishedLoading)
├── services.ts # Service-specific steps
├── hooks.ts # Before/After hooks for demo app setup
└── ...
Step definitions are global — any .ts file in cypress/integration/ is loaded for all feature files (configured via "stepDefinitions": "cypress/integration/**/*.{js,ts}" in package.json). This means steps defined in table.ts or navigation.ts are available to every feature file.
Writing a new test — step by step:
-
Choose the right feature file — add scenarios to an existing
.featurefile if the feature area matches. Only create a new file for an entirely new feature area. -
Tag the scenario — use the appropriate tag(s) for the test suite it belongs to:
@bookinfo-app @core-2 Scenario: My new test scenario Given user is at administrator perspective And user is at the "services" page When user selects the "bookinfo" namespace Then user sees "productpage" in the table
@bookinfo-app,@error-rates-app,@sleep-app— hooks use these to ensure demo apps are installed@core-1,@core-2,@smoke— which CI suite runs this test- Use
@selectedtemporarily during development to run only your test
-
Reuse existing step definitions — check what's already available before writing new steps:
navigation.ts:user is at the {string} page,user is at administrator perspectivetable.ts:user selects the {string} namespace,user sees {string} in the table,table length should be {int},the {string} column on the {string} row has a link ending in {string}transition.ts:ensureKialiFinishedLoading()— wait for loading spinners to disappearservices.ts,apps.ts,workloads.ts— domain-specific steps
-
Write new step definitions if needed — add them to the appropriate file in
cypress/integration/common/:import { Then, When } from '@badeball/cypress-cucumber-preprocessor'; import { ensureKialiFinishedLoading } from './transition'; Then('the service details page shows {string}', (expectedText: string) => { cy.get('[data-test="service-details"]').should('contain.text', expectedText); });
-
Key patterns for step definitions:
cy.getBySel('name')→ selects[data-test="name"](custom Kiali command)cy.get('td[data-label="Name"]')→ table cells by column headergetColWithRowText(rowText, colName)→ find a cell by row content and column name (fromtable.ts)ensureKialiFinishedLoading()→ wait for Kiali to finish loading (fromtransition.ts)openTab(tabName)→ click a tab in the details page (fromtransition.ts)- Use
data-testattributes on React components for reliable selectors
Running your new test:
Ensure the cluster and Kiali are running (see Prerequisites in the MCP debugging section above).
# 1. Setup cluster (if not already running — check with `kind get clusters`)
hack/run-integration-tests.sh --test-suite local --setup-only true
10BC8
# 2. Start Kiali (if not already running — check with `curl -s http://localhost:20001/kiali/api`)
$(go env GOPATH)/bin/kiali \
-c hack/ci-yaml/ci-test-config-no-cache.yaml run \
--cluster-name-overrides kind-ci=cluster-default \
--port-forward-tracing --enable-tracing \
--port-forward-prom --port-forward-grafana --no-browser &
# 3. Run just your test with debug browser (use @selected tag for fast iteration)
cd frontend
CYPRESS_BASE_URL=http://localhost:20001 \
CYPRESS_REMOTE_DEBUGGING_PORT=9222 \
npx cypress run --browser chrome --headed --no-exit \
-e TAGS="@selected" \
--spec "cypress/integration/featureFiles/<your-feature>.feature"
# Or run headless for quick pass/fail (no debug browser)
CYPRESS_BASE_URL=http://localhost:20001 \
npx cypress run -e TAGS="@selected"With CYPRESS_REMOTE_DEBUGGING_PORT=9222 and --no-exit, the cypress-debugger MCP tools can connect to inspect the browser, debug failures, and re-run the test with Ctrl+R. See the Debugging Cypress Tests with Playwright MCP section for details.
Iteration loop:
- Write or modify the test
- Run it with the debug browser flags above — if it fails, examine the error message
- Use
cypress-debuggerMCP tools to inspect the browser state if the error isn't clear - Determine if the failure is a test issue (wrong selector, wrong assertion) or a code issue (feature not working)
- Fix the test or the code accordingly
- Re-run with
Ctrl+Rviabrowser_press_keyif the debug browser is still open, or re-launch the cypress command - Remove the
@selectedtag and verify the test passes with its real suite tag
Before committing:
- Remove the
@selectedtag - Ensure the test has the correct suite tag (
@core-1,@core-2, etc.) - Ensure the test has the correct demo app tag (
@bookinfo-app,@error-rates-app, etc.) if it depends on demo apps - Run
make format linton any changed Go code - Verify the test passes headless:
npx cypress run -e TAGS="@your-suite-tag" --spec "your-feature.feature"
# Specify Istio version
hack/run-integration-tests.sh --test-suite frontend --istio-version 1.29.1
# Use minikube instead of kind
hack/run-integration-tests.sh --test-suite backend --cluster-type minikube
# Record video for Cypress tests
hack/run-integration-tests.sh --test-suite frontend --with-video true
# Enable ambient mode for multi-primary
hack/run-integration-tests.sh --test-suite frontend-multi-primary --ambient true
# Full help
hack/run-integration-tests.sh --helpThis is the fastest way to develop and doesn't require a cluster:
Terminal 1 - Backend:
make build-ui # Only needed once or when UI changes
make run-backend
# Pass additional arguments:
# make KIALI_RUN_ARGS="--log-level debug" run-backend
# Multi-cluster:
# make KIALI_RUN_ARGS="--remote-cluster-contexts kind-mesh --cluster-name-overrides kind-mesh=mesh" run-backendTerminal 2 - Frontend:
make run-frontend
# Opens browser automatically at http://localhost:3000Both backend and frontend will hot-reload on code changes.
The following sections provide complete workflows for developing Kiali on different cluster types. Each cluster type has specific requirements and commands.
Minikube is the most commonly used development environment for Kiali.
export CLUSTER_TYPE=minikube
export MINIKUBE_PROFILE=minikube # use any profile name you want; "ci" is used in examples
export DORP=docker # or "podman"
export CLIENT_EXE=kubectl # or 'oc' for OpenShift clusters# Set to false if your local helm-charts repo has a branch with no remote
export HELM_CHARTS_REPO_PULL=false# Start basic minikube cluster
./hack/k8s-minikube.sh -mp ${MINIKUBE_PROFILE} start
# Start with Hydra enabled (for auth testing - this is required for some molecule tests)
./hack/k8s-minikube.sh --hydra-enabled true -mp ${MINIKUBE_PROFILE} start
# For all available options, run:
./hack/k8s-minikube.sh --help
# Install Istio after cluster is ready
./hack/istio/install-istio-via-istioctl.sh --client-exe ${CLIENT_EXE}# Check cluster status
make CLUSTER_TYPE=minikube MINIKUBE_PROFILE=${MINIKUBE_PROFILE} cluster-status
# Open Kubernetes dashboard
./hack/k8s-minikube.sh -mp ${MINIKUBE_PROFILE} dashboard# Build UI and backend, then push images to cluster
make CLUSTER_TYPE=minikube MINIKUBE_PROFILE=${MINIKUBE_PROFILE} build-ui build test cluster-push
# Deploy operator
make CLUSTER_TYPE=minikube MINIKUBE_PROFILE=${MINIKUBE_PROFILE} operator-create
# Deploy Kiali
make CLUSTER_TYPE=minikube MINIKUBE_PROFILE=${MINIKUBE_PROFILE} kiali-create# Rebuild and reload just Kiali (faster)
make CLUSTER_TYPE=minikube MINIKUBE_PROFILE=${MINIKUBE_PROFILE} \
build cluster-push-kiali kiali-reload-image
# Rebuild and reload operator
make CLUSTER_TYPE=minikube MINIKUBE_PROFILE=${MINIKUBE_PROFILE} \
cluster-push-operator operator-reload-image# Install Bookinfo demo
./hack/k8s-minikube.sh -mp ${MINIKUBE_PROFILE} bookinfo
# Or use dedicated script:
./hack/istio/install-bookinfo-demo.sh -c ${CLIENT_EXE}
# Install error rates demo (for testing)
./hack/istio/install-testing-demos.sh -c ${CLIENT_EXE}# Port forward to Kiali
./hack/k8s-minikube.sh -mp ${MINIKUBE_PROFILE} port-forward
# Or get ingress URL
./hack/k8s-minikube.sh -mp ${MINIKUBE_PROFILE} ingress
# Direct port forward:
kubectl port-forward -n istio-system svc/kiali 20001:20001# Delete Kiali
make CLUSTER_TYPE=minikube MINIKUBE_PROFILE=${MINIKUBE_PROFILE} kiali-delete
# Delete operator
make CLUSTER_TYPE=minikube MINIKUBE_PROFILE=${MINIKUBE_PROFILE} operator-delete
# Purge everything
./hack/purge-kiali-from-cluster.sh -c ${CLIENT_EXE}
# Stop minikube (preserves cluster)
./hack/k8s-minikube.sh -mp ${MINIKUBE_PROFILE} stop
# Delete minikube cluster completely
./hack/k8s-minikube.sh -mp ${MINIKUBE_PROFILE} deleteKinD is useful for testing in a lightweight, disposable Kubernetes environment.
export CLUSTER_TYPE=kind
export KIND_NAME=kiali-testing # cluster name
export DORP=docker # podman not fully supported for kind yet
export CLIENT_EXE=kubectl# Set to false if your local helm-charts repo has a branch with no remote
export HELM_CHARTS_REPO_PULL=false# Start basic KinD cluster with MetalLB
./hack/start-kind.sh -n ${KIND_NAME}
# Start with custom load balancer IP range
./hack/start-kind.sh -n ${KIND_NAME} --load-balancer-range "255.70-255.84"
# Start with image registry enabled
./hack/start-kind.sh -n ${KIND_NAME} --enable-image-registry true
# Start with Hydra for auth testing
./hack/start-kind.sh -n ${KIND_NAME} --enable-hydra true
# For all available options, run:
./hack/start-kind.sh --help
# Install Istio after cluster is ready
./hack/istio/install-istio-via-istioctl.sh --client-exe ${CLIENT_EXE}# List KinD clusters
kind get clusters
# Check cluster is accessible
kubectl cluster-info --context kind-${KIND_NAME}
# Check if cluster is ready for Kiali
make CLUSTER_TYPE=kind KIND_NAME=${KIND_NAME} cluster-status# Build UI and backend, then push images to cluster
make CLUSTER_TYPE=kind KIND_NAME=${KIND_NAME} build-ui build test cluster-push
# Deploy operator
make CLUSTER_TYPE=kind KIND_NAME=${KIND_NAME} operator-create
# Deploy Kiali
make CLUSTER_TYPE=kind KIND_NAME=${KIND_NAME} kiali-create# Rebuild and reload just Kiali (faster)
make CLUSTER_TYPE=kind KIND_NAME=${KIND_NAME} \
build cluster-push-kiali kiali-reload-image
# Rebuild and reload operator
make CLUSTER_TYPE=kind KIND_NAME=${KIND_NAME} \
cluster-push-operator operator-reload-image# Install Bookinfo
./hack/istio/install-bookinfo-demo.sh -c ${CLIENT_EXE}
# Install testing demos
./hack/istio/install-testing-demos.sh -c ${CLIENT_EXE}# Port forward to Kiali
kubectl port-forward -n istio-system svc/kiali 20001:20001
# Access at: http://localhost:20001/kiali# Delete Kiali
make CLUSTER_TYPE=kind KIND_NAME=${KIND_NAME} kiali-delete
# Delete operator
make CLUSTER_TYPE=kind KIND_NAME=${KIND_NAME} operator-delete
# Purge everything
./hack/purge-kiali-from-cluster.sh -c ${CLIENT_EXE}
# Delete entire KinD cluster
kind delete cluster --name ${KIND_NAME}OpenShift can be run locally using CodeReady Containers (CRC) or on a remote cluster.
export CLUSTER_TYPE=openshift
export DORP=podman # or docker, depending on your setup
export CLIENT_EXE=oc# Set to false if your local helm-charts repo has a branch with no remote
export HELM_CHARTS_REPO_PULL=false# Start CRC cluster (downloads CRC if not present)
./hack/crc-openshift.sh start
# Check CRC status
./hack/crc-openshift.sh status
# Install Istio after cluster is ready
./hack/istio/install-istio-via-istioctl.sh -c ${CLIENT_EXE}
# Optional: Install OpenShift Service Mesh (OSSM) instead of upstream Istio
# ./hack/istio/sail/install-ossm-release.sh install-operators
# ./hack/istio/sail/install-ossm-release.sh install-istio
# Get all routes
./hack/crc-openshift.sh routes
# Get all service endpoints
./hack/crc-openshift.sh services# Check CRC status
./hack/crc-openshift.sh status
# ... or use make target
make CLUSTER_TYPE=openshift cluster-status# Get CRC status and credentials
./hack/crc-openshift.sh status
# Login to OpenShift using kubeadmin credentials shown in status output
# oc login -u kubeadmin -p <password-from-status> https://api.crc.testing:6443
# Build UI and backend, then push images to cluster
make CLUSTER_TYPE=openshift build-ui build test cluster-push
# Deploy operator (uses OLM on OpenShift by default)
make CLUSTER_TYPE=openshift olm-operator-create
# Or deploy without OLM:
make CLUSTER_TYPE=openshift operator-create
# Deploy Kiali
make CLUSTER_TYPE=openshift kiali-create
# Or deploy with specific version from OperatorHub
make CLUSTER_TYPE=openshift operator-install# For OpenShift, typically build and push to registry
make CLUSTER_TYPE=openshift build cluster-push-kiali
# Reload Kiali
make CLUSTER_TYPE=openshift kiali-reload-image
# For operator changes:
make CLUSTER_TYPE=openshift cluster-push-operator
make CLUSTER_TYPE=openshift operator-reload-image# Install Bookinfo
./hack/istio/install-bookinfo-demo.sh -c ${CLIENT_EXE}
# Install testing demos
./hack/istio/install-testing-demos.sh -c ${CLIENT_EXE}# Get Kiali route URL
oc get route kiali -n istio-system -o jsonpath='{.spec.host}'
# Or use the CRC routes command
./hack/crc-openshift.sh routes | grep kiali
# Open in browser
xdg-open https://$(oc get route kiali -n istio-system -o jsonpath='{.spec.host}')# Expose cluster to remote clients (adds firewall rules)
./hack/crc-openshift.sh expose
# Remove remote access
./hack/crc-openshift.sh unexpose
# SSH into CRC VM
./hack/crc-openshift.sh ssh
# Debug in CRC VM
./hack/crc-openshift.sh sshoc
# Change domain from crc.testing to nip.io
./hack/crc-openshift.sh changedomain# Delete Kiali
make CLUSTER_TYPE=openshift kiali-delete
# Delete operator
make CLUSTER_TYPE=openshift operator-delete
# Or if using OLM:
make CLUSTER_TYPE=openshift olm-operator-delete
# Purge everything
./hack/purge-kiali-from-cluster.sh -c ${CLIENT_EXE}
# Stop CRC (preserves cluster)
./hack/crc-openshift.sh stop
# Delete CRC cluster completely
./hack/crc-openshift.sh delete
# Complete cleanup including CRC cache
./hack/crc-openshift.sh cleanupThese commands work across all cluster types. Ensure CLUSTER_TYPE and CLIENT_EXE are set appropriately.
# Delete Kiali CR (operator removes Kiali)
make CLUSTER_TYPE=<your-cluster-type> kiali-delete
# Force remove if operator fails
make CLUSTER_TYPE=<your-cluster-type> kiali-purge
# Remove operator
make CLUSTER_TYPE=<your-cluster-type> operator-delete
# Purge everything (works with all cluster types)
./hack/purge-kiali-from-cluster.sh -c ${CLIENT_EXE}The operator repository should be linked into the main Kiali repository:
# Clone operator separately
cd ~/source
git clone git@github.com:kiali/kiali-operator.git
# Link into kiali repo
cd ~/source/kiali/kiali
ln -s ~/source/kiali-operator operatorWhy use a symlink? If you clone directly into kiali/operator, checking out old Kiali branches can delete your operator changes. A symlink preserves your work.
These commands require CLUSTER_TYPE to be set. Examples shown for minikube:
# Build and push operator image
make CLUSTER_TYPE=minikube MINIKUBE_PROFILE=minikube cluster-push-operator
# Deploy operator via Helm
make CLUSTER_TYPE=minikube MINIKUBE_PROFILE=minikube operator-create
# Deploy operator via OLM (if OLM is installed, typically on OpenShift)
make CLUSTER_TYPE=openshift olm-operator-create
# Delete operator
make CLUSTER_TYPE=minikube MINIKUBE_PROFILE=minikube operator-delete
make CLUSTER_TYPE=openshift olm-operator-delete # If using OLM on OpenShiftFor quick testing of Ansible playbook changes:
# Test Kiali playbooks
make run-operator-playbook-kiali
# Test OSSMConsole playbooks
make run-operator-playbook-ossmconsoleConfiguration files are in kiali-operator/dev-playbook-config/.
Requirements:
- Python3 in PATH
- Ansible collections installed:
ansible-galaxy collection install -r operator/requirements.yml --force-with-deps - Python libraries:
python -m pip install --user --upgrade -r operator/molecule/requirements.txt
To run the operator locally with full infrastructure:
make run-operatorThis runs the ansible-operator process locally, watching for Kiali and OSSMConsole CRs in the cluster.
Configure operator behavior:
# Allow ad-hoc namespaces
make operator-set-config-allow-ad-hoc-kiali-namespace
# Allow ad-hoc images
make operator-set-config-allow-ad-hoc-kiali-image
# Enable debug logging
make operator-set-config-ansible-debug-logs
# Set verbosity (0-7)
make operator-set-config-ansible-verbosity
# Enable profiler
make operator-set-config-ansible-profiler-onTo enable profiling, set ANSIBLE_CONFIG=/opt/ansible/ansible-profiler.cfg in the operator deployment.
The profiler report shows task execution times. For cumulative results on looped tasks, save the report and pipe through this script:
awk -F~ '
{
val=$2;
$2="@";
a[$0]+=val
}
!b[$0]++{
c[++count]=$0}
END{
for(i=1;i<=count;i++){
sub("@",a[c[i]],c[i]);
print c[i]}
}' OFS=\~ <(cat - | sed 's/\(.*\) -\+ \(.*\)s/\1~\2/') | sort -n -t '~' -k 2 -r | column -s~ -tMolecule tests validate operator functionality end-to-end.
Molecule tests work on minikube, KinD, and OpenShift. The workflow is similar for all three.
Step 1: Ensure cluster is ready
For Minikube:
export CLUSTER_TYPE=minikube
export MINIKUBE_PROFILE=ci
export DORP=podman
export CLIENT_EXE=kubectl
# Start cluster with Hydra (required for some molecule tests)
./hack/k8s-minikube.sh --hydra-enabled true -mp ci start
# Install Istio
./hack/istio/install-istio-via-istioctl.sh --client-exe ${CLIENT_EXE}For KinD:
export CLUSTER_TYPE=kind
export KIND_NAME=ci
export DORP=docker # podman will likely not work, using only docker
export CLIENT_EXE=kubectl
# Start cluster with Hydra (required for some molecule tests)
./hack/start-kind.sh --name ${KIND_NAME} --enable-hydra true
# Install Istio
./hack/istio/install-istio-via-istioctl.sh --client-exe ${CLIENT_EXE}For OpenShift:
export CLUSTER_TYPE=openshift
export DORP=podman
export CLIENT_EXE=oc
# Start cluster
./hack/crc-openshift.sh start
# Install Istio
./hack/istio/install-istio-via-istioctl.sh -c ${CLIENT_EXE}Step 2: Verify Kiali is not already deployed
${CLIENT_EXE} get deployments -A -l app.kubernetes.io/name=kiali
${CLIENT_EXE} get deployments -A -l app.kubernetes.io/name=kiali-operator
# If Kiali is deployed, purge it
./hack/purge-kiali-from-cluster.sh -c ${CLIENT_EXE}
# Wait for CRDs to be removed
timeout 60 bash -c "until ! ${CLIENT_EXE} get crd | grep kiali; do sleep 2; done"Step 3: Build and push dev images
# For Minikube:
make CLUSTER_TYPE=minikube MINIKUBE_PROFILE=ci build-ui build test cluster-push
# For KinD:
make CLUSTER_TYPE=kind KIND_NAME=ci build-ui build test cluster-push
# For OpenShift:
make CLUSTER_TYPE=openshift build-ui build test cluster-pushStep 4: Run molecule tests
The -udi (--use-dev-images) flag controls whether to use dev images:
- Set to
trueto test your local code changes (requires Step 3: build and push dev images first) - Set to
falseto test with latest images published on quay.io
The -hcrp (--helm-charts-repo-pull) flag controls whether to pull the helm-charts repository:
- Set to
trueto pull the latest helm-charts from the remote repository - Set to
falseif your local helm-charts repo has a branch with no remote (avoids git pull errors)
# For Minikube:
./hack/run-molecule-tests.sh \
--client-exe "$(which kubectl)" \
--cluster-type minikube \
--minikube-profile ci \
-udi true \
-hcrp false \
-at "token-test"
# For KinD:
./hack/run-molecule-tests.sh \
--client-exe "$(which kubectl)" \
--cluster-type kind \
-udi true \
-hcrp false \
-at "token-test"
# For OpenShift:
./hack/run-molecule-tests.sh \
--client-exe "$(which oc)" \
--cluster-type openshift \
-udi true \
-hcrp false \
-at "token-test"Tests are in kiali-operator/molecule/. Each subdirectory is a test scenario (e.g., config-values-test, token-test).
Versioned Operator Roles:
kiali-operator/roles/v1.73/,roles/v2.4/, etc. - Only modify thedefaultrole
Old CSV Versions:
kiali-operator/manifests/*/[version]/- Only modify the LATEST version
CRD Copies:
- Any CRD file that is not in
kiali-operator/crd-docs/crd/ - Golden copies are in
crd-docs/crd/- these are the source of truth - Sync changes using:
make sync-crdsin kiali-operator repo (this can make changes to your helm-charts repo as well as the kiali-operator repo)
Generated Documentation:
kiali.io/content/en/docs/Configuration/kialis.kiali.io.mdkiali.io/content/en/docs/Configuration/ossmconsoles.kiali.io.md
Output Directories:
- Never modify
_output/directories or subdirectories - These contain build artifacts and generated files
CRITICAL: When modifying operator resources (roles, permissions, CSVs), check if changes break older supported versions in kiali-operator/roles/ (e.g., v1.24).
If changes break old versions that are still supported, only make breaking changes after old versions are no longer supported.
When modifying Kubernetes resources or Kiali configuration, you must update multiple locations to support all installation methods.
- Update golden copy:
kiali-operator/manifests/kiali-upstream/ - Update golden copy:
kiali-operator/manifests/kiali-ossm/manifests/kiali.clusterserviceversion.yaml - Update Helm chart:
helm-charts/kiali-operator/templates/ - If modifying CRD schema: Run
make sync-crdsin kiali-operator repo (requires helm-charts PR)
- Update Kubernetes templates:
kiali-operator/roles/default/kiali-deploy/templates/kubernetes/ - Update OpenShift templates:
kiali-operator/roles/default/kiali-deploy/templates/openshift/ - Check if removal needed:
kiali-operator/roles/default/kiali-remove/ - Update Helm chart:
helm-charts/kiali-server/templates/
- Update:
kiali-operator/manifests/kiali-upstream/CSVs - Update:
kiali-operator/manifests/kiali-ossm/manifests/kiali.clusterserviceversion.yaml - Update:
kiali-operator/roles/default/kiali-deploy/templates/kubernetes/role.yaml - Update:
kiali-operator/roles/default/kiali-deploy/templates/kubernetes/role-viewer.yaml - Update:
kiali-operator/roles/default/kiali-deploy/templates/openshift/role.yaml - Update:
kiali-operator/roles/default/kiali-deploy/templates/openshift/role-viewer.yaml - Update:
helm-charts/kiali-operator/templates/clusterrole.yaml - Update:
helm-charts/kiali-server/templates/role.yaml - Update:
helm-charts/kiali-server/templates/role-viewer.yaml
- Update:
kiali-operator/manifests/kiali-upstream/CSVs - Update:
kiali-operator/manifests/kiali-ossm/manifests/kiali.clusterserviceversion.yaml - Update:
helm-charts/kiali-operator/templates/clusterrole.yaml
- Set default:
kiali-operator/roles/default/kiali-deploy/defaults/main.yml - If new top-level setting: Add to
kiali-operator/roles/default/kiali-deploy/vars/main.yml - Document in CRD:
kiali-operator/crd-docs/crd/kiali.io_kialis.yaml - Add example to:
kiali-operator/crd-docs/cr/kiali.io_v1alpha1_kiali.yaml - Validate CR:
make validate-crin kiali-operator repo - If modified CRD: Run
make sync-crdsin kiali-operator repo - Only if appropriate (usually not): Set value in
cr.specsection ofhelm-charts/kiali-operator/values.yaml - Set default:
helm-charts/kiali-server/values.yaml - Sort alphabetically in all files where added
- If appropriate: Add test to
kiali-operator/molecule/config-values-test/converge.yml - If CRD property added/removed/modified: Update
kiali-operator/molecule/config-values-test/kiali-cr-all-yaml
- Modify built-in templates:
kiali/config/dashboards/dashboards.go
- Duplicate changes from
roles/default/to versioned roles (e.g.,roles/v1.24/) - Cherry-pick changes to appropriate git branches
- Add new role directory by copying
kiali-operator/roles/default/and naming itvX.Y(where X.Y is the new version) - Add new RELATED_IMAGE reference and relatedImages entry to
kiali-operator/manifests/kiali-ossm/manifests/kiali.clusterserviceversion.yaml - Add new version to
kiali-operator/playbooks/kiali-default-supported-images.yml - Add new version to
kiali-operator/playbooks/ossmconsole-default-supported-images.yml
- Delete the role directory from
kiali-operator/roles/ - Remove RELATED_IMAGE reference from
kiali-operator/manifests/kiali-ossm/manifests/kiali.clusterserviceversion.yaml - Remove version from
kiali-operator/playbooks/kiali-default-supported-images.yml - Remove version from
kiali-operator/playbooks/ossmconsole-default-supported-images.yml
- Edit golden copy:
kiali-operator/crd-docs/crd/kiali.io_kialis.yamlorossmconsoles.yaml - Validate:
make validate-crin kiali-operator repo - Sync to all locations:
make sync-crdsin kiali-operator repo - Verify sync:
make validate-crd-syncin kiali-operator repo - Create PR for kiali-operator repo
- Create separate PR for helm-charts repo with synced files
Golden Copies (source of truth):
kiali-operator/crd-docs/crd/kiali.io_kialis.yamlkiali-operator/crd-docs/crd/kiali.io_ossmconsoles.yaml
Synchronized Copies (do not edit directly):
kiali-operator/manifests/kiali-ossm/manifests/kiali.crd.yamlkiali-operator/manifests/kiali-ossm/manifests/ossmconsole.crd.yamlkiali-operator/manifests/kiali-upstream/[version]/manifests/kiali.crd.yamlhelm-charts/kiali-operator/crds/crds.yamlhelm-charts/kiali-operator/templates/ossmconsole-crd.yaml
# Update go.mod
go mod edit -go=x.y
# Update dependencies
go mod tidy -v
# Verify everything builds
make clean build build-ui test
# Commit and create PR# Check for updates
npx npm-check-updates -t latest -f '/^@patternfly/'
# Update yarn.lock (from the frontend/ directory)
yarn install
# Commit package.json and yarn.lockNote: Yarn is managed via corepack. Run
corepack enableonce before usingyarn. The exact Yarn version is pinned infrontend/package.jsonvia thepackageManagerfield.
The hack/ directory contains many useful scripts:
Cluster Setup:
hack/k8s-minikube.sh- Start minikube clusterhack/crc-openshift.sh- Start local OpenShift clusterhack/start-kind.sh- Start KinD clusterhack/istio/install-istio-via-istioctl.sh- Install Istiohack/istio/install-bookinfo-demo.sh- Install Bookinfo demo
Development:
hack/run-kiali.sh- Run Kiali standalonehack/run-integration-tests.sh- Run full integration test suitehack/run-molecule-tests.sh- Run Molecule testshack/purge-kiali-from-cluster.sh- Remove all Kiali resources
Configuration:
hack/configure-operator.sh- Configure operator settings
All scripts support --help for detailed usage information.
Build Failures:
# Ensure correct Go version
make go-check
# Clean everything and rebuild
make clean-all
make build-ui build testCluster Push Failures:
# Verify cluster is accessible (specify your cluster type)
make CLUSTER_TYPE=minikube MINIKUBE_PROFILE=${MINIKUBE_PROFILE} cluster-status
# For minikube, ensure profile is set
export MINIKUBE_PROFILE=ci
minikube status -p ci
# For kind
export KIND_NAME=kiali-testing
kind get clusters
make CLUSTER_TYPE=kind KIND_NAME=${KIND_NAME} cluster-statusOperator Not Working:
# Check operator logs
kubectl logs -n kiali-operator deployment/kiali-operator -f
# Check Kiali CR status
kubectl get kiali -A -o yaml
# Verify operator has correct image
kubectl get deployment kiali-operator -n kiali-operator -o yaml | grep image:Molecule Test Failures:
# Ensure cluster is clean
./hack/purge-kiali-from-cluster.sh -c kubectl
kubectl get crd | grep kiali # Should return nothing
# Rebuild images
make CLUSTER_TYPE=minikube MINIKUBE_PROFILE=ci build-ui build test cluster-push
# Run with debug
export MOLECULE_DEBUG=true
./hack/run-molecule-tests.sh ...Frontend Not Loading:
# Check backend is running
curl http://localhost:20001/kiali/api
# Verify proxy setting in package.json
grep proxy frontend/package.json
# Restart frontend dev server
make run-frontend# Run debug info collection script
./hack/ci-get-debug-info.sh
# Get operator logs
kubectl logs -n kiali-operator deployment/kiali-operator --tail=100
# Get Kiali server logs
kubectl logs -n istio-system deployment/kiali --tail=100
# Check all Kiali-related resources
kubectl get all,kiali,ossmconsole -A | grep kialiIf you encounter permission errors with molecule tests:
# Temporarily disable SELinux
sudo setenforce 0
# Re-enable after testing
sudo setenforce 1- Main Documentation: https://kiali.io/docs
- Contributing Guide: CONTRIBUTING.md
- Style Guide: STYLE_GUIDE.adoc
- README: README.adoc
- Frontend README: frontend/README.adoc
- Operator Development: operator/DEVELOPING.adoc
- Release Process: RELEASING.adoc
Makefile Help:
make help # Show all available targetsCommunity:
- GitHub Discussions: https://github.com/kiali/kiali/discussions
- GitHub Issues: https://github.com/kiali/kiali/issues
- Community Page: https://kiali.io/community/
# Development cycle with hot reload
make build-ui && make run-backend # Terminal 1
make run-frontend # Terminal 2# Setup
export CLUSTER_TYPE=minikube
export MINIKUBE_PROFILE=minikube # use any profile name; examples below use "ci"
export DORP=docker
export CLIENT_EXE=kubectl
# Start cluster and install Istio
./hack/k8s-minikube.sh -mp ${MINIKUBE_PROFILE} start
./hack/istio/install-istio-via-istioctl.sh --client-exe ${CLIENT_EXE}
# Build and deploy
make CLUSTER_TYPE=minikube MINIKUBE_PROFILE=${MINIKUBE_PROFILE} build-ui build test cluster-push
make CLUSTER_TYPE=minikube MINIKUBE_PROFILE=${MINIKUBE_PROFILE} operator-create
make CLUSTER_TYPE=minikube MINIKUBE_PROFILE=${MINIKUBE_PROFILE} kiali-create
# Quick iteration
make CLUSTER_TYPE=minikube MINIKUBE_PROFILE=${MINIKUBE_PROFILE} \
build cluster-push-kiali kiali-reload-image
# Access
./hack/k8s-minikube.sh -mp ${MINIKUBE_PROFILE} port-forward
# Cleanup
make CLUSTER_TYPE=minikube MINIKUBE_PROFILE=${MINIKUBE_PROFILE} kiali-delete operator-delete
./hack/k8s-minikube.sh -mp ${MINIKUBE_PROFILE} stop# Setup
export CLUSTER_TYPE=kind
export KIND_NAME=kiali-testing
export DORP=docker
export CLIENT_EXE=kubectl
# Start cluster and install Istio
./hack/start-kind.sh -n ${KIND_NAME}
./hack/istio/install-istio-via-istioctl.sh --client-exe ${CLIENT_EXE}
# Build and deploy
make CLUSTER_TYPE=kind KIND_NAME=${KIND_NAME} build-ui build test cluster-push
make CLUSTER_TYPE=kind KIND_NAME=${KIND_NAME} operator-create
make CLUSTER_TYPE=kind KIND_NAME=${KIND_NAME} kiali-create
# Quick iteration
make CLUSTER_TYPE=kind KIND_NAME=${KIND_NAME} \
build cluster-push-kiali kiali-reload-image
# Access
kubectl port-forward -n istio-system svc/kiali 20001:20001
# Cleanup
kind delete cluster --name ${KIND_NAME}# Setup
export CLUSTER_TYPE=openshift
export DORP=podman
export CLIENT_EXE=oc
# Start cluster and install Istio
./hack/crc-openshift.sh start
./hack/istio/install-istio-via-istioctl.sh -c ${CLIENT_EXE}
# Get credentials and login
./hack/crc-openshift.sh status
# oc login -u kubeadmin -p <password-from-status> https://api.crc.testing:6443
# Build and deploy
make CLUSTER_TYPE=openshift build-ui build test cluster-push
make CLUSTER_TYPE=openshift operator-create # or make CLUSTER_TYPE=openshift olm-operator-create
make CLUSTER_TYPE=openshift kiali-create
# Quick iteration
make CLUSTER_TYPE=openshift build cluster-push-kiali kiali-reload-image
# Access
./hack/crc-openshift.sh routes | grep kiali
# Cleanup
make CLUSTER_TYPE=openshift kiali-delete operator-delete # or olm-operator-delete
./hack/crc-openshift.sh stop# Backend tests
make test
# Frontend tests
make cypress-gui # Interactive
make cypress-run # Headless
# Molecule tests (example uses minikube with profile "ci")
./hack/run-molecule-tests.sh --client-exe "$(which kubectl)" \
--cluster-type minikube --minikube-profile ci -udi true -hcrp false# Format and lint
make format lint
# Check Go version
make go-check
# Clean builds
make clean-all# Test playbook locally (fastest)
make run-operator-playbook-kiali
# Run full operator locally
make run-operator
# Build and push operator
make cluster-push-operatormake help
./hack/k8s-minikube.sh --help
./hack/start-kind.sh --help
./hack/crc-openshift.sh --helpBuild Order:
- Always build UI before backend:
make build-uithenmake build
Frontend Tooling:
- Yarn is managed via corepack. Run
corepack enableonce before usingyarn. The pinned version is infrontend/package.json(packageManagerfield).
Environment Variables:
- Set
CLUSTER_TYPE(minikube, kind, or openshift) - Set
MINIKUBE_PROFILEfor minikube - Set
KIND_NAMEfor KinD - Set
DORP(docker or podman) - Set
CLIENT_EXE(kubectl for minikube/KinD, oc for OpenShift)
Before Committing:
- Format and lint:
make format lint - Run tests:
make test - Remove trailing whitespace
- Sort struct fields and YAML keys alphabetically
Operator Changes:
- Ask to run molecule tests after operator changes
- Update all installation methods (Helm, OLM, Operator templates)
- Check backward compatibility with versioned roles
Protected Files:
- Never modify versioned operator roles (
roles/v1.*/,roles/v2.*/) - Never modify old CSV versions (only modify LATEST version)
- Never modify CRD copies (only golden copies in
crd-docs/crd/) - Never modify generated documentation files
- Never modify
_output/directories