Skip to content

Commit 6393d6a

Browse files
author
Dominik Fleischmann
committed
Add Local Charm Upgrade support
This commit will add support for upgrading from a locally built charm with local resources.
1 parent 81ea905 commit 6393d6a

6 files changed

Lines changed: 135 additions & 7 deletions

File tree

examples/local_refresh.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
"""
2+
This example:
3+
4+
1. Connects to the current model
5+
2. Upgrades previously deployed ubuntu charm
6+
7+
"""
8+
from juju import loop
9+
from juju.model import Model
10+
11+
12+
async def main():
13+
model = Model()
14+
print('Connecting to model')
15+
# connect to current model with current user, per Juju CLI
16+
await model.connect()
17+
18+
try:
19+
print('Get deployed application')
20+
app = model.appplications["ubuntu"]
21+
22+
print('Refresh/Upgrade Ubuntu charm with local charm')
23+
await app.refresh(path="path/to/local/ubuntu.charm")
24+
finally:
25+
print('Disconnecting from model')
26+
await model.disconnect()
27+
28+
29+
if __name__ == '__main__':
30+
loop.run(main())

juju/application.py

Lines changed: 70 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,17 @@
1515
import asyncio
1616
import json
1717
import logging
18+
import zipfile
19+
import os
20+
import yaml
21+
from pathlib import Path
1822

1923
from . import model, tag
2024
from .status import derive_status
2125
from .annotationhelper import _get_annotations, _set_annotations
2226
from .client import client
2327
from .errors import JujuError
28+
from .bundle import get_charm_series
2429
from .placement import parse as parse_placement
2530

2631
log = logging.getLogger(__name__)
@@ -578,9 +583,10 @@ async def refresh(
578583
:param str switch: Crossgrade charm url
579584
580585
"""
581-
# TODO: Support local upgrades
582586
if path is not None:
583-
raise NotImplementedError("path option is not implemented")
587+
await self.local_refresh(channel, force, force_series, force_units,
588+
path, resources)
589+
return
584590
if resources is not None:
585591
raise NotImplementedError("resources option is not implemented")
586592

@@ -684,6 +690,68 @@ async def refresh(
684690

685691
upgrade_charm = refresh
686692

693+
async def local_refresh(
694+
self, channel=None, force=False, force_series=False, force_units=False,
695+
path=None, resources=None):
696+
"""Refresh the charm for this application with a local charm.
697+
698+
:param str channel: Channel to use when getting the charm from the
699+
charm store, e.g. 'development'
700+
:param bool force_series: Refresh even if series of deployed
701+
application is not supported by the new charm
702+
:param bool force_units: Refresh all units immediately, even if in
703+
error state
704+
:param str path: Refresh to a charm located at path
705+
:param dict resources: Dictionary of resource name/filepath pairs
706+
:param int revision: Explicit refresh revision
707+
:param str switch: Crossgrade charm url
708+
709+
"""
710+
app_facade = self._facade()
711+
712+
charm_dir = os.path.abspath(
713+
os.path.expanduser(path))
714+
model_config = await self.get_config()
715+
716+
series = get_charm_series(charm_dir)
717+
if not series:
718+
model_config = await self.get_config()
719+
default_series = model_config.get("default-series")
720+
if default_series:
721+
series = default_series.value
722+
723+
charm_url = await self.model.add_local_charm_dir(charm_dir, series)
724+
if resources is not None:
725+
if str(path).endswith('.charm'):
726+
with zipfile.ZipFile(path, 'r') as charm_file:
727+
metadata = yaml.load(charm_file.read('metadata.yaml'), Loader=yaml.FullLoader)
728+
else:
729+
entity_path = Path(path)
730+
metadata_path = entity_path / 'metadata.yaml'
731+
metadata = yaml.load(metadata_path.read_text(), Loader=yaml.FullLoader)
732+
resources = await self.model.add_local_resources(self.entity_id,
733+
charm_url,
734+
metadata,
735+
resources=resources)
736+
737+
# Update application
738+
await app_facade.SetCharm(
739+
application=self.entity_id,
740+
channel=channel,
741+
charm_url=charm_url,
742+
config_settings=None,
743+
config_settings_yaml=None,
744+
force=force,
745+
force_series=force_series,
746+
force_units=force_units,
747+
resource_ids=resources,
748+
storage_constraints=None,
749+
)
750+
751+
await self.model.block_until(
752+
lambda: self.data['charm-url'] == charm_url
753+
)
754+
687755
async def get_metrics(self):
688756
"""Get metrics for this application's units.
689757

juju/model.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1492,10 +1492,10 @@ async def deploy(
14921492
"Pass a 'series' kwarg to Model.deploy().".format(
14931493
charm_dir))
14941494
entity_id = await self.add_local_charm_dir(charm_dir, series)
1495-
resources = await self._add_local_resources(application_name,
1496-
entity_id,
1497-
metadata,
1498-
resources=resources)
1495+
resources = await self.add_local_resources(application_name,
1496+
entity_id,
1497+
metadata,
1498+
resources=resources)
14991499

15001500
if config is None:
15011501
config = {}
@@ -1561,7 +1561,7 @@ async def _add_store_resources(self, application, entity_url,
15611561
in zip(resources, response.pending_ids)}
15621562
return resource_map
15631563

1564-
async def _add_local_resources(self, application, entity_url, metadata, resources):
1564+
async def add_local_resources(self, application, entity_url, metadata, resources):
15651565
if not resources:
15661566
return None
15671567

tests/integration/test_application.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import asyncio
2+
from pathlib import Path
23

34
import pytest
45

@@ -143,6 +144,20 @@ async def test_upgrade_charm_switch(event_loop):
143144
assert app.data['charm-url'] == 'cs:ubuntu-8'
144145

145146

147+
@base.bootstrapped
148+
@pytest.mark.asyncio
149+
async def test_upgrade_local_charm(event_loop):
150+
async with base.CleanModel() as model:
151+
tests_dir = Path(__file__).absolute().parent
152+
charm_path = tests_dir / 'upgrade-charm'
153+
app = await model.deploy('ubuntu-0')
154+
await model.block_until(lambda: (len(app.units) > 0 and
155+
app.units[0].machine))
156+
assert app.data['charm-url'] == 'cs:ubuntu-0'
157+
await app.upgrade_charm(path=charm_path)
158+
assert app.data['charm-url'] == 'local:ubuntu'
159+
160+
146161
@base.bootstrapped
147162
@pytest.mark.asyncio
148163
async def test_upgrade_charm_resource(event_loop):
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: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
name: ubuntu
2+
series: ["focal"]
3+
summary: "test"
4+
description: "test"
5+
maintainers: ["test"]

0 commit comments

Comments
 (0)