Copilot commented on code in PR #64754:
URL: https://github.com/apache/airflow/pull/64754#discussion_r3066502782


##########
providers/akeyless/src/airflow/providers/akeyless/hooks/akeyless.py:
##########
@@ -0,0 +1,220 @@
+# 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.
+"""Hook for Akeyless Vault Platform."""
+
+from __future__ import annotations
+
+from functools import cached_property
+from typing import Any
+
+import akeyless
+
+from airflow.providers.common.compat.sdk import BaseHook
+
+VALID_AUTH_TYPES = ("api_key", "aws_iam", "gcp", "azure_ad", "uid", "jwt", 
"k8s", "certificate")

Review Comment:
   If `access_type` is set to an unsupported value (or even a supported one but 
misspelled), the code currently falls through and calls `auth()` with an 
incomplete `Auth` body (e.g., missing `access_key` / missing cloud_id / etc.). 
This leads to a hard-to-debug runtime failure. Add explicit validation (e.g., 
`if access_type not in VALID_AUTH_TYPES: raise ValueError(...)`) and include a 
clear message listing allowed values.



##########
providers/akeyless/src/airflow/providers/akeyless/hooks/akeyless.py:
##########
@@ -0,0 +1,220 @@
+# 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.
+"""Hook for Akeyless Vault Platform."""
+
+from __future__ import annotations
+
+from functools import cached_property
+from typing import Any
+
+import akeyless
+
+from airflow.providers.common.compat.sdk import BaseHook
+
+VALID_AUTH_TYPES = ("api_key", "aws_iam", "gcp", "azure_ad", "uid", "jwt", 
"k8s", "certificate")
+
+
+class AkeylessHook(BaseHook):
+    """
+    Hook to interact with the Akeyless Vault Platform.
+
+    Thin wrapper around the ``akeyless`` Python SDK.
+
+    .. seealso::
+        - https://docs.akeyless.io/
+        - https://github.com/akeylesslabs/akeyless-python
+
+    Connection fields:
+
+    * **Host**     -> API URL (default ``https://api.akeyless.io``)
+    * **Login**    -> Access ID
+    * **Password** -> Access Key (for ``api_key`` auth)
+    * **Extra**    -> JSON with ``access_type`` and auth-method-specific fields
+
+    :param akeyless_conn_id: Airflow connection ID.
+    """
+
+    conn_name_attr = "akeyless_conn_id"
+    default_conn_name = "akeyless_default"
+    conn_type = "akeyless"
+    hook_name = "Akeyless"
+
+    def __init__(self, akeyless_conn_id: str = default_conn_name, **kwargs: 
Any) -> None:
+        super().__init__()
+        self.akeyless_conn_id = akeyless_conn_id
+        self._conn = self.get_connection(akeyless_conn_id)
+        self._extra = self._conn.extra_dejson or {}
+
+    @cached_property
+    def client(self) -> akeyless.V2Api:
+        """Return an ``akeyless.V2Api`` client (cached)."""
+        api_url = self._conn.host or self._extra.get("api_url", 
"https://api.akeyless.io";)
+        if not api_url.startswith("http"):
+            api_url = f"https://{api_url}";
+        return 
akeyless.V2Api(akeyless.ApiClient(akeyless.Configuration(host=api_url)))
+
+    def get_conn(self) -> akeyless.V2Api:
+        """Return the underlying ``akeyless.V2Api`` client."""
+        return self.client
+
+    def authenticate(self) -> str:
+        """
+        Authenticate and return an API token.
+
+        For ``uid`` auth the token is the UID token itself.
+        For all other methods, calls ``akeyless.Auth``.
+        """
+        access_type = self._extra.get("access_type", "api_key")
+
+        if access_type == "uid":
+            return self._extra["uid_token"]
+
+        body = akeyless.Auth(access_id=self._conn.login)
+
+        if access_type == "api_key":
+            body.access_key = self._conn.password
+        elif access_type in ("aws_iam", "gcp", "azure_ad"):
+            body.access_type = access_type
+            body.cloud_id = self._get_cloud_id(access_type)
+        elif access_type == "jwt":
+            body.access_type = "jwt"
+            body.jwt = self._extra.get("jwt")
+        elif access_type == "k8s":
+            body.access_type = "k8s"
+            body.k8s_auth_config_name = self._extra.get("k8s_auth_config_name")
+        elif access_type == "certificate":
+            body.access_type = "cert"
+            body.cert_data = self._extra.get("certificate_data")
+            body.key_data = self._extra.get("private_key_data")
+
+        return self.client.auth(body).token

