elaunira-airflow-provider-r2index 0.3.0__tar.gz → 0.3.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {elaunira_airflow_provider_r2index-0.3.0 → elaunira_airflow_provider_r2index-0.3.2}/PKG-INFO +1 -1
- elaunira_airflow_provider_r2index-0.3.2/README.md +169 -0
- {elaunira_airflow_provider_r2index-0.3.0 → elaunira_airflow_provider_r2index-0.3.2}/pyproject.toml +1 -1
- {elaunira_airflow_provider_r2index-0.3.0 → elaunira_airflow_provider_r2index-0.3.2}/src/elaunira/airflow/provider/r2index/hooks/r2index.py +10 -3
- {elaunira_airflow_provider_r2index-0.3.0 → elaunira_airflow_provider_r2index-0.3.2}/src/elaunira/airflow/provider/r2index/links/r2index.py +1 -1
- elaunira_airflow_provider_r2index-0.3.0/README.md +0 -75
- {elaunira_airflow_provider_r2index-0.3.0 → elaunira_airflow_provider_r2index-0.3.2}/.gitignore +0 -0
- {elaunira_airflow_provider_r2index-0.3.0 → elaunira_airflow_provider_r2index-0.3.2}/LICENSE +0 -0
- {elaunira_airflow_provider_r2index-0.3.0 → elaunira_airflow_provider_r2index-0.3.2}/src/elaunira/__init__.py +0 -0
- {elaunira_airflow_provider_r2index-0.3.0 → elaunira_airflow_provider_r2index-0.3.2}/src/elaunira/airflow/__init__.py +0 -0
- {elaunira_airflow_provider_r2index-0.3.0 → elaunira_airflow_provider_r2index-0.3.2}/src/elaunira/airflow/provider/__init__.py +0 -0
- {elaunira_airflow_provider_r2index-0.3.0 → elaunira_airflow_provider_r2index-0.3.2}/src/elaunira/airflow/provider/r2index/__init__.py +0 -0
- {elaunira_airflow_provider_r2index-0.3.0 → elaunira_airflow_provider_r2index-0.3.2}/src/elaunira/airflow/provider/r2index/decorators/__init__.py +0 -0
- {elaunira_airflow_provider_r2index-0.3.0 → elaunira_airflow_provider_r2index-0.3.2}/src/elaunira/airflow/provider/r2index/decorators/r2index.py +0 -0
- {elaunira_airflow_provider_r2index-0.3.0 → elaunira_airflow_provider_r2index-0.3.2}/src/elaunira/airflow/provider/r2index/hooks/__init__.py +0 -0
- {elaunira_airflow_provider_r2index-0.3.0 → elaunira_airflow_provider_r2index-0.3.2}/src/elaunira/airflow/provider/r2index/links/__init__.py +0 -0
- {elaunira_airflow_provider_r2index-0.3.0 → elaunira_airflow_provider_r2index-0.3.2}/src/elaunira/airflow/provider/r2index/operators/__init__.py +0 -0
- {elaunira_airflow_provider_r2index-0.3.0 → elaunira_airflow_provider_r2index-0.3.2}/src/elaunira/airflow/provider/r2index/operators/r2index.py +0 -0
{elaunira_airflow_provider_r2index-0.3.0 → elaunira_airflow_provider_r2index-0.3.2}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: elaunira-airflow-provider-r2index
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.2
|
|
4
4
|
Summary: Airflow provider for Elaunira R2Index connections
|
|
5
5
|
Project-URL: Repository, https://github.com/elaunira/elaunira-airflow-provider-r2index
|
|
6
6
|
License-Expression: MIT
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
# Elaunira Airflow Provider for R2Index
|
|
2
|
+
|
|
3
|
+
Airflow provider package for R2Index, providing connection type, operators, and TaskFlow decorators.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install elaunira-airflow-provider-r2index
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Features
|
|
12
|
+
|
|
13
|
+
- **R2Index** connection type in Airflow UI
|
|
14
|
+
- `R2IndexUploadOperator` and `R2IndexDownloadOperator` for file transfers
|
|
15
|
+
- `@task.r2index_upload` and `@task.r2index_download` TaskFlow decorators
|
|
16
|
+
|
|
17
|
+
## Connection Configuration
|
|
18
|
+
|
|
19
|
+
After installation, the **R2Index** connection type will be available in Airflow's connection UI.
|
|
20
|
+
|
|
21
|
+
### Vault/OpenBao Mode (Recommended)
|
|
22
|
+
|
|
23
|
+
Fetches credentials dynamically from HashiCorp Vault or OpenBao:
|
|
24
|
+
|
|
25
|
+
| Field | Description |
|
|
26
|
+
|-------|-------------|
|
|
27
|
+
| Vault Connection ID | Airflow Vault connection ID (e.g., `openbao-elaunira`) |
|
|
28
|
+
| Vault Namespace | OpenBao namespace (e.g., `elaunira/production`) |
|
|
29
|
+
| Vault Secrets Mapping | JSON mapping of config keys to secret paths |
|
|
30
|
+
|
|
31
|
+
Example Vault Secrets Mapping:
|
|
32
|
+
```json
|
|
33
|
+
{
|
|
34
|
+
"r2index_api_url": "cloudflare/r2index#api-url",
|
|
35
|
+
"r2index_api_token": "cloudflare/r2index#api-token",
|
|
36
|
+
"r2_access_key_id": "cloudflare/r2/e2e-tests#access-key-id",
|
|
37
|
+
"r2_secret_access_key": "cloudflare/r2/e2e-tests#secret-access-key",
|
|
38
|
+
"r2_endpoint_url": "cloudflare/r2/e2e-tests#endpoint-url"
|
|
39
|
+
}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Secret path format: `path#key` (e.g., `cloudflare/r2index#api-url`)
|
|
43
|
+
|
|
44
|
+
### Direct Mode
|
|
45
|
+
|
|
46
|
+
Store credentials directly in the connection:
|
|
47
|
+
|
|
48
|
+
| Field | Description |
|
|
49
|
+
|-------|-------------|
|
|
50
|
+
| R2Index API URL | API endpoint URL |
|
|
51
|
+
| R2Index API Token | API authentication token |
|
|
52
|
+
| R2 Access Key ID | Cloudflare R2 access key |
|
|
53
|
+
| R2 Secret Access Key | Cloudflare R2 secret key |
|
|
54
|
+
| R2 Endpoint URL | Cloudflare R2 endpoint |
|
|
55
|
+
|
|
56
|
+
### Environment Variables Fallback
|
|
57
|
+
|
|
58
|
+
If no connection is configured, the hook falls back to environment variables:
|
|
59
|
+
|
|
60
|
+
- `R2INDEX_API_URL`
|
|
61
|
+
- `R2INDEX_API_TOKEN`
|
|
62
|
+
- `R2_ACCESS_KEY_ID`
|
|
63
|
+
- `R2_SECRET_ACCESS_KEY`
|
|
64
|
+
- `R2_ENDPOINT_URL`
|
|
65
|
+
|
|
66
|
+
## Operators
|
|
67
|
+
|
|
68
|
+
### R2IndexUploadOperator
|
|
69
|
+
|
|
70
|
+
Upload files to R2Index:
|
|
71
|
+
|
|
72
|
+
```python
|
|
73
|
+
from elaunira.airflow.provider.r2index.operators import R2IndexUploadOperator, UploadItem
|
|
74
|
+
|
|
75
|
+
upload = R2IndexUploadOperator(
|
|
76
|
+
task_id="upload_file",
|
|
77
|
+
bucket="my-bucket",
|
|
78
|
+
r2index_conn_id="my_r2index_connection",
|
|
79
|
+
items=UploadItem(
|
|
80
|
+
source="/tmp/data.csv",
|
|
81
|
+
category="example",
|
|
82
|
+
entity="sample-data",
|
|
83
|
+
extension="csv",
|
|
84
|
+
media_type="text/csv",
|
|
85
|
+
destination_path="example/data",
|
|
86
|
+
destination_filename="data.csv",
|
|
87
|
+
destination_version="{{ ds }}",
|
|
88
|
+
),
|
|
89
|
+
)
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### R2IndexDownloadOperator
|
|
93
|
+
|
|
94
|
+
Download files from R2Index:
|
|
95
|
+
|
|
96
|
+
```python
|
|
97
|
+
from elaunira.airflow.provider.r2index.operators import R2IndexDownloadOperator, DownloadItem
|
|
98
|
+
|
|
99
|
+
download = R2IndexDownloadOperator(
|
|
100
|
+
task_id="download_file",
|
|
101
|
+
bucket="my-bucket",
|
|
102
|
+
r2index_conn_id="my_r2index_connection",
|
|
103
|
+
items=DownloadItem(
|
|
104
|
+
source_path="example/data",
|
|
105
|
+
source_filename="data.csv",
|
|
106
|
+
source_version="{{ ds }}",
|
|
107
|
+
destination="/tmp/downloaded.csv",
|
|
108
|
+
),
|
|
109
|
+
)
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## TaskFlow Decorators
|
|
113
|
+
|
|
114
|
+
### @task.r2index_upload
|
|
115
|
+
|
|
116
|
+
```python
|
|
117
|
+
from airflow.sdk import dag, task
|
|
118
|
+
from elaunira.airflow.provider.r2index.operators import UploadItem
|
|
119
|
+
|
|
120
|
+
@dag(schedule=None)
|
|
121
|
+
def my_dag():
|
|
122
|
+
@task.r2index_upload(bucket="my-bucket", r2index_conn_id="my_connection")
|
|
123
|
+
def prepare_upload() -> UploadItem:
|
|
124
|
+
return UploadItem(
|
|
125
|
+
source="/tmp/data.csv",
|
|
126
|
+
category="example",
|
|
127
|
+
entity="sample-data",
|
|
128
|
+
extension="csv",
|
|
129
|
+
media_type="text/csv",
|
|
130
|
+
destination_path="example/data",
|
|
131
|
+
destination_filename="data.csv",
|
|
132
|
+
destination_version="2024-01-01",
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
prepare_upload()
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### @task.r2index_download
|
|
139
|
+
|
|
140
|
+
```python
|
|
141
|
+
from airflow.sdk import dag, task
|
|
142
|
+
from elaunira.airflow.provider.r2index.operators import DownloadItem
|
|
143
|
+
|
|
144
|
+
@dag(schedule=None)
|
|
145
|
+
def my_dag():
|
|
146
|
+
@task.r2index_download(bucket="my-bucket", r2index_conn_id="my_connection")
|
|
147
|
+
def prepare_download() -> DownloadItem:
|
|
148
|
+
return DownloadItem(
|
|
149
|
+
source_path="example/data",
|
|
150
|
+
source_filename="data.csv",
|
|
151
|
+
source_version="2024-01-01",
|
|
152
|
+
destination="/tmp/downloaded.csv",
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
prepare_download()
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
## Hook Usage
|
|
159
|
+
|
|
160
|
+
```python
|
|
161
|
+
from elaunira.airflow.provider.r2index.hooks import R2IndexHook
|
|
162
|
+
|
|
163
|
+
hook = R2IndexHook(r2index_conn_id="my_r2index_connection")
|
|
164
|
+
client = hook.get_conn()
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
## License
|
|
168
|
+
|
|
169
|
+
MIT
|
|
@@ -191,14 +191,20 @@ class R2IndexHook(BaseHook):
|
|
|
191
191
|
|
|
192
192
|
return secret_cache[path].get(key)
|
|
193
193
|
|
|
194
|
-
|
|
194
|
+
config = {
|
|
195
195
|
"index_api_url": get_secret_value("r2index_api_url"),
|
|
196
196
|
"index_api_token": get_secret_value("r2index_api_token"),
|
|
197
197
|
"r2_access_key_id": get_secret_value("r2_access_key_id"),
|
|
198
198
|
"r2_secret_access_key": get_secret_value("r2_secret_access_key"),
|
|
199
199
|
"r2_endpoint_url": get_secret_value("r2_endpoint_url"),
|
|
200
200
|
}
|
|
201
|
-
|
|
201
|
+
# Log which values are missing
|
|
202
|
+
missing = [k for k, v in config.items() if v is None]
|
|
203
|
+
if missing:
|
|
204
|
+
self.log.warning("Missing Vault secrets: %s", missing)
|
|
205
|
+
return config
|
|
206
|
+
except Exception as e:
|
|
207
|
+
self.log.error("Failed to get config from Vault: %s", e)
|
|
202
208
|
return None
|
|
203
209
|
|
|
204
210
|
def _get_config_from_connection(self) -> dict[str, str | None] | None:
|
|
@@ -233,7 +239,8 @@ class R2IndexHook(BaseHook):
|
|
|
233
239
|
"r2_secret_access_key": extra.get("r2_secret_access_key"),
|
|
234
240
|
"r2_endpoint_url": extra.get("r2_endpoint_url"),
|
|
235
241
|
}
|
|
236
|
-
except Exception:
|
|
242
|
+
except Exception as e:
|
|
243
|
+
self.log.error("Failed to get config from connection: %s", e)
|
|
237
244
|
return None
|
|
238
245
|
|
|
239
246
|
def get_conn(self) -> R2IndexClient:
|
|
@@ -29,7 +29,7 @@ class R2IndexFileLink(BaseOperatorLink):
|
|
|
29
29
|
"""Get the link to the R2Index file."""
|
|
30
30
|
from airflow.models import XCom
|
|
31
31
|
|
|
32
|
-
result = XCom.get_value(ti_key=ti_key)
|
|
32
|
+
result = XCom.get_value(ti_key=ti_key, key="return_value")
|
|
33
33
|
if result and isinstance(result, dict):
|
|
34
34
|
file_id = result.get("id") or result.get("file_record", {}).get("id")
|
|
35
35
|
if file_id:
|
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
# Elaunira Airflow Provider for R2Index
|
|
2
|
-
|
|
3
|
-
Airflow provider package that adds the **Elaunira R2Index** connection type for managing R2Index credentials.
|
|
4
|
-
|
|
5
|
-
## Installation
|
|
6
|
-
|
|
7
|
-
```bash
|
|
8
|
-
pip install elaunira-airflow-provider-r2index
|
|
9
|
-
```
|
|
10
|
-
|
|
11
|
-
## Usage
|
|
12
|
-
|
|
13
|
-
After installation, a new connection type **Elaunira R2Index** will be available in Airflow's connection UI.
|
|
14
|
-
|
|
15
|
-
### Connection Configuration
|
|
16
|
-
|
|
17
|
-
The connection supports two modes:
|
|
18
|
-
|
|
19
|
-
#### 1. Vault/OpenBao Mode (Recommended)
|
|
20
|
-
|
|
21
|
-
Fetches credentials dynamically from HashiCorp Vault or OpenBao:
|
|
22
|
-
|
|
23
|
-
| Field | Description |
|
|
24
|
-
|-------|-------------|
|
|
25
|
-
| Vault Connection ID | Airflow Vault connection ID (e.g., `openbao-ipregistry`) |
|
|
26
|
-
| Vault Namespace | OpenBao namespace (e.g., `ipregistry/production`) |
|
|
27
|
-
| Vault Secrets (JSON) | JSON mapping of config keys to secret paths |
|
|
28
|
-
|
|
29
|
-
Example Vault Secrets JSON:
|
|
30
|
-
```json
|
|
31
|
-
{
|
|
32
|
-
"r2index_api_url": "cloudflare/r2index#api-url",
|
|
33
|
-
"r2index_api_token": "cloudflare/r2index#api-token",
|
|
34
|
-
"r2_access_key_id": "cloudflare/r2/airflow#access-key-id",
|
|
35
|
-
"r2_secret_access_key": "cloudflare/r2/airflow#secret-access-key",
|
|
36
|
-
"r2_endpoint_url": "cloudflare/r2/airflow#endpoint-url"
|
|
37
|
-
}
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
Secret path format: `path#key` (e.g., `cloudflare/r2index#api-url`)
|
|
41
|
-
|
|
42
|
-
#### 2. Direct Mode
|
|
43
|
-
|
|
44
|
-
Store credentials directly in the connection:
|
|
45
|
-
|
|
46
|
-
| Field | Description |
|
|
47
|
-
|-------|-------------|
|
|
48
|
-
| R2Index API URL | API endpoint URL |
|
|
49
|
-
| R2Index API Token | API authentication token |
|
|
50
|
-
| R2 Access Key ID | Cloudflare R2 access key |
|
|
51
|
-
| R2 Secret Access Key | Cloudflare R2 secret key |
|
|
52
|
-
| R2 Endpoint URL | Cloudflare R2 endpoint |
|
|
53
|
-
|
|
54
|
-
### Environment Variables Fallback
|
|
55
|
-
|
|
56
|
-
If no connection is configured, the hook falls back to environment variables:
|
|
57
|
-
|
|
58
|
-
- `R2INDEX_API_URL`
|
|
59
|
-
- `R2INDEX_API_TOKEN`
|
|
60
|
-
- `R2_ACCESS_KEY_ID`
|
|
61
|
-
- `R2_SECRET_ACCESS_KEY`
|
|
62
|
-
- `R2_ENDPOINT_URL`
|
|
63
|
-
|
|
64
|
-
## Hook Usage
|
|
65
|
-
|
|
66
|
-
```python
|
|
67
|
-
from elaunira.airflow.provider.r2index.hooks import R2IndexHook
|
|
68
|
-
|
|
69
|
-
hook = R2IndexHook(r2index_conn_id="my_r2index_connection")
|
|
70
|
-
client = hook.get_conn()
|
|
71
|
-
```
|
|
72
|
-
|
|
73
|
-
## License
|
|
74
|
-
|
|
75
|
-
MIT
|
{elaunira_airflow_provider_r2index-0.3.0 → elaunira_airflow_provider_r2index-0.3.2}/.gitignore
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|