Skip to content

Commit 0128cb1

Browse files
committed
refactor code by putting common geoserver llogics into the python_geoservercloud lib
1 parent b1e4eb4 commit 0128cb1

File tree

3 files changed

+128
-263
lines changed

3 files changed

+128
-263
lines changed
Lines changed: 76 additions & 250 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
11
import re
22
import logging
3-
import json
43
from functools import cache
54
from io import BytesIO
65
from typing import Any
76
from geonetwork import GnApi
87
from geoservercloud.services import RestService # type: ignore
8+
from geoservercloud import GeoServerCloudSync # type: ignore
9+
from geoservercloud.exceptions import DatastoreMissing, WorkspaceMissing # type: ignore
910
from maelstro.metadata import Meta
1011
from maelstro.config import app_config as config
1112
from maelstro.common.types import GsLayer
1213
from maelstro.common.models import CopyPreview, InfoRecord, SuccessRecord
1314
from maelstro.common.exceptions import ParamError
1415
from .georchestra import GeorchestraHandler
15-
from .operations import raise_for_status
1616

1717

1818
logger = logging.getLogger()
@@ -90,35 +90,29 @@ def copy_preview(
9090
)
9191
dst_gs_url = dst_gs_info["url"]
9292

93-
geoservers = self.meta.get_gs_layers(config.get_gs_sources())
94-
for server_url, layer_names in geoservers.items():
95-
styles: set[str] = set()
96-
for layer_name in layer_names:
97-
93+
if self.include_layers or self.include_styles:
94+
geoservers = self.meta.get_gs_layers(config.get_gs_sources())
95+
for server_url, layer_names in geoservers.items():
96+
styles: set[str] = set()
97+
layers: set[str] = set()
9898
gs_src = self.geo_hnd.get_gs_service(server_url, True)
99-
layers = {}
10099
for layer_name in layer_names:
101-
resp = gs_src.rest_client.get(f"/rest/layers/{layer_name}.json")
102-
raise_for_status(resp)
103-
layers[layer_name] = resp.json()
100+
layer, status = gs_src.get_layer(None, layer_name)
104101

105-
for layer in layers.values():
106-
styles.update(self.get_styles_from_layer(layer).keys())
102+
if status == 200:
103+
layers.add(str(layer_name))
104+
styles.update(layer.all_style_names)
107105

108-
if layer_names or styles:
109-
# only output servers where some layers or styles have been identified
110-
preview["geoserver_resources"].append(
111-
{
112-
"src": server_url,
113-
"dst": dst_gs_url,
114-
"layers": (
115-
[str(layer_name) for layer_name in layer_names]
116-
if self.include_layers
117-
else []
118-
),
119-
"styles": list(styles) if self.include_styles else [],
120-
}
121-
)
106+
if layers or styles:
107+
# only output servers where some layers or styles have been identified
108+
preview["geoserver_resources"].append(
109+
{
110+
"src": server_url,
111+
"dst": dst_gs_url,
112+
"layers": list(layers) if self.include_layers else [],
113+
"styles": list(styles) if self.include_styles else [],
114+
}
115+
)
122116

123117
return CopyPreview(**preview) # type: ignore
124118