Review Comment:
   If `access_type` is set to an unsupported value (or even a supported one but 
misspelled), the code currently falls through and calls `auth()` with an 
incomplete `Auth` body (e.g., missing `access_key` / missing cloud_id / etc.). 
This leads to a hard-to-debug runtime failure. Add explicit validation (e.g., 
`if access_type not in VALID_AUTH_TYPES: raise ValueError(...)`) and include a 
clear message listing allowed values.



##########
providers/akeyless/src/airflow/providers/akeyless/hooks/akeyless.py:
##########
@@ -0,0 +1,220 @@
+# 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.
+"""Hook for Akeyless Vault Platform."""
+
+from __future__ import annotations
+
+from functools import cached_property
+from typing import Any
+
+import akeyless
+
+from airflow.providers.common.compat.sdk import BaseHook
+
+VALID_AUTH_TYPES = ("api_key", "aws_iam", "gcp", "azure_ad", "uid", "jwt", 
"k8s", "certificate")
+
+
+class AkeylessHook(BaseHook):
+    """
+    Hook to interact with the Akeyless Vault Platform.
+
+    Thin wrapper around the ``akeyless`` Python SDK.
+
+    .. seealso::
+        - https://docs.akeyless.io/
+        - https://github.com/akeylesslabs/akeyless-python
+
+    Connection fields:
+
+    * **Host**     -> API URL (default ``https://api.akeyless.io``)
+    * **Login**    -> Access ID
+    * **Password** -> Access Key (for ``api_key`` auth)
+    * **Extra**    -> JSON with ``access_type`` and auth-method-specific fields
+
+    :param akeyless_conn_id: Airflow connection ID.
+    """
+
+    conn_name_attr = "akeyless_conn_id"
+    default_conn_name = "akeyless_default"
+    conn_type = "akeyless"
+    hook_name = "Akeyless"
+
+    def __init__(self, akeyless_conn_id: str = default_conn_name, **kwargs: 
Any) -> None:
+        super().__init__()
+        self.akeyless_conn_id = akeyless_conn_id
+        self._conn = self.get_connection(akeyless_conn_id)
+        self._extra = self._conn.extra_dejson or {}
+
+    @cached_property
+    def client(self) -> akeyless.V2Api:
+        """Return an ``akeyless.V2Api`` client (cached)."""
+        api_url = self._conn.host or self._extra.get("api_url", 
"https://api.akeyless.io";)
+        if not api_url.startswith("http"):
+            api_url = f"https://{api_url}";
+        return 
akeyless.V2Api(akeyless.ApiClient(akeyless.Configuration(host=api_url)))
+
+    def get_conn(self) -> akeyless.V2Api:
+        """Return the underlying ``akeyless.V2Api`` client."""
+        return self.client
+
+    def authenticate(self) -> str:
+        """
+        Authenticate and return an API token.
+
+        For ``uid`` auth the token is the UID token itself.
+        For all other methods, calls ``akeyless.Auth``.
+        """
+        access_type = self._extra.get("access_type", "api_key")
+
+        if access_type == "uid":
+            return self._extra["uid_token"]
+
+        body = akeyless.Auth(access_id=self._conn.login)
+
+        if access_type == "api_key":
+            body.access_key = self._conn.password
+        elif access_type in ("aws_iam", "gcp", "azure_ad"):
+            body.access_type = access_type
+            body.cloud_id = self._get_cloud_id(access_type)
+        elif access_type == "jwt":
+            body.access_type = "jwt"
+            body.jwt = self._extra.get("jwt")
+        elif access_type == "k8s":
+            body.access_type = "k8s"
+            body.k8s_auth_config_name = self._extra.get("k8s_auth_config_name")
+        elif access_type == "certificate":
+            body.access_type = "cert"
+            body.cert_data = self._extra.get("certificate_data")
+            body.key_data = self._extra.get("private_key_data")
+
+        return self.client.auth(body).token
+
+    def _get_cloud_id(self, access_type: str) -> str:
+        from akeyless_cloud_id import CloudId
+
+        cid = CloudId()
+        if access_type == "aws_iam":
+            return cid.generate()
+        if access_type == "gcp":
+            return cid.generateGcp(self._extra.get("gcp_audience"))
+        if access_type == "azure_ad":
+            return cid.generateAzure(self._extra.get("azure_object_id"))
+        raise ValueError(f"No cloud-id generator for {access_type!r}")
+
+    # ------------------------------------------------------------------
+    # Secret operations — thin delegates to the SDK
+    # ------------------------------------------------------------------
+
+    def get_secret_value(self, name: str) -> str | None:
+        """Get a static secret value by path."""
+        token = self.authenticate()
+        res = 
self.client.get_secret_value(akeyless.GetSecretValue(names=[name], token=token))
+        return res.get(name)
+
+    def get_secret_values(self, names: list[str]) -> dict[str, str]:
+        """Get multiple static secret values."""
+        token = self.authenticate()
+        return 
dict(self.client.get_secret_value(akeyless.GetSecretValue(names=names, 
token=token)))
+
+    def create_secret(self, name: str, value: str, description: str | None = 
None) -> None:
+        """Create a static secret."""
+        token = self.authenticate()
+        body = akeyless.CreateSecret(name=name, value=value, token=token)
+        if description:
+            body.description = description
+        self.client.create_secret(body)
+
+    def update_secret_value(self, name: str, value: str) -> None:
+        """Update a static secret's value."""
+        token = self.authenticate()
+        self.client.update_secret_val(akeyless.UpdateSecretVal(name=name, 
value=value, token=token))
+
+    def delete_item(self, name: str) -> None:
+        """Delete a secret/item."""
+        token = self.authenticate()
+        self.client.delete_item(akeyless.DeleteItem(name=name, token=token))
+
+    def describe_item(self, name: str) -> dict[str, Any] | None:
+        """Describe a secret/item (returns metadata)."""
+        token = self.authenticate()
+        return self.client.describe_item(akeyless.DescribeItem(name=name, 
token=token)).to_dict()
+
+    def list_items(self, path: str = "/") -> list[dict[str, Any]]:
+        """List items under a path."""
+        token = self.authenticate()
+        res = self.client.list_items(akeyless.ListItems(token=token, 
path=path))
+        return [item.to_dict() for item in (res.items or [])]
+
+    def get_dynamic_secret_value(self, name: str) -> dict[str, Any]:
+        """Generate a dynamic secret value (e.g. DB credentials)."""
+        token = self.authenticate()
+        res = 
self.client.get_dynamic_secret_value(akeyless.GetDynamicSecretValue(name=name, 
token=token))
+        return res if isinstance(res, dict) else res.to_dict()
+
+    def get_rotated_secret_value(self, name: str) -> dict[str, Any]:
+        """Retrieve a rotated secret value."""
+        token = self.authenticate()
+        res = 
self.client.get_rotated_secret_value(akeyless.GetRotatedSecretValue(names=name, 
token=token))

