Skip to content

Commit 88eb6c4

Browse files
Merge pull request #777 from cderici/series-or-base-for-local-charms
[JUJU-2276] Series or base for local charms
2 parents 90c7690 + b47d270 commit 88eb6c4

9 files changed

Lines changed: 84 additions & 38 deletions

File tree

juju/application.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -823,9 +823,12 @@ async def local_refresh(
823823

824824
series = (
825825
await self.get_series() or
826-
self.model.info.get('default-series', '') or
827-
await get_charm_series(charm_dir, self.model)
826+
self.model.info.get('default-series', '')
828827
)
828+
if not series:
829+
metadata = utils.get_local_charm_metadata(charm_dir)
830+
await get_charm_series(metadata, self.model)
831+
829832
if not series:
830833
default_series = model_config.get("default-series")
831834
if default_series:

juju/bundle.py

Lines changed: 11 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -124,11 +124,10 @@ async def _handle_local_charms(self, bundle, bundle_dir):
124124
pass
125125
except FileNotFoundError:
126126
continue
127-
series = (
128-
app_dict.get('series') or
129-
default_series or
130-
await get_charm_series(charm_dir, self.model)
131-
)
127+
series = (app_dict.get('series') or default_series)
128+
if not series:
129+
metadata = utils.get_local_charm_metadata(charm_dir)
130+
series = await get_charm_series(metadata, self.model)
132131
if not self.model.connection().is_using_old_client and not series:
133132
raise JujuError(
134133
"Couldn't determine series for charm at {}. "
@@ -430,32 +429,17 @@ def is_local_charm(charm_url):
430429
return charm_url.startswith('.') or charm_url.startswith('local:') or os.path.isabs(charm_url)
431430

432431

433-
async def get_charm_series(path, model):
434-
"""Inspects the charm directory at ``path`` and returns a default
435-
series from its metadata.yaml (the first item in the 'series' list).
432+
async def get_charm_series(metadata, model):
433+
"""Inspects the given metadata and returns a default series from its
434+
metadata.yaml (the first item in the 'series' list).
436435
437-
Tries to extract the informiation from the given model if no
438-
series is determined from the path.
436+
Tries to extract the information from the given model if no
437+
series is determined from the given metadata.
439438
Returns None if no series can be determined.
440439
441440
"""
442-
path = Path(path)
443-
try:
444-
if path.suffix == '.charm':
445-
md = "metadata.yaml in %s" % path
446-
with zipfile.ZipFile(str(path), 'r') as charm_file:
447-
data = yaml.safe_load(charm_file.read('metadata.yaml'))
448-
else:
449-
md = path / "metadata.yaml"
450-
if not md.exists():
451-
return None
452-
data = yaml.safe_load(md.open())
453-
except yaml.YAMLError as exc:
454-
if hasattr(exc, "problem_mark"):
455-
mark = exc.problem_mark
456-
log.error("Error parsing YAML file {}, line {}, column: {}".format(md, mark.line, mark.column))
457-
raise
458-
_series = data.get('series')
441+
442+
_series = metadata.get('series')
459443
series = _series[0] if _series else None
460444

461445
if series is None:

juju/model.py

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1791,20 +1791,24 @@ async def deploy(
17911791
identifier)
17921792
else:
17931793
# We have a local charm dir that needs to be uploaded
1794-
charm_dir = os.path.abspath(
1795-
os.path.expanduser(identifier))
1794+
charm_dir = os.path.abspath(os.path.expanduser(identifier))
17961795
charm_origin = res.origin
1796+
base = None
17971797

17981798
metadata = utils.get_local_charm_metadata(charm_dir)
1799-
# TODO (cderici) : pass the metadata into get_charm_series, as
1800-
# it also reads that file redundantly
1801-
charm_series = charm_series or await get_charm_series(charm_dir,
1799+
charm_series = charm_series or await get_charm_series(metadata,
18021800
self)
1803-
1804-
charm_origin.base = utils.get_local_charm_base(charm_series, channel, metadata, charm_dir, client.Base)
1805-
1801+
base = utils.get_local_charm_base(
1802+
charm_series, channel, metadata, charm_dir, client.Base)
1803+
charm_origin.base = base
1804+
if not application_name:
1805+
application_name = metadata['name']
18061806
if not application_name:
18071807
application_name = metadata['name']
1808+
if base is None and charm_series is None:
1809+
raise JujuError(
1810+
"Either series or base is needed to deploy the "
1811+
"charm at {}. ".format(charm_dir))
18081812

18091813
identifier = await self.add_local_charm_dir(charm_dir,
18101814
charm_series)

juju/utils.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -239,7 +239,7 @@ def get_local_charm_data(path, yaml_file):
239239
240240
:patam str path: Path of charm directory or .charm file
241241
:patam str yaml_file: name of the yaml file, can be either
242-
"metadata.yaml", or "manifest.yaml"
242+
"metadata.yaml", or "manifest.yaml", or "charmcraft.yaml"
243243
244244
:return: Object of charm metadata
245245
"""
@@ -249,6 +249,8 @@ def get_local_charm_data(path, yaml_file):
249249
else:
250250
entity_path = Path(path)
251251
metadata_path = entity_path / yaml_file
252+
if not metadata_path.exists():
253+
return {}
252254
metadata = yaml.load(metadata_path.read_text(), Loader=yaml.FullLoader)
253255

254256
return metadata
@@ -262,6 +264,10 @@ def get_local_charm_manifest(path):
262264
return get_local_charm_data(path, 'manifest.yaml')
263265

264266

267+
def get_local_charm_charmcraft_yaml(path):
268+
return get_local_charm_data(path, 'charmcraft.yaml')
269+
270+
265271
PRECISE = "precise"
266272
QUANTAL = "quantal"
267273
RARING = "raring"
@@ -376,6 +382,12 @@ def get_local_charm_base(series, channel_from_arg, charm_metadata,
376382
if 'bases' in charm_manifest:
377383
channel_for_base = charm_manifest['bases'][0]['channel']
378384
os_name_for_base = charm_manifest['bases'][0]['name']
385+
else:
386+
# Also check the charmcraft.yaml
387+
charmcraft_yaml = get_local_charm_charmcraft_yaml(charm_path)
388+
if 'bases' in charmcraft_yaml:
389+
channel_for_base = charmcraft_yaml['bases'][0]['run-on'][0]['channel']
390+
os_name_for_base = charmcraft_yaml['bases'][0]['run-on'][0]['name']
379391

380392
if channel_for_base == '':
381393
raise errors.JujuError("Unable to determine base for charm : %s" %
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# Copyright 2021 Canonical Ltd.
2+
# See LICENSE file for licensing details.
3+
type: charm
4+
bases:
5+
- build-on:
6+
- name: "ubuntu"
7+
channel: "20.04"
8+
run-on:
9+
- name: "ubuntu"
10+
channel: "20.04"
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
options:
2+
status:
3+
type: string
4+
default: "active"
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
#!/bin/bash
2+
3+
status="$(config-get status)"
4+
5+
if [[ "$status" == "error" ]]; then
6+
if [[ -e .errored ]]; then
7+
status="active"
8+
else
9+
touch .errored
10+
exit 1
11+
fi
12+
fi
13+
status-set "$status"
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
name: charm
2+
summary: "test"
3+
description: "test"
4+
maintainers: ["test"]

tests/integration/test_model.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,18 @@ async def test_deploy_local_charm(event_loop):
162162
assert model.units['charm/0'].workload_status == 'active'
163163

164164

165+
@base.bootstrapped
166+
@pytest.mark.asyncio
167+
async def test_deploy_local_charm_base_charmcraft_yaml(event_loop):
168+
charm_path = HERE_DIR / 'charm-base-charmcraft-yaml'
169+
170+
async with base.CleanModel() as model:
171+
await model.deploy(str(charm_path))
172+
assert 'charm' in model.applications
173+
await model.wait_for_idle(status="active")
174+
assert model.units['charm/0'].workload_status == 'active'
175+
176+
165177
@base.bootstrapped
166178
@pytest.mark.asyncio
167179
async def test_wait_local_charm_blocked(event_loop):

0 commit comments

Comments
 (0)