Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
jack-w-shaw committed Oct 23, 2023
1 parent 5134335 commit c143fb8
Show file tree
Hide file tree
Showing 4 changed files with 92 additions and 63 deletions.
59 changes: 38 additions & 21 deletions juju/bundle.py
Original file line number Diff line number Diff line change
Expand Up @@ -223,24 +223,28 @@ def _resolve_include_file_config(self, bundle_dir):

return self.bundle, self.overlays

async def fetch_plan(self, charm_url, origin, overlays=[]):
entity_id = charm_url.path()
is_local = Schema.LOCAL.matches(charm_url.schema)
async def fetch_plan(self, bundle, origin, overlays=[]):
bundle_dir = None

if is_local and os.path.isfile(entity_id):
bundle_yaml = Path(entity_id).read_text()
bundle_dir = Path(entity_id).parent
elif is_local and os.path.isdir(entity_id):
bundle_yaml = (Path(entity_id) / "bundle.yaml").read_text()
bundle_dir = Path(entity_id)
if is_local_bundle(str(bundle)):
path = str(bundle)
if path.startswith("local:"):
path = path[6:]
bundle_yaml, bundle_dir = read_local_bundle(path)

if Schema.CHARM_STORE.matches(charm_url.schema):
bundle_yaml = await self.charmstore.files(entity_id,
filename='bundle.yaml',
read_file=True)
elif Schema.CHARM_HUB.matches(charm_url.schema):
bundle_yaml = await self._download_bundle(charm_url, origin)
else:
if client.CharmsFacade.best_facade_version(self.connection()) < 3:
url = URL.parse(bundle, default_store=Schema.CHARM_STORE)
else:
url = URL.parse(bundle)
path = url.path

if Schema.CHARM_STORE.matches(url.schema):
bundle_yaml = await self.charmstore.files(bundle,
filename='bundle.yaml',
read_file=True)
elif Schema.CHARM_HUB.matches(url.schema):
bundle_yaml = await self._download_bundle(charm_url, origin)

if not bundle_yaml:
raise JujuError('empty bundle, nothing to deploy')
Expand All @@ -267,7 +271,7 @@ async def fetch_plan(self, charm_url, origin, overlays=[]):

self.bundle = await self._validate_bundle(self.bundle)

if is_local:
if is_local_bundle(path):
self.bundle = await self._handle_local_charms(self.bundle, bundle_dir)

self.bundle, self.overlays = self._resolve_include_file_config(bundle_dir)
Expand All @@ -278,7 +282,7 @@ async def fetch_plan(self, charm_url, origin, overlays=[]):
yaml_data = "---\n".join(_yaml_data)

self.plan = await self.bundle_facade.GetChanges(
bundleurl=entity_id,
bundleurl=path,
yaml=yaml_data)

if self.plan.errors:
Expand Down Expand Up @@ -415,6 +419,19 @@ def resolve(self, reference):
def is_local_charm(charm_url):
return charm_url.startswith('.') or charm_url.startswith('local:') or os.path.isabs(charm_url)

is_local_bundle = is_local_charm

def read_local_bundle(path):
path = Path(path)
if os.path.isfile(path):
bundle_yaml = path.read_text()
bundle_dir = path.parent
elif os.path.isdir(path):
bundle_yaml = (path / "bundle.yaml").read_text()
bundle_dir = path

return (bundle_yaml, bundle_dir)