Review Comment:
   `akeyless.GetRotatedSecretValue` is called with `names=name` (a string). In 
most Akeyless SDK request models, `names` is a list of secret names (similar to 
`GetSecretValue(names=[...])` above). Passing a string risks incorrect request 
serialization. Use a list for `names` (e.g. `[name]`) to match the SDK’s 
expected shape.
   ```suggestion
           res = 
self.client.get_rotated_secret_value(akeyless.GetRotatedSecretValue(names=[name],
 token=token))
   ```



##########
providers/akeyless/src/airflow/providers/akeyless/hooks/akeyless.py:
##########
@@ -0,0 +1,220 @@
+# 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.
+"""Hook for Akeyless Vault Platform."""
+
+from __future__ import annotations
+
+from functools import cached_property
+from typing import Any
+
+import akeyless
+
+from airflow.providers.common.compat.sdk import BaseHook
+
+VALID_AUTH_TYPES = ("api_key", "aws_iam", "gcp", "azure_ad", "uid", "jwt", 
"k8s", "certificate")
+
+
+class AkeylessHook(BaseHook):
+    """
+    Hook to interact with the Akeyless Vault Platform.
+
+    Thin wrapper around the ``akeyless`` Python SDK.
+
+    .. seealso::
+        - https://docs.akeyless.io/
+        - https://github.com/akeylesslabs/akeyless-python
+
+    Connection fields:
+
+    * **Host**     -> API URL (default ``https://api.akeyless.io``)
+    * **Login**    -> Access ID
+    * **Password** -> Access Key (for ``api_key`` auth)
+    * **Extra**    -> JSON with ``access_type`` and auth-method-specific fields
+
+    :param akeyless_conn_id: Airflow connection ID.
+    """
+
+    conn_name_attr = "akeyless_conn_id"
+    default_conn_name = "akeyless_default"
+    conn_type = "akeyless"
+    hook_name = "Akeyless"
+
+    def __init__(self, akeyless_conn_id: str = default_conn_name, **kwargs: 
Any) -> None:
+        super().__init__()
+        self.akeyless_conn_id = akeyless_conn_id
+        self._conn = self.get_connection(akeyless_conn_id)
+        self._extra = self._conn.extra_dejson or {}
+
+    @cached_property
+    def client(self) -> akeyless.V2Api:
+        """Return an ``akeyless.V2Api`` client (cached)."""
+        api_url = self._conn.host or self._extra.get("api_url", 
"https://api.akeyless.io";)
+        if not api_url.startswith("http"):
+            api_url = f"https://{api_url}";
+        return 
akeyless.V2Api(akeyless.ApiClient(akeyless.Configuration(host=api_url)))
+
+    def get_conn(self) -> akeyless.V2Api:
+        """Return the underlying ``akeyless.V2Api`` client."""
+        return self.client
+
+    def authenticate(self) -> str:
+        """
+        Authenticate and return an API token.
+
+        For ``uid`` auth the token is the UID token itself.
+        For all other methods, calls ``akeyless.Auth``.
+        """
+        access_type = self._extra.get("access_type", "api_key")
+
+        if access_type == "uid":
+            return self._extra["uid_token"]
+
+        body = akeyless.Auth(access_id=self._conn.login)
+
+        if access_type == "api_key":
+            body.access_key = self._conn.password
+        elif access_type in ("aws_iam", "gcp", "azure_ad"):
+            body.access_type = access_type
+            body.cloud_id = self._get_cloud_id(access_type)
+        elif access_type == "jwt":
+            body.access_type = "jwt"
+            body.jwt = self._extra.get("jwt")
+        elif access_type == "k8s":
+            body.access_type = "k8s"
+            body.k8s_auth_config_name = self._extra.get("k8s_auth_config_name")
+        elif access_type == "certificate":
+            body.access_type = "cert"
+            body.cert_data = self._extra.get("certificate_data")
+            body.key_data = self._extra.get("private_key_data")
+
+        return self.client.auth(body).token
+
+    def _get_cloud_id(self, access_type: str) -> str:
+        from akeyless_cloud_id import CloudId

