Merge ~cjwatson/lp-codeimport:charm-codeimport-storage into lp-codeimport:master

Proposed by Colin Watson
Status: Merged
Approved by: Colin Watson
Approved revision: a4ad063a88ca98356dddd77820a39d8b19690a99
Merge reported by: Otto Co-Pilot
Merged at revision: not available
Proposed branch: ~cjwatson/lp-codeimport:charm-codeimport-storage
Merge into: lp-codeimport:master
Prerequisite: ~cjwatson/lp-codeimport:charmcraft
Diff against target: 417 lines (+281/-6)
15 files modified
charm/lp-codeimport-storage/README.md (+3/-0)
charm/lp-codeimport-storage/charmcraft.yaml (+44/-0)
charm/lp-codeimport-storage/config.yaml (+5/-0)
charm/lp-codeimport-storage/hooks/relations/codeimport-storage/__init__.py (+0/-0)
charm/lp-codeimport-storage/hooks/relations/codeimport-storage/provides.py (+39/-0)
charm/lp-codeimport-storage/icon.svg (+1/-0)
charm/lp-codeimport-storage/layer.yaml (+2/-0)
charm/lp-codeimport-storage/metadata.yaml (+14/-0)
charm/lp-codeimport-storage/reactive/lp-codeimport-storage.py (+74/-0)
charm/lp-codeimport-storage/templates/authorized_keys.j2 (+2/-0)
charm/lp-codeimport/config.yaml (+7/-4)
charm/lp-codeimport/hooks/relations/codeimport-storage/__init__.py (+0/-0)
charm/lp-codeimport/hooks/relations/codeimport-storage/requires.py (+32/-0)
charm/lp-codeimport/metadata.yaml (+3/-0)
charm/lp-codeimport/reactive/lp-codeimport.py (+55/-2)
Reviewer Review Type Date Requested Status
Guruprasad Approve
Review via email: mp+439271@code.launchpad.net

Commit message

charm: Add an lp-codeimport-storage charm

Description of the change

This can be used to provide import data storage instead of the older approach of setting `bazaar_branch_store` and `foreign_tree_store` on `lp-codeimport`. It isn't much more than a vanilla system with SSH key authorization and a couple of pre-created directories, but the new `codeimport-storage` relation makes it a little easier to set things up in a Mojo spec.

Data migration from any previous storage systems needs to be handled manually.

To post a comment you must log in.
Revision history for this message
Guruprasad (lgp171188) wrote :

LGTM 👍🏼 Don't we have/need tests for this charm?

review: Approve
Revision history for this message
Colin Watson (cjwatson) wrote :

I haven't figured out how to get integration tests set up for our charms yet :-( Definitely something we need though!

a4ad063... by Colin Watson

