Repository: canonical/charmcraft

Issue:
Implement `charmcraft close`
There needs to be a way to close channels, bringing over syntax from snapcraft, the following syntax should be possible:

    charmcraft close <name> <channel>...


Comments:

Patch:
diff --git a/charmcraft/commands/store/__init__.py b/charmcraft/commands/store/__init__.py
index 010e273..eeec82b 100644
--- a/charmcraft/commands/store/__init__.py
+++ b/charmcraft/commands/store/__init__.py
@@ -507,7 +507,7 @@ class ReleaseCommand(BaseCommand):
             charmcraft release mycharm --revision=14 \\
                 --channel=beta --resource=thedb:4
 
-        Listing revisions will take you through login if needed.
+        Releasing a revision will take you through login if needed.
     """
     )
     common = True
@@ -560,6 +560,43 @@ class ReleaseCommand(BaseCommand):
         logger.info(msg, *args)
 
 
+class CloseCommand(BaseCommand):
+    """Close a channel for a charm or bundle."""
+
+    name = "close"
+    help_msg = "Close a channel for a charm or bundle"
+    overview = textwrap.dedent(
+        """
+        Close the specified channel for a charm or bundle.
+
+        The channel is made up of `track/risk/branch` with both the track and
+        the branch as optional items, so formally:
+
+          [track/]risk[/branch]
+
+        Channel risk must be one of stable, candidate, beta or edge. The
+        track defaults to `latest` and branch has no default.
+
+        Closing a channel will take you through login if needed.
+    """
+    )
+    common = True
+
+    def fill_parser(self, parser):
+        """Add own parameters to the general parser."""
+        parser.add_argument("name", help="The name of charm or bundle")
+        parser.add_argument("channel", help="The channel to close")
+
+    def run(self, parsed_args):
+        """Run the command."""
+        store = Store(self.config.charmhub)
+        revision = None  # revision None will actually close the channel
+        channels = [parsed_args.channel]  # the API accepts multiple channels, we have only one
+        resources = []  # not really used when closing channels
+        store.release(parsed_args.name, revision, channels, resources)
+        logger.info("Closed %r channel for %r.", parsed_args.channel, parsed_args.name)
+
+
 class StatusCommand(BaseCommand):
     """Show channel status for a charm or bundle."""
 
diff --git a/charmcraft/main.py b/charmcraft/main.py
index 3fba456..ccdfffe 100644
--- a/charmcraft/main.py
+++ b/charmcraft/main.py
@@ -122,6 +122,7 @@ COMMAND_GROUPS = [
             # release process, and show status
             store.ReleaseCommand,
             store.StatusCommand,
+            store.CloseCommand,
             # libraries support
             store.CreateLibCommand,
             store.PublishLibCommand,
diff --git a/completion.bash b/completion.bash
index 1c1aa31..e524618 100644
--- a/completion.bash
+++ b/completion.bash
@@ -23,6 +23,7 @@ _charmcraft()
         analyze
         build
         clean
+        close
         create-lib 
         fetch-lib 
         help init 


Test patch:
diff --git a/tests/commands/test_store_commands.py b/tests/commands/test_store_commands.py
index ee9c0b8..4d83108 100644
--- a/tests/commands/test_store_commands.py
+++ b/tests/commands/test_store_commands.py
@@ -32,6 +32,7 @@ import yaml
 from charmcraft.config import CharmhubConfig
 from charmcraft.cmdbase import CommandError
 from charmcraft.commands.store import (
+    CloseCommand,
     CreateLibCommand,
     EntityType,
     FetchLibCommand,
@@ -919,6 +920,24 @@ def test_release_parameters_bad(config, sysargs):
         parser.parse_args(sysargs)
 
 
+# -- tests for the close command
+
+
+def test_close_simple_ok(caplog, store_mock, config):
+    """Simple case of closing a channel."""
+    caplog.set_level(logging.INFO, logger="charmcraft.commands")
+
+    args = Namespace(name="testcharm", channel="somechannel")
+    CloseCommand("group", config).run(args)
+
+    assert store_mock.mock_calls == [
+        call.release("testcharm", None, ["somechannel"], []),
+    ]
+
+    expected = "Closed 'somechannel' channel for 'testcharm'."
+    assert [expected] == [rec.message for rec in caplog.records]
+
+
 # -- tests for the status command