Skip to content

feat: add --format otel to scan command for ComplyBeacon evidence export#463

Merged
gvauter merged 10 commits intocomplytime:mainfrom
gvauter:003-complybeacon-export
Apr 21, 2026
Merged

feat: add --format otel to scan command for ComplyBeacon evidence export#463
gvauter merged 10 commits intocomplytime:mainfrom
gvauter:003-complybeacon-export

Conversation

@gvauter
Copy link
Copy Markdown
Member

@gvauter gvauter commented Apr 3, 2026

Summary

Implements the ComplyBeacon evidence export feature as specified in spec 003. This adds otel as a new output format for the complyctl scan command, enabling compliance evidence to be exported to a ComplyBeacon collector (or any OTLP-compatible endpoint) as OpenTelemetry log records.

When --format otel is specified, complyctl scan runs the scan as usual, then initiates an export phase where it calls each plugin's new Export RPC. The collector endpoint and optional OIDC client credentials are configured in complytime.yaml. Plugin stubs are included for OpenSCAP, AMPEL, and the test plugin — real ProofWatch-based implementations will follow in a subsequent PR.

Related Issues

Review Hints

Best reviewed commit-by-commit in order — each commit is self-contained and builds on the previous:

  • b562e48 proto — new Export RPC, CollectorConfig message, and supports_export field in the gRPC contract
  • 7fe9769 plugin SDK — domain types, client/server methods, and RouteExport in the manager
  • e602ef1 config — CollectorConfig and AuthConfig structs in complytime.yaml, plus the otel output format constant
  • f88be26 scan command — --format otel validation, OIDC token resolution, export orchestration, and summary table
  • 1188597 doctor — new CheckCollector diagnostic for the collector configuration
  • f5d1785 plugin stubs — Export method in OpenSCAP, AMPEL, and test plugins (returns "not yet supported")

Notes:
The scan command handles OIDC token exchange itself (via golang.org/x/oauth2/clientcredentials) and passes the resolved bearer token to plugins in the ExportRequest. Plugins never see client credentials directly.

All plugin Export implementations are currently stubs that return Success: false. A follow-up PR will implement real export using ProofWatch in one or more plugins.

To test locally: configure a collector section in complytime.yaml with an endpoint, then run complyctl scan --policy-id --format otel. Without a collector configured, the command returns a clear error message. complyctl doctor will also report the collector configuration status.

Assisted by Cursor AI

@gvauter gvauter self-assigned this Apr 3, 2026
@gvauter gvauter requested review from a team as code owners April 3, 2026 13:20
Copy link
Copy Markdown
Member

@jpower432 jpower432 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added a PR with a suggestion to your branch. I would like to find a way to avoid plugin author needs to Export stubs if they don't implement it. I think this additional feature should be fully opt in for the plugin author.

gvauter#1

Comment thread api/plugin/plugin.proto
@gvauter gvauter force-pushed the 003-complybeacon-export branch from 54f3f31 to 9a753d4 Compare April 15, 2026 23:03
@gvauter
Copy link
Copy Markdown
Member Author

gvauter commented Apr 15, 2026

I added a PR with a suggestion to your branch. I would like to find a way to avoid plugin author needs to Export stubs if they don't implement it. I think this additional feature should be fully opt in for the plugin author.

gvauter#1

Great thanks Jenn, I've merged your PR.

@gvauter gvauter force-pushed the 003-complybeacon-export branch from 9a753d4 to 2ae7d72 Compare April 15, 2026 23:41
@gvauter gvauter requested a review from jpower432 April 15, 2026 23:53
@gvauter gvauter force-pushed the 003-complybeacon-export branch 2 times, most recently from 77c4c7d to f9337b7 Compare April 16, 2026 00:56
marcusburghardt added a commit to marcusburghardt/org-infra that referenced this pull request Apr 16, 2026
The post-comment job in ci_crapload.yml was skipped when the CRAP Load
analysis detected regressions because the implicit success() condition
on the needs dependency prevented it from running. This meant PR authors
never saw the detailed report when they needed it most.

Add if: !cancelled() so the comment job runs on both success and failure.
Handle missing artifacts with a fallback comment linking to the Job
Summary, and truncate oversized comments to stay within GitHub limits.

Refs: complytime/complyctl#463

