Skip to content

Commit 1a53e81

Browse files
committed
✨ Add CLI to validate elastic/workflows examples in CI
1 parent 209d0b6 commit 1a53e81

56 files changed

Lines changed: 3446 additions & 525 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.buildkite/pipelines/on_merge.yml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,25 @@ steps:
172172
- exit_status: '-1'
173173
limit: 3
174174

175+
- command: .buildkite/scripts/steps/workflows/validate_examples.sh
176+
label: 'Validate elastic/workflows examples'
177+
agents:
178+
image: family/kibana-ubuntu-2404
179+
imageProject: elastic-images-prod
180+
provider: gcp
181+
machineType: n2-standard-2
182+
preemptible: true
183+
spotZones: us-central1-b,us-central1-c,us-central1-f
184+
diskSizeGb: 105
185+
key: validate_workflow_examples
186+
timeout_in_minutes: 20
187+
artifact_paths:
188+
- target/workflow-examples-junit.xml
189+
retry:
190+
automatic:
191+
- exit_status: '-1'
192+
limit: 3
193+
175194
# - command: .buildkite/scripts/steps/checks/api_contracts.sh
176195
# label: 'Check API Contracts'
177196
# agents:
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
steps:
2+
- command: .buildkite/scripts/steps/workflows/validate_examples.sh
3+
label: Validate elastic/workflows examples against the current Kibana schema
4+
timeout_in_minutes: 20
5+
agents:
6+
image: family/kibana-ubuntu-2404
7+
imageProject: elastic-images-prod
8+
provider: gcp
9+
machineType: n2-standard-2
10+
preemptible: true
11+
artifact_paths:
12+
- target/workflow-examples-junit.xml
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
4+
WORKFLOWS_REPO_URL="${WORKFLOWS_REPO_URL:-https://github.com/elastic/workflows}"
5+
WORKFLOWS_BRANCH="${WORKFLOWS_BRANCH:-main}"
6+
WORKFLOWS_CHECKOUT="$PARENT_DIR/workflows"
7+
EXAMPLES_SUBPATH="${WORKFLOWS_EXAMPLES_SUBPATH:-examples}"
8+
JUNIT_OUT="$KIBANA_DIR/target/workflow-examples-junit.xml"
9+
10+
WORKFLOW_SCHEMA_PATH_PATTERN='^(src/platform/packages/shared/kbn-workflows/|src/platform/packages/shared/kbn-workflows-examples-cli/|src/platform/packages/shared/kbn-workflows-yaml/|src/platform/plugins/shared/workflows_management/|\.buildkite/scripts/steps/workflows/)'
11+
12+
report_main_step () {
13+
echo "--- $1"
14+
}
15+
16+
skip_unless_workflow_schema_changed () {
17+
if [[ "${WORKFLOWS_VALIDATE_FORCE:-}" == "true" ]]; then
18+
echo "WORKFLOWS_VALIDATE_FORCE=true — running validation regardless of changed paths"
19+
return 0
20+
fi
21+
22+
report_main_step "Checking whether this merge touched workflow schema paths"
23+
local changed
24+
if ! changed="$(git -C "$KIBANA_DIR" diff --name-only HEAD~1 HEAD 2>/dev/null)"; then
25+
echo "Warning: could not diff HEAD~1..HEAD; running validation anyway." >&2
26+
return 0
27+
fi
28+
29+
if echo "$changed" | grep -qE "$WORKFLOW_SCHEMA_PATH_PATTERN"; then
30+
echo "Workflow schema paths changed — running validation"
31+
return 0
32+
fi
33+
34+
echo "No workflow schema changes in HEAD~1..HEAD — skipping validation"
35+
exit 0
36+
}
37+
38+
clone_workflows_examples () {
39+
report_main_step "Cloning elastic/workflows @ $WORKFLOWS_BRANCH"
40+
rm -rf "$WORKFLOWS_CHECKOUT"
41+
git clone --filter=blob:none --branch "$WORKFLOWS_BRANCH" --single-branch \
42+
"$WORKFLOWS_REPO_URL" "$WORKFLOWS_CHECKOUT"
43+
}
44+
45+
main () {
46+
skip_unless_workflow_schema_changed
47+
clone_workflows_examples
48+
49+
report_main_step "Bootstrapping Kibana"
50+
cd "$KIBANA_DIR"
51+
.buildkite/scripts/bootstrap.sh
52+
53+
local examples_dir="$WORKFLOWS_CHECKOUT/$EXAMPLES_SUBPATH"
54+
if [[ ! -d "$examples_dir" ]; then
55+
echo "Error: examples directory '$examples_dir' does not exist." >&2
56+
echo "Override the subpath with WORKFLOWS_EXAMPLES_SUBPATH if upstream layout changed." >&2
57+
exit 1
58+
fi
59+
60+
mkdir -p "$(dirname "$JUNIT_OUT")"
61+
62+
report_main_step "Validating workflow examples"
63+
node scripts/validate_workflow_examples.js \
64+
--dir "$examples_dir" \
65+
--junit-out "$JUNIT_OUT"
66+
}
67+
68+
main

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1892,6 +1892,7 @@
18921892
"@kbn/validate-oas": "link:src/platform/packages/private/kbn-validate-oas",
18931893
"@kbn/web-worker-stub": "link:packages/kbn-web-worker-stub",
18941894
"@kbn/whereis-pkg-cli": "link:packages/kbn-whereis-pkg-cli",
1895+
"@kbn/workflows-examples-cli": "link:src/platform/packages/shared/kbn-workflows-examples-cli",
18951896
"@kbn/workspaces": "link:src/platform/packages/shared/kbn-workspaces",
18961897
"@kbn/yarn-install-scripts": "link:packages/kbn-yarn-install-scripts",
18971898
"@kbn/yarn-lock-validator": "link:packages/kbn-yarn-lock-validator",
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the "Elastic License
4+
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
5+
* Public License v 1"; you may not use this file except in compliance with, at
6+
* your election, the "Elastic License 2.0", the "GNU Affero General Public
7+
* License v3.0 only", or the "Server Side Public License, v 1".
8+
*/
9+
10+
require('@kbn/setup-node-env');
11+
require('@kbn/workflows-examples-cli').runValidateExamplesCli();

src/cli/tsconfig.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
"@kbn/config",
2828
"@kbn/dev-utils",
2929
"@kbn/projects-solutions-groups",
30+
"@kbn/workflows-examples-cli",
3031
],
3132
"exclude": [
3233
"target/**/*",
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
# @kbn/workflows-examples-cli
2+
3+
Static validation for workflow YAML examples.
4+
5+
Used in CI to detect when YAML parsing or basic schema constraints break the
6+
example workflows published in
7+
[`elastic/workflows`](https://github.com/elastic/workflows). Runs without
8+
booting Kibana.
9+
10+
## Usage
11+
12+
```
13+
node scripts/validate_workflow_examples --dir <path-to-examples> [--junit-out <path>]
14+
```
15+
16+
- `--dir` (required): directory of `.yml`/`.yaml` files. Walked recursively;
17+
dotfiles and hidden directories are skipped.
18+
- `--junit-out` (optional): writes a JUnit XML report for Buildkite to pick up.
19+
When set, the CLI also fails if no YAML examples are found (CI misconfiguration guard).
20+
21+
Exits non-zero on any failure.
22+
23+
## What this CLI validates
24+
25+
- YAML syntax errors (parser-level failures).
26+
- Examples exceeding `MAX_WORKFLOW_YAML_LENGTH`.
27+
- Structural schema regressions on the workflow definition — top-level `name`,
28+
`enabled`, `triggers`, `inputs`, `settings`, `steps` — using a schema built
29+
from:
30+
- **Static connectors** via `getAllStaticConnectors()` in `@kbn/workflows`
31+
(Elasticsearch/Kibana built-ins, stack connectors such as `slack`, `http`,
32+
`inference`, `jira`, etc., and connector-specs sub-actions such as
33+
`virustotal.*`).
34+
- **Extension steps** registered by platform plugins (`data.*`, `ai.*`,
35+
`cases.*`, `search.rerank`, `ai.agent`) via
36+
`getExtensionStepContracts()`.
37+
- Validation runs with `loose: true` (same mode as the YAML editor).
38+
39+
## What this CLI does **not** validate strictly
40+
41+
- **`security.*` steps** (`security.buildAlertEntityGraph`,
42+
`security.renderAlertNarrative`): included as permissive `z.any()` placeholders
43+
because the security solution plugin is not importable from this platform
44+
package. Param drift for those step types is not caught here.
45+
- **Dynamic connectors** resolved at runtime from the Actions client (only the
46+
static catalog is available offline).
47+
- **End-to-end execution** against a running Kibana stack.
48+
49+
This CLI is a **merge gate** on workflow-schema changes: it catches the cheapest
50+
classes of regression without booting Kibana. It does not replace functional or
51+
API integration tests.
52+
53+
## CI behavior
54+
55+
On-merge validation (`.buildkite/scripts/steps/workflows/validate_examples.sh`)
56+
runs only when the merge commit touches workflow schema paths, unless
57+
`WORKFLOWS_VALIDATE_FORCE=true`. Upstream examples are cloned from the
58+
`main` branch of [`elastic/workflows`](https://github.com/elastic/workflows)
59+
at run time (no pinned ref).
60+
61+
## Programmatic use
62+
63+
```ts
64+
import {
65+
runValidation,
66+
validateExampleYaml,
67+
buildWorkflowSchema,
68+
} from '@kbn/workflows-examples-cli';
69+
```
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the "Elastic License
4+
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
5+
* Public License v 1"; you may not use this file except in compliance with, at
6+
* your election, the "Elastic License 2.0", the "GNU Affero General Public
7+
* License v3.0 only", or the "Server Side Public License, v 1".
8+
*/
9+
10+
import Path from 'path';
11+
import { mkdir, writeFile } from 'fs/promises';
12+
import { run } from '@kbn/dev-cli-runner';
13+
import { createFailError } from '@kbn/dev-cli-errors';
14+
15+
import { runValidation } from './src/run_validation';
16+
import { renderJUnitXml } from './src/junit_report';
17+
18+
export { runValidation } from './src/run_validation';
19+
export { validateExampleYaml } from './src/validate_example';
20+
export type { ValidationOutcome, SchemaIssue } from './src/validate_example';
21+
export { buildWorkflowSchema } from './src/build_schema';
22+
export { discoverExampleFiles } from './src/discover_examples';
23+
export { renderJUnitXml } from './src/junit_report';
24+
export type { ExampleResult } from './src/junit_report';
25+
26+
export function runValidateExamplesCli(): void {
27+
run(
28+
async ({ flagsReader, log }) => {
29+
const dir = flagsReader.requiredString('dir');
30+
const rootDir = Path.resolve(dir);
31+
const junitOutFlag = flagsReader.string('junit-out');
32+
33+
const summary = await runValidation({ rootDir, log });
34+
35+
if (junitOutFlag) {
36+
const junitPath = Path.resolve(junitOutFlag);
37+
await mkdir(Path.dirname(junitPath), { recursive: true });
38+
await writeFile(junitPath, renderJUnitXml(summary.results), 'utf8');
39+
log.info(`Wrote JUnit report to ${junitPath}`);
40+
}
41+
42+
if (summary.empty && junitOutFlag) {
43+
throw createFailError(`No workflow YAML files found under ${rootDir}`);
44+
}
45+
46+
if (summary.failed > 0) {
47+
throw createFailError(
48+
`${summary.failed} of ${summary.results.length} workflow example(s) failed validation`
49+
);
50+
}
51+
},
52+
{
53+
description:
54+
'Validate workflow YAML examples (from elastic/workflows or any directory) against the Kibana workflow schema.',
55+
usage: 'node scripts/validate_workflow_examples --dir <path> [--junit-out <path>]',
56+
flags: {
57+
string: ['dir', 'junit-out'],
58+
help: `
59+
--dir (required) Directory containing workflow YAML examples (.yml/.yaml).
60+
The directory is walked recursively; dotfiles and hidden directories
61+
are skipped.
62+
--junit-out Optional path to write a JUnit XML report (consumed by Buildkite).
63+
`,
64+
},
65+
}
66+
);
67+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the "Elastic License
4+
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
5+
* Public License v 1"; you may not use this file except in compliance with, at
6+
* your election, the "Elastic License 2.0", the "GNU Affero General Public
7+
* License v3.0 only", or the "Server Side Public License, v 1".
8+
*/
9+
10+
module.exports = {
11+
preset: '@kbn/test/jest_node',
12+
rootDir: '../../../../..',
13+
roots: ['<rootDir>/src/platform/packages/shared/kbn-workflows-examples-cli'],
14+
};
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"type": "shared-common",
3+
"id": "@kbn/workflows-examples-cli",
4+
"owner": "@elastic/workflows-eng",
5+
"group": "platform",
6+
"visibility": "shared",
7+
"devOnly": true
8+
}

0 commit comments

Comments
 (0)