Skip to content

Commit c16ccc9

Browse files
letmaikachamayou
andauthored
Update collections of JS modules in one proposal (#1557)
* Update collections of JS modules in one proposal. Fixes #1479. * formatting Co-authored-by: Amaury Chamayou <amchamay@microsoft.com>
1 parent 321e9cb commit c16ccc9

3 files changed

Lines changed: 100 additions & 11 deletions

File tree

python/ccf/proposal_generator.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import collections
66
import inspect
77
import json
8+
import glob
89
import os
910
import sys
1011
from pathlib import PurePosixPath
@@ -294,6 +295,40 @@ def remove_module(module_name: str, **kwargs):
294295
return build_proposal("remove_module", module_name, **kwargs)
295296

296297

298+
@cli_proposal
299+
def update_modules(module_name_prefix: str, modules_path: Optional[str], **kwargs):
300+
LOG.debug("Generating update_modules proposal")
301+
302+
# Validate module name prefix
303+
module_name_prefix_ = PurePosixPath(module_name_prefix)
304+
if not module_name_prefix_.is_absolute():
305+
raise ValueError("module name prefix must be an absolute path")
306+
if any(folder in [".", ".."] for folder in module_name_prefix_.parents):
307+
raise ValueError("module name prefix must not contain . or .. components")
308+
if not module_name_prefix.endswith("/"):
309+
raise ValueError("module name prefix must end with /")
310+
311+
# Read module files and build relative module names
312+
modules = []
313+
if modules_path:
314+
for path in glob.glob(f"{modules_path}/**/*.js", recursive=True):
315+
rel_module_name = os.path.relpath(path, modules_path)
316+
rel_module_name = rel_module_name.replace("\\", "/") # Windows support
317+
with open(path) as f:
318+
js = f.read()
319+
modules.append({"rel_name": rel_module_name, "module": {"js": js}})
320+
321+
proposal_args = {"prefix": module_name_prefix, "modules": modules}
322+
323+
return build_proposal("update_modules", proposal_args, **kwargs)
324+
325+
326+
@cli_proposal
327+
def remove_modules(module_name_prefix: str, **kwargs):
328+
LOG.debug("Generating update_modules proposal (remove only)")
329+
return update_modules(module_name_prefix, modules_path=None)
330+
331+
297332
@cli_proposal
298333
def trust_node(node_id: int, **kwargs):
299334
return build_proposal("trust_node", node_id, **kwargs)

src/runtime_config/gov.lua

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,4 +97,27 @@ return {
9797
end
9898
end
9999
return true]],
100+
101+
update_modules = [[
102+
tables, args = ...
103+
function starts_with(str, start)
104+
return str:sub(1, #start) == start
105+
end
106+
function remove_modules_with_prefix(prefix)
107+
tables["ccf.modules"]:foreach(function(module_name, _)
108+
if starts_with(module_name, prefix) then
109+
tables["ccf.modules"]:remove(module_name)
110+
end
111+
end)
112+
end
113+
function add_modules_with_prefix(prefix, modules)
114+
for _, module in pairs(modules) do
115+
module_name = prefix .. module.rel_name
116+
tables["ccf.modules"]:put(module_name, module.module)
117+
end
118+
end
119+
remove_modules_with_prefix(args.prefix)
120+
add_modules_with_prefix(args.prefix, args.modules)
121+
return true
122+
]]
100123
}

tests/modules.py

Lines changed: 42 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
import http
55
import subprocess
66
import os
7-
import glob
87
import infra.network
98
import infra.path
109
import infra.proc
@@ -18,6 +17,7 @@
1817

1918
THIS_DIR = os.path.dirname(__file__)
2019

