-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathicechunk_utils.py
More file actions
187 lines (156 loc) · 5.65 KB
/
Copy pathicechunk_utils.py
File metadata and controls
187 lines (156 loc) · 5.65 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
"""
Helpers for opening Icechunk repositories on Source Cooperative using
temporary credentials managed by the source-coop CLI.
"""
import json
import subprocess
from datetime import datetime, timedelta, timezone
from pathlib import Path
import icechunk
SOURCE_COOP_CLI = "/home/jovyan/.cargo/bin/source-coop"
_DEFAULT_CREDS_CACHE = "/home/jovyan/.cache/source-coop/credentials/_default.json"
def get_source_credentials(creds_cache: str = _DEFAULT_CREDS_CACHE):
"""
Refresh and return Source Cooperative temporary credentials.
Calls the source-coop CLI to ensure the cached token is up to date,
then reads the credentials from the cache file.
Returns
-------
source_creds : dict
Keys: aws_access_key_id, aws_secret_access_key, aws_session_token,
region_name, endpoint_url.
expiration : datetime
Token expiration as a timezone-aware UTC datetime.
"""
subprocess.run(
[SOURCE_COOP_CLI, "creds"],
check=True,
stdout=subprocess.DEVNULL,
)
with Path(creds_cache).open() as f:
cached = json.load(f)
expiration = datetime.fromisoformat(cached["expiration"])
source_creds = {
"aws_access_key_id": cached["access_key_id"],
"aws_secret_access_key": cached["secret_access_key"],
"aws_session_token": cached["session_token"],
"region_name": "us-east-1",
"endpoint_url": "https://data.source.coop",
}
return source_creds, expiration
def open_source_icechunk_repo(
bucket: str,
prefix: str,
config=None,
min_minutes_left: int = 15,
create_if_missing: bool = True,
verbose: bool = True,
check_expiration: bool = True,
):
"""
Open or create an Icechunk repo on Source Cooperative.
Refreshes credentials via the source-coop CLI before opening.
Parameters
----------
bucket
Source Cooperative bucket name (e.g. "ocean-icechunks").
prefix
Key prefix inside the bucket for the Icechunk repository.
config
Optional icechunk.RepositoryConfig (e.g. with VirtualChunkContainers).
min_minutes_left
If the token has fewer than this many minutes remaining and
check_expiration=True, returns (None, None, None, time_left).
create_if_missing
Create the repository if it does not already exist.
verbose
Print status messages.
check_expiration
Raise a clean stop instead of a cryptic error when the token is expired.
Returns
-------
repo : icechunk.Repository or None
storage : icechunk storage object or None
source_creds : dict or None
time_left : timedelta
"""
source_creds, expiration = get_source_credentials()
now = datetime.now(timezone.utc)
time_left = expiration - now
if check_expiration and time_left < timedelta(minutes=0):
print(
f"Stopping cleanly. Source credentials expired. "
f"Run: {SOURCE_COOP_CLI} login --duration 1d --port 8400"
)
return None, None, None, time_left
storage = icechunk.s3_storage(
bucket=bucket,
prefix=prefix,
region=source_creds["region_name"],
endpoint_url=source_creds["endpoint_url"],
force_path_style=True,
access_key_id=source_creds["aws_access_key_id"],
secret_access_key=source_creds["aws_secret_access_key"],
session_token=source_creds["aws_session_token"],
)
if create_if_missing:
try:
repo = icechunk.Repository.create(storage, config)
if verbose:
print("Created new Icechunk repo")
except Exception:
repo = icechunk.Repository.open(storage, config=config)
if verbose:
print("Opened existing Icechunk repo")
else:
repo = icechunk.Repository.open(storage, config=config)
if verbose:
print("Opened existing Icechunk repo")
if verbose:
print(f"Time remaining on token: {time_left}")
return repo, storage, source_creds, time_left
def wait_for_fresh_repo(
bucket: str,
prefix: str,
config=None,
min_minutes_left: int = 15,
verbose: bool = True,
):
"""
Open the Icechunk repo, prompting for a token refresh if needed.
Loops until the token has at least min_minutes_left remaining, or the
user chooses to stop. Useful before starting a long write loop.
Returns
-------
repo, storage, source_creds, time_left
Returns (None, None, None, time_left) if the user stops.
"""
while True:
repo, storage, source_creds, time_left = open_source_icechunk_repo(
bucket=bucket,
prefix=prefix,
config=config,
min_minutes_left=min_minutes_left,
create_if_missing=True,
check_expiration=True,
verbose=False,
)
if time_left >= timedelta(minutes=min_minutes_left):
if verbose:
print(f"Token okay. Time remaining: {time_left}")
return repo, storage, source_creds, time_left
print(
f"Source credentials expire in about {time_left}. "
f"Refresh with: {SOURCE_COOP_CLI} login --duration 1d --port 8400"
)
try:
answer = input("Enter y after refreshing the token, or n to stop: ").strip().lower()
except KeyboardInterrupt:
print("Input interrupted. Stopping cleanly.")
return None, None, None, time_left
if answer == "y":
continue
if answer == "n":
print("Stopping. Resume with start_index set to the last committed file.")
return None, None, None, time_left
print("Please enter y or n.")