async def get_charm_series(metadata, model):
"""Inspects the given metadata and returns a default series from its
Expand Down Expand Up @@ -709,14 +726,14 @@ async def run(self, context):

# We don't add local charms because they've already been added
# by self._handle_local_charms
url = URL.parse(str(self.charm))
ch = None
identifier = None
if Schema.LOCAL.matches(url.schema):
if is_local_charm(str(self.charm)):
origin = client.CharmOrigin(source="local", risk="stable")
context.origins[self.charm] = {str(None): origin}
return self.charm

url = URL.parse(str(self.charm))
ch = None
identifier = None
if Schema.CHARM_STORE.matches(url.schema):
entity_id = await context.charmstore.entityId(self.charm, channel=self.channel)
log.debug('Adding %s', entity_id)
Expand Down
72 changes: 36 additions & 36 deletions juju/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -452,18 +452,16 @@ class LocalDeployType:
"""LocalDeployType deals with local only deployments.
"""

async def resolve(self, url, architecture, app_name=None, channel=None, series=None, revision=None,
async def resolve(self, charm_path, architecture, app_name=None, channel=None, series=None, revision=None,
entity_url=None, force=False):
"""resolve attempts to resolve a local charm or bundle using the url
and architecture. If information is missing, it will attempt to backfill
that information, before sending the result back.
"""

entity_url = url.path()
entity_path = Path(entity_url)
entity_path = Path(charm_path)
bundle_path = entity_path / 'bundle.yaml'

identifier = entity_url
origin = client.CharmOrigin(source="local", architecture=architecture)
if not (entity_path.is_dir() or entity_path.is_file()):
raise JujuError('{} path not found'.format(entity_url))
Expand All @@ -474,28 +472,23 @@ async def resolve(self, url, architecture, app_name=None, channel=None, series=N
)

if app_name is None:
app_name = url.name

if not is_bundle:
entity_url = url.path()
entity_path = Path(entity_url)
if entity_path.suffix == '.charm':
with zipfile.ZipFile(str(entity_path), 'r') as charm_file:
metadata = yaml.load(charm_file.read('metadata.yaml'), Loader=yaml.FullLoader)
if is_bundle:
if entity_path.suffix == '.yaml' and entity_path.exists():
bundle = yaml.load(entity_path.read_text(), Loader=yaml.FullLoader)
else:
metadata_path = entity_path / 'metadata.yaml'
metadata = yaml.load(metadata_path.read_text(), Loader=yaml.FullLoader)
app_name = metadata['name']
bundle = yaml.load(bundle_path.read_text(), Loader=yaml.FullLoader)
app_name = bundle['name']
else:
app_name = get_local_charm_metadata(entity_path)["name"]

return DeployTypeResult(
identifier=identifier,
identifier=charm_path,
origin=origin,
app_name=app_name,
is_local=True,
is_bundle=is_bundle,
)


class CharmStoreDeployType:
"""CharmStoreDeployType defines a class for resolving and deploying charm
store charms and bundle.
Expand Down Expand Up @@ -1603,7 +1596,7 @@ async def deploy(
storage=None, to=None, devices=None, trust=False):
"""Deploy a new service or bundle.
:param str entity_url: Charm or bundle url
:param str entity_url: Charm or bundle to deploy. Not necessarily a url
:param str application_name: Name to give the service
:param dict bind: <charm endpoint>:<network space> pairs
:param str channel: Charm store channel from which to retrieve
Expand Down Expand Up @@ -1646,23 +1639,30 @@ async def deploy(
raise NotImplementedError("trusted is not supported on model version {}".format(self.info.agent_version))

# Ensure what we pass in, is a string.
entity_url = str(entity_url)
if is_local_charm(entity_url) and not entity_url.startswith("local:"):
entity_url = "local:{}".format(entity_url)
entity = str(entity_url)
if is_local_charm(entity):
if entity.startswith("local:"):
entity = entity[6:]
architecture = await self._resolve_architecture()
schema = "local"

if client.CharmsFacade.best_facade_version(self.connection()) < 3:
url = URL.parse(str(entity_url), default_store=Schema.CHARM_STORE)
else:
url = URL.parse(str(entity_url))
if client.CharmsFacade.best_facade_version(self.connection()) < 3:
url = URL.parse(entity, default_store=Schema.CHARM_STORE)
else:
url = URL.parse(entity)
entity = str(url)

architecture = await self._resolve_architecture(url)
architecture = await self._resolve_architecture(url)
schema = url.scheme
name = url.name

if str(url.schema) not in self.deploy_types:
raise JujuError("unknown deploy type {}, expected charmhub, charmstore or local".format(url.schema))
if schema not in self.deploy_types:
raise JujuError("unknown deploy type {}, expected charmhub, charmstore or local".format(schema))

res = await self.deploy_types[str(url.schema)].resolve(url, architecture,
application_name, channel, series,
revision, entity_url, force)
res = await self.deploy_types[schema].resolve(entity, architecture,
application_name, channel, series,
revision, entity_url, force)

if res.identifier is None:
raise JujuError('unknown charm or bundle {}'.format(entity_url))
Expand All @@ -1672,7 +1672,7 @@ async def deploy(
charm_series = series
if res.is_bundle:
handler = BundleHandler(self, trusted=trust, forced=force)
await handler.fetch_plan(url, res.origin, overlays=overlays)
await handler.fetch_plan(entity, res.origin, overlays=overlays)
await handler.execute_plan()
extant_apps = {app for app in self.applications}
pending_apps = handler.applications - extant_apps
Expand Down Expand Up @@ -1700,11 +1700,11 @@ async def deploy(
else:
charm_origin = add_charm_res.charm_origin

if Schema.CHARM_HUB.matches(url.schema):
if Schema.CHARM_HUB.matches(schema):
resources = await self._add_charmhub_resources(res.app_name,
identifier,
add_charm_res.charm_origin)
charm_info = await self.charmhub.info(url.name)
charm_info = await self.charmhub.info(name)
is_subordinate = False
try:
is_subordinate = charm_info.result.charm.subordinate
Expand All @@ -1715,7 +1715,7 @@ async def deploy(
raise JujuError("cannot use num_units with subordinate application")
num_units = 0

if Schema.CHARM_STORE.matches(url.schema):
if Schema.CHARM_STORE.matches(schema):
resources = await self._add_store_resources(res.app_name,
identifier)
else:
Expand Down Expand Up @@ -1820,8 +1820,8 @@ async def _resolve_charm(self, url, origin, force=False):

return result.url, result.charm_origin

async def _resolve_architecture(self, url):
if url.architecture:
async def _resolve_architecture(self, url=None):
if url is not None and url.architecture:
return url.architecture

constraints = await self.get_constraints()
Expand Down
20 changes: 14 additions & 6 deletions juju/url.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,11 @@ def parse(s, default_store=Schema.CHARM_HUB):
if u.query != "" or u.fragment != "" or u.username or u.password:
raise JujuError("charm or bundle URL {} has unrecognized parts".format(u))

if Schema.LOCAL.matches(u.scheme):
c = URL(Schema.LOCAL, name=u.path)
elif Schema.CHARM_STORE.matches(u.scheme) or \
if Schema.CHARM_STORE.matches(u.scheme) or \
(u.scheme == "" and Schema.CHARM_STORE.matches(default_store)):
c = parse_v1_url(Schema.CHARM_STORE, u, s)
else:
c = parse_v2_url(u, s)
c = parse_v2_url(u, s, default_store)

if not c or not c.schema:
raise JujuError("expected schema for charm or bundle URL {}".format(u))
Expand Down Expand Up @@ -121,8 +119,18 @@ def parse_v1_url(schema, u, s):
return c


def parse_v2_url(u, s):
c = URL(Schema.CHARM_HUB)
def parse_v2_url(u, s, default_store):
if not u.scheme:
c = URL(default_store)
elif Schema.CHARM_HUB.matches(u.scheme):
c = URL(Schema.CHARM_HUB)
elif Schema.LOCAL.matches(u.scheme):
c = URL(Schema.LOCAL)
else:
raise JujuError("invalid charm url schema {}".format(u.scheme))

# local:bionic/ubu-12
# local:/path/to/cha/ubunt

parts = u.path.split("/")
num = len(parts)
Expand Down
4 changes: 4 additions & 0 deletions tests/unit/test_url.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,7 @@ def test_parse_v2_revision(self):
def test_parse_v2_large_revision(self):
u = URL.parse("ch:mysql-12345")
self.assertEqual(u, URL(Schema.CHARM_HUB, name="mysql", revision=12345))

def test_parse_v2_without_store(self):
u = URL.parse("mysql-1", default_store=Schema.CHARM_HUB)
self.assertEqual(u, URL(Schema.CHARM_HUB, name="mysql", revision=1))

0 comments on commit c143fb8

Please sign in to comment.