Skip to content

Commit 9016398

Browse files
CharmHub Info
The following introduces queries to the charmhub store API. Now that all requests go through a juju controller it makes it very trivial for other clients like pylibjuju to gain the same powers as the go CLI. The code is rather simple, it farms off a new type to encapsulate the charmhub related calls (info and find which will follow) as the model type is large and unwieldly, once there we just create a new facade and perform the query. Offically it would be nice to have to channel validation, but for now we'll let the controller handle that.
1 parent 73604c8 commit 9016398

4 files changed

Lines changed: 97 additions & 0 deletions

File tree

examples/charmhub.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
"""
2+
Example to show how to connect to the current model and query the charm-hub
3+
store for information about a given charm.
4+
"""
5+
import logging
6+
7+
from juju import loop
8+
from juju.model import Model
9+
10+
log = logging.getLogger(__name__)
11+
12+
13+
async def main():
14+
model = Model()
15+
try:
16+
# connect to the current model with the current user, per the Juju CLI
17+
await model.connect()
18+
19+
charm = await model.charmhub.info("mattermost")
20+
print(charm)
21+
finally:
22+
if model.is_connected():
23+
print('Disconnecting from model')
24+
await model.disconnect()
25+
26+
27+
if __name__ == '__main__':
28+
logging.basicConfig(level=logging.INFO)
29+
loop.run(main())

juju/charmhub.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
from .client import client
2+
from .errors import JujuError
3+
4+
5+
class CharmHub:
6+
def __init__(self, model):
7+
self.model = model
8+
9+
async def info(self, name, channel=None):
10+
"""info displays detailed information about a CharmHub charm. The charm
11+
can be specified by name or by path.
12+
13+
"""
14+
if not name:
15+
raise JujuError("name expected")
16+
17+
if channel is None:
18+
channel = "latest"
19+
20+
facade = self._facade()
21+
return await facade.Info(tag="application-{}".format(name), channel=channel)
22+
23+
def _facade(self):
24+
return client.CharmHubFacade.from_connection(self.model.connection())

juju/model.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
from . import provisioner, tag, utils
2424
from .annotationhelper import _get_annotations, _set_annotations
2525
from .bundle import BundleHandler, get_charm_series
26+
from .charmhub import CharmHub
2627
from .client import client, connector
2728
from .client.client import ConfigValue, Value
2829
from .client.overrides import Caveat, Macaroon
@@ -776,6 +777,14 @@ def relations(self):
776777
"""
777778
return list(self.state.relations.values())
778779

780+
@property
781+
def charmhub(self):
782+
"""Return a charmhub store for requesting charm information using
783+
the charm-hub-url model config.
784+
785+
"""
786+
return CharmHub(self)
787+
779788
async def get_info(self):
780789
"""Return a client.ModelInfo object for this Model.
781790

tests/integration/test_charmhub.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import pytest
2+
3+
from .. import base
4+
from juju.errors import JujuAPIError
5+
6+
7+
@base.bootstrapped
8+
@pytest.mark.asyncio
9+
async def test_info(event_loop):
10+
async with base.CleanModel() as model:
11+
result = await model.charmhub.info("mattermost")
12+
13+
assert result.result.name == "mattermost"
14+
15+
16+
@base.bootstrapped
17+
@pytest.mark.asyncio
18+
async def test_info_with_channel(event_loop):
19+
async with base.CleanModel() as model:
20+
result = await model.charmhub.info("mattermost", "latest/stable")
21+
22+
assert result.result.name == "mattermost"
23+
assert "latest/stable" in result.result.channel_map
24+
25+
26+
@base.bootstrapped
27+
@pytest.mark.asyncio
28+
async def test_info_not_found(event_loop):
29+
async with base.CleanModel() as model:
30+
try:
31+
await model.charmhub.info("badnameforapp")
32+
except JujuAPIError as e:
33+
assert e.message == "No charm or bundle with name 'badnameforapp'."
34+
else:
35+
raise

0 commit comments

Comments
 (0)