Assisted-by: OpenCode (claude-opus-4-6)
Signed-off-by: Marcus Burghardt <maburgha@redhat.com>
marcusburghardt added a commit to complytime/org-infra that referenced this pull request Apr 16, 2026
The post-comment job in ci_crapload.yml was skipped when the CRAP Load
analysis detected regressions because the implicit success() condition
on the needs dependency prevented it from running. This meant PR authors
never saw the detailed report when they needed it most.

Add if: !cancelled() so the comment job runs on both success and failure.
Handle missing artifacts with a fallback comment linking to the Job
Summary, and truncate oversized comments to stay within GitHub limits.

Refs: complytime/complyctl#463

Assisted-by: OpenCode (claude-opus-4-6)
Signed-off-by: Marcus Burghardt <maburgha@redhat.com>
gvauter and others added 7 commits April 20, 2026 12:02
Signed-off-by: George Vauter <gvauter@redhat.com>
Made-with: Cursor
Signed-off-by: George Vauter <gvauter@redhat.com>
Made-with: Cursor
Signed-off-by: George Vauter <gvauter@redhat.com>
Made-with: Cursor
Signed-off-by: George Vauter <gvauter@redhat.com>
Made-with: Cursor
Signed-off-by: George Vauter <gvauter@redhat.com>
Made-with: Cursor
Signed-off-by: George Vauter <gvauter@redhat.com>
Made-with: Cursor
Export was part of the Plugin interface, forcing every plugin to
carry a no-op stub even when it has no export capability. This
violates the Interface Segregation Principle and contradicts
FR-013 ("plugins that do not implement Export MUST NOT be required
to change").

Introduce a separate Exporter interface. The grpcServer adapter
performs a runtime type assertion so the proto-level Export RPC
still works on the wire. Plugin authors only implement Exporter
when they opt in via supports_export=true in DescribeResponse.

Made-with: Cursor
Signed-off-by: Jennifer Power <barnabei.jennifer@gmail.com>
Made-with: Cursor
@gvauter gvauter force-pushed the 003-complybeacon-export branch from f9337b7 to ce70be4 Compare April 20, 2026 16:53
Split runExport and formatExportSummary into smaller functions
(resolveCollectorAuth, exportToPlugins, exportSinglePlugin,
appendExportRow, appendResponseRow, exportResponseStatus,
authRequired, validateAuthCredentials) so each stays below
the CRAP threshold of 30 at 0% coverage.

Signed-off-by: George Vauter <gvauter@redhat.com>
Made-with: Cursor
Signed-off-by: George Vauter <gvauter@redhat.com>
Made-with: Cursor
@gvauter gvauter force-pushed the 003-complybeacon-export branch from ce70be4 to a2f3c44 Compare April 20, 2026 18:54
jpower432
jpower432 previously approved these changes Apr 20, 2026
Copy link
Copy Markdown
Member

@jpower432 jpower432 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Tested E2E with a local collector and testing plugin.

runExport previously returned nil even when plugins reported export
failures. User Story 1 Acceptance Scenario 4 requires a non-zero exit
code when an unreachable collector or plugin-level failure occurs.
Add countExportFailures to detect transport errors, Success==false,
and FailedCount>0, then propagate an error from runExport.

Add unit tests for export orchestration helpers (scan.go):
authRequired, validateAuthCredentials, countExportFailures,
exportResponseStatus, formatExportSummary, and the otel-without-
collector early-exit path.

Add unit tests for CheckCollector and checkCollectorAuth (doctor.go)
covering nil collector, empty endpoint, valid endpoint with and without
auth, and each incomplete-auth warning path.

Assisted-by: OpenCode (claude-sonnet-4-6@default)
Signed-off-by: Marcus Burghardt <maburgha@redhat.com>
Copy link
Copy Markdown
Contributor

@marcusburghardt marcusburghardt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@gvauter the automated review got some gaps with tests, so I created a PR against your branch already covering them: gvauter#2 Besides that, LGTM.

Test fixtures use placeholder credentials, not real secrets.

Signed-off-by: George Vauter <gvauter@redhat.com>
Made-with: Cursor
Signed-off-by: George Vauter <gvauter@redhat.com>
Made-with: Cursor
@gvauter gvauter force-pushed the 003-complybeacon-export branch from c9d8fcf to 69ea907 Compare April 21, 2026 11:57
Copy link
Copy Markdown
Contributor

@marcusburghardt marcusburghardt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Thanks @gvauter

Copy link
Copy Markdown
Member

@jpower432 jpower432 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@gvauter gvauter merged commit e6128f5 into complytime:main Apr 21, 2026
27 checks passed
marcusburghardt added a commit to marcusburghardt/complyctl that referenced this pull request Apr 21, 2026
…ovider

scan.go: Replace remaining pkg/plugin import alias usages in the
export path (executeScanPhase, maybeExport, runExport, exportToPlugins,
exportSinglePlugin, exportResult) with pkg/provider equivalents.

cli_test.go: Update import from pkg/plugin to pkg/provider and rename
all pluginID field references to providerID.

These references were missed during the pkg/plugin → pkg/provider rename
because upstream's Export RPC additions (PR complytime#463) landed in the same
functions during the rebase, leaving a mix of old and new package names.

Assisted-by: OpenCode (claude-sonnet-4-6@default)
Signed-off-by: Marcus Burghardt <maburgha@redhat.com>
marcusburghardt added a commit to marcusburghardt/complyctl that referenced this pull request Apr 21, 2026
…ovider

scan.go: Replace remaining pkg/plugin import alias usages in the
export path (executeScanPhase, maybeExport, runExport, exportToPlugins,
exportSinglePlugin, exportResult) with pkg/provider equivalents.

cli_test.go: Update import from pkg/plugin to pkg/provider and rename
all pluginID field references to providerID.

These references were missed during the pkg/plugin → pkg/provider rename
because upstream's Export RPC additions (PR complytime#463) landed in the same
functions during the rebase, leaving a mix of old and new package names.

Assisted-by: OpenCode (claude-sonnet-4-6@default)
Signed-off-by: Marcus Burghardt <maburgha@redhat.com>
marcusburghardt added a commit to marcusburghardt/complyctl that referenced this pull request Apr 22, 2026
Export WorkspaceDir from pkg/provider so provider authors can import the
canonical workspace directory name instead of duplicating it locally.
Providers in complytime-providers were hard-coding ".complytime" because
internal/complytime.WorkspaceDir is not importable by external modules.

Update contracts/provider-sdk.md to address review feedback (gvauter):
- Remove Export from the Provider interface; Export is optional via the
  separate Exporter interface (ISP design finalised in PR complytime#463)
- Add SupportsExport bool to DescribeResponse struct definition
- Replace the stub implementation example with the correct opt-in pattern:
  implement Exporter + set SupportsExport: true in DescribeResponse
- Update the contract stability table to list Exporter separately

Assisted-by: OpenCode (claude-sonnet-4-6@default)
Signed-off-by: Marcus Burghardt <maburgha@redhat.com>
marcusburghardt added a commit to marcusburghardt/complyctl that referenced this pull request Apr 22, 2026
…ovider

scan.go: Replace remaining pkg/plugin import alias usages in the
export path (executeScanPhase, maybeExport, runExport, exportToPlugins,
exportSinglePlugin, exportResult) with pkg/provider equivalents.

cli_test.go: Update import from pkg/plugin to pkg/provider and rename
all pluginID field references to providerID.

These references were missed during the pkg/plugin → pkg/provider rename
because upstream's Export RPC additions (PR complytime#463) landed in the same
functions during the rebase, leaving a mix of old and new package names.

Assisted-by: OpenCode (claude-sonnet-4-6@default)
Signed-off-by: Marcus Burghardt <maburgha@redhat.com>
marcusburghardt added a commit to marcusburghardt/complyctl that referenced this pull request Apr 22, 2026
Export WorkspaceDir from pkg/provider so provider authors can import the
canonical workspace directory name instead of duplicating it locally.
Providers in complytime-providers were hard-coding ".complytime" because
internal/complytime.WorkspaceDir is not importable by external modules.

Update contracts/provider-sdk.md to address review feedback (gvauter):
- Remove Export from the Provider interface; Export is optional via the
  separate Exporter interface (ISP design finalised in PR complytime#463)
- Add SupportsExport bool to DescribeResponse struct definition
- Replace the stub implementation example with the correct opt-in pattern:
  implement Exporter + set SupportsExport: true in DescribeResponse
- Update the contract stability table to list Exporter separately

Assisted-by: OpenCode (claude-sonnet-4-6@default)
Signed-off-by: Marcus Burghardt <maburgha@redhat.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Implement spec for export functionality

3 participants