This is an automated email from the ASF dual-hosted git repository.
Lee-W pushed a commit to branch airflow-ctl/v0-1-test
in repository https://gitbox.apache.org/repos/asf/airflow.git
The following commit(s) were added to refs/heads/airflow-ctl/v0-1-test by this
push:
new 566c59e8095 Run CI on airflow-ctl release-branch pull requests and
pushes (#70724)
566c59e8095 is described below
commit 566c59e809553f2c41b84e8bf409063839ebedbd
Author: Wei Lee <[email protected]>
AuthorDate: Mon Oct 26 10:39:01 2026 +0800
Run CI on airflow-ctl release-branch pull requests and pushes (#70724)
Co-authored-by: Shahar Epstein <[email protected]>
---
.github/workflows/codeql-analysis.yml | 14 +++-
.../airflowctl_tests/test_airflowctl_commands.py | 45 ++++++++----
.../airflowctl/api/datamodels/asset_state_store.py | 83 ++++++++++++++++++++++
.../src/airflowctl/api/datamodels/generated.py | 57 ---------------
airflow-ctl/src/airflowctl/api/operations.py | 8 ++-
.../tests/airflow_ctl/api/test_operations.py | 6 +-
6 files changed, 135 insertions(+), 78 deletions(-)
diff --git a/.github/workflows/codeql-analysis.yml
b/.github/workflows/codeql-analysis.yml
index a6ae94fd3fa..9ed0c7fa890 100644
--- a/.github/workflows/codeql-analysis.yml
+++ b/.github/workflows/codeql-analysis.yml
@@ -20,9 +20,19 @@ name: "CodeQL"
on: # yamllint disable-line rule:truthy
pull_request:
- branches: ['main', 'v[0-9]+-[0-9]+-test', 'v[0-9]+-[0-9]+-stable']
+ branches:
+ - main
+ - v[0-9]+-[0-9]+-test
+ - v[0-9]+-[0-9]+-stable
+ - chart/v[0-9]+-[0-9]+x-test
+ - chart/v[0-9]+-[0-9]+x-stable
+ - airflow-ctl/v[0-9]+-[0-9]+-test
+ - airflow-ctl/v[0-9]+-[0-9]+-stable
push:
- branches: [main]
+ branches:
+ - main
+ - chart/v[0-9]+-[0-9]+x-test
+ - airflow-ctl/v[0-9]+-[0-9]+-test
schedule:
- cron: '0 2 * * *'
diff --git
a/airflow-ctl-tests/tests/airflowctl_tests/test_airflowctl_commands.py
b/airflow-ctl-tests/tests/airflowctl_tests/test_airflowctl_commands.py
index 4c475ddd550..7862b163e59 100644
--- a/airflow-ctl-tests/tests/airflowctl_tests/test_airflowctl_commands.py
+++ b/airflow-ctl-tests/tests/airflowctl_tests/test_airflowctl_commands.py
@@ -56,12 +56,6 @@ TEST_COMMANDS = [
"assets list",
"assets get 1",
"assets create-event --asset-id=1",
- "assets set-state-store 1 test_key test_value",
- "assets get-state-store 1 test_key",
- "assets list-state-store 1",
- "assets delete-state-store 1 test_key",
- "assets set-state-store 1 clear_key value",
- "assets clear-state-store 1",
# Backfill commands
"backfill list example_bash_operator",
# Config commands
@@ -140,6 +134,20 @@ TEST_COMMANDS = [
"plugins list-import-errors",
]
+# This release branch targets core 3.2.x, which has no asset state store API,
so these commands stay
+# skipped here. Integration coverage for them lives on `main`.
+ASSET_STATE_STORE_TEST_COMMANDS = [
+ "assets set-state-store 1 test_key test_value",
+ "assets get-state-store 1 test_key",
+ "assets list-state-store 1",
+ "assets delete-state-store 1 test_key",
+ "assets set-state-store 1 clear_key value",
+ "assets clear-state-store 1",
+]
+_ASSET_STATE_STORE_SKIP = pytest.mark.skip(
+ reason="core 3.2.x has no asset state store API; covered on main, skipped
for this release branch"
+)
+
NO_AUTH_TEST_COMMANDS = [
"version --remote",
]
@@ -150,12 +158,21 @@ DATE_PARAM_2 = date_param()
# Unique xcom key per test run to avoid "already exists" errors from leftover
state
_XCOM_KEY_1 = f"test_xcom_key_{DATE_PARAM_1.replace(':', '').replace('+',
'').replace('-', '')[:16]}"
_XCOM_KEY_2 = f"test_xcom_key_{DATE_PARAM_2.replace(':', '').replace('+',
'').replace('-', '')[:16]}"
-TEST_COMMANDS_DEBUG_MODE = [LOGIN_COMMAND] + [
- test.format(date_param=DATE_PARAM_1, xcom_key=_XCOM_KEY_1) for test in
TEST_COMMANDS
-]
-TEST_COMMANDS_SKIP_KEYRING = [LOGIN_COMMAND_SKIP_KEYRING] + [
- test.format(date_param=DATE_PARAM_2, xcom_key=_XCOM_KEY_2) for test in
TEST_COMMANDS
-]
+TEST_COMMANDS_DEBUG_MODE = (
+ [LOGIN_COMMAND]
+ + [test.format(date_param=DATE_PARAM_1, xcom_key=_XCOM_KEY_1) for test in
TEST_COMMANDS]
+ + [pytest.param(test, marks=_ASSET_STATE_STORE_SKIP) for test in
ASSET_STATE_STORE_TEST_COMMANDS]
+)
+TEST_COMMANDS_SKIP_KEYRING = (
+ [LOGIN_COMMAND_SKIP_KEYRING]
+ + [test.format(date_param=DATE_PARAM_2, xcom_key=_XCOM_KEY_2) for test in
TEST_COMMANDS]
+ + [pytest.param(test, marks=_ASSET_STATE_STORE_SKIP) for test in
ASSET_STATE_STORE_TEST_COMMANDS]
+)
+
+
+def _command_id(command) -> str:
+ command = command.values[0] if hasattr(command, "values") else command
+ return " ".join(command.split(" ", 2)[:2])
def test_hardcoded_xcom_key_would_collide():
@@ -182,7 +199,7 @@ def test_hardcoded_xcom_key_would_collide():
@pytest.mark.parametrize(
"command",
TEST_COMMANDS_DEBUG_MODE,
- ids=[" ".join(command.split(" ", 2)[:2]) for command in
TEST_COMMANDS_DEBUG_MODE],
+ ids=[_command_id(command) for command in TEST_COMMANDS_DEBUG_MODE],
)
def test_airflowctl_commands(command: str, run_command):
"""Test airflowctl commands using docker-compose environment."""
@@ -192,7 +209,7 @@ def test_airflowctl_commands(command: str, run_command):
@pytest.mark.parametrize(
"command",
TEST_COMMANDS_SKIP_KEYRING,
- ids=[" ".join(command.split(" ", 2)[:2]) for command in
TEST_COMMANDS_SKIP_KEYRING],
+ ids=[_command_id(command) for command in TEST_COMMANDS_SKIP_KEYRING],
)
def test_airflowctl_commands_skip_keyring(command: str, api_token: str,
run_command):
"""Test airflowctl commands using docker-compose environment without using
keyring."""
diff --git a/airflow-ctl/src/airflowctl/api/datamodels/asset_state_store.py
b/airflow-ctl/src/airflowctl/api/datamodels/asset_state_store.py
new file mode 100644
index 00000000000..d58217ae5c0
--- /dev/null
+++ b/airflow-ctl/src/airflowctl/api/datamodels/asset_state_store.py
@@ -0,0 +1,83 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+"""
+Hand-written asset state store datamodels.
+
+The 3.2.x core API does not expose the asset state store endpoints, so these
models cannot be produced
+by ``datamodel-codegen``. They stay hand-written for the life of this release
branch; the generated
+models live on ``main``.
+"""
+
+from __future__ import annotations
+
+from datetime import datetime
+from enum import Enum
+from typing import Annotated
+
+from pydantic import BaseModel, ConfigDict, Field
+
+from airflowctl.api.datamodels.generated import JsonValue # noqa: TC001 -
pydantic needs it at runtime
+
+
+class AssetStateStoreWriterKind(str, Enum):
+ """
+ Identifies what kind of writer last updated an asset state store entry.
+
+ ``TASK`` — written by a task via the execution API.
+ ``WATCHER`` — written by a ``BaseEventTrigger`` (no task instance).
+ ``API`` — written directly through the Core API (e.g. manual admin write).
+ """
+
+ TASK = "task"
+ WATCHER = "watcher"
+ API = "api"
+
+
+class AssetStateStoreBody(BaseModel):
+ """Request body for setting an asset state store value."""
+
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ value: JsonValue
+
+
+class AssetStateStoreLastUpdatedBy(BaseModel):
+ """Writer info for the last write to an asset state store entry."""
+
+ kind: AssetStateStoreWriterKind
+ dag_id: Annotated[str | None, Field(title="Dag Id")] = None
+ run_id: Annotated[str | None, Field(title="Run Id")] = None
+ task_id: Annotated[str | None, Field(title="Task Id")] = None
+ map_index: Annotated[int | None, Field(title="Map Index")] = None
+
+
+class AssetStateStoreResponse(BaseModel):
+ """A single asset state store key/value pair with metadata."""
+
+ key: Annotated[str, Field(title="Key")]
+ value: JsonValue
+ updated_at: Annotated[datetime, Field(title="Updated At")]
+ last_updated_by: AssetStateStoreLastUpdatedBy | None = None
+
+
+class AssetStateStoreCollectionResponse(BaseModel):
+ """All asset state store entries for an asset."""
+
+ asset_state_store: Annotated[list[AssetStateStoreResponse],
Field(title="Asset State Store")]
+ total_entries: Annotated[int, Field(title="Total Entries")]
diff --git a/airflow-ctl/src/airflowctl/api/datamodels/generated.py
b/airflow-ctl/src/airflowctl/api/datamodels/generated.py
index df61627c77b..43719cbb096 100644
--- a/airflow-ctl/src/airflowctl/api/datamodels/generated.py
+++ b/airflow-ctl/src/airflowctl/api/datamodels/generated.py
@@ -67,20 +67,6 @@ class BaseInfoResponse(BaseModel):
status: Annotated[str | None, Field(title="Status")] = None
-class AssetStateStoreWriterKind(str, Enum):
- """
- Identifies what kind of writer last updated an asset state store entry.
-
- ``TASK`` — written by a task via the execution API.
- ``WATCHER`` — written by a ``BaseEventTrigger`` (no task instance).
- ``API`` — written directly through the Core API (e.g. manual admin write).
- """
-
- TASK = "task"
- WATCHER = "watcher"
- API = "api"
-
-
class BulkActionNotOnExistence(str, Enum):
"""
Bulk Action to be taken if the entity does not exist.
@@ -1132,40 +1118,6 @@ class AssetResponse(BaseModel):
last_asset_event: LastAssetEventResponse | None = None
-class AssetStateStoreBody(BaseModel):
- """
- Request body for setting an asset state store value.
- """
-
- model_config = ConfigDict(
- extra="forbid",
- )
- value: JsonValue
-
-
-class AssetStateStoreLastUpdatedBy(BaseModel):
- """
- Writer info for the last write to an asset state store entry.
- """
-
- kind: AssetStateStoreWriterKind
- dag_id: Annotated[str | None, Field(title="Dag Id")] = None
- run_id: Annotated[str | None, Field(title="Run Id")] = None
- task_id: Annotated[str | None, Field(title="Task Id")] = None
- map_index: Annotated[int | None, Field(title="Map Index")] = None
-
-
-class AssetStateStoreResponse(BaseModel):
- """
- A single asset state store key/value pair with metadata.
- """
-
- key: Annotated[str, Field(title="Key")]
- value: JsonValue
- updated_at: Annotated[datetime, Field(title="Updated At")]
- last_updated_by: AssetStateStoreLastUpdatedBy | None = None
-
-
class BackfillPostBody(BaseModel):
"""
Object used for create backfill request.
@@ -1912,15 +1864,6 @@ class AssetEventCollectionResponse(BaseModel):
total_entries: Annotated[int, Field(title="Total Entries")]
-class AssetStateStoreCollectionResponse(BaseModel):
- """
- All asset state store entries for an asset.
- """
-
- asset_state_store: Annotated[list[AssetStateStoreResponse],
Field(title="Asset State Store")]
- total_entries: Annotated[int, Field(title="Total Entries")]
-
-
class BackfillCollectionResponse(BaseModel):
"""
Backfill Collection serializer for responses.
diff --git a/airflow-ctl/src/airflowctl/api/operations.py
b/airflow-ctl/src/airflowctl/api/operations.py
index 39062ed86a5..fac122ac924 100644
--- a/airflow-ctl/src/airflowctl/api/operations.py
+++ b/airflow-ctl/src/airflowctl/api/operations.py
@@ -25,6 +25,11 @@ import httpx
import structlog
from pydantic import BaseModel, ValidationError
+from airflowctl.api.datamodels.asset_state_store import (
+ AssetStateStoreBody,
+ AssetStateStoreCollectionResponse,
+ AssetStateStoreResponse,
+)
from airflowctl.api.datamodels.auth_generated import LoginBody, LoginResponse
from airflowctl.api.datamodels.generated import (
AssetAliasCollectionResponse,
@@ -32,9 +37,6 @@ from airflowctl.api.datamodels.generated import (
AssetCollectionResponse,
AssetEventResponse,
AssetResponse,
- AssetStateStoreBody,
- AssetStateStoreCollectionResponse,
- AssetStateStoreResponse,
BackfillCollectionResponse,
BackfillPostBody,
BackfillResponse,
diff --git a/airflow-ctl/tests/airflow_ctl/api/test_operations.py
b/airflow-ctl/tests/airflow_ctl/api/test_operations.py
index 1cd123ada78..35f59563310 100644
--- a/airflow-ctl/tests/airflow_ctl/api/test_operations.py
+++ b/airflow-ctl/tests/airflow_ctl/api/test_operations.py
@@ -29,6 +29,10 @@ import pytest
from pydantic import BaseModel
from airflowctl.api.client import Client, ClientKind
+from airflowctl.api.datamodels.asset_state_store import (
+ AssetStateStoreCollectionResponse,
+ AssetStateStoreResponse,
+)
from airflowctl.api.datamodels.auth_generated import LoginBody, LoginResponse
from airflowctl.api.datamodels.generated import (
AssetAliasCollectionResponse,
@@ -36,8 +40,6 @@ from airflowctl.api.datamodels.generated import (
AssetCollectionResponse,
AssetEventResponse,
AssetResponse,
- AssetStateStoreCollectionResponse,
- AssetStateStoreResponse,
BackfillCollectionResponse,
BackfillPostBody,
BackfillResponse,