Repository: python-gitlab/python-gitlab

Issue:
AttributeError: 'ProjectRegistryTagManager' object has no attribute 'delete-in-bulk'
## Description of the problem, including code/CLI snippet
 When I try to use `delete-in-bulk` option, using `gitlab project-registry-tag delete-in-bulk --project-id <id>--repository-id <id> --name ".*" --name-regex ".*" --older-than 1week` it fails with error 

> AttributeError: 'ProjectRegistryTagManager' object has no attribute 'delete-in-bulk'

## Expected Behavior

delete in bulk api gets called

## Actual Behavior

I'm getting error

## Specifications

  - python-gitlab version: 1.10.0
  - API version you are using (v3/v4): v4
  - Gitlab server version (or gitlab.com): 12.1.1 (f9abaa7d833)



Discussion:
alse this command has misconfigured optional and required parameters:

- `name` is not a parameter for this api
- `name_regex` is required for this api
I believe this is working now. If not please re-open.
I think the error still happening using python-gitlab-3.1.0.
API version you are using (v3/v4): v4
Gitlab server version (or gitlab.com): 14.5.2

If I'm using the following line
` gitlab -c gitlab-config.cfg project-registry-tag delete-in-bulk --project-id $CI_PROJECT_ID --repository-id $id --name '.*' --name-regex-delete '^.*dev.*$' --older-than  '1m'`

I get the following output
```
Traceback (most recent call last):
  File "/usr/local/bin/gitlab", line 8, in <module>
    sys.exit(main())
  File "/usr/local/lib/python3.10/site-packages/gitlab/cli.py", line 358, in main
    gitlab.v4.cli.run(gl, what, action, args_dict, verbose, output, fields)
  File "/usr/local/lib/python3.10/site-packages/gitlab/v4/cli.py", line 495, in run
    data = g_cli()
  File "/usr/local/lib/python3.10/site-packages/gitlab/v4/cli.py", line 94, in __call__
    return self.do_custom()
  File "/usr/local/lib/python3.10/site-packages/gitlab/v4/cli.py", line 113, in do_custom
    return getattr(self.mgr, self.action)(**self.args)
AttributeError: 'ProjectRegistryTagManager' object has no attribute 'delete-in-bulk'. Did you mean: 'delete_in_bulk'?
```

And if I replace dashes with underscores
`gitlab -c gitlab-config.cfg project-registry-tag delete_in_bulk --project-id $CI_PROJECT_ID --repository-id $id --name '.*' --name-regex-delete '^.*dev.*$' --older-than  '1m'`

```
usage: gitlab project-registry-tag [-h] {list,get,delete,delete-in-bulk} ...
gitlab project-registry-tag: error: argument whaction: invalid choice: 'delete_in_bulk' (choose from 'list', 'get', 'delete', 'delete-in-bulk')
```

If I remove the `name` parameter as [it should be optional](https://docs.gitlab.com/ee/api/container_registry.html#delete-registry-repository-tags-in-bulk), I have the following output

```
usage: gitlab project-registry-tag delete-in-bulk [-h] --project-id PROJECT_ID
                                                  --repository-id
                                                  REPOSITORY_ID [--sudo SUDO]
                                                  --name NAME
                                                  --name-regex-delete
                                                  NAME_REGEX_DELETE
                                                  [--keep-n KEEP_N]
                                                  [--name-regex-keep NAME_REGEX_KEEP]
                                                  [--older-than OLDER_THAN]
gitlab project-registry-tag delete-in-bulk: error: the following arguments are required: --name
```

I'm I missing something somewhere? 
@JohnVillalovos  : Don't know how to reopen this issue, should I open a new one event if it's seems to be the same problem?
Any help would be appreciated 😄 


@Quiwy the problem is that the original CLI code only expects custom method names on objects but not managers, so it doesn't transform the argument into a method name.

Not sure if that helps you, but this small change fixes it:

```diff
(.venv) nejc@zbook-fury:~/repos/python-gitlab$ git diff -- gitlab/v4/cli.py
diff --git a/gitlab/v4/cli.py b/gitlab/v4/cli.py
index 504b7a9..33bbfae 100644
--- a/gitlab/v4/cli.py
+++ b/gitlab/v4/cli.py
@@ -106,11 +106,12 @@ class GitlabCLI(object):
                 if TYPE_CHECKING:
                     assert isinstance(self.cls._id_attr, str)
                 data[self.cls._id_attr] = self.args.pop(self.cls._id_attr)
-            obj = self.cls(self.mgr, data)
-            method_name = self.action.replace("-", "_")
-            return getattr(obj, method_name)(**self.args)
+            resource = self.cls(self.mgr, data)
         else:
-            return getattr(self.mgr, self.action)(**self.args)
+            resource = self.mgr
+
+        method_name = self.action.replace("-", "_")
+        return getattr(resource, method_name)(**self.args)
 
     def do_project_export_download(self) -> None:
         try:
```

I'll push a proper fix with some tests later today but if you're desperate, feel free to apply this already locally ;)

