Skip to content

Commit a5c48c6

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

File tree

4 files changed

+141
-265
lines changed

4 files changed

+141
-265
lines changed
Lines changed: 78 additions & 249 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,17 @@
1-
import re
21
import logging
3-
import json
42
from functools import cache
53
from io import BytesIO
64
from typing import Any
75
from geonetwork import GnApi
86
from geoservercloud.services import RestService # type: ignore
7+
from geoservercloud import GeoServerCloudSync # type: ignore
8+
from geoservercloud.exceptions import DatastoreMissing, WorkspaceMissing # type: ignore
99
from maelstro.metadata import Meta
1010
from maelstro.config import app_config as config
1111
from maelstro.common.types import GsLayer
1212
from maelstro.common.models import CopyPreview, InfoRecord, SuccessRecord
1313
from maelstro.common.exceptions import ParamError
1414
from .georchestra import GeorchestraHandler
15-
from .operations import raise_for_status
1615

1716

1817
logger = logging.getLogger()
@@ -90,35 +89,29 @@ def copy_preview(
9089
)
9190
dst_gs_url = dst_gs_info["url"]
9291

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-
92+
if self.include_layers or self.include_styles:
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+
layers: set[str] = set()
9897
gs_src = self.geo_hnd.get_gs_service(server_url, True)
99-
layers = {}
10098
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()
99+
layer, status = gs_src.get_layer(None, layer_name)
104100

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

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-
)
105+
if layers or styles:
106+
# only output servers where some layers or styles have been identified
107+
preview["geoserver_resources"].append(
108+
{
109+
"src": server_url,
110+
"dst": dst_gs_url,
111+
"layers": list(layers) if self.include_layers else [],
112+
"styles": list(styles) if self.include_styles else [],
113+
}
114+
)
122115

123116
return CopyPreview(**preview) # type: ignore
124117

@@ -141,7 +134,17 @@ def copy_dataset(
141134
return []
142135

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

146149
if self.include_meta:
147150
xsl_transformations = config.get_transformation_pair(
@@ -176,231 +179,57 @@ def copy_dataset(
176179
return str(results["msg"])
177180
return "copy_successful"
178181

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],
182+
def copy_styles(
183+
self, sync_service: GeoServerCloudSync, layer_names: set[GsLayer]
321184
) -> 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,
185+
# styles must be copied first
186+
styles = set(
187+
style_name
188+
for l in layer_names
189+
for layer, status in [sync_service.src_instance.get_layer(None, l)]
190+
for style_name in layer.all_style_names
191+
if status == 200
335192
)
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"},
193+
with self.geo_hnd.log_handler.logger_context("Style"):
194+
for style in styles:
195+
try:
196+
_, status = sync_service.copy_style(style)
197+
if status != 200:
198+
raise ParamError
199+
except (DatastoreMissing, WorkspaceMissing) as err:
200+
raise ParamError(
201+
context="dst",
202+
key=f"Style {style}",
203+
err=f"{err.detail.message} on destination Geoserver {self.dst_name}",
204+
operations=self.geo_hnd.log_handler.get_json_responses(),
205+
) from err
206+
self.geo_hnd.log_handler.log_info(
207+
SuccessRecord(
208+
message="Styles copied successfully",
209+
detail={"styles": list(styles)},
346210
)
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"},
358211
)
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)
371212

372-
def copy_style(
373-
self,
374-
gs_src: RestService,
375-
style: dict[str, Any],
213+
def copy_layers(
214+
self, sync_service: GeoServerCloudSync, layer_names: set[GsLayer]
376215
) -> 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)
385-
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
216+
# styles must be available when cloning layers
217+
with self.geo_hnd.log_handler.logger_context("Layer"):
218+
for layer_name in layer_names:
219+
try:
220+
_, status = sync_service.copy_layer(None, layer_name)
221+
if status != 200:
222+
raise ParamError
223+
except (DatastoreMissing, WorkspaceMissing) as err:
224+
raise ParamError(
225+
context="dst",
226+
key=f"Layer {layer_name}",
227+
err=f"{err.detail.message} on destination Geoserver {self.dst_name}",
228+
operations=self.geo_hnd.log_handler.get_json_responses(),
229+
) from err
230+
self.geo_hnd.log_handler.log_info(
231+
SuccessRecord(
232+
message="Layers copied successfully",
233+
detail={"layers": list(layer_names)},
398234
)
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"]},
405235
)
406-
raise_for_status(dst_style_def)

0 commit comments

Comments
 (0)