Review Comment:
   `akeyless_cloud_id` is an optional dependency, but importing it directly 
will raise a bare `ImportError` when users pick `aws_iam`/`gcp`/`azure_ad` auth 
without installing the extra. Catch `ImportError` and raise a clearer exception 
that explains how to install the dependency (e.g., 
`apache-airflow-providers-akeyless[cloud_id]`).
   ```suggestion
           try:
               from akeyless_cloud_id import CloudId
           except ImportError as exc:
               raise ImportError(
                   "`akeyless_cloud_id` is required for `aws_iam`, `gcp`, and 
`azure_ad` "
                   "authentication. Install it with "
                   "`apache-airflow-providers-akeyless[cloud_id]`."
               ) from exc
   ```



##########
providers/akeyless/src/airflow/providers/akeyless/secrets/akeyless.py:
##########
@@ -0,0 +1,156 @@
+# 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.
+"""Secrets Backend for sourcing Connections, Variables, and Config from 
Akeyless."""
+
+from __future__ import annotations
+
+import json
+from functools import cached_property
+from typing import TYPE_CHECKING, Any
+
+import akeyless
+
+from airflow.secrets import BaseSecretsBackend
+from airflow.utils.log.logging_mixin import LoggingMixin
+
+if TYPE_CHECKING:
+    from airflow.models.connection import Connection
+
+
+class AkeylessBackend(BaseSecretsBackend, LoggingMixin):
+    """
+    Retrieve Connections, Variables, and Configuration from Akeyless.
+
+    Configurable via ``airflow.cfg``:
+
+    .. code-block:: ini
+
+        [secrets]
+        backend = airflow.providers.akeyless.secrets.akeyless.AkeylessBackend
+        backend_kwargs = {
+            "connections_path": "/airflow/connections",
+            "variables_path": "/airflow/variables",
+            "api_url": "https://api.akeyless.io";,
+            "access_id": "p-xxxx",
+            "access_key": "xxxx"
+        }
+
+    Secrets are looked up by joining ``<base_path>/<key>``.
+
+    :param connections_path: Akeyless path prefix for Connections (None to 
disable).
+    :param variables_path: Akeyless path prefix for Variables (None to 
disable).
+    :param config_path: Akeyless path prefix for Config (None to disable).
+    :param sep: Separator between base path and key.
+    :param api_url: Akeyless API endpoint.
+    :param access_id: Access ID.
+    :param access_key: Access Key (for ``api_key`` auth).
+    :param access_type: Auth type (default ``api_key``).
+    """
+
+    def __init__(
+        self,
+        connections_path: str | None = "/airflow/connections",
+        variables_path: str | None = "/airflow/variables",
+        config_path: str | None = "/airflow/config",
+        sep: str = "/",
+        api_url: str = "https://api.akeyless.io";,
+        access_id: str | None = None,
+        access_key: str | None = None,
+        access_type: str = "api_key",
+        **kwargs: Any,
+    ) -> None:
+        super().__init__()
+        self.connections_path = connections_path.rstrip("/") if 
connections_path else None
+        self.variables_path = variables_path.rstrip("/") if variables_path 
else None
+        self.config_path = config_path.rstrip("/") if config_path else None
+        self.sep = sep
+        self._api_url = api_url
+        self._access_id = access_id
+        self._access_key = access_key
+        self._access_type = access_type
+        self._extra = kwargs
+
+    @cached_property
+    def _client(self) -> akeyless.V2Api:
+        return 
akeyless.V2Api(akeyless.ApiClient(akeyless.Configuration(host=self._api_url)))
+
+    def _authenticate(self) -> str:
+        """Return an API token via ``akeyless.Auth``."""
+        if self._access_type == "uid":
+            return self._extra["uid_token"]
+        body = akeyless.Auth(access_id=self._access_id, 
access_key=self._access_key)
+        if self._access_type != "api_key":
+            body.access_type = self._access_type
+        return self._client.auth(body).token

