Skip to content

Commit 3e2695a

Browse files
authored
OpenTelemetry exporter plugin, and built-in plugin provision (#3014)
* Deterministic idempotency key * oplog forwarding unit tests * Fixed existing oplog-processor tests * More oplog processor tests and supporting code * OplogProcessorCheckpoint oplog entry * Tracking oplog processor checkpoints in agent status record * New get-last-processed-index method to oplog processor interface * ForwardingOplog live path improvements * Fix * Periodic locality recovery * Logging skill * Oplog processor test component updates * Some more tests * Test fix * Updated testing skill and idempotency_key check fix * Fixes * Fix how locality recovery determines that the target already processed the entries * Accept pending state too * Clippy & format * Missing file * TS SDK fix * Updated openapi * Fix build-components.sh * Rebuilt test components * Fix * Clippy * OTLP plugin initial version * Abort fix * Sequential test running * Initial tests and fixes for the otlp plugin * Fixes and debug tools * Log and metrics export * Clippy, format and regenerate * Use golem_version as plugin version * Tagged the new plugin and otlp tests and running them in a new CI job * Fixes * Remove unnecessary step from the provisioning * Trying to fix the docker issue on CI * Separate account for built-in plugins * Hash based component check in provision * Cleanup the provision logic * Do not allow deleting built-in plugin grants * Fix * Fixes * Regenerated plugins * Rebuilt wasms * Rebuilt wasm * Oplog processor fixes * Fix flaky test * Flaky test fix
1 parent dcd2b38 commit 3e2695a

File tree

96 files changed

+5872
-65366
lines changed

Some content is hidden

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

96 files changed

+5872
-65366
lines changed
Lines changed: 291 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,291 @@
1+
---
2+
name: creating-new-builtin-plugins
3+
description: Adding a new built-in plugin to Golem. Use when creating a brand new plugin that ships with Golem and is auto-provisioned at startup, covering project scaffolding, WASM compilation, provisioning wiring, CLI embedding, and test framework integration.
4+
---
5+
6+
# Creating a New Built-in Plugin
7+
8+
This skill covers how to add a **new** built-in plugin to Golem end-to-end. Built-in plugins are WASM components compiled with the Golem CLI, committed as `.wasm` files, embedded in the `golem` CLI binary, and automatically provisioned by the registry service at startup.
9+
10+
For modifying an **existing** plugin, load the `modifying-builtin-plugins` skill instead.
11+
12+
## Architecture Overview
13+
14+
A built-in plugin has these integration points (all must be wired up):
15+
16+
1. **Plugin source code** — a standalone Golem application under `plugins/`
17+
2. **Compiled WASM artifact** — committed to git at `plugins/<name>.wasm`
18+
3. **Build task** — added to `Makefile.toml` under `build-plugins`
19+
4. **Registry service config**`BuiltinPluginsConfig` struct with WASM bytes/path fields
20+
5. **Provisioner**`builtin_plugin_provisioner.rs` creates the component, deploys it, registers the plugin, and grants it
21+
6. **Bootstrap wiring**`golem-registry-service/src/bootstrap/mod.rs` loads WASM and calls the provisioner
22+
7. **CLI embedding**`cli/golem/src/launch.rs` uses `include_bytes!` to embed the WASM
23+
8. **Test framework**`golem-test-framework/src/components/registry_service/` loads WASM from filesystem and passes it via env vars
24+
25+
## Step-by-Step Guide
26+
27+
### 1. Create the Plugin Project
28+
29+
Create a new Golem application under `plugins/`:
30+
31+
```
32+
plugins/
33+
my-plugin/
34+
components-rust/my-plugin/
35+
src/lib.rs # Plugin implementation
36+
Cargo.toml # crate-type = ["cdylib"]
37+
golem.yaml # Component manifest with copy command
38+
Cargo.toml # Workspace Cargo.toml
39+
golem.yaml # Application manifest
40+
.gitignore # Ignore target/ and golem-temp/
41+
```
42+
43+
Use the existing `plugins/otlp-exporter/` as a template:
44+
45+
**`plugins/my-plugin/.gitignore`:**
46+
```
47+
target/
48+
golem-temp/
49+
```
50+
51+
**`plugins/my-plugin/Cargo.toml`** (workspace root):
52+
```toml
53+
[workspace]
54+
resolver = "2"
55+
members = ["components-rust/*"]
56+
57+
[profile.release]
58+
opt-level = "s"
59+
lto = true
60+
61+
[workspace.dependencies]
62+
golem-rust = { path = "../../sdks/rust/golem-rust", features = ["export_oplog_processor"] }
63+
# Add other dependencies as needed
64+
```
65+
66+
Note: The `features` on `golem-rust` depend on the plugin type. For oplog processors use `export_oplog_processor`. Adjust based on the plugin kind.
67+
68+
**`plugins/my-plugin/golem.yaml`:**
69+
```yaml
70+
app: my-plugin
71+
72+
includes:
73+
- components-*/*/golem.yaml
74+
75+
environments:
76+
local:
77+
server: local
78+
componentPresets: debug
79+
cloud:
80+
server: cloud
81+
componentPresets: release
82+
```
83+
84+
**`plugins/my-plugin/components-rust/my-plugin/Cargo.toml`:**
85+
```toml
86+
[package]
87+
name = "my_plugin"
88+
version = "0.0.1"
89+
edition = "2021"
90+
91+
[lib]
92+
crate-type = ["cdylib"]
93+
path = "src/lib.rs"
94+
95+
[dependencies]
96+
golem-rust = { workspace = true }
97+
```
98+
99+
**`plugins/my-plugin/components-rust/my-plugin/golem.yaml`:**
100+
```yaml
101+
components:
102+
my:plugin:
103+
templates: rust
104+
105+
customCommands:
106+
copy:
107+
- command: cp ../../golem-temp/agents/my_plugin_release.wasm ../../../my-plugin.wasm
108+
```
109+
110+
The copy command filename is derived from the component name: colons become underscores, suffixed with `_release.wasm`. Verify the actual output filename in `golem-temp/agents/` after building.
111+
112+
### 2. Implement the Plugin
113+
114+
Write the plugin code in `plugins/my-plugin/components-rust/my-plugin/src/lib.rs`. The implementation depends on the plugin type (currently only `OplogProcessor` is supported):
115+
116+
```rust
117+
use golem_rust::oplog_processor::exports::golem::api::oplog_processor::Guest as OplogProcessorGuest;
118+
use golem_rust::bindings::golem::api::oplog::{OplogEntry, OplogIndex};
119+
use golem_rust::golem_wasm::golem_core_1_5_x::types::{AgentId, ComponentId};
120+
121+
struct MyPluginComponent;
122+
123+
impl OplogProcessorGuest for MyPluginComponent {
124+
fn process(
125+
_account_info: golem_rust::oplog_processor::exports::golem::api::oplog_processor::AccountInfo,
126+
config: Vec<(String, String)>,
127+
component_id: ComponentId,
128+
worker_id: AgentId,
129+
metadata: golem_rust::bindings::golem::api::host::AgentMetadata,
130+
_first_entry_index: OplogIndex,
131+
entries: Vec<OplogEntry>,
132+
) -> Result<(), String> {
133+
// Plugin logic here
134+
Ok(())
135+
}
136+
}
137+
138+
golem_rust::oplog_processor::export_oplog_processor!(MyPluginComponent with_types_in golem_rust::oplog_processor);
139+
```
140+
141+
### 3. Build and Commit the WASM
142+
143+
```shell
144+
cd plugins/my-plugin
145+
golem build -P release
146+
golem exec -P release copy
147+
```
148+
149+
Verify `plugins/my-plugin.wasm` was created, then **commit it to git**. This file is required at compile time by the CLI.
150+
151+
### 4. Add to `Makefile.toml`
152+
153+
Update the `build-plugins` task in `Makefile.toml` to include the new plugin:
154+
155+
```toml
156+
[tasks.build-plugins]
157+
dependencies = ["build"]
158+
description = "Builds built-in plugins (requires golem CLI to be built first)"
159+
script_runner = "@duckscript"
160+
script = '''
161+
cd plugins/otlp-exporter
162+
exec --fail-on-error ../../target/debug/golem build -P release --force-build
163+
exec --fail-on-error ../../target/debug/golem exec -P release copy
164+
cd ../..
165+
cd plugins/my-plugin
166+
exec --fail-on-error ../../target/debug/golem build -P release --force-build
167+
exec --fail-on-error ../../target/debug/golem exec -P release copy
168+
cd ../..
169+
'''
170+
```
171+
172+
### 5. Add Config Fields
173+
174+
In `golem-registry-service/src/config.rs`, add fields to `BuiltinPluginsConfig`:
175+
176+
```rust
177+
#[derive(Clone, Debug, Serialize, Deserialize, Default)]
178+
pub struct BuiltinPluginsConfig {
179+
pub enabled: bool,
180+
#[serde(skip)]
181+
pub otlp_exporter_wasm: Option<Arc<[u8]>>,
182+
pub otlp_exporter_wasm_path: Option<PathBuf>,
183+
#[serde(skip)]
184+
pub my_plugin_wasm: Option<Arc<[u8]>>, // New
185+
pub my_plugin_wasm_path: Option<PathBuf>, // New
186+
}
187+
```
188+
189+
The `#[serde(skip)]` field holds the in-memory WASM bytes (set programmatically). The path field is set via environment variable `GOLEM__BUILTIN_PLUGINS__MY_PLUGIN_WASM_PATH`.
190+
191+
### 6. Wire Up Provisioning
192+
193+
In `golem-registry-service/src/services/builtin_plugin_provisioner.rs`, add provisioning logic for the new plugin. The provisioner follows a consistent pattern:
194+
195+
1. **Get or skip** — check if the WASM bytes are provided; skip if not
196+
2. **Create component** — upload WASM into the `builtin-plugins` environment (idempotent: handle `ComponentWithNameAlreadyExists`)
197+
3. **Deploy environment** — call `deployment_write_service.create_deployment()` so the component becomes deployed
198+
4. **Register plugin** — create a `PluginRegistrationCreation` with the appropriate `PluginSpecDto` variant (idempotent: handle `PluginNameAndVersionAlreadyExists`)
199+
5. **Grant to environments** — iterate all environments and grant the plugin
200+
201+
All plugins share the same `golem-system` application and `builtin-plugins` environment. Add new component and plugin constants:
202+
203+
```rust
204+
const MY_PLUGIN_COMPONENT_NAME: &str = "my:plugin";
205+
const MY_PLUGIN_NAME: &str = "golem-my-plugin";
206+
const MY_PLUGIN_VERSION: &str = "1.0.0";
207+
```
208+
209+
### 7. Wire Up Bootstrap
210+
211+
In `golem-registry-service/src/bootstrap/mod.rs`, load the new plugin's WASM from the filesystem path (similar to the OTLP exporter pattern):
212+
213+
```rust
214+
if builtin_plugins.my_plugin_wasm.is_none() {
215+
if let Some(ref path) = builtin_plugins.my_plugin_wasm_path {
216+
match std::fs::read(path) {
217+
Ok(bytes) => {
218+
tracing::info!("Loaded my-plugin WASM from {}", path.display());
219+
builtin_plugins.my_plugin_wasm = Some(Arc::from(bytes));
220+
}
221+
Err(e) => {
222+
return Err(anyhow!(
223+
"Failed to read my-plugin WASM from {}: {e}",
224+
path.display()
225+
));
226+
}
227+
}
228+
}
229+
}
230+
```
231+
232+
### 8. Embed in the CLI
233+
234+
In `cli/golem/src/launch.rs`, add an `include_bytes!` for the new WASM and pass it in the config:
235+
236+
```rust
237+
static MY_PLUGIN_WASM: &[u8] = include_bytes!("../../../plugins/my-plugin.wasm");
238+
239+
// In the BuiltinPluginsConfig construction:
240+
builtin_plugins: BuiltinPluginsConfig {
241+
enabled: true,
242+
otlp_exporter_wasm: Some(Arc::from(OTLP_EXPORTER_WASM)),
243+
my_plugin_wasm: Some(Arc::from(MY_PLUGIN_WASM)),
244+
..Default::default()
245+
},
246+
```
247+
248+
### 9. Wire Up Test Framework
249+
250+
In `golem-test-framework/src/components/registry_service/`:
251+
252+
**`spawned.rs`** — load the WASM path:
253+
```rust
254+
let my_plugin_wasm = working_directory.join("../plugins/my-plugin.wasm");
255+
let my_plugin_wasm_path = if my_plugin_wasm.exists() {
256+
Some(my_plugin_wasm.as_path())
257+
} else {
258+
None
259+
};
260+
```
261+
262+
**`mod.rs`** — pass the path as an env var in `env_vars()`:
263+
```rust
264+
// Add parameter: my_plugin_wasm_path: Option<&Path>
265+
let builder = if let Some(wasm_path) = my_plugin_wasm_path {
266+
builder.with(
267+
"GOLEM__BUILTIN_PLUGINS__MY_PLUGIN_WASM_PATH",
268+
wasm_path.to_string_lossy().to_string(),
269+
)
270+
} else {
271+
builder
272+
};
273+
```
274+
275+
## Checklist
276+
277+
- [ ] Plugin source created under `plugins/<name>/`
278+
- [ ] Plugin compiles: `cd plugins/<name> && golem build -P release && golem exec -P release copy`
279+
- [ ] `plugins/<name>.wasm` exists and is committed to git
280+
- [ ] `Makefile.toml` `build-plugins` task updated
281+
- [ ] `BuiltinPluginsConfig` has new WASM and path fields
282+
- [ ] `builtin_plugin_provisioner.rs` provisions the new plugin (create component, deploy, register, grant)
283+
- [ ] `bootstrap/mod.rs` loads WASM from filesystem path
284+
- [ ] `cli/golem/src/launch.rs` embeds WASM via `include_bytes!`
285+
- [ ] Test framework passes WASM path via env var
286+
- [ ] Plugin version constant defined in provisioner
287+
- [ ] Integration test written (optional but recommended)
288+
289+
## Plugin Types
290+
291+
Currently only `OplogProcessor` plugins are supported (see `PluginSpecDto` enum in `golem-common/src/model/plugin_registration.rs`). If adding a new plugin type, you'll also need to extend `PluginSpecDto` and the associated model/repo/service code.

0 commit comments

Comments
 (0)