Skip to content

Commit 0145b73

Browse files
authored
Merge branch 'master' into fix/machine_ssh_cherry_picked
2 parents d7c7f2a + 16149db commit 0145b73

64 files changed

Lines changed: 63856 additions & 688 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/test.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ jobs:
6565
uses: charmed-kubernetes/actions-operator@main
6666
with:
6767
provider: lxd
68-
juju-channel: 3.2/stable
68+
juju-channel: 3.3/stable
6969
# 2023-01-11 Commented until we discover a
7070
# clear approach for this.
7171
# - name: Set proxy in controller
@@ -102,4 +102,4 @@ jobs:
102102
run: pip install tox
103103
- name: Run integration
104104
# Force one single concurrent test
105-
run: tox -e integration -- -n 1
105+
run: tox -e integration

.github/workflows/test_candidate.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ jobs:
5454
uses: charmed-kubernetes/actions-operator@main
5555
with:
5656
provider: lxd
57-
juju-channel: 3.2/candidate
57+
juju-channel: 3.3/candidate
5858
- name: Setup Python
5959
if: ${{ env.next-test != 'NA' }}
6060
uses: actions/setup-python@v4

.github/workflows/test_edge.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ jobs:
5454
uses: charmed-kubernetes/actions-operator@main
5555
with:
5656
provider: lxd
57-
juju-channel: 3.2/edge
57+
juju-channel: 3.3/edge
5858
- name: Setup Python
5959
if: ${{ env.next-test != 'NA' }}
6060
uses: actions/setup-python@v4

Makefile

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,17 @@ client:
1919
tox -r --notest -e lint,py3
2020
$(PY) -m juju.client.facade -s "juju/client/schemas*" -o juju/client/
2121

22-
.PHONY: test
23-
test: lint
22+
.PHONY: run-unit-tests
23+
run-unit-tests: lint .tox
2424
tox -e py3
25+
26+
.PHONY: run-integration-tests
27+
run-integration-tests: lint .tox
2528
tox -e integration
2629