@@ -141,7 +135,17 @@ def copy_dataset(
141135
return []
142136

143137
if self.include_layers or self.include_styles:
144-
self.copy_layers()
138+
server_layers = self.meta.get_gs_layers(config.get_gs_sources())
139+
for gs_url, layer_names in server_layers.items():
140+
if not layer_names:
141+
continue
142+
sync_service = self.geo_hnd.get_gs_sync_service(gs_url, self.dst_name)
143+
144+
# styles must be copied first
145+
if self.include_styles:
146+
self.copy_styles(sync_service, layer_names)
147+
if self.include_layers:
148+
self.copy_layers(sync_service, layer_names)
145149

146150
if self.include_meta:
147151
xsl_transformations = config.get_transformation_pair(
@@ -176,231 +180,53 @@ def copy_dataset(
176180
return str(results["msg"])
177181
return "copy_successful"
178182

179-
def copy_layers(self) -> None:
180-
server_layers = self.meta.get_gs_layers(config.get_gs_sources())
181-
for gs_url, layer_names in server_layers.items():
182-
if layer_names:
183-
gs_src = self.geo_hnd.get_gs_service(gs_url, True)
184-
layers = {}
185-
for layer_name in layer_names:
186-
resp = gs_src.rest_client.get(f"/rest/layers/{layer_name}.json")
187-
raise_for_status(resp)
188-
layers[layer_name] = resp.json()
189-
190-
stores = {}
191-
workspaces = {}
192-
styles = {}
193-
194-
# fill in workspaces used in styles
195-
if self.include_styles:
196-
for layer_data in layers.values():
197-
styles.update(self.get_styles_from_layer(layer_data))
198-
199-
for style in styles.values():
200-
try:
201-
workspaces.update(self.get_workspaces_from_style(style))
202-
except KeyError:
203-
# skip styles without a workspace
204-
pass
205-
206-
# fill in workspaces and datastores used in layers
207-
if self.include_layers:
208-
stores.update(self.get_stores_from_layers(gs_src, layers))
209-
210-
for store in stores.values():
211-
workspaces.update(self.get_workspaces_from_store(gs_src, store))
212-
213-
self.check_workspaces(gs_src, workspaces)
214-
self.check_datastores(gs_src, stores)
215-
216-
# styles must be copieed first
217-
if self.include_styles:
218-
with self.geo_hnd.log_handler.logger_context("Style"):
219-
for style in styles.values():
220-
self.copy_style(gs_src, style)
221-
self.geo_hnd.log_handler.log_info(
222-
SuccessRecord(
223-
message="Styles copied successfully",
224-
detail={"styles": list(styles.keys())},
225-
)
226-
)
227-
228-
# styles must be available when cloning layers
229-
if self.include_layers:
230-
with self.geo_hnd.log_handler.logger_context("Layer"):
231-
for layer_name, layer_data in layers.items():
232-
self.copy_layer(gs_src, layer_name, layer_data)
233-
self.geo_hnd.log_handler.log_info(
234-
SuccessRecord(
235-
message="Layers copied successfully",
236-
detail={"layers": list(layers.keys())},
237-
)
238-
)
239-
240-
def get_styles_from_layer(self, layer_data: dict[str, Any]) -> dict[str, Any]:
241-
default_style = layer_data["layer"]["defaultStyle"]
242-
additional_styles = layer_data["layer"].get("styles", {}).get("style", [])
243-
if isinstance(additional_styles, dict):
244-
# in case of a single element in the list, this may be provided by the API
245-
# as a dict, it must be converted to a list of dicts
246-
additional_styles = [additional_styles]
247-
all_styles = {
248-
style["name"]: style for style in [default_style] + additional_styles
249-
}
250-
return all_styles
251-
252-
def get_workspaces_from_style(self, style: dict[str, Any]) -> dict[str, Any]:
253-
return {
254-
# workspace references in style data have no href or other details
255-
style["workspace"]: None
256-
}
257-
258-
def get_stores_from_layers(
259-
self, gs_src: RestService, layers: dict[GsLayer, Any]
260-
) -> dict[str, Any]:
261-
stores = {}
262-
resources = {
263-
layer_data["layer"]["resource"]["name"]: layer_data["layer"]["resource"]
264-
for layer_data in layers.values()
265-
}
266-
for res in resources.values():
267-
resource_class = res["@class"]
268-
resource_route = res["href"].replace(gs_src.url, "")
269-
resource_resp = gs_src.rest_client.get(resource_route)
270-
raise_for_status(resource_resp)
271-
resource_info = resource_resp.json()
272-
store = resource_info[resource_class]["store"]
273-
stores[store["name"]] = store
274-
return stores
275-
276-
def get_workspaces_from_store(
277-
self, gs_src: RestService, store: dict[str, Any]
278-
) -> dict[str, Any]:
279-
store_class = store["@class"]
280-
store_route = store["href"].replace(gs_src.url, "")
281-
store_resp = gs_src.rest_client.get(store_route)
282-
raise_for_status(store_resp)
283-
store_info = store_resp.json()
284-
workspace = store_info[store_class]["workspace"]
285-
return {workspace["name"]: workspace}
286-
287-
def check_workspaces(self, gs_src: RestService, workspaces: dict[str, Any]) -> None:
288-
for workspace_name, workspace in workspaces.items():
289-
if workspace is None:
290-
workspace_route = f"/rest/workspaces/{workspace_name}"
291-
else:
292-
workspace_route = workspace["href"].replace(gs_src.url, "")
293-
has_workspace = self.gs_dst.rest_client.get(workspace_route)
294-
if has_workspace.status_code == 404:
295-
raise ParamError(
296-
context="dst",
297-
key=workspace_route,
298-
err=f"Workspace {workspace_name} not found on destination Geoserver {self.dst_name}",
299-
operations=self.geo_hnd.log_handler.get_json_responses(),
300-
)
301-
raise_for_status(has_workspace)
302-
303-
def check_datastores(self, gs_src: RestService, datastores: dict[str, Any]) -> None:
304-
for store_name, store in datastores.items():
305-
store_route = store["href"].replace(gs_src.url, "")
306-
has_datastore = self.gs_dst.rest_client.get(store_route)
307-
if has_datastore.status_code == 404:
308-
raise ParamError(
309-
context="dst",
310-
key=store_route,
311-
err=f"Datastore {store_name} not found on destination Geoserver {self.dst_name}",
312-
operations=self.geo_hnd.log_handler.get_json_responses(),
313-
)
314-
raise_for_status(has_datastore)
315-
316-
def copy_layer(
317-
self,
318-
gs_src: RestService,
319-
layer_name: GsLayer,
320-
layer_data: dict[str, Any],
321-
) -> None:
322-
resource_route = layer_data["layer"]["resource"]["href"].replace(gs_src.url, "")
323-
324-
layer_string = json.dumps(layer_data)
325-
layer_data = json.loads(layer_string.replace(gs_src.url, self.gs_dst.url))
326-
327-
has_resource = self.gs_dst.rest_client.get(resource_route)
328-
has_layer = self.gs_dst.rest_client.get(f"/rest/layers/{layer_name}")
329-
330-
xml_resource_route = resource_route.replace(".json", ".xml")
331-
resource_post_route = re.sub(
332-
r"/[^/]*\.xml$",
333-
"",
334-
xml_resource_route,
183+
def copy_styles(self, sync_service: GeoServerCloudSync, layer_names: set[GsLayer]) -> None:
184+
# styles must be copied first
185+
styles = set(
186+
style_name
187+
for l in layer_names
188+
for layer, status in [sync_service.src_instance.get_layer(None, l)]
189+
for style_name in layer.all_style_names
190+
if status == 200
335191
)
336-
resource = gs_src.rest_client.get(xml_resource_route)
337-
338-
if has_resource.status_code == 200:
339-
if has_layer.status_code != 200:
340-
resp = self.gs_dst.rest_client.delete(resource_route)
341-
raise_for_status(resp)
342-
resp = self.gs_dst.rest_client.post(
343-
resource_post_route,
344-
data=resource.content,
345-
headers={"content-type": "application/xml"},
192+
with self.geo_hnd.log_handler.logger_context("Style"):
193+
for style in styles:
194+
try:
195+
resp, status = sync_service.copy_style(style)
196+
if status != 200:
197+
raise ParamError
198+
except (DatastoreMissing, WorkspaceMissing) as err:
199+
raise ParamError(
200+
context="dst",
201+
key=f"Style {style}",
202+
err=f"{err.detail.message} on destination Geoserver {self.dst_name}",
203+
operations=self.geo_hnd.log_handler.get_json_responses(),
204+
) from err
205+
self.geo_hnd.log_handler.log_info(
206+
SuccessRecord(
207+
message="Styles copied successfully",
208+
detail={"styles": list(styles)},
346209
)
347-
else:
348-
resp = self.gs_dst.rest_client.put(
349-
xml_resource_route,
350-
data=resource.content,
351-
headers={"content-type": "application/xml"},
352-
)
353-
else:
354-
resp = self.gs_dst.rest_client.post(
355-
resource_post_route,
356-
data=resource.content,
357-
headers={"content-type": "application/xml"},
358210
)
359-
if resp.status_code == 404:
360-
raise ParamError(
361-
context="dst",
362-
key=resource_post_route,
363-
err="Route not found. Check Workspace and datastore",
364-
)
365-
raise_for_status(resp)
366-
367-
resp = self.gs_dst.rest_client.put(
368-
f"/rest/layers/{layer_name}", json=layer_data
369-
)
370-
raise_for_status(resp)
371-
372-
def copy_style(
373-
self,
374-
gs_src: RestService,
375-
style: dict[str, Any],
376-
) -> None:
377-
if gs_src.url in style["href"]:
378-
style_route = style["href"].replace(gs_src.url, "")
379-
resp = gs_src.rest_client.get(style_route)
380-
raise_for_status(resp)
381-
style_info = resp.json()
382-
style_format = style_info["style"]["format"]
383-
style_def_route = style_route.replace(".json", f".{style_format}")
384-
style_def = gs_src.rest_client.get(style_def_route)
385211

386-
dst_style = self.gs_dst.rest_client.get(style_route)
387-
if dst_style.status_code == 200:
388-
dst_style = self.gs_dst.rest_client.put(style_route, json=style_info)
389-
raise_for_status(dst_style)
390-
else:
391-
style_post_route = re.sub(
392-
r"/styles/.*\.json",
393-
"/styles",
394-
style_route,
395-
)
396-
dst_style = self.gs_dst.rest_client.post(
397-
style_post_route, json=style_info
212+
def copy_layers(self, sync_service: GeoServerCloudSync, layer_names: set[GsLayer]) -> None:
213+
# styles must be available when cloning layers
214+
with self.geo_hnd.log_handler.logger_context("Layer"):
215+
for layer_name in layer_names:
216+
try:
217+
resp, status = sync_service.copy_layer(None, layer_name)
218+
if status != 200:
219+
raise ParamError
220+
except (DatastoreMissing, WorkspaceMissing) as err:
221+
raise ParamError(
222+
context="dst",
223+
key=f"Layer {layer_name}",
224+
err=f"{err.detail.message} on destination Geoserver {self.dst_name}",
225+
operations=self.geo_hnd.log_handler.get_json_responses(),
226+
) from err
227+
self.geo_hnd.log_handler.log_info(
228+
SuccessRecord(
229+
message="Layers copied successfully",
230+
detail={"layers": list(layer_names)},
398231
)
399-
# raise_for_status(dst_style)
400-
401-
dst_style_def = self.gs_dst.rest_client.put(
402-
style_def_route,
403-
data=style_def.content,
404-
headers={"content-type": style_def.headers["content-type"]},
405232
)
406-
raise_for_status(dst_style_def)

0 commit comments

Comments
 (0)