geoserver-configops 0.1.0__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.
- geoserver_configops-0.1.0/LICENSE +21 -0
- geoserver_configops-0.1.0/PKG-INFO +341 -0
- geoserver_configops-0.1.0/README.md +325 -0
- geoserver_configops-0.1.0/configops/__init__.py +4 -0
- geoserver_configops-0.1.0/configops/__main__.py +3 -0
- geoserver_configops-0.1.0/configops/assets.py +166 -0
- geoserver_configops-0.1.0/configops/auth.py +253 -0
- geoserver_configops-0.1.0/configops/cli.py +157 -0
- geoserver_configops-0.1.0/configops/csp_default.xml +63 -0
- geoserver_configops-0.1.0/configops/database.py +61 -0
- geoserver_configops-0.1.0/configops/details.py +447 -0
- geoserver_configops-0.1.0/configops/geoserver.py +549 -0
- geoserver_configops-0.1.0/configops/import_config.py +353 -0
- geoserver_configops-0.1.0/configops/importer.py +841 -0
- geoserver_configops-0.1.0/configops/keywords.py +37 -0
- geoserver_configops-0.1.0/configops/loader.py +123 -0
- geoserver_configops-0.1.0/configops/plan.py +836 -0
- geoserver_configops-0.1.0/configops/refresh.py +130 -0
- geoserver_configops-0.1.0/configops/schema/v1alpha1.json +4560 -0
- geoserver_configops-0.1.0/configops/secrets.py +55 -0
- geoserver_configops-0.1.0/configops/security.py +382 -0
- geoserver_configops-0.1.0/configops/security_defaults.json +250 -0
- geoserver_configops-0.1.0/configops/server.py +421 -0
- geoserver_configops-0.1.0/configops/standard.py +345 -0
- geoserver_configops-0.1.0/configops/status.py +67 -0
- geoserver_configops-0.1.0/configops/stores.py +414 -0
- geoserver_configops-0.1.0/configops/tiles.py +187 -0
- geoserver_configops-0.1.0/configops/update.py +279 -0
- geoserver_configops-0.1.0/configops/urlchecks.py +63 -0
- geoserver_configops-0.1.0/configops/validate.py +428 -0
- geoserver_configops-0.1.0/geoserver_configops.egg-info/PKG-INFO +341 -0
- geoserver_configops-0.1.0/geoserver_configops.egg-info/SOURCES.txt +80 -0
- geoserver_configops-0.1.0/geoserver_configops.egg-info/dependency_links.txt +1 -0
- geoserver_configops-0.1.0/geoserver_configops.egg-info/entry_points.txt +2 -0
- geoserver_configops-0.1.0/geoserver_configops.egg-info/requires.txt +7 -0
- geoserver_configops-0.1.0/geoserver_configops.egg-info/top_level.txt +1 -0
- geoserver_configops-0.1.0/pyproject.toml +35 -0
- geoserver_configops-0.1.0/setup.cfg +4 -0
- geoserver_configops-0.1.0/tests/test_configops_auth.py +75 -0
- geoserver_configops-0.1.0/tests/test_configops_details.py +107 -0
- geoserver_configops-0.1.0/tests/test_configops_geoserver.py +171 -0
- geoserver_configops-0.1.0/tests/test_configops_import_config.py +112 -0
- geoserver_configops-0.1.0/tests/test_configops_keywords.py +9 -0
- geoserver_configops-0.1.0/tests/test_configops_plan.py +395 -0
- geoserver_configops-0.1.0/tests/test_configops_secrets.py +24 -0
- geoserver_configops-0.1.0/tests/test_configops_security.py +257 -0
- geoserver_configops-0.1.0/tests/test_configops_server.py +162 -0
- geoserver_configops-0.1.0/tests/test_configops_standard.py +133 -0
- geoserver_configops-0.1.0/tests/test_configops_status_import.py +97 -0
- geoserver_configops-0.1.0/tests/test_configops_stores.py +304 -0
- geoserver_configops-0.1.0/tests/test_configops_tiles.py +63 -0
- geoserver_configops-0.1.0/tests/test_configops_update.py +151 -0
- geoserver_configops-0.1.0/tests/test_configops_validate.py +185 -0
- geoserver_configops-0.1.0/tests/test_configops_workspaces.py +72 -0
- geoserver_configops-0.1.0/tests/test_e2e_appschema.py +135 -0
- geoserver_configops-0.1.0/tests/test_e2e_assets.py +242 -0
- geoserver_configops-0.1.0/tests/test_e2e_auth.py +100 -0
- geoserver_configops-0.1.0/tests/test_e2e_blob_store_options.py +81 -0
- geoserver_configops-0.1.0/tests/test_e2e_blob_stores.py +218 -0
- geoserver_configops-0.1.0/tests/test_e2e_coverage.py +116 -0
- geoserver_configops-0.1.0/tests/test_e2e_details.py +210 -0
- geoserver_configops-0.1.0/tests/test_e2e_file_stores.py +154 -0
- geoserver_configops-0.1.0/tests/test_e2e_geoserver_client.py +170 -0
- geoserver_configops-0.1.0/tests/test_e2e_layer_data.py +150 -0
- geoserver_configops-0.1.0/tests/test_e2e_logins.py +205 -0
- geoserver_configops-0.1.0/tests/test_e2e_oracle.py +105 -0
- geoserver_configops-0.1.0/tests/test_e2e_plan_apply.py +206 -0
- geoserver_configops-0.1.0/tests/test_e2e_raster.py +130 -0
- geoserver_configops-0.1.0/tests/test_e2e_security.py +173 -0
- geoserver_configops-0.1.0/tests/test_e2e_security_files.py +110 -0
- geoserver_configops-0.1.0/tests/test_e2e_security_objects.py +159 -0
- geoserver_configops-0.1.0/tests/test_e2e_service_options.py +99 -0
- geoserver_configops-0.1.0/tests/test_e2e_services.py +430 -0
- geoserver_configops-0.1.0/tests/test_e2e_settings.py +242 -0
- geoserver_configops-0.1.0/tests/test_e2e_sqlserver.py +95 -0
- geoserver_configops-0.1.0/tests/test_e2e_standard_only.py +265 -0
- geoserver_configops-0.1.0/tests/test_e2e_stores.py +210 -0
- geoserver_configops-0.1.0/tests/test_e2e_tiles.py +255 -0
- geoserver_configops-0.1.0/tests/test_e2e_url_checks.py +192 -0
- geoserver_configops-0.1.0/tests/test_e2e_workspaces.py +128 -0
- geoserver_configops-0.1.0/tests/test_geoserver_coverage.py +121 -0
- geoserver_configops-0.1.0/tests/test_spec_examples.py +26 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Nathan Leeks
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: geoserver-configops
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: GeoServer ConfigOps: GeoServer and GeoServer Cloud configuration as code, with plan, apply, import and drift detection
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
Requires-Python: >=3.10
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Requires-Dist: pyyaml>=6
|
|
10
|
+
Requires-Dist: jsonschema>=4.18
|
|
11
|
+
Requires-Dist: requests>=2.28
|
|
12
|
+
Requires-Dist: psycopg2-binary>=2.9
|
|
13
|
+
Provides-Extra: dev
|
|
14
|
+
Requires-Dist: pytest; extra == "dev"
|
|
15
|
+
Dynamic: license-file
|
|
16
|
+
|
|
17
|
+
# GeoServer ConfigOps
|
|
18
|
+
|
|
19
|
+
  Of the 662 settings GeoServer can configure, the e2e tests prove configops sets 618 on GeoServer Cloud (44 more it can't set) and 662 on standard GeoServer ([field by field](docs/geoserver-fields.yaml)).
|
|
20
|
+
|
|
21
|
+
GeoServer configuration as code, for [GeoServer Cloud](https://github.com/geoserver/geoserver-cloud) and standard GeoServer. `configops` keeps what GeoServer publishes and how it is set up in YAML files, with an OpenTofu-style `plan` and `apply`:
|
|
22
|
+
|
|
23
|
+
- **publish** layers, styles and layer groups from PostGIS tables, SQL views and other stores (Cloud-Optimised GeoTIFFs, GeoPackages and other files, cascaded WMS, WMTS and WFS, other databases), with tile caching;
|
|
24
|
+
- **set GeoServer up:** its settings and services (contact details, titles, limits), and its security (users, groups, roles, who may see and do what, the master password);
|
|
25
|
+
- **catch drift and keep it:** `plan` on a schedule shows when GeoServer no longer matches the files, `apply` puts it back, and `import` brings a change made in the web UI back into the files instead. `import` is also how you start from a GeoServer you already have.
|
|
26
|
+
|
|
27
|
+
Point it at a GeoServer you already run: `https://<host>/geoserver-cloud` for GeoServer Cloud, `https://<host>/geoserver` for standard GeoServer. Standard GeoServer can also take a few settings GeoServer Cloud can't (below); when the files set one, configops asks GeoServer which it is (by the version it reports), and on GeoServer Cloud `plan` shows it as `!` with the reason and leaves it out.
|
|
28
|
+
|
|
29
|
+
Loading open data into PostGIS (Ordnance Survey, CKAN portals such as data.gov.uk, WFS, OGC API Features) and keeping it up to date is a separate tool, [geodata-sync](https://github.com/Yibbzz/geodata-sync); configops publishes the tables, whoever loads them.
|
|
30
|
+
|
|
31
|
+
> **Status: early.** The same e2e tests run against GeoServer Cloud and against standard GeoServer 3.0.2 (the version GeoServer Cloud 3.0 is built on). Expect breaking changes. [docs/coverage.md](docs/coverage.md) goes through the web UI page by page; the figures above count every field of GeoServer's configuration, read from its source (`tests/geoserver_coverage.py`), and what one of them can't set counts against it.
|
|
32
|
+
|
|
33
|
+
## Install
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
pip install geoserver-configops # the configops command: Python 3.10+, nothing else
|
|
37
|
+
docker run --rm ghcr.io/yibbzz/geoserver-configops --help # or its image
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Quick start
|
|
41
|
+
|
|
42
|
+
A whole GeoServer set up from one file, [`examples/quickstart/project.yaml`](examples/quickstart/project.yaml): a workspace, the contact details and WMS title, a Sentinel-2 image read in place on AWS (a Cloud-Optimised GeoTIFF) with its own style, EOX's cloudless mosaic cascaded from their WMS, both as one cached map, and an editor with what only editors may do. Nothing to load.
|
|
43
|
+
|
|
44
|
+
```yaml
|
|
45
|
+
apiVersion: configops/v1alpha1
|
|
46
|
+
kind: Project
|
|
47
|
+
metadata: { name: quickstart }
|
|
48
|
+
spec:
|
|
49
|
+
geoserver:
|
|
50
|
+
url: http://localhost:8080/geoserver # or https://<host>/geoserver-cloud
|
|
51
|
+
credentials: { secret: geoserver-admin } # keys username, password: secrets.yaml, env or a Kubernetes Secret
|
|
52
|
+
workspace: demo
|
|
53
|
+
---
|
|
54
|
+
# Who runs this GeoServer, as its capabilities documents say
|
|
55
|
+
apiVersion: configops/v1alpha1
|
|
56
|
+
kind: Settings
|
|
57
|
+
metadata: { name: global }
|
|
58
|
+
spec:
|
|
59
|
+
contact: { contactPerson: Map Team, contactOrganization: Example Council, contactEmail: maps@example.org }
|
|
60
|
+
---
|
|
61
|
+
apiVersion: configops/v1alpha1
|
|
62
|
+
kind: Service
|
|
63
|
+
metadata: { name: wms }
|
|
64
|
+
spec: { title: Example Council maps, maxRenderingTime: 60 }
|
|
65
|
+
---
|
|
66
|
+
# A Sentinel-2 image, a Cloud-Optimised GeoTIFF read in place on AWS
|
|
67
|
+
apiVersion: configops/v1alpha1
|
|
68
|
+
kind: Store
|
|
69
|
+
metadata: { name: sentinel-2 }
|
|
70
|
+
spec:
|
|
71
|
+
type: cog
|
|
72
|
+
url: https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/30/U/XC/2025/8/S2C_30UXC_20250825_0_L2A/TCI.tif
|
|
73
|
+
---
|
|
74
|
+
apiVersion: configops/v1alpha1
|
|
75
|
+
kind: Layer
|
|
76
|
+
metadata: { name: imagery }
|
|
77
|
+
spec: { store: sentinel-2, title: Sentinel-2 true colour, style: imagery } # styles/imagery.sld
|
|
78
|
+
---
|
|
79
|
+
# EOX's cloudless mosaic, cascaded from their WMS
|
|
80
|
+
apiVersion: configops/v1alpha1
|
|
81
|
+
kind: Store
|
|
82
|
+
metadata: { name: eox }
|
|
83
|
+
spec: { type: wms, url: "https://tiles.maps.eox.at/wms?service=WMS&request=GetCapabilities" }
|
|
84
|
+
---
|
|
85
|
+
apiVersion: configops/v1alpha1
|
|
86
|
+
kind: Layer
|
|
87
|
+
metadata: { name: cloudless }
|
|
88
|
+
spec: { store: eox, resource: s2cloudless-2024, title: Sentinel-2 cloudless (EOX) }
|
|
89
|
+
---
|
|
90
|
+
# Both as one map, its tiles cached
|
|
91
|
+
apiVersion: configops/v1alpha1
|
|
92
|
+
kind: LayerGroup
|
|
93
|
+
metadata: { name: basemap }
|
|
94
|
+
spec:
|
|
95
|
+
title: Basemap
|
|
96
|
+
layers: [cloudless, imagery] # drawn bottom first
|
|
97
|
+
cache: true
|
|
98
|
+
---
|
|
99
|
+
# An editor, and what only editors may do
|
|
100
|
+
apiVersion: configops/v1alpha1
|
|
101
|
+
kind: Role
|
|
102
|
+
metadata: { name: editor } # GeoServer role EDITOR
|
|
103
|
+
spec: {}
|
|
104
|
+
---
|
|
105
|
+
apiVersion: configops/v1alpha1
|
|
106
|
+
kind: User
|
|
107
|
+
metadata: { name: alice }
|
|
108
|
+
spec: { password: { secret: geoserver-users, key: alice }, roles: [EDITOR] }
|
|
109
|
+
---
|
|
110
|
+
apiVersion: configops/v1alpha1
|
|
111
|
+
kind: AccessRule
|
|
112
|
+
metadata: { name: demo-write }
|
|
113
|
+
spec: { layer: { workspace: demo, layer: "*", access: write }, roles: [EDITOR] }
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Start a GeoServer to try it on ([`examples/compose.yaml`](examples/compose.yaml)), give configops the logins the file names, and see what it would do:
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
docker compose -f examples/compose.yaml up -d --wait # standard GeoServer on :8080; a minute or two
|
|
120
|
+
|
|
121
|
+
cat > examples/quickstart/secrets.yaml <<'EOF' # git-ignored
|
|
122
|
+
geoserver-admin: { username: admin, password: geoserver }
|
|
123
|
+
geoserver-users: { alice: change-me }
|
|
124
|
+
EOF
|
|
125
|
+
|
|
126
|
+
configops plan -C examples/quickstart
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
```
|
|
130
|
+
+ workspace demo
|
|
131
|
+
~ settings global
|
|
132
|
+
contact.contactPerson: None -> "Map Team"
|
|
133
|
+
contact.contactOrganization: None -> "Example Council"
|
|
134
|
+
contact.contactEmail: None -> "maps@example.org"
|
|
135
|
+
~ service wms
|
|
136
|
+
title: None -> "Example Council maps"
|
|
137
|
+
maxRenderingTime: 0 -> 60
|
|
138
|
+
+ role EDITOR
|
|
139
|
+
+ user alice (groups: none; roles: EDITOR)
|
|
140
|
+
+ store demo:sentinel_2 (cog: https://sentinel-cogs.s3.us-west-2.amazonaws.com/...)
|
|
141
|
+
+ store demo:eox (wms: https://tiles.maps.eox.at/wms?service=WMS&request=GetCapabilities)
|
|
142
|
+
+ style demo:imagery (styles/imagery.sld)
|
|
143
|
+
+ layer demo:imagery (cog store sentinel-2)
|
|
144
|
+
+ layer demo:cloudless (wms store eox: s2cloudless-2024)
|
|
145
|
+
+ group demo:basemap (2 members)
|
|
146
|
+
+ rule layers demo.*.w (roles: EDITOR)
|
|
147
|
+
|
|
148
|
+
Plan: 10 to add, 2 to change, 0 to delete, 0 unmanaged.
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Make it so, and look: <http://localhost:8080/geoserver/demo/wms?service=WMS&version=1.3.0&request=GetMap&layers=demo:basemap&styles=&crs=EPSG:4326&bbox=50.4,-3.0,51.4,-1.4&width=768&height=480&format=image/png>
|
|
152
|
+
|
|
153
|
+
```bash
|
|
154
|
+
configops apply -C examples/quickstart # ... Applied 12 change(s).
|
|
155
|
+
configops plan -C examples/quickstart # No changes: GeoServer matches the files.
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Now change something in the web UI, say the cloudless layer's title (Layers → cloudless). `plan` notices, with exit code 2, which is what a scheduled drift check looks for:
|
|
159
|
+
|
|
160
|
+
```
|
|
161
|
+
$ configops plan -C examples/quickstart
|
|
162
|
+
~ layer demo:cloudless
|
|
163
|
+
title: "Cloudless, renamed in the web UI" -> "Sentinel-2 cloudless (EOX)"
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
Either `apply` puts it back, or `import` keeps it: it updates the file in place, only what changed, and leaves the rest of the file, its order and its comments as they were.
|
|
167
|
+
|
|
168
|
+
```
|
|
169
|
+
$ configops import demo -C examples/quickstart
|
|
170
|
+
Updated the files: 1 changed: Layer cloudless (title)
|
|
171
|
+
Next: git diff, then configops plan -C examples/quickstart
|
|
172
|
+
|
|
173
|
+
$ configops status -C examples/quickstart
|
|
174
|
+
LAYER PUBLISHED DRIFTED TILES CACHED
|
|
175
|
+
demo:imagery yes no no
|
|
176
|
+
demo:cloudless yes no no
|
|
177
|
+
demo:basemap (group) yes no yes
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
`docker compose -f examples/compose.yaml down -v` removes it all. [docs/spec.md](docs/spec.md) describes every kind and field.
|
|
181
|
+
|
|
182
|
+
## Examples
|
|
183
|
+
|
|
184
|
+
| Project | What it holds |
|
|
185
|
+
| --- | --- |
|
|
186
|
+
| [`examples/quickstart`](examples/quickstart/) | The quick start above: one file, no data to load |
|
|
187
|
+
| [`examples/uk-basemap`](examples/uk-basemap/) | The worked example: OS Open Roads, OS Boundary-Line, the Environment Agency's flood zones and Surrey's conservation areas, from tables geodata-sync loads (its own `examples/uk-basemap`); 6 layers with their own styles, a basemap group drawn as one layer, tile caching on British National Grid, WMS and WFS service settings, and a layer on a table you load yourself |
|
|
188
|
+
| [`examples/terrain`](examples/terrain/) | A raster in GeoServer's `/data` folder (OS Terrain 50, which geodata-sync's `examples/terrain` makes) drawn with a colour ramp |
|
|
189
|
+
|
|
190
|
+
## How to
|
|
191
|
+
|
|
192
|
+
**Publish a table:** a `Layer` with `table: roads` (in the Project's `database.schema`) or `table: { schema: field_data, name: sites }`. The Project names the database, as GeoServer reaches it (`jndi`) and as configops does (`connection`, to see whether a table exists yet). Until the table exists, `plan` shows the layer as `!` (can't be done yet) and `apply` leaves it, and its place in groups, for later.
|
|
193
|
+
|
|
194
|
+
**After loading new data,** refresh GeoServer: `configops refresh -C my-project data_layers.roads` works the layer's bounds out again and clears its cached tiles, and those of the groups that hold it. `geodata sync --loaded loaded.txt` writes what it loaded, for `configops refresh --from loaded.txt`.
|
|
195
|
+
|
|
196
|
+
**Keep the logins out of the files.** A project names Secrets (`credentials: { secret: geoserver-admin }`); configops reads each from a git-ignored `secrets.yaml` in the project folder, from `CONFIGOPS_<SECRET>_<KEY>` environment variables, or from files `$CONFIGOPS_SECRETS_DIR/<secret>/<key>` (how Kubernetes mounts Secrets).
|
|
197
|
+
|
|
198
|
+
**See what would change, or what's loaded:**
|
|
199
|
+
|
|
200
|
+
```bash
|
|
201
|
+
configops plan -C my-project # exit code 2 when there are changes
|
|
202
|
+
configops status -C my-project # layers: published, drifted, tiles cached
|
|
203
|
+
configops plan -C my-project --geoserver https://test.example.org/geoserver # the same files against another GeoServer
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
**Catch drift.** Run `configops plan` on a schedule: it exits 2 when GeoServer no longer matches the files, say a layer changed in the web UI. `configops apply` puts GeoServer back; or keep the change by running `configops import` (below) and committing it.
|
|
207
|
+
|
|
208
|
+
**Run it in Kubernetes**, beside GeoServer: [docs/kubernetes.md](docs/kubernetes.md) has a Job that applies the project and an hourly drift check, from an image with your project in it.
|
|
209
|
+
|
|
210
|
+
**Publish data GeoServer reads where it is:** a `Store` for a Cloud-Optimised GeoTIFF (over `https://` or `s3://`), a remote WMS, WMTS or WFS to cascade, or another PostGIS database, and a `Layer` with `store:`:
|
|
211
|
+
|
|
212
|
+
```yaml
|
|
213
|
+
apiVersion: configops/v1alpha1
|
|
214
|
+
kind: Store
|
|
215
|
+
metadata: { name: sentinel-2-surrey }
|
|
216
|
+
spec: { type: cog, url: "https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/30/U/XC/2025/8/S2C_30UXC_20250825_0_L2A/TCI.tif" }
|
|
217
|
+
---
|
|
218
|
+
apiVersion: configops/v1alpha1
|
|
219
|
+
kind: Layer
|
|
220
|
+
metadata: { name: sentinel-2 }
|
|
221
|
+
spec: { store: sentinel-2-surrey, title: Sentinel-2 true colour }
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
Files in GeoServer's `/data` folder (GeoTIFFs, GeoPackages, shapefiles, FlatGeobuf, PMTiles, GeoParquet, CSV, image mosaics and pyramids, app-schema mappings) are `Store`s with `path:`; SQL Server and Oracle are `Store`s too.
|
|
225
|
+
|
|
226
|
+
**Publish a SQL view:** a Layer with `sql: { schema, query, key, geometry }` instead of a table.
|
|
227
|
+
|
|
228
|
+
**Cache tiles:** `cache: true` on a layer or group, or `cache: { gridsets, formats, metaTiles, blobStore, ... }`; a `Gridset` adds your own (British National Grid in uk-basemap) and a `BlobStore` keeps tiles in a folder on the data volume or in S3.
|
|
229
|
+
|
|
230
|
+
**Set contact details, service titles and limits:** a `Settings` resource for GeoServer's global settings (or a workspace's own), and a `Service` per OGC service, in GeoServer's own field names. Only the fields you set are managed:
|
|
231
|
+
|
|
232
|
+
```yaml
|
|
233
|
+
apiVersion: configops/v1alpha1
|
|
234
|
+
kind: Service
|
|
235
|
+
metadata: { name: wfs } # wms, wfs, wcs or wmts
|
|
236
|
+
spec:
|
|
237
|
+
title: Example Council features
|
|
238
|
+
maxFeatures: 100000
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
**Control who sees and does what:** `Role`, `Group` and `User` resources, with passwords from a Secret, and an `AccessRule` per layer, service or REST rule:
|
|
242
|
+
|
|
243
|
+
```yaml
|
|
244
|
+
apiVersion: configops/v1alpha1
|
|
245
|
+
kind: User
|
|
246
|
+
metadata: { name: alice }
|
|
247
|
+
spec: { password: { secret: geoserver-users, key: alice }, groups: [editors] }
|
|
248
|
+
---
|
|
249
|
+
apiVersion: configops/v1alpha1
|
|
250
|
+
kind: AccessRule
|
|
251
|
+
metadata: { name: flood-zones-for-editors }
|
|
252
|
+
spec: { layer: { workspace: uk, layer: flood_zones, access: read }, roles: [EDITOR] }
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
`plan` spots a changed password by logging in with it, and a REST rule GeoServer would never reach. GeoServer's own `admin` user, roles and default rules are left alone unless a file names them.
|
|
256
|
+
|
|
257
|
+
**Bring in an existing GeoServer:** `configops import <workspace> --settings --security -C my-project` writes the files that describe the workspace, GeoServer's settings and services, and its security, so `plan` on them has nothing to do. Passwords can't be read back: put each user's in Secret `geoserver-users`, as the import's note says.
|
|
258
|
+
|
|
259
|
+
**Allow web pages on other sites to call GeoServer (CORS).** On standard GeoServer, `Settings` `global.cors`. GeoServer Cloud's gateway answers cross-origin requests itself, from any origin; to allow only yours, give the gateway the environment variable `SPRING_APPLICATION_JSON` = `{"spring": {"cloud": {"gateway": {"server": {"webmvc": {"globalcors": {"cors-configurations": {"[/**]": {"allowedOrigins": ["https://maps.example.org"]}}}}}}}}}` (`allowedMethods`, `allowedHeaders`, `allowCredentials` and `maxAge` go beside `allowedOrigins`).
|
|
260
|
+
|
|
261
|
+
**Let GeoServer fetch remote styles and graphics.** GeoServer Cloud checks every external URL (a style given as `SLD=<url>` in a GetMap, a graphic a style points to) and refuses those no check allows, which at first is all of them. Allow yours with a `URLCheck`:
|
|
262
|
+
|
|
263
|
+
```yaml
|
|
264
|
+
apiVersion: configops/v1alpha1
|
|
265
|
+
kind: URLCheck
|
|
266
|
+
metadata: { name: council-styles }
|
|
267
|
+
spec: { regex: '^https://styles\.example\.org/.*$', description: Our published styles }
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
`Settings` `global.urlChecks: false` turns the checks off. `global.requestLogging: { enabled: true, headers: true }` logs every request in the services' logs.
|
|
271
|
+
|
|
272
|
+
**Keep a change made in the web UI:** run the same `import` in the project folder. It updates the files in place, only what changed, keeping your names, order and comments; review it with `git diff` and commit. `--prune` also takes out what GeoServer no longer has.
|
|
273
|
+
|
|
274
|
+
## What can't be done as code
|
|
275
|
+
|
|
276
|
+
All of GeoServer's configuration can be kept in the files on standard GeoServer: [docs/coverage.md](docs/coverage.md) goes page by page, and [docs/geoserver-fields.yaml](docs/geoserver-fields.yaml) field by field. GeoServer Cloud has no way to set these, so they work only on standard GeoServer:
|
|
277
|
+
|
|
278
|
+
| What | In the files | Why GeoServer Cloud can't |
|
|
279
|
+
| --- | --- | --- |
|
|
280
|
+
| Group styles | `LayerGroup` `styles` | Its catalog database doesn't keep their layers: the group breaks after a restart |
|
|
281
|
+
| Oracle stores | `Store` `type: oracle` | Its images don't ship Oracle's JDBC driver |
|
|
282
|
+
| Tile cache disk quota | `Settings` `global.diskQuota` | It switches the disk quota off and has no REST endpoint for it |
|
|
283
|
+
| Content Security Policy | `Settings` `global.contentSecurityPolicy` | Its services don't send GeoServer's policy header; the web UI sends its own |
|
|
284
|
+
| CORS | `Settings` `global.cors` | Its gateway answers CORS: set the gateway's instead (above) |
|
|
285
|
+
|
|
286
|
+
Some settings take effect only when GeoServer's services restart, and `apply` says so: the tile caching defaults, and on standard GeoServer CORS and the disk quota's store. A few belong to how GeoServer is deployed rather than its configuration: which GeoServer Cloud services run, logging, WPS and GeoServer ACL.
|
|
287
|
+
|
|
288
|
+
## How it fits together
|
|
289
|
+
|
|
290
|
+
```
|
|
291
|
+
project files (YAML, SLD) ──► configops plan / apply / import / status ──► GeoServer or GeoServer Cloud (REST API)
|
|
292
|
+
│
|
|
293
|
+
geodata-sync, or your own loads ──► PostGIS tables ──── GeoServer reads ───────────┤
|
|
294
|
+
└─► configops refresh: bounds, cached tiles ▼
|
|
295
|
+
WMS / WFS / WCS / tiles
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
## Repository layout
|
|
299
|
+
|
|
300
|
+
| Path | What |
|
|
301
|
+
| --- | --- |
|
|
302
|
+
| `configops/`, `pyproject.toml`, `Dockerfile` | The `configops` command and its image |
|
|
303
|
+
| `examples/` | Example projects, and `compose.yaml`: a GeoServer and a database to try them on |
|
|
304
|
+
| `docs/spec.md` | The file format and every command |
|
|
305
|
+
| `docs/kubernetes.md`, `docs/kubernetes/` | Running configops in Kubernetes |
|
|
306
|
+
| `docs/coverage.md` | Every page of GeoServer's web UI, and what the tool covers |
|
|
307
|
+
| `docs/geoserver-cloud-notes.md` | What we learned about GeoServer Cloud's REST API, GeoWebCache and services: quirks, shapes, timing |
|
|
308
|
+
| `tests/` | Unit tests; `tests/e2e/` runs GeoServer Cloud, or standard GeoServer, in Docker for the e2e tests |
|
|
309
|
+
|
|
310
|
+
## Testing
|
|
311
|
+
|
|
312
|
+
CI runs on every pull request and push to `main`: shellcheck and a schema check of the Kubernetes example (lint), the unit tests (test), the image build (configops-image), and the e2e tests against GeoServer Cloud in Docker, with its gateway, REST, WMS, WFS, WCS and tile cache services (e2e-cloud), and against standard GeoServer with the same extensions (e2e-standard).
|
|
313
|
+
|
|
314
|
+
Locally (with `docker`, `curl`, and GDAL's command-line tools, which some e2e tests use to make files):
|
|
315
|
+
|
|
316
|
+
```bash
|
|
317
|
+
python3 -m venv .venv && .venv/bin/pip install -e '.[dev]'
|
|
318
|
+
.venv/bin/pytest # e2e tests skip without their services
|
|
319
|
+
|
|
320
|
+
tests/e2e/up.sh # GeoServer Cloud; about 3 GB of RAM
|
|
321
|
+
E2E_GEOSERVER_URL=http://localhost:59090/geoserver-cloud/rest E2E_SERVICES_URL=http://localhost:59090/geoserver-cloud \
|
|
322
|
+
E2E_DB_PORT=55433 E2E_DATA_DIR=tests/e2e/data E2E_LDAP_URL=ldap://ldap:389 \
|
|
323
|
+
E2E_REST_CONTAINER=gscloud-e2e-rest-1 E2E_WMS_CONTAINER=gscloud-e2e-wms-1 .venv/bin/pytest -m e2e
|
|
324
|
+
tests/e2e/up.sh down
|
|
325
|
+
|
|
326
|
+
tests/e2e/up.sh standard # standard GeoServer instead (downloads its extensions once)
|
|
327
|
+
E2E_GEOSERVER_URL=http://localhost:59091/geoserver/rest E2E_SERVICES_URL=http://localhost:59091/geoserver \
|
|
328
|
+
E2E_GWC_URL=http://localhost:59091/geoserver/gwc/rest E2E_DB_PORT=55433 E2E_DB_HOST_IN_GEOSERVER=db \
|
|
329
|
+
E2E_DATA_DIR=tests/e2e/data E2E_LDAP_URL=ldap://ldap:389 E2E_REST_CONTAINER=gscloud-e2e-geoserver-1 .venv/bin/pytest -m e2e
|
|
330
|
+
tests/e2e/up.sh down
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
## Releasing
|
|
334
|
+
|
|
335
|
+
Set the version in `pyproject.toml` and `configops/__init__.py`, merge, then push the tag `v<version>`: the Release workflow publishes the package to PyPI (`geoserver-configops`), the image to `ghcr.io/yibbzz/geoserver-configops`, and a GitHub release.
|
|
336
|
+
|
|
337
|
+
## Licence and data attribution
|
|
338
|
+
|
|
339
|
+
MIT (see `LICENSE`); see `NOTICE.md` for credits.
|
|
340
|
+
|
|
341
|
+
Data you publish keeps its own licence: give a layer the attribution it asks for (`attribution:`). OS OpenData is released under the [Open Government Licence v3.0](https://www.nationalarchives.gov.uk/doc/open-government-licence/version/3/) and needs "Contains OS data © Crown copyright and database right <year>", as the examples' OS layers say.
|