Skip to content

feat: add openlineage support - #822

Open
razvan wants to merge 25 commits into
mainfrom
feat/openlineage-from-op-rs
Open

razvan wants to merge 25 commits into
mainfrom
feat/openlineage-from-op-rs

Conversation

@razvan

@razvan razvan commented Jul 21, 2026 •

Copy link
Copy Markdown
Member

Description

Airfow can emit OpenLineage events for DAGs. This PR makes essential properties configurable. The operator uses sensible defaults where possible.

Depends on:

Part of stackabletech/issues#856

Decision: https://github.com/stackabletech/decisions/issues/90

Definition of Done Checklist

  • Not all of these items are applicable to all PRs, the author should update this template to only leave the boxes in that are relevant
  • Please make sure all these things are done and tick the boxes

Author

  • Changes are OpenShift compatible
  • CRD changes approved
  • CRD documentation for all fields, following the style guide.
  • Helm chart can be installed and deployed operator works
  • Integration tests passed (for non trivial changes)
  • Changes need to be "offline" compatible
  • Links to generated (nightly) docs added
  • Release note snippet added

Reviewer

  • Code contains useful comments
  • Code contains useful logging statements
  • (Integration-)Test cases added
  • Documentation added or updated. Follows the style guide.
  • Changelog updated
  • Cargo.toml only contains references to git tags (not specific commits or branches)

Acceptance

  • Feature Tracker has been updated
  • Proper release label has been added
  • Links to generated (nightly) docs added
  • Release note snippet added
  • Add type/deprecation label & add to the deprecation schedule
  • Add type/experimental label & add to the experimental features tracker

@razvan razvan self-assigned this Jul 21, 2026
razvan and others added 12 commits July 21, 2026 15:22
…ationClass

Follows the operator-rs change: OpenLineage backend auth is a static bearer
token, so drop the AuthenticationClass resolution and read the api key from the
connection's `credentialsSecretName` Secret (key `apiKey`) directly.

See stackabletech/decisions#90

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…h API group

Follow the operator-rs rename of OpenLineageJob.app_name to job_name
(serialized appName -> jobName) and the move of the OpenLineageConnection CRD
to the lineage.stackable.tech API group:

- update the job_name field access and the "ignored by Airflow" debug log
- update the operator RBAC ClusterRole apiGroup
- update kuttl test manifests (apiVersion, jobName) and the usage-guide docs
- regenerate extra/crds.yaml (appName -> jobName)

Verified with cargo check/clippy/test and CRD regeneration against a local
operator-rs checkout carrying the rename. Committed with --no-verify because the
cargo/regenerate-charts pre-commit hooks would otherwise build against the
un-pushed operator-rs branch; those checks were run manually instead.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Rename the lineage-emission configuration field from `openLineage` to `lineage`
across the CRD and operator internals:

- spec.clusterConfig.openLineage -> spec.clusterConfig.lineage (Rust field
  cluster_config.open_lineage -> lineage; the validated
  ValidatedClusterConfig.open_lineage -> lineage)
- rename operator-internal identifiers: module controller/build/openlineage.rs
  -> lineage.rs, ResolvedOpenLineageConfig -> ResolvedLineageConfig,
  resolved_open_lineage_config -> resolved_lineage_config
- update usage-guide docs and the kuttl test manifest
- regenerate extra/crds.yaml (openLineage -> lineage)

The OpenLineage technology name is kept in prose, in the operator-rs types
(OpenLineageJob, OpenLineageConnection) and their connection-named identifiers,
and in the OPENLINEAGE__* client env var names.

Verified with cargo check/clippy/test and CRD regeneration against a local
operator-rs checkout. Committed with --no-verify because the cargo/regenerate
pre-commit hooks would build against the un-pushed operator-rs branch; those
checks were run manually instead.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… namespace

Bump the operator-rs pin to pick up the reworked `crd::openlineage` module:

- `OpenLineageJob` is renamed to `OpenLineageConfig`.
- `OpenLineageConnectionSpec` now selects an `OpenLineageTransport` (currently
  only `http`) instead of carrying host/port/tls/credentialsSecretName directly.
