Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,9 @@ jobs:


integration:
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
# TODO temporarily disabled: credentials now actually reach the tests (Parameters:-prefixed
# secrets) which surfaced failures — re-enabling is tracked in a dedicated PR.
if: false
runs-on: docs-builder-latest-16
environment: integration-tests
permissions:
Expand Down Expand Up @@ -224,8 +226,8 @@ jobs:
ES_KEY=$(aws ssm get-parameter --name /elastic-docs-v3/prod/docs-elasticsearch-github-actions-readonly-api-key --with-decryption --query Parameter.Value --output text)
echo "::add-mask::$ES_URL"
echo "::add-mask::$ES_KEY"
dotnet user-secrets set "DocumentationElasticUrl" "$ES_URL" --project aspire
dotnet user-secrets set "DocumentationElasticApiKey" "$ES_KEY" --project aspire
dotnet user-secrets set "Parameters:ElasticsearchUrl" "$ES_URL" --project aspire
dotnet user-secrets set "Parameters:ElasticsearchApiKey" "$ES_KEY" --project aspire

- name: Integration Tests
env:
Expand Down
2 changes: 2 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ Elastic's documentation build toolchain. Processes Markdown from multiple repos
|---|---|
| `src/Elastic.Markdown/` | Core Markdown parser and Myst directive/role engine |
| `src/tooling/docs-builder/` | CLI — entry point `Program.cs`, commands in `Commands/` |
| `src/tooling/essc/` | AOT CLI indexing elastic.co content (Contentstack, Labs) into Elasticsearch; ships as `ghcr.io/elastic/website-search-essc` |
| `src/services/search/Elastic.Documentation.Search.Contract/` | Shared search document/mapping/API contract used by docs search and essc |
| `src/Elastic.Documentation.Site/` | Frontend (TypeScript · React · Parcel) |
| `src/Elastic.Documentation.Configuration/` | YAML config schema and loading |
| `src/Elastic.Documentation.Navigation/` | Nav tree assembly and validation |
Expand Down
6 changes: 4 additions & 2 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
<PackageVersion Include="AWSSDK.SQS" Version="4.0.3.1" />
<PackageVersion Include="AWSSDK.S3" Version="4.0.23.4" />
<PackageVersion Include="Elastic.OpenTelemetry" Version="1.4.0" />
<PackageVersion Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="10.0.8" />
<PackageVersion Include="Microsoft.Extensions.Configuration.UserSecrets" Version="10.0.8" />
<PackageVersion Include="Microsoft.Extensions.Configuration.Abstractions" Version="10.0.8" />
<PackageVersion Include="Microsoft.Extensions.Telemetry.Abstractions" Version="10.6.0" />
Expand All @@ -50,9 +51,8 @@
<PackageVersion Include="Elastic.Aspire.Hosting.Elasticsearch" Version="9.3.0" />
<PackageVersion Include="Elastic.Clients.Elasticsearch" Version="9.4.2" />
<PackageVersion Include="FakeItEasy" Version="9.0.1" />
<PackageVersion Include="Elastic.Ingest.Elasticsearch" Version="0.48.0" />
<PackageVersion Include="Elastic.Ingest.Elasticsearch" Version="0.49.0" />
<PackageVersion Include="Elastic.Mapping" Version="0.49.0" />
<PackageVersion Include="Elastic.Internal.Search.Contract" Version="0.12.0" />
<PackageVersion Include="InMemoryLogger" Version="1.0.66" />
<PackageVersion Include="MartinCostello.Logging.XUnit.v3" Version="0.7.1" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.8" />
Expand Down Expand Up @@ -93,6 +93,7 @@
<PackageVersion Include="Samboy063.Tomlet" Version="6.0.0" />
<PackageVersion Include="Sep" Version="0.13.0" />
<PackageVersion Include="Slugify.Core" Version="4.0.1" />
<PackageVersion Include="Spectre.Console" Version="0.55.0" />
<PackageVersion Include="SoftCircuits.IniFileParser" Version="2.7.0" />
<PackageVersion Include="System.IO.Abstractions" Version="22.1.1" />
<PackageVersion Include="Microsoft.Extensions.Hosting" Version="10.0.8" />
Expand All @@ -116,6 +117,7 @@
<ItemGroup>
<PackageVersion Include="Aspire.Hosting.AppHost" Version="13.4.3" />
<PackageVersion Include="AngleSharp.Diffing" Version="1.1.1" />
<PackageVersion Include="AutoBogus" Version="2.13.1" />
<PackageVersion Include="DiffPlex" Version="1.9.0" />
<PackageVersion Include="AwesomeAssertions" Version="9.4.0" />
<PackageVersion Include="FsUnit.xUnit" Version="7.1.1" />
Expand Down
4 changes: 2 additions & 2 deletions aspire/AppHost.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@ internal static async Task Run(
var llmUrl = builder.AddParameter("LlmGatewayUrl", secret: true);
var llmServiceAccountPath = builder.AddParameter("LlmGatewayServiceAccountPath", secret: true);

var elasticsearchUrl = builder.AddParameter("DocumentationElasticUrl", secret: true);
var elasticsearchApiKey = builder.AddParameter("DocumentationElasticApiKey", secret: true);
var elasticsearchUrl = builder.AddParameter("ElasticsearchUrl", secret: true);
var elasticsearchApiKey = builder.AddParameter("ElasticsearchApiKey", secret: true);

var cloneAll = builder.AddProject<Projects.docs_builder>(AssemblerClone);
string[] cloneArgs = assumeCloned ? ["--assume-cloned"] : [];
Expand Down
19 changes: 14 additions & 5 deletions aspire/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,17 +83,26 @@ Should have these secrets

> Parameters:LlmGatewayUrl = https://****
> Parameters:LlmGatewayServiceAccountPath = <PATH_TO_GCP_SERVICE_CREDENTIALS_FILE>
> Parameters:DocumentationElasticUrl = https://*.elastic.cloud:443
> Parameters:DocumentationElasticApiKey = ****
> Parameters:ElasticsearchUrl = https://*.elastic.cloud:443
> Parameters:ElasticsearchApiKey = ****

To set them:

```bash
dotnet user-secrets --project aspire set Parameters:DocumentationElasticApiKey <VALUE>
dotnet user-secrets --project aspire set Parameters:ElasticsearchApiKey <VALUE>
```

Do note `dotnet user-secrets` should only be used on local development machines and not on CI.

The store id is `docs-builder`. If you set up secrets before the rename from the old GUID id,
migrate your existing store:

```bash
mv ~/.microsoft/usersecrets/72f50f33-6fb9-4d08-bff3-39568fe370b3 ~/.microsoft/usersecrets/docs-builder
```

(On Windows: `%APPDATA%\Microsoft\UserSecrets\`.)

## Integration Tests

The `Elastic.Assembler.IntegrationTests` project includes integration tests for various components, including the search functionality.
Expand All @@ -110,8 +119,8 @@ The tests require a valid Elasticsearch instance. Choose one of these options:

1. **External Elasticsearch** - Set up user secrets:
```bash
dotnet user-secrets --project aspire set Parameters:DocumentationElasticUrl <YOUR_ELASTICSEARCH_URL>
dotnet user-secrets --project aspire set Parameters:DocumentationElasticApiKey <YOUR_API_KEY>
dotnet user-secrets --project aspire set Parameters:ElasticsearchUrl <YOUR_ELASTICSEARCH_URL>
dotnet user-secrets --project aspire set Parameters:ElasticsearchApiKey <YOUR_API_KEY>
```

2. **Local Elasticsearch** - The `--start-elasticsearch` flag will be automatically handled by the test fixture:
Expand Down
2 changes: 1 addition & 1 deletion aspire/aspire.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<UserSecretsId>72f50f33-6fb9-4d08-bff3-39568fe370b3</UserSecretsId>
<UserSecretsId>docs-builder</UserSecretsId>
<IsTestProject>false</IsTestProject>
<RootNamespace>Elastic.Documentation.Aspire</RootNamespace>
<NoWarn>IDE0350;IDE0060</NoWarn>
Expand Down
3 changes: 3 additions & 0 deletions build/Targets.fs
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,8 @@ let private runLocalContainer _ =
| _ -> "edge"
let image = $"elastic/docs-builder:%s{tag}"
exec { run "docker" ["docker"; "run"; image; "--help"] }
let esscImage = $"elastic/website-search-essc:%s{tag}"
exec { run "docker" ["docker"; "run"; esscImage; "--help"] }

let private publishContainers _ =

Expand Down Expand Up @@ -136,6 +138,7 @@ let private publishContainers _ =
createImage "src/tooling/docs-builder/docs-builder.csproj" "docs-builder"
createImage "src/api/Elastic.Documentation.Mcp.Remote/Elastic.Documentation.Mcp.Remote.csproj" "docs-builder-mcp"
createImage "src/api/Elastic.Documentation.Api/Elastic.Documentation.Api.csproj" "docs-builder-api"
createImage "src/tooling/essc/essc.csproj" "website-search-essc"

let private runTests (testSuite: TestSuite) _ =
let testFilter =
Expand Down
3 changes: 3 additions & 0 deletions docs-builder.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -77,10 +77,12 @@
</Folder>
<Folder Name="/src/services/search/">
<Project Path="src/services/search/Elastic.Documentation.Search/Elastic.Documentation.Search.csproj" />
<Project Path="src/services/search/Elastic.Documentation.Search.Contract/Elastic.Documentation.Search.Contract.csproj" />
</Folder>
<Folder Name="/src/tooling/">
<File Path="src/tooling/Directory.Build.props" />
<Project Path="src/tooling/docs-builder/docs-builder.csproj" />
<Project Path="src/tooling/essc/essc.csproj" />
</Folder>
<Folder Name="/tests-integration/">
<File Path="tests-integration/Directory.Build.props" />
Expand All @@ -102,6 +104,7 @@
<Project Path="tests/Navigation.Tests/Navigation.Tests.csproj" />
<Project Path="tests/Elastic.Changelog.Tests/Elastic.Changelog.Tests.csproj" />
<Project Path="tests/Elastic.Documentation.Integrations.Tests/Elastic.Documentation.Integrations.Tests.csproj" />
<Project Path="tests/Elastic.SiteSearch.Tests/Elastic.SiteSearch.Tests.csproj" />
</Folder>
<Project Path=".github/.github.csproj">
<Build Project="false" />
Expand Down
175 changes: 175 additions & 0 deletions docs/development/essc.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
---
navigation_title: essc — sourcing CLI
---

# essc — sourcing CLI

`essc` is an AOT-compiled .NET CLI for indexing elastic.co content into
Elasticsearch. It lives in `src/tooling/essc/` and shares the search document
contract (`Elastic.Documentation.Search.Contract`) with the docs indexing
pipeline, keeping every content source schema-consistent. Commands are
namespaced by content source so new sources can be added without breaking the
existing interface.

Current sources:

| Source | Namespace | Description |
| ------------ | ------------------- | ---------------------------------------------------- |
| Contentstack | `contentstack` | elastic.co marketing, blog, product, and event pages |
| Labs | `labs` | Search, security, and observability labs properties |
| Legacy docs | `guide` _(planned)_ | `/guide` legacy documentation |

## Installation

Pull the distroless container image:

```bash
docker pull ghcr.io/elastic/website-search-essc:latest
```

Available tags: `latest`, `edge` (latest main), and per-version tags.

Or run from source:

```bash
dotnet run --project src/tooling/essc -- --help
```

## Configuration

`essc` resolves credentials in this order of precedence:

1. CLI flags (`--es-url`, `--es-api-key`)
2. Environment variables (see table below)
3. Dotnet user-secrets store `docs-builder` (local development) — the same store the
rest of this repository uses, so aspire and essc share `Parameters:ElasticsearchUrl`
and `Parameters:ElasticsearchApiKey`

### Environment variables

| Environment variable | Description |
| ----------------------------- | ------------------------------------- |
| `ELASTICSEARCH_URL` | Full Elasticsearch URL including port |
| `ELASTICSEARCH_API_KEY` | Elasticsearch API key |
| `CONTENTSTACK_API_KEY` | Contentstack Content Delivery API key |
| `CONTENTSTACK_DELIVERY_TOKEN` | Contentstack delivery token |

The dotnet configuration section keys (`Parameters:ElasticsearchUrl`, `ContentStack:ApiKey`,
etc.) are also accepted. For CI, use the flat environment variable names above.

For local development, populate the secrets store directly:

```bash
dotnet user-secrets set --id docs-builder Parameters:ElasticsearchUrl <url>
dotnet user-secrets set --id docs-builder Parameters:ElasticsearchApiKey <key>
dotnet user-secrets set --id docs-builder ContentStack:ApiKey <key>
dotnet user-secrets set --id docs-builder ContentStack:DeliveryToken <token>
```

If you previously used essc from the website-search-data repository, copy the
`Parameters:Elasticsearch*` and `ContentStack:*` values from your old
`elastic-website-ai-search` store into the `docs-builder` store
(`~/.microsoft/usersecrets/`, on Windows `%APPDATA%\Microsoft\UserSecrets\`).

## Contentstack commands

### `contentstack sync`

Fetches all published content from Contentstack and indexes it into Elasticsearch.
Uses 5 parallel lanes. Cursors are saved after every page so interrupted runs
resume automatically.

```bash
essc contentstack sync
```

| Flag | Default | Description |
| ---------------- | ------------------ | ----------------------------------------------------------------------- |
| `--force` | false | Delete stored cursors and reindex from scratch |
| `--no-index` | false | Fetch and map only — skip Elasticsearch indexing |
| `--no-ai` | false | Skip generative AI (no post-sync enrich batch) |
| `--max-ai-docs` | 100 (when omitted) | Positive cap on documents enriched after finalize; omit for default 100 |
| `--max-ai-time` | none | Wall-clock cap for post-sync AI (minimum `1m` when set) |
| `--es-url` | from secrets | Override Elasticsearch endpoint |
| `--es-api-key` | from secrets | Override Elasticsearch API key |
| `--page-per` | 0 (unlimited) | Max pages per content type (useful for testing) |
| `--cache-folder` | OS app data | Override cursor state directory |

`--max-ai-docs` uses DataAnnotations: it must be at least **1** when passed; **`0` is invalid**. For a large or unbounded batch without re-syncing, use **`contentstack ai`** instead.

The sync runs in two phases:

1. **Fetch + index** — pages through Contentstack in parallel, maps each item to a
`SiteDocument`, and bulk-indexes into both the lexical and semantic indices.
2. **Finalize** — flushes the remaining buffer, reindexes the lexical index into
the semantic index, publishes synonyms and query rules, then runs a **bounded**
generative AI enrichment pass on the semantic write index (unless `--no-ai`).

### `contentstack ai`

Runs generative AI enrichment on existing **`site-*`** semantic indices without calling Contentstack. Same Elasticsearch overrides as `contentstack sync`.

```bash
essc contentstack ai
```

| Flag | Default | Description |
| ---------------- | ------------- | ----------------------------------------- |
| `--max-run-time` | unlimited | Stop enrichment after N minutes |
| `--max-run-docs` | 0 (unlimited) | Enrich at most N documents (`0` = no cap) |
| `--es-url` | from secrets | Override Elasticsearch endpoint |
| `--es-api-key` | from secrets | Override Elasticsearch API key |

### `contentstack types`

Lists all Contentstack content types and their document counts.

```bash
essc contentstack types
```

### `contentstack samples`

Dumps raw Contentstack entries for a given content type, useful for inspecting
the source data structure.

```bash
essc contentstack samples <content-type>
```

## Labs commands

### `labs sync`

Discovers labs URLs from sitemaps, crawls HTML, and bulk-ingests into **`labs-*`** indices. See `essc labs sync --help` for crawl flags (`--dry-run`, `--force`, `--no-ai`, `--max-ai-docs`, `--max-ai-time`, etc.).

### `labs ai`

Runs generative AI enrichment on existing **`labs-*`** semantic indices without re-crawling. Flags match **`contentstack ai`** (`--max-run-time`, `--max-run-docs`, `--es-url`, `--es-api-key`).

```bash
essc labs ai
```

## CI behaviour

When `CI=true` or stdout is not a TTY, `essc` switches from interactive progress
widgets to plain log lines:

```
[Lane 1] 100% blog — 1,240 fetched, 1,198 indexed
[Lane 2] 14% customer_tile — 42 fetched, 40 indexed (2 skipped)
… 14% secondary reindex — 1,240/8,500
✓ 100% secondary reindex — 8,500/8,500
```

## Release pipeline

`essc` ships as a **distroless container** only, published to
`ghcr.io/elastic/website-search-essc` using the .NET SDK's native container
support as part of the docs-builder release pipeline (`./build.sh publishcontainers`).
Base image: `mcr.microsoft.com/dotnet/nightly/runtime-deps:10.0-noble-chiseled`.
Tagged `edge;latest;<version>` on release, `edge` on every push to main.

AOT compilation is validated on every CI run by building the container image and
running `essc --help` against it.
1 change: 1 addition & 0 deletions docs/development/toc.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
toc:
- file: index.md
- file: changelog-bundle-registry.md
- file: essc.md
- folder: ingest
children:
- file: index.md
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@
using Elastic.Documentation.Configuration.Inference;
using Elastic.Documentation.Configuration.Versions;
using Elastic.Documentation.Search;
using Elastic.Documentation.Search.Contract;
using Elastic.Documentation.Versions;
using Elastic.Internal.Search;
using Microsoft.OpenApi;
using Microsoft.OpenApi.Reader;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

using System.Collections.Frozen;
using Elastic.Documentation.Configuration.Versions;
using Elastic.Internal.Search;
using Elastic.Documentation.Search.Contract;
using YamlDotNet.Serialization;

namespace Elastic.Documentation.Configuration.Products;
Expand Down
Loading
Loading