30+
.PHONY: run-all-tests
31+
test: run-unit-tests run-integration-tests
32+
2733
.PHONY: lint
2834
lint:
2935
@./scripts/copyright.sh
@@ -39,7 +45,8 @@ release:
3945
git fetch --tags
4046
rm dist/*.tar.gz || true
4147
$(PY) setup.py sdist
42-
$(BIN)/twine upload --repository-url https://upload.pypi.org/legacy/ dist/*
48+
$(BIN)/twine check dist/*
49+
$(BIN)/twine upload --repository juju dist/*
4350
git tag ${VERSION}
4451
git push --tags
4552

VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
3.2.0.1
1+
3.3.0.0

docs/changelog.rst

Lines changed: 67 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,78 @@
11
Changelog
22
---------
33

4+
3.3.0.0
5+
^^^^^^^
6+
7+
## What's Changed
8+
9+
The main contribution of this release is the user secrets that's released as a part of Juju 3.3.
10+
11+
Thursday 30th Nov 2023
12+
13+
* Free pyblijuju from relying on juju client when connecting to a controller by @cderici in https://github.com/juju/python-libjuju/pull/984
14+
* Handle FileNotFoundError on current_controller() by @DanielArndt in https://github.com/juju/python-libjuju/pull/937
15+
* Add support for adding user secrets by @cderici in https://github.com/juju/python-libjuju/pull/986
16+
* Complete support for user secrets by @cderici in https://github.com/juju/python-libjuju/pull/987
17+
18+
3.2.3.0
19+
^^^^^^^
20+
21+
Thursday 26th Oct 2023
22+
23+
* Repository Maintenance Improvements by @cderici in https://github.com/juju/python-libjuju/pull/922
24+
* Stale bot to not bother feature requests by @cderici in https://github.com/juju/python-libjuju/pull/926
25+
* Fix linter issues by @cderici in https://github.com/juju/python-libjuju/pull/928
26+
* Fix docstring typo by @DanielArndt in https://github.com/juju/python-libjuju/pull/927
27+
* Fix asyncio on README by @marceloneppel in https://github.com/juju/python-libjuju/pull/930
28+
* Fix integration/test_application.test_action by @cderici in https://github.com/juju/python-libjuju/pull/932
29+
* Update 3.2 facade clients by @cderici in https://github.com/juju/python-libjuju/pull/931
30+
* [JUJU-4488] Add licence headers to source files on 3.x by @cderici in https://github.com/juju/python-libjuju/pull/934
31+
* Update async tests to use builtin python suite by @DanielArndt in https://github.com/juju/python-libjuju/pull/935
32+
* Pass correct charm url to series selector by @cderici in https://github.com/juju/python-libjuju/pull/942
33+
* Green CI cleanup for python-libjuju by @cderici in https://github.com/juju/python-libjuju/pull/939
34+
* Bring forward support for nested assumes expressions on 3x by @cderici in https://github.com/juju/python-libjuju/pull/943
35+
* Release 3.2.2.0 notes by @cderici in https://github.com/juju/python-libjuju/pull/945
36+
* Cleanup release process for 3.x by @cderici in https://github.com/juju/python-libjuju/pull/946
37+
* Use new DeployFromRepository endpoint for deploy by @cderici in https://github.com/juju/python-libjuju/pull/949
38+
* Handle pending upload resources deployfromrepository by @cderici in https://github.com/juju/python-libjuju/pull/953
39+
* Optimize connection teardown by @cderici in https://github.com/juju/python-libjuju/pull/952
40+
* Use `log.warning` instead of the deprecated `warn` by @sed-i in https://github.com/juju/python-libjuju/pull/954
41+
* Find controller name by endpoint on 3.x track by @cderici in https://github.com/juju/python-libjuju/pull/966
42+
* Optimize & fix unit removal by @cderici in https://github.com/juju/python-libjuju/pull/967
43+
* Allow switch kwarg in refresh to switch to local charms by @jack-w-shaw in https://github.com/juju/python-libjuju/pull/971
44+
* Parse charm URLs consistantly for local charms by @jack-w-shaw in https://github.com/juju/python-libjuju/pull/974
45+
* Juju config directory location fix on 3.x by @cderici in https://github.com/juju/python-libjuju/pull/976
46+
* [JUJU-4779] Ensure valid charm origin for local charm switches by @jack-w-shaw in https://github.com/juju/python-libjuju/pull/978
47+
* Application refresh with resources on 3.x by @cderici in https://github.com/juju/python-libjuju/pull/973
48+
49+
3.2.2.0
50+
^^^^^^^
51+
52+
Wednesday 6th September 2023
53+
54+
This is a minor release on the 3.x track, works with any Juju 3.x controller.
55+
56+
* Repository Maintenance Improvements by @cderici in https://github.com/juju/python-libjuju/pull/922
57+
* Stale bot to not bother feature requests by @cderici in https://github.com/juju/python-libjuju/pull/926
58+
* Fix linter issues by @cderici in https://github.com/juju/python-libjuju/pull/928
59+
* Fix docstring typo by @DanielArndt in https://github.com/juju/python-libjuju/pull/927
60+
* Fix asyncio on README by @marceloneppel in https://github.com/juju/python-libjuju/pull/930
61+
* Fix integration/test_application.test_action by @cderici in https://github.com/juju/python-libjuju/pull/932
62+
* Update 3.2 facade clients by @cderici in https://github.com/juju/python-libjuju/pull/931
63+
* [JUJU-4488] Add licence headers to source files on 3.x by @cderici in https://github.com/juju/python-libjuju/pull/934
64+
* Update async tests to use builtin python suite by @DanielArndt in https://github.com/juju/python-libjuju/pull/935
65+
* Pass correct charm url to series selector by @cderici in https://github.com/juju/python-libjuju/pull/942
66+
* Green CI cleanup for python-libjuju by @cderici in https://github.com/juju/python-libjuju/pull/939
67+
* Bring forward support for nested assumes expressions on 3x by @cderici in https://github.com/juju/python-libjuju/pull/943
68+
469
3.2.0.1
570
^^^^^^^
671

772
Thursday 20th July 2023
873

974
This is a point release on the 3.x track, works with any Juju 3.x controller.
1075

11-
What's Changed
12-
==============
13-
1476
* Update readme and add some docstrings for functions by @cderici in https://github.com/juju/python-libjuju/pull/873
1577
* Forward port subordinate utils by @cderici in https://github.com/juju/python-libjuju/pull/880
1678
* [JUJU-3952] Revisit access control levels by @cderici in https://github.com/juju/python-libjuju/pull/882
@@ -81,8 +143,7 @@ This version is only tested using Juju 3.1.0.
81143
* Add compatibility for juju 3.1.0 by @juanmanuel-tirado in https://github.com/juju/python-libjuju/pull/799
82144
* Replace schemas.json with a wellformed version. by @juanmanuel-tirado in https://github.com/juju/python-libjuju/pull/800
83145

84-
New Contributors
85-
================
146+
## New Contributors
86147

87148
* @mert-kirpici made their first contribution in https://github.com/juju/python-libjuju/pull/731
88149

@@ -97,8 +158,7 @@ Wednesday 26th October
97158
* [JUJU-2026] Improve resolve charm by @cderici in https://github.com/juju/python-libjuju/pull/761
98159
* Add owner and data to license file by @arturo-seijas in https://github.com/juju/python-libjuju/pull/760
99160

100-
New Contributors
101-
================
161+
## New Contributors
102162

103163
* @arturo-seijas made their first contribution in https://github.com/juju/python-libjuju/pull/760
104164

docs/readme.rst

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,7 @@ Here's a simple example that shows basic usage of the library. The example
4040
connects to the currently active Juju model, deploys a single unit of the
4141
ubuntu charm, then exits:
4242

43-
.. note::
44-
45-
Pylibjuju requires an already bootstrapped Juju controller to connect to.
43+
Note : Pylibjuju requires an already bootstrapped Juju controller to connect to.
4644

4745
.. code:: python
4846

examples/local_refresh.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ async def main():
2020

2121
try:
2222
print('Get deployed application')
23-
app = model.appplications["ubuntu"]
23+
app = model.applications["ubuntu"]
2424

2525
print('Refresh/Upgrade Ubuntu charm with local charm')
2626
await app.refresh(path="path/to/local/ubuntu.charm")

juju/application.py

Lines changed: 68 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,20 @@
44
import hashlib
55
import json
66
import logging
7-
import pathlib
7+
import typing
8+
from pathlib import Path
89

9-
from . import model, tag, utils, jasyncio
10-
from .url import URL
11-
from .status import derive_status
10+
from . import jasyncio, model, tag, utils
1211
from .annotationhelper import _get_annotations, _set_annotations
12+
from .bundle import get_charm_series, is_local_charm
1313
from .client import client
14-
from .errors import JujuError, JujuApplicationConfigError
15-
from .bundle import get_charm_series
16-
from .placement import parse as parse_placement
14+
from .errors import JujuApplicationConfigError, JujuError
1715
from .origin import Channel, Source
16+
from .placement import parse as parse_placement
17+
from .relation import Relation
18+
from .status import derive_status
19+
from .url import URL
20+
from .utils import block_until
1821

1922
log = logging.getLogger(__name__)
2023

@@ -59,14 +62,14 @@ def subordinate_units(self):
5962
return [u for u in self.units if u.is_subordinate]
6063

6164
@property
62-
def relations(self):
65+
def relations(self) -> typing.List[Relation]:
6366
return [rel for rel in self.model.relations if rel.matches(self.name)]
6467

6568
def related_applications(self, endpoint_name=None):
6669
apps = {}
6770
for rel in self.relations:
6871
if rel.is_peer:
69-
local_ep, remote_ep = rel.endpoints[0]
72+
local_ep, remote_ep = rel.endpoints
7073
else:
7174
def is_us(ep):
7275
return ep.application.name == self.name
@@ -191,12 +194,13 @@ async def scale(self, scale=None, scale_change=None):
191194
scale_change=scale_change)
192195
])
193196

194-
async def destroy_relation(self, local_relation, remote_relation):
197+
async def destroy_relation(self, local_relation, remote_relation, block_until_done: bool = False):
195198
"""Remove a relation to another application.
196199
197200
:param str local_relation: Name of relation on this application
198201
:param str remote_relation: Name of relation on the other
199202
application in the form '<application>[:<relation_name>]'
203+
:param bool block_until_done: Wait until the relation is completely removed.
200204
201205
"""
202206
if ':' not in local_relation:
@@ -207,8 +211,16 @@ async def destroy_relation(self, local_relation, remote_relation):
207211
log.debug(
208212
'Destroying relation %s <-> %s', local_relation, remote_relation)
209213

210-
return await app_facade.DestroyRelation(endpoints=[
214+
await app_facade.DestroyRelation(endpoints=[
211215
local_relation, remote_relation])
216+
if block_until_done:
217+
await block_until(
218+
lambda: not any(
219+
relation.matches(local_relation, remote_relation)
220+
for relation in self.relations
221+
)
222+
)
223+
212224
remove_relation = destroy_relation
213225

214226
async def destroy_unit(self, *unit_names):
@@ -661,6 +673,8 @@ async def refresh(
661673
:param str switch: Crossgrade charm url
662674
663675
"""
676+
if switch is not None and path is not None:
677+
raise ValueError("switch and path are mutually exclusive")
664678

665679
if switch is not None and revision is not None:
666680
raise ValueError("switch and revision are mutually exclusive")
@@ -677,17 +691,15 @@ async def refresh(
677691
if charm_url_origin_result.error is not None:
678692
err = charm_url_origin_result.error
679693
raise JujuError(f'{err.code} : {err.message}')
680-
charm_url = switch or charm_url_origin_result.url
681694
origin = charm_url_origin_result.charm_origin
682695

683-
if path is not None:
696+
if path is not None or (switch is not None and is_local_charm(switch)):
684697
await self.local_refresh(origin, force, force_series,
685-
force_units, path, resources)
698+
force_units, path or switch, resources)
686699
return
687700

688-
if resources is not None:
689-
raise NotImplementedError("resources option is not implemented")
690-
701+
# If switch is not None at this point, that means it's a switch to a store charm
702+
charm_url = switch or charm_url_origin_result.url
691703
parsed_url = URL.parse(charm_url)
692704
charm_name = parsed_url.name
693705

@@ -735,6 +747,20 @@ async def refresh(
735747

736748
# Now take care of the resources:
737749

750+
# user supplied resources to be used in refresh,
751+
# will override the default values if there's any
752+
arg_resources = resources or {}
753+
754+
# need to process the given resources, as they can be
755+
# paths or revisions
756+
_arg_res_filenames = {}
757+
_arg_res_revisions = {}
758+
for res, filename_or_rev in arg_resources.items():
759+
if isinstance(filename_or_rev, int):
760+
_arg_res_revisions[res] = filename_or_rev
761+
else:
762+
_arg_res_filenames[res] = filename_or_rev
763+
738764
# Already prepped the charm_resources
739765
# Now get the existing resources from the ResourcesFacade
740766
request_data = [client.Entity(self.tag)]
@@ -748,23 +774,25 @@ async def refresh(
748774
# Compute the difference btw resources needed and the existing resources
749775
resources_to_update = []
750776
for resource in charm_resources:
751-
if utils.should_upgrade_resource(resource, existing_resources):
777+
if utils.should_upgrade_resource(resource, existing_resources, arg_resources):
752778
resources_to_update.append(resource)
753779

754780
# Update the resources
755781
if resources_to_update:
756782
request_data = []
757783
for resource in resources_to_update:
784+
res_name = resource.get('Name', resource.get('name'))
758785
request_data.append(client.CharmResource(
759786
description=resource.get('Description', resource.get('description')),
760-
fingerprint=resource.get('Fingerprint', resource.get('fingerprint')),
761-
name=resource.get('Name', resource.get('name')),
762-
path=resource.get('Path', resource.get('filename')),
763-
revision=resource.get('Revision', resource.get('revision', -1)),
764-
size=resource.get('Size', resource.get('size')),
787+
name=res_name,
788+
path=_arg_res_filenames.get(res_name,
789+
resource.get('Path',
790+
resource.get('filename', ''))),
791+
revision=_arg_res_revisions.get(res_name, -1),
765792
type_=resource.get('Type', resource.get('type')),
766793
origin='store',
767794
))
795+
768796
response = await resources_facade.AddPendingResources(
769797
application_tag=self.tag,
770798
charm_url=charm_url,
@@ -808,22 +836,22 @@ async def local_refresh(
808836
path=None, resources=None):
809837
"""Refresh the charm for this application with a local charm.
810838
811-
:param str channel: Channel to use when getting the charm from the
812-
charm store, e.g. 'development'
839+
:param dict charm_origin: The charm origin of the destination charm
840+
we're refreshing to
841+
:param bool force: Refresh even if validation checks fail
813842
:param bool force_series: Refresh even if series of deployed
814843
application is not supported by the new charm
815844
:param bool force_units: Refresh all units immediately, even if in
816845
error state
817846
:param str path: Refresh to a charm located at path
818847
:param dict resources: Dictionary of resource name/filepath pairs
819-
:param int revision: Explicit refresh revision
820-
:param str switch: Crossgrade charm url
821848
822849
"""
823850
app_facade = self._facade()
824851

825-
if not isinstance(path, pathlib.Path):
826-
path = pathlib.Path(path)
852+
if isinstance(path, str) and path.startswith("local:"):
853+
path = path[6:]
854+
path = Path(path)
827855
charm_dir = path.expanduser().resolve()
828856
model_config = await self.get_config()
829857

@@ -847,6 +875,17 @@ async def local_refresh(
847875
metadata,
848876
resources=resources)
849877

878+
# We know this charm is a local charm, but this charm origin could be
879+
# the charm origin of a charmhub charm. Ensure that we update/remove
880+
# the appropriate fields.
881+
charm_origin.source = "local"
882+
charm_origin.track = None
883+
charm_origin.risk = None
884+
charm_origin.branch = None
885+
charm_origin.hash_ = None
886+
charm_origin.id_ = None
887+
charm_origin.revision = URL.parse(charm_url).revision
888+
850889
set_charm_args = {
851890
'application': self.entity_id,
852891
'charm_origin': charm_origin,

0 commit comments

Comments
 (0)