Edit: and `--name` actually applies to `tag_name` which is part of the URL unfortunately, so it's required. But that's confusing, not sure if just an oversight or if gitalb changed it at some point.
> I'll push a proper fix with some tests later today but if you're desperate, feel free to apply this already locally ;)

I'll wait for a proper fix, thank you very much 😄 

> Edit: and `--name` actually applies to `tag_name` which is part of the URL unfortunately, so it's required. But that's confusing, not sure if just an oversight or if gitalb changed it at some point.

Ok, it was not clear for me, thank you for the explanation

@Quiwy I might have lied about the `name` arg there :) Either way, with this fix you should be able to just pass an arbitrary name for the time being, and hopefully the API will just ignore it

In the meantime we need to introduce a `DeleteWithoutIdMixin`. But that shouldn't block this 

Resolving patch:
diff --git a/gitlab/v4/cli.py b/gitlab/v4/cli.py
--- a/gitlab/v4/cli.py
+++ b/gitlab/v4/cli.py
@@ -94,6 +94,7 @@ def __call__(self) -> Any:
         return self.do_custom()
 
     def do_custom(self) -> Any:
+        class_instance: Union[gitlab.base.RESTManager, gitlab.base.RESTObject]
         in_obj = cli.custom_actions[self.cls_name][self.action][2]
 
         # Get the object (lazy), then act
@@ -106,11 +107,12 @@ def do_custom(self) -> Any:
                 if TYPE_CHECKING:
                     assert isinstance(self.cls._id_attr, str)
                 data[self.cls._id_attr] = self.args.pop(self.cls._id_attr)
-            obj = self.cls(self.mgr, data)
-            method_name = self.action.replace("-", "_")
-            return getattr(obj, method_name)(**self.args)
+            class_instance = self.cls(self.mgr, data)
         else:
-            return getattr(self.mgr, self.action)(**self.args)
+            class_instance = self.mgr
+
+        method_name = self.action.replace("-", "_")
+        return getattr(class_instance, method_name)(**self.args)
 
     def do_project_export_download(self) -> None:
         try:


Test patch:
diff --git a/tests/functional/cli/conftest.py b/tests/functional/cli/conftest.py
--- a/tests/functional/cli/conftest.py
+++ b/tests/functional/cli/conftest.py
@@ -33,3 +33,12 @@ def resp_get_project():
         "content_type": "application/json",
         "status": 200,
     }
+
+
+@pytest.fixture
+def resp_delete_registry_tags_in_bulk():
+    return {
+        "method": responses.DELETE,
+        "url": f"{DEFAULT_URL}/api/v4/projects/1/registry/repositories/1/tags",
+        "status": 202,
+    }
diff --git a/tests/functional/cli/test_cli_projects.py b/tests/functional/cli/test_cli_projects.py
new file mode 100644
--- /dev/null
+++ b/tests/functional/cli/test_cli_projects.py
@@ -0,0 +1,27 @@
+import pytest
+import responses
+
+
+@pytest.mark.script_launch_mode("inprocess")
+@responses.activate
+def test_project_registry_delete_in_bulk(
+    script_runner, resp_delete_registry_tags_in_bulk
+):
+    responses.add(**resp_delete_registry_tags_in_bulk)
+    cmd = [
+        "gitlab",
+        "project-registry-tag",
+        "delete-in-bulk",
+        "--project-id",
+        "1",
+        "--repository-id",
+        "1",
+        "--name-regex-delete",
+        "^.*dev.*$",
+        # TODO: remove `name` after deleting without ID is possible
+        # See #849 and #1631
+        "--name",
+        ".*",
+    ]
+    ret = ret = script_runner.run(*cmd)
+    assert ret.success