diff --git a/charmcraft/commands/store/__init__.py b/charmcraft/commands/store/__init__.py index e595b0345..07395b03e 100644 --- a/charmcraft/commands/store/__init__.py +++ b/charmcraft/commands/store/__init__.py @@ -358,6 +358,16 @@ def fill_parser(self, parser): action="append", help="The channel(s) to release to (this option can be indicated multiple times)", ) + parser.add_argument( + "--resource", + action="append", + type=ResourceOption(), + default=[], + help=( + "The resource(s) to attach to the release, in the : format " + "(this option can be indicated multiple times)" + ), + ) def _validate_template_is_handled(self, filepath): """Verify the zip does not have any file with the 'init' template TODO marker. @@ -391,8 +401,20 @@ def run(self, parsed_args): logger.info("Revision %s of %r created", result.revision, str(name)) if parsed_args.release: # also release! - store.release(name, result.revision, parsed_args.release) - logger.info("Revision released to %s", ", ".join(parsed_args.release)) + store.release( + name, result.revision, parsed_args.release, parsed_args.resource + ) + msg = "Revision released to %s" + args = [", ".join(parsed_args.release)] + if parsed_args.resource: + msg += " (attaching resources: %s)" + args.append( + ", ".join( + "{!r} r{}".format(r.name, r.revision) + for r in parsed_args.resource + ) + ) + logger.info(msg, *args) else: logger.info("Upload failed with status %r:", result.status) for error in result.errors: diff --git a/completion.bash b/completion.bash index b3576cb8c..e01ddbf1f 100644 --- a/completion.bash +++ b/completion.bash @@ -114,7 +114,7 @@ _charmcraft() COMPREPLY=( $(compgen -W "${globals[*]} --name --author --force" -- "$cur") ) ;; upload) - COMPREPLY=( $(compgen -W "${globals[*]} --release" -- "$cur") ) + COMPREPLY=( $(compgen -W "${globals[*]} --release --resource" -- "$cur") ) ;; upload-resource) case "$prev" in diff --git a/tests/commands/test_store_commands.py b/tests/commands/test_store_commands.py index c3e53c804..7cffc9ba3 100644 --- a/tests/commands/test_store_commands.py +++ b/tests/commands/test_store_commands.py @@ -487,12 +487,12 @@ def test_upload_call_ok_including_release(caplog, store_mock, config, tmp_path): test_charm = tmp_path / "mystuff.charm" _build_zip_with_yaml(test_charm, "metadata.yaml", content={"name": "mycharm"}) - args = Namespace(filepath=test_charm, release=["edge"]) + args = Namespace(filepath=test_charm, release=["edge"], resource=[]) UploadCommand("group", config).run(args) assert store_mock.mock_calls == [ call.upload("mycharm", test_charm), - call.release("mycharm", 7, ["edge"]), + call.release("mycharm", 7, ["edge"], []), ] expected = [ "Revision 7 of 'mycharm' created", @@ -512,12 +512,12 @@ def test_upload_call_ok_including_release_multiple( test_charm = tmp_path / "mystuff.charm" _build_zip_with_yaml(test_charm, "metadata.yaml", content={"name": "mycharm"}) - args = Namespace(filepath=test_charm, release=["edge", "stable"]) + args = Namespace(filepath=test_charm, release=["edge", "stable"], resource=[]) UploadCommand("group", config).run(args) assert store_mock.mock_calls == [ call.upload("mycharm", test_charm), - call.release("mycharm", 7, ["edge", "stable"]), + call.release("mycharm", 7, ["edge", "stable"], []), ] expected = [ "Revision 7 of 'mycharm' created", @@ -526,6 +526,40 @@ def test_upload_call_ok_including_release_multiple( assert expected == [rec.message for rec in caplog.records] +def test_upload_including_release_with_resources(caplog, store_mock, config, tmp_path): + """Releasing with resources.""" + caplog.set_level(logging.INFO, logger="charmcraft.commands") + + store_response = Uploaded(ok=True, status=200, revision=7, errors=[]) + store_mock.upload.return_value = store_response + + test_charm = tmp_path / "mystuff.charm" + _build_zip_with_yaml(test_charm, "metadata.yaml", content={"name": "mycharm"}) + r1 = ResourceOption(name="foo", revision=3) + r2 = ResourceOption(name="bar", revision=17) + args = Namespace(filepath=test_charm, release=["edge"], resource=[r1, r2]) + UploadCommand("group", config).run(args) + + assert store_mock.mock_calls == [ + call.upload("mycharm", test_charm), + call.release("mycharm", 7, ["edge"], [r1, r2]), + ] + expected = [ + "Revision 7 of 'mycharm' created", + "Revision released to edge (attaching resources: 'foo' r3, 'bar' r17)", + ] + assert expected == [rec.message for rec in caplog.records] + + +def test_upload_options_resource(config): + """The --resource option implies a set of validations.""" + cmd = UploadCommand("group", config) + parser = ArgumentParser() + cmd.fill_parser(parser) + (action,) = [action for action in parser._actions if action.dest == "resource"] + assert isinstance(action.type, ResourceOption) + + def test_upload_call_error_including_release(caplog, store_mock, config, tmp_path): """Upload with a realsea but the upload went wrong, so no release.""" caplog.set_level(logging.INFO, logger="charmcraft.commands")