Add icon

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/charm/lp-codeimport-storage/README.md b/charm/lp-codeimport-storage/README.md
2new file mode 100644
3index 0000000..cad1a3b
4--- /dev/null
5+++ b/charm/lp-codeimport-storage/README.md
6@@ -0,0 +1,3 @@
7+# Launchpad code import storage
8+
9+This charm provides storage for the `lp-codeimport` charm.
10diff --git a/charm/lp-codeimport-storage/charmcraft.yaml b/charm/lp-codeimport-storage/charmcraft.yaml
11new file mode 100644
12index 0000000..ac76470
13--- /dev/null
14+++ b/charm/lp-codeimport-storage/charmcraft.yaml
15@@ -0,0 +1,44 @@
16+type: charm
17+bases:
18+ - build-on:
19+ - name: ubuntu
20+ channel: "22.04"
21+ architectures: [amd64]
22+ run-on:
23+ - name: ubuntu
24+ channel: "22.04"
25+ architectures: [amd64]
26+parts:
27+ charm-wheels:
28+ source: https://git.launchpad.net/~ubuntuone-hackers/ols-charm-deps/+git/wheels
29+ source-commit: "59b32ae07f98051385c96d6d8e7e02ca4f197fe5"
30+ source-submodules: []
31+ source-type: git
32+ plugin: dump
33+ organize:
34+ "*": charm-wheels/
35+ prime:
36+ - "-charm-wheels"
37+ ols-layers:
38+ source: https://git.launchpad.net/ols-charm-deps
39+ source-commit: "1ca8acbef7eb49b8a2cc81e5e13479b4f226a48b"
40+ source-submodules: []
41+ source-type: git
42+ plugin: dump
43+ organize:
44+ "*": layers/
45+ stage:
46+ - layers
47+ prime:
48+ - "-layers"
49+ lp-codeimport-storage:
50+ after:
51+ - charm-wheels
52+ - ols-layers
53+ source: .
54+ plugin: reactive
55+ build-snaps: [charm/2.x/stable]
56+ build-environment:
57+ - CHARM_LAYERS_DIR: $CRAFT_STAGE/layers/layer
58+ - PIP_NO_INDEX: "true"
59+ - PIP_FIND_LINKS: $CRAFT_STAGE/charm-wheels
60diff --git a/charm/lp-codeimport-storage/config.yaml b/charm/lp-codeimport-storage/config.yaml
61new file mode 100644
62index 0000000..4f9deac
63--- /dev/null
64+++ b/charm/lp-codeimport-storage/config.yaml
65@@ -0,0 +1,5 @@
66+options:
67+ public_ssh_key:
68+ type: string
69+ default: ""
70+ description: Base64-encoded public SSH key of the code import workers.
71diff --git a/charm/lp-codeimport-storage/hooks/relations/codeimport-storage/__init__.py b/charm/lp-codeimport-storage/hooks/relations/codeimport-storage/__init__.py
72new file mode 100644
73index 0000000..e69de29
74--- /dev/null
75+++ b/charm/lp-codeimport-storage/hooks/relations/codeimport-storage/__init__.py
76diff --git a/charm/lp-codeimport-storage/hooks/relations/codeimport-storage/provides.py b/charm/lp-codeimport-storage/hooks/relations/codeimport-storage/provides.py
77new file mode 100644
78index 0000000..c6b06a0
79--- /dev/null
80+++ b/charm/lp-codeimport-storage/hooks/relations/codeimport-storage/provides.py
81@@ -0,0 +1,39 @@
82+# Copyright 2023 Canonical Ltd. This software is licensed under the
83+# GNU Affero General Public License version 3 (see the file LICENSE).
84+
85+from charmhelpers.core import hookenv
86+from charms.reactive import (
87+ Endpoint,
88+ remove_state,
89+ set_state,
90+ when,
91+ when_not,
92+ )
93+
94+
95+class CodeImportStorageProvides(Endpoint):
96+ @when("endpoint.{endpoint_name}.joined")
97+ @when_not("endpoint.{endpoint_name}.available")
98+ def joined(self):
99+ for relation in self.relations:
100+ ingress_address = hookenv.network_get(
101+ relation.endpoint_name, relation_id=relation.relation_id
102+ )["ingress-addresses"][0]
103+ relation.to_publish[
104+ "bazaar_branch_store"
105+ ] = f"sftp://importd@{ingress_address}/srv/importd/www/"
106+ relation.to_publish[
107+ "foreign_tree_store"
108+ ] = f"sftp://importd@{ingress_address}/srv/importd/sources/"
109+ set_state(self.expand_name("endpoint.{endpoint_name}.available"))
110+
111+ @when("endpoint.{endpoint_name}.available")
112+ @when_not("endpoint.{endpoint_name}.joined")
113+ def departed(self):
114+ remove_state(self.expand_name("endpoint.{endpoint_name}.available"))
115+
116+ def get_codeimport_subnets(self):
117+ subnets = set()
118+ for unit in self.all_joined_units:
119+ subnets.update(unit.received_raw["egress-subnets"].split(","))
120+ return sorted(subnets)
121diff --git a/charm/lp-codeimport-storage/icon.svg b/charm/lp-codeimport-storage/icon.svg
122new file mode 100644
123index 0000000..b2889cc
124--- /dev/null
125+++ b/charm/lp-codeimport-storage/icon.svg
126@@ -0,0 +1 @@
127+<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 165.39062 165.39062"><defs><style>.cls-1{fill:#e9500e;}.cls-2{fill:#fff;}</style></defs><rect class="cls-1" width="165.39062" height="165.39062"/><path class="cls-2" d="M29.63876,57.97189C43.189,67.692,61.13456,69.25577,77.65457,62.15038c16.25576-6.87157,27.74036-21.43444,29.97828-38.0075.04663-.34331.11016-.81367-1.59861-1.24044l-10.10934-2.197c-.3254-.04494-.79136-.04967-1.15258,1.22455C91.37844,36.07384,84.34062,45.04243,72.6347,50.1123c-11.77316,5.10029-23.18748,4.05279-35.91893-3.29386-.58119-.27843-.91909-.26086-1.45568.52577l-5.77947,8.65163A1.34512,1.34512,0,0,0,29.63876,57.97189Z" transform="translate(0.39062 0.39062)"/><path class="cls-2" d="M79.86106,139.66026l10.3631.565c1.74155.03446,1.79122-.42981,1.83717-.77312,2.23826-16.5734-4.97222-33.66107-18.81739-44.59422C59.196,83.62132,41.47815,80.36935,25.83365,86.14747a1.33956,1.33956,0,0,0-.67918,1.85373l3.28,9.88226c.30952.90153.62816,1.011,1.26443.89409,14.22464-3.70543,25.50717-1.68748,35.50635,6.3512,9.94174,7.9934,14.34865,18.50754,13.86883,33.08867C79.08524,139.50144,79.53735,139.615,79.86106,139.66026Z" transform="translate(0.39062 0.39062)"/><path class="cls-2" d="M86.50488,70.59048a10.50817,10.50817,0,0,0-1.39587-.09461A9.35237,9.35237,0,0,0,79.39915,72.382a9.61981,9.61981,0,1,0,7.10573-1.79156Z" transform="translate(0.39062 0.39062)"/><path class="cls-2" d="M138.26869,53.18923,133.457,43.97736c-.68628-1.51583-1.22793-1.36985-1.79594-1.17657-15.382,6.63165-25.99848,21.22156-28.40434,39.03776-2.40755,17.82971,3.97169,34.72681,17.0647,45.19906a1.177,1.177,0,0,0,.90794.32844,1.48362,1.48362,0,0,0,.99546-.54l6.76175-8.11166c.62342-.78393.35783-1.18333.0321-1.52461-10.60639-10.44454-14.5764-20.81677-12.84905-33.60769,1.73682-12.86121,8.51918-22.08254,21.34457-29.019C138.52854,53.95289,138.36533,53.421,138.26869,53.18923Z" transform="translate(0.39062 0.39062)"/></svg>
128\ No newline at end of file
129diff --git a/charm/lp-codeimport-storage/layer.yaml b/charm/lp-codeimport-storage/layer.yaml
130new file mode 100644
131index 0000000..590c59f
132--- /dev/null
133+++ b/charm/lp-codeimport-storage/layer.yaml
134@@ -0,0 +1,2 @@
135+includes:
136+ - layer:basic
137diff --git a/charm/lp-codeimport-storage/metadata.yaml b/charm/lp-codeimport-storage/metadata.yaml
138new file mode 100644
139index 0000000..95de7f8
140--- /dev/null
141+++ b/charm/lp-codeimport-storage/metadata.yaml
142@@ -0,0 +1,14 @@
143+name: lp-codeimport-storage
144+display-name: lp-codeimport-storage
145+summary: Launchpad code import storage
146+maintainer: Colin Watson <cjwatson@canonical.com>
147+description: Storage for use with lp-codeimport workers.
148+tags:
149+ # https://juju.is/docs/charm-metadata#heading--charm-store-fields
150+ - network
151+series:
152+ - jammy
153+subordinate: false
154+provides:
155+ codeimport-storage:
156+ interface: codeimport-storage
157diff --git a/charm/lp-codeimport-storage/reactive/lp-codeimport-storage.py b/charm/lp-codeimport-storage/reactive/lp-codeimport-storage.py
158new file mode 100644
159index 0000000..5be4349
160--- /dev/null
161+++ b/charm/lp-codeimport-storage/reactive/lp-codeimport-storage.py
162@@ -0,0 +1,74 @@
163+# Copyright 2023 Canonical Ltd. This software is licensed under the
164+# GNU Affero General Public License version 3 (see the file LICENSE).
165+
166+import base64
167+import os.path
168+
169+from charmhelpers.core import (
170+ hookenv,
171+ host,
172+ templating,
173+ )
174+from charms.reactive import (
175+ endpoint_from_flag,
176+ hook,
177+ remove_state,
178+ set_state,
179+ when,
180+ when_not,
181+ )
182+
183+
184+@hook("upgrade-charm")
185+def upgrade_charm():
186+ remove_state("service.configured")
187+
188+
189+@hook("config.changed")
190+def config_changed():
191+ remove_state("service.configured")
192+
193+
194+@hook(
195+ "{provides:codeimport-storage}-relation-{joined,changed,broken,departed}"
196+)
197+def codeimport_storage_changed(*args):
198+ remove_state("service.configured")
199+
200+
201+@when("config.set.public_ssh_key", "endpoint.codeimport-storage.available")
202+@when_not("service.configured")
203+def configure():
204+ codeimport_storage = endpoint_from_flag(
205+ "endpoint.codeimport-storage.available"
206+ )
207+ hookenv.log("Creating service user")
208+ host.add_group("importd")
209+ host.adduser("importd", primary_group="importd")
210+ for directory in ("/srv/importd/sources", "/srv/importd/www"):
211+ if not os.path.exists(directory):
212+ host.mkdir(
213+ directory, owner="importd", group="importd", perms=0o755
214+ )
215+ ssh_dir = "/home/importd/.ssh"
216+ if not os.path.exists(ssh_dir):
217+ host.mkdir(ssh_dir, owner="importd", group="importd", perms=0o700)
218+ config = dict(hookenv.config())
219+ config["codeimport_subnets"] = codeimport_storage.get_codeimport_subnets()
220+ config["public_ssh_key"] = base64.b64decode(
221+ config["public_ssh_key"].encode("ASCII")
222+ ).decode("ASCII")
223+ templating.render(
224+ "authorized_keys.j2",
225+ os.path.join(ssh_dir, "authorized_keys"),
226+ config,
227+ owner="importd",
228+ group="importd",
229+ perms=0o600,
230+ )
231+ set_state("service.configured")
232+
233+
234+@when("service.configured")
235+def check_is_running():
236+ hookenv.status_set("active", "Ready")
237diff --git a/charm/lp-codeimport-storage/templates/authorized_keys.j2 b/charm/lp-codeimport-storage/templates/authorized_keys.j2
238new file mode 100644
239index 0000000..77d398e
240--- /dev/null
241+++ b/charm/lp-codeimport-storage/templates/authorized_keys.j2
242@@ -0,0 +1,2 @@
243+restrict,from="{{ codeimport_subnets|join(",") }}" {{ public_ssh_key }}
244+
245diff --git a/charm/lp-codeimport/config.yaml b/charm/lp-codeimport/config.yaml
246index cac88ce..37f990c 100644
247--- a/charm/lp-codeimport/config.yaml
248+++ b/charm/lp-codeimport/config.yaml
249@@ -11,13 +11,16 @@ options:
250 default is to use the global CA infrastructure.
251 bazaar_branch_store:
252 type: string
253- default: sftp://hoover@code-import-storage.launchpad.test/srv/importd/www/
254- description: Where the Bazaar imports are stored.
255+ default: ""
256+ description: >
257+ Where the Bazaar imports are stored. If unset, rely on the
258+ codeimport-storage relation instead.
259 foreign_tree_store:
260 type: string
261- default: sftp://hoover@code-import-storage.launchpad.test/srv/importd/sources/
262+ default: ""
263 description: >
264- Where the tarballs of foreign branches are uploaded for storage.
265+ Where the tarballs of foreign branches are uploaded for storage. If
266+ unset, rely on the codeimport-storage relation instead.
267 private_ssh_key:
268 type: string
269 default: ""
270diff --git a/charm/lp-codeimport/hooks/relations/codeimport-storage/__init__.py b/charm/lp-codeimport/hooks/relations/codeimport-storage/__init__.py
271new file mode 100644
272index 0000000..e69de29
273--- /dev/null
274+++ b/charm/lp-codeimport/hooks/relations/codeimport-storage/__init__.py
275diff --git a/charm/lp-codeimport/hooks/relations/codeimport-storage/requires.py b/charm/lp-codeimport/hooks/relations/codeimport-storage/requires.py
276new file mode 100644
277index 0000000..16e31d4
278--- /dev/null
279+++ b/charm/lp-codeimport/hooks/relations/codeimport-storage/requires.py
280@@ -0,0 +1,32 @@
281+# Copyright 2023 Canonical Ltd. This software is licensed under the
282+# GNU Affero General Public License version 3 (see the file LICENSE).
283+
284+from charms.reactive import (
285+ clear_flag,
286+ Endpoint,
287+ toggle_flag,
288+ when,
289+ when_not,
290+ )
291+
292+
293+class CodeImportStorageRequires(Endpoint):
294+ @when("endpoint.{endpoint_name}.changed")
295+ def joined(self):
296+ toggle_flag(
297+ self.expand_name("endpoint.{endpoint_name}.available"),
298+ bool(self.bazaar_branch_store) and bool(self.foreign_tree_store),
299+ )
300+
301+ @when("endpoint.{endpoint_name}.available")
302+ @when_not("endpoint.{endpoint_name}.joined")
303+ def departed(self):
304+ clear_flag(self.expand_name("endpoint.{endpoint_name}.available"))
305+
306+ @property
307+ def bazaar_branch_store(self):
308+ return self.all_joined_units.received["bazaar_branch_store"]
309+
310+ @property
311+ def foreign_tree_store(self):
312+ return self.all_joined_units.received["foreign_tree_store"]
313diff --git a/charm/lp-codeimport/metadata.yaml b/charm/lp-codeimport/metadata.yaml
314index 0c4ee40..f688508 100644
315--- a/charm/lp-codeimport/metadata.yaml
316+++ b/charm/lp-codeimport/metadata.yaml
317@@ -9,3 +9,6 @@ tags:
318 series:
319 - bionic
320 subordinate: false
321+requires:
322+ codeimport-storage:
323+ interface: codeimport-storage
324diff --git a/charm/lp-codeimport/reactive/lp-codeimport.py b/charm/lp-codeimport/reactive/lp-codeimport.py
325index 04df2a6..9c7c9e7 100644
326--- a/charm/lp-codeimport/reactive/lp-codeimport.py
327+++ b/charm/lp-codeimport/reactive/lp-codeimport.py
328@@ -19,9 +19,12 @@ from charmhelpers.core import (
329 templating,
330 )
331 from charms.reactive import (
332+ endpoint_from_flag,
333 remove_state,
334 set_state,
335 when,
336+ when_any,
337+ when_none,
338 when_not,
339 )
340 from ols import base
341@@ -166,9 +169,51 @@ def configure_rsync(config):
342 raise RuntimeError('Failed to restart rsync')
343
344
345-@when('ols.configured')
346+@when_any(
347+ "endpoint.codeimport-storage.available",
348+ "config.set.bazaar_branch_store",
349+)
350+def bazaar_branch_store_available():
351+ set_state("service.bazaar_branch_store.available")
352+
353+
354+@when_none(
355+ "endpoint.codeimport-storage.available",
356+ "config.set.bazaar_branch_store",
357+)
358+def bazaar_branch_store_unavailable():
359+ remove_state("service.bazaar_branch_store.available")
360+ remove_state("service.configured")
361+
362+
363+@when_any(
364+ "endpoint.codeimport-storage.available",
365+ "config.set.foreign_tree_store",
366+)
367+def foreign_tree_store_available():
368+ set_state("service.foreign_tree_store.available")
369+
370+
371+@when_none(
372+ "endpoint.codeimport-storage.available",
373+ "config.set.foreign_tree_store",
374+)
375+def foreign_tree_store_unavailable():
376+ remove_state("service.foreign_tree_store.available")
377+ remove_state("service.configured")
378+
379+
380+@when(
381+ "ols.configured",
382+ "service.bazaar_branch_store.available",
383+ "service.foreign_tree_store.available",
384+)
385 @when_not('service.configured')
386 def configure():
387+ codeimport_storage = endpoint_from_flag(
388+ "endpoint.codeimport-storage.available"
389+ )
390+
391 ensure_lp_directories()
392
393 system_packages = os.path.join(base.code_dir(), 'system-packages.txt')
394@@ -188,6 +233,14 @@ def configure():
395 config_path = os.path.join(
396 base.code_dir(), 'production-configs', 'charm', 'codeimport-lazr.conf')
397 svc_config = dict(config)
398+ if not svc_config["bazaar_branch_store"]:
399+ svc_config["bazaar_branch_store"] = (
400+ codeimport_storage.bazaar_branch_store
401+ )
402+ if not svc_config["foreign_tree_store"]:
403+ svc_config["foreign_tree_store"] = (
404+ codeimport_storage.foreign_tree_store
405+ )
406 svc_config.update({
407 'base_dir': base.base_dir(),
408 'code_dir': base.code_dir(),
409@@ -199,7 +252,7 @@ def configure():
410 'home_dir': home_dir(),
411 'user': base.user(),
412 'code_import_storage_host': (
413- urlparse(config['bazaar_branch_store']).hostname),
414+ urlparse(svc_config['bazaar_branch_store']).hostname),
415 # Chosen to allow distributing dispatch start time over a 30-second
416 # interval.
417 'dispatch_offset': host.modulo_distribution(modulo=6, wait=5),

Subscribers

People subscribed via source and target branches