Skip to content

Commit 0eed3c1

Browse files
author
Dominik Fleischmann
committed
Add local resources support for oci-images
This commit enables support for local resources of the oci-image type.
1 parent 5e23162 commit 0eed3c1

5 files changed

Lines changed: 158 additions & 1 deletion

File tree

examples/deploy_local_resource.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
"""
2+
This example:
3+
4+
1. Connects to the current model
5+
2. Deploy a local charm with a oci-image resource and waits until it reports
6+
itself active
7+
3. Destroys the unit and application
8+
9+
"""
10+
from juju import loop
11+
from juju.model import Model
12+
from pathlib import Path
13+
14+
15+
async def main():
16+
model = Model()
17+
print('Connecting to model')
18+
# connect to current model with current user, per Juju CLI
19+
await model.connect()
20+
21+
try:
22+
print('Deploying local-charm')
23+
base_dir = Path(__file__).absolute().parent.parent
24+
charm_path = '{}/tests/integration/oci-image-charm'.format(base_dir)
25+
resources = {"oci-image": "ubuntu/latest"}
26+
application = await model.deploy(
27+
charm_path,
28+
resources=resources,
29+
)
30+
31+
print('Waiting for active')
32+
await model.block_until(
33+
lambda: all(unit.workload_status == 'active'
34+
for unit in application.units),
35+
timeout=120,
36+
)
37+
38+
print('Removing Charm')
39+
await application.remove()
40+
finally:
41+
print('Disconnecting from model')
42+
await model.disconnect()
43+
44+
45+
if __name__ == '__main__':
46+
loop.run(main())

juju/model.py

Lines changed: 72 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1392,7 +1392,7 @@ async def deploy(
13921392
13931393
TODO::
13941394
1395-
- support local resources
1395+
- support local file resources
13961396
13971397
"""
13981398
if storage:
@@ -1474,10 +1474,16 @@ async def deploy(
14741474
"Pass a 'series' kwarg to Model.deploy().".format(
14751475
charm_dir))
14761476
entity_id = await self.add_local_charm_dir(charm_dir, series)
1477+
resources = await self._add_local_resources(application_name,
1478+
entity_id,
1479+
metadata,
1480+
resources=resources)
1481+
14771482
if config is None:
14781483
config = {}
14791484
if trust:
14801485
config["trust"] = "true"
1486+
14811487
return await self._deploy(
14821488
charm_url=entity_id,
14831489
application=application_name,
@@ -1537,6 +1543,71 @@ async def _add_store_resources(self, application, entity_url,
15371543
in zip(resources, response.pending_ids)}
15381544
return resource_map
15391545

1546+
async def _add_local_resources(self, application, entity_url, metadata, resources):
1547+
if not resources:
1548+
return None
1549+
1550+
resource_map = dict()
1551+
1552+
for name, path in resources.items():
1553+
resource_type = metadata["resources"][name]["type"]
1554+
if resource_type != "oci-image":
1555+
# For now only oci-images are supported
1556+
log.info("Resource {} of type {} is not supported".format(name, resource_type))
1557+
continue
1558+
1559+
charmresource = {
1560+
'description': '',
1561+
'fingerprint': '',
1562+
'name': name,
1563+
'path': path,
1564+
'revision': 0,
1565+
'size': 0,
1566+
'type_': 'oci-image',
1567+
'origin': 'upload',
1568+
}
1569+
1570+
resources_facade = client.ResourcesFacade.from_connection(
1571+
self.connection())
1572+
response = await resources_facade.AddPendingResources(
1573+
application_tag=tag.application(application),
1574+
charm_url=entity_url,
1575+
resources=[client.CharmResource(**charmresource)])
1576+
pending_id = response.pending_ids[0]
1577+
resource_map[name] = pending_id
1578+
1579+
# TODO Docker Image validation and support for local images.
1580+
docker_image_details = {
1581+
'registrypath': path,
1582+
'username': '',
1583+
'password': '',
1584+
}
1585+
1586+
data = yaml.dump(docker_image_details)
1587+
1588+
charmresource['fingerprint'] = hashlib.sha3_384(bytes(data, 'utf-8')).digest()
1589+
1590+
conn, headers, path_prefix = self.connection().https_connection()
1591+
1592+
query = "?pendingid={}".format(pending_id)
1593+
url = "{}/applications/{}/resources/{}{}".format(
1594+
path_prefix, application, name, query)
1595+
disp = "multipart/form-data; filename=\"{}\"".format(path)
1596+
1597+
headers['Content-Type'] = 'application/octet-stream'
1598+
headers['Content-Length'] = len(data)
1599+
headers['Content-Sha384'] = charmresource['fingerprint'].hex()
1600+
headers['Content-Disposition'] = disp
1601+
1602+
conn.request('PUT', url, data, headers)
1603+
1604+
response = conn.getresponse()
1605+
result = response.read().decode()
1606+
if not response.status == 200:
1607+
raise JujuError(result)
1608+
1609+
return resource_map
1610+
15401611
async def _deploy(self, charm_url, application, series, config,
15411612
constraints, endpoint_bindings, resources, storage,
15421613
channel=None, num_units=None, placement=None,
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
#!/bin/bash
2+
3+
resource_file="$(resource-get oci-image)"
4+
if [[ -z "$resource_file" ]]; then
5+
status-set waiting
6+
elif grep -q "ubuntu/latest" "$resource_file"; then
7+
status-set active
8+
else
9+
status-set error
10+
fi
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
name: oci-image-charm
2+
series: ["focal"]
3+
summary: "test"
4+
description: "test"
5+
maintainers: ["test"]
6+
resources:
7+
oci-image:
8+
type: oci-image
9+
description: 'Backing OCI image'

tests/integration/test_model.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -568,6 +568,27 @@ async def test_store_resources_charm(event_loop):
568568
assert ghost.units[0].workload_status == 'active'
569569

570570

571+
@base.bootstrapped
572+
@pytest.mark.asyncio
573+
async def test_local_oci_image_resource_charm(event_loop):
574+
pytest.skip('test_local_resources_charm intermittent test failure')
575+
576+
tests_dir = Path(__file__).absolute().parent.parent
577+
charm_path = tests_dir / 'oci-image-charm'
578+
async with base.CleanModel() as model:
579+
resources = {"oci-image": "ubuntu/latest"}
580+
charm = await model.deploy(str(charm_path), resources=resources)
581+
assert 'oci-image-charm' in model.applications
582+
terminal_statuses = ('active', 'error', 'blocked')
583+
await model.block_until(
584+
lambda: (
585+
len(charm.units) > 0 and
586+
charm.units[0].workload_status in terminal_statuses),
587+
timeout=120,
588+
)
589+
assert charm.units[0].workload_status == 'active'
590+
591+
571592
@base.bootstrapped
572593
@pytest.mark.asyncio
573594
async def test_store_resources_bundle(event_loop):

0 commit comments

Comments
 (0)