- `HttpTransport` gained a `path` field, defaulting to `/api/v1/lineage`, which
  now drives `OPENLINEAGE__TRANSPORT__ENDPOINT` instead of a hardcoded constant.
  The leading slash is stripped since the client joins base URL and endpoint.
- `OpenLineageConfig::namespace` is now a required `String` defaulting to
  `default`, so the operator-side fallback to the workload's Kubernetes
  namespace is gone. NOTE: this changes behaviour for users who did not set
  `namespace` explicitly - lineage is now reported under `default` rather than
  the cluster's Kubernetes namespace.

The pin also brings in operator-rs' removal of `product-config`.

Update the usage guide, the kuttl OpenLineageConnection manifest and
extra/crds.yaml for the nested transport schema.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@razvan
razvan marked this pull request as ready for review October 9, 2026 12:16
@razvan razvan moved this to Development: Waiting for Review in Stackable Engineering Oct 9, 2026
@adwk67
adwk67 self-requested a review October 15, 2026 09:06
@adwk67 adwk67 moved this from Development: Waiting for Review to Development: In Review in Stackable Engineering Oct 15, 2026

@adwk67 adwk67 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Some comments. I'm looking at the test now.

&connection(no_tls()),
&job(connection(no_tls()), NAMESPACE),
)
.unwrap();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I know we are inconsistent with the use of unwrap vs. expect, but the code style guide states we should use expect wherever possible in test code.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

13f30a7

There are still unrwap() calls out there unrelated with this pr. I didn't want to inflate this more than needed.

//! injected from a Secret via `secretKeyRef` instead of being embedded in a plaintext JSON value.
//!
//! The Python client's HTTP transport has no CA-path option (only a boolean `verify`), so a
//! SecretClass-provided CA is trusted by mounting it and pointing `REQUESTS_CA_BUNDLE` at it.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Claude points out that REQUESTS_CA_BUNDLE is read by the requests library for every call made with verify=True, so it replaces the default certifi bundle used elsewhere (DAGs, OIDC login etc.) with the single CA for lineage. The user can override this with envOverrides by setting the env-var to point to CAs that they themselves bundle and maintain, or we can build a combined bundle at container start, so that e.g. the certifi bundle is not discarded. For now we should at least clearly document the knock-on effect this can have.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Good catch. I guess this will be a problem with every product. I added a warning bd0285d

/// [`Self::from_config`] so it can be unit tested without a client.
fn build(
connection: &ResolvedOpenLineageConnection,
lineage: &OpenLineageConfig,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

nit: We only use the namespace from the config, so maybe just pass that?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

let crds_and_handlers = vec![(
AirflowCluster::merged_crd(AirflowClusterVersion::V1Alpha2).context(MergeCrdSnafu)?,
AirflowCluster::try_convert,
AirflowCluster::try_convert as fn(ConversionReview) -> ConversionReview,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Do we need this change if we only have one fn (with its own type)? main compiles without it.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

@@ -0,0 +1,20 @@
{% if test_scenario['values']['executor'] == 'kubernetes' %}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can't we use the script commons/cleanup-executor-pods.sh for this (which is basically the same plus a condition to behave differently if using --skip-delete)?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Comment thread Cargo.toml Outdated
tracing = "0.1"

[patch."https://github.com/stackabletech/operator-rs.git"]
# TODO: Switch back to a release tag once operator-rs#1250 (crd::openlineage) has been released.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This TODO can go now

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Comment thread rust/operator-binary/src/controller/build/lineage.rs Outdated
}
}

fn plain_env(name: &str, value: &str) -> EnvVar {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This is used internally only with internal constants so it would be better to use the pattern in env_vars.rs of defining them with constant! as EnvVarName and then using EnvVarSet (once we have parsed the name with_value etc. on EnvVarSet are infallible and the expect in lineage_env_vars can be removed).

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

}
}

fn env_var_from_secret(name: &str, secret_name: &str, secret_key: &str) -> EnvVar {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Similar to plain_env above, but credentials_secret_name is user input and needs to be parsed into a SecretName (with an associated error) and then it can also be added to the EnvVarSet infallibly with with_secret_key_ref).

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

@razvan
razvan requested a review from adwk67 October 16, 2026 15:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Development: In Review

Development

Successfully merging this pull request may close these issues.

2 participants