20+
MODULE_PREFIX_1 = "/app/"
2121
MODULE_PATH_1 = "/app/foo.js"
2222
MODULE_RETURN_1 = "Hello world!"
2323
MODULE_CONTENT_1 = f"""
@@ -85,7 +85,7 @@ def make_module_set_proposal(path, content, network):
8585
def test_module_set_and_remove(network, args):
8686
primary, _ = network.find_nodes()
8787

88-
LOG.info("Member makes a module update proposal")
88+
LOG.info("Member makes a module set proposal")
8989
make_module_set_proposal(MODULE_PATH_1, MODULE_CONTENT_1, network)
9090

9191
with primary.client(
@@ -110,6 +110,35 @@ def test_module_set_and_remove(network, args):
110110
return network
111111

112112

113+
@reqs.description("Test prefix-based modules remove")
114+
def test_modules_remove(network, args):
115+
primary, _ = network.find_nodes()
116+
117+
LOG.info("Member makes a module set proposal")
118+
make_module_set_proposal(MODULE_PATH_1, MODULE_CONTENT_1, network)
119+
120+
with primary.client(
121+
f"member{network.consortium.get_any_active_member().member_id}"
122+
) as c:
123+
r = c.post("/gov/read", {"table": "ccf.modules", "key": MODULE_PATH_1})
124+
assert r.status_code == http.HTTPStatus.OK, r.status_code
125+
assert r.body["js"] == MODULE_CONTENT_1, r.body
126+
127+
LOG.info("Member makes a prefix-based modules remove proposal")
128+
proposal_body, _ = ccf.proposal_generator.remove_modules(MODULE_PREFIX_1)
129+
proposal = network.consortium.get_any_active_member().propose(
130+
primary, proposal_body
131+
)
132+
network.consortium.vote_using_majority(primary, proposal)
133+
134+
with primary.client(
135+
f"member{network.consortium.get_any_active_member().member_id}"
136+
) as c:
137+
r = c.post("/gov/read", {"table": "ccf.modules", "key": MODULE_PATH_1})
138+
assert r.status_code == http.HTTPStatus.BAD_REQUEST, r.status_code
139+
return network
140+
141+
113142
@reqs.description("Test module import")
114143
def test_module_import(network, args):
115144
primary, _ = network.find_nodes()
@@ -132,7 +161,7 @@ def test_module_import(network, args):
132161
return network
133162

134163

135-
@reqs.description("Test Node.js/npm app")
164+
@reqs.description("Test Node.js/npm app with prefix-based modules update")
136165
def test_npm_app(network, args):
137166
primary, _ = network.find_nodes()
138167

@@ -142,15 +171,16 @@ def test_npm_app(network, args):
142171
subprocess.run(["npm", "run", "build"], cwd=app_dir, check=True)
143172

144173
LOG.info("Deploying npm app modules")
145-
kv_prefix = "/my-npm-app"
174+
module_name_prefix = "/my-npm-app/"
146175
dist_dir = os.path.join(app_dir, "dist")
147-
for module_path in glob.glob(os.path.join(dist_dir, "**", "*.js"), recursive=True):
148-
module_name = os.path.join(kv_prefix, os.path.relpath(module_path, dist_dir))
149-
proposal_body, _ = ccf.proposal_generator.set_module(module_name, module_path)
150-
proposal = network.consortium.get_any_active_member().propose(
151-
primary, proposal_body
152-
)
153-
network.consortium.vote_using_majority(primary, proposal)
176+
177+
proposal_body, _ = ccf.proposal_generator.update_modules(
178+
module_name_prefix, dist_dir
179+
)
180+
proposal = network.consortium.get_any_active_member().propose(
181+
primary, proposal_body
182+
)
183+
network.consortium.vote_using_majority(primary, proposal)
154184

155185
LOG.info("Deploying endpoint script")
156186
with tempfile.NamedTemporaryFile("w") as f:
@@ -186,6 +216,7 @@ def run(args):
186216
) as network:
187217
network.start_and_join(args)
188218
network = test_module_set_and_remove(network, args)
219+
network = test_modules_remove(network, args)
189220
network = test_module_import(network, args)
190221
network = test_npm_app(network, args)
191222

0 commit comments

Comments
 (0)