Review Comment:
   For non-`api_key` auth types (e.g. `aws_iam`, `gcp`, `azure_ad`, `jwt`, 
`k8s`, `certificate`), the backend sets `body.access_type` but does not 
populate the auth-method-specific fields that the Akeyless API typically 
requires (cloud id / jwt / k8s config / cert+key). This makes those 
`access_type` values effectively unusable in `AkeylessBackend`. Either 
implement the missing fields (mirroring `AkeylessHook.authenticate`) or 
explicitly restrict/validate `access_type` in the backend (e.g. only `api_key` 
and `uid`) and update docs accordingly.



##########
providers/akeyless/src/airflow/providers/akeyless/secrets/akeyless.py:
##########
@@ -0,0 +1,156 @@
+# 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.
+"""Secrets Backend for sourcing Connections, Variables, and Config from 
Akeyless."""
+
+from __future__ import annotations
+
+import json
+from functools import cached_property
+from typing import TYPE_CHECKING, Any
+
+import akeyless
+
+from airflow.secrets import BaseSecretsBackend
+from airflow.utils.log.logging_mixin import LoggingMixin
+
+if TYPE_CHECKING:
+    from airflow.models.connection import Connection
+
+
+class AkeylessBackend(BaseSecretsBackend, LoggingMixin):
+    """
+    Retrieve Connections, Variables, and Configuration from Akeyless.
+
+    Configurable via ``airflow.cfg``:
+
+    .. code-block:: ini
+
+        [secrets]
+        backend = airflow.providers.akeyless.secrets.akeyless.AkeylessBackend
+        backend_kwargs = {
+            "connections_path": "/airflow/connections",
+            "variables_path": "/airflow/variables",
+            "api_url": "https://api.akeyless.io";,
+            "access_id": "p-xxxx",
+            "access_key": "xxxx"
+        }
+
+    Secrets are looked up by joining ``<base_path>/<key>``.
+
+    :param connections_path: Akeyless path prefix for Connections (None to 
disable).
+    :param variables_path: Akeyless path prefix for Variables (None to 
disable).
+    :param config_path: Akeyless path prefix for Config (None to disable).
+    :param sep: Separator between base path and key.
+    :param api_url: Akeyless API endpoint.
+    :param access_id: Access ID.
+    :param access_key: Access Key (for ``api_key`` auth).
+    :param access_type: Auth type (default ``api_key``).
+    """
+
+    def __init__(
+        self,
+        connections_path: str | None = "/airflow/connections",
+        variables_path: str | None = "/airflow/variables",
+        config_path: str | None = "/airflow/config",
+        sep: str = "/",
+        api_url: str = "https://api.akeyless.io";,
+        access_id: str | None = None,
+        access_key: str | None = None,
+        access_type: str = "api_key",
+        **kwargs: Any,
+    ) -> None:
+        super().__init__()
+        self.connections_path = connections_path.rstrip("/") if 
connections_path else None
+        self.variables_path = variables_path.rstrip("/") if variables_path 
else None
+        self.config_path = config_path.rstrip("/") if config_path else None
+        self.sep = sep
+        self._api_url = api_url
+        self._access_id = access_id
+        self._access_key = access_key
+        self._access_type = access_type
+        self._extra = kwargs
+
+    @cached_property
+    def _client(self) -> akeyless.V2Api:
+        return 
akeyless.V2Api(akeyless.ApiClient(akeyless.Configuration(host=self._api_url)))
+
+    def _authenticate(self) -> str:
+        """Return an API token via ``akeyless.Auth``."""
+        if self._access_type == "uid":
+            return self._extra["uid_token"]
+        body = akeyless.Auth(access_id=self._access_id, 
access_key=self._access_key)
+        if self._access_type != "api_key":
+            body.access_type = self._access_type
+        return self._client.auth(body).token
+
+    def _get_secret(self, base_path: str | None, key: str) -> str | None:
+        if base_path is None:
+            return None
+        path = f"{base_path}{self.sep}{key}"
+        try:
+            token = self._authenticate()
+            res = 
self._client.get_secret_value(akeyless.GetSecretValue(names=[path], 
token=token))
+            return res.get(path)

Review Comment:
   Each secret lookup authenticates (`_authenticate()`) and fetches a new 
token. Secrets backends can be called frequently (e.g., during DAG parsing / 
task execution), so repeated auth requests can become a noticeable latency and 
rate-limit risk. Consider caching the token on the backend instance with a 
timestamp/TTL (and refreshing on auth failure), so multiple lookups reuse the 
same token when valid.



##########
providers/akeyless/docs/index.rst:
##########
@@ -0,0 +1,135 @@
+
+ .. 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.
+

Review Comment:
   This file starts with a blank line and then an indented comment block (` .. 
Licensed...`). In reStructuredText, leading indentation at the top level can 
trigger `Unexpected indentation` warnings (often treated as a block quote). 
Other new provider docs here use `.. Licensed...` without the extra leading 
space. Remove the initial blank line and align the license comment to column 1 
for consistent, warning-free docs builds.
   ```suggestion
   .. 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.
   ```



##########
providers/akeyless/provider.yaml:
##########
@@ -0,0 +1,104 @@
+# 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.
+
+---
+package-name: apache-airflow-providers-akeyless
+name: Akeyless
+description: |
+  `Akeyless <https://www.akeyless.io/>`__ Vault Platform provider for Apache 
Airflow.
+  Provides a Hook, Connection type, and Secrets Backend for managing static 
secrets,
+  dynamic secrets, rotated secrets, and more.
+
+state: ready
+lifecycle: incubation
+source-date-epoch: 1712361600
+versions:
+  - 1.0.0
+
+integrations:
+  - integration-name: Akeyless
+    external-doc-url: https://docs.akeyless.io/
+    tags: [software]

Review Comment:
   Provider metadata is inconsistent: `provider.yaml` sets integration tags to 
`[software]`, while `get_provider_info.py` uses `['security', 'secrets']` and 
the PR description frames this as a secrets/security provider. Align tags 
across these two metadata sources to avoid confusing or conflicting 
categorization in generated provider docs/registry.
   ```suggestion
       tags: [security, secrets]
   ```



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to