Skip to content

Commit ff1690c

Browse files
pablogrsBartoszBlizniakCopilot
authored
feat: Add policy deny command (#241)
* feat: Add policy deny command * fix: isort passing now * fix: black formatting * fix: address PR comments * Apply suggestion from @Copilot Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Bartosz Blizniak <55028730+BartoszBlizniak@users.noreply.github.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent b9c839d commit ff1690c

File tree

6 files changed

+491
-0
lines changed

6 files changed

+491
-0
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
77

88
## [Unreleased]
99

10+
### Added
11+
12+
- Added support for deny policy management commands (list, create, get, update, delete)
13+
14+
1015
## [1.9.4] - 2025-11-07
1116

1217
> No code changes in this release. Version bump performed for release process consistency and to address packaging/metadata updates.

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ The CLI currently supports the following commands (and sub-commands):
5252
- `packages`: Retrieve package usage for repository.
5353
- `move`|`mv`|`promote`: Move (promote) a package to another repo.
5454
- `policy`: Manage policies for an organization.
55+
- `deny`: Manage deny policies for an organization.
5556
- `license`: Manage license policies for an organization.
5657
- `vulnerability`: Manage vulnerability policies for an organization.
5758
- `push`|`upload`: Push (upload) a new package to a repository.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
1+
from .deny import deny_policy as policy_deny
12
from .license import licence as policy_license
23
from .vulnerability import vulnerability as policy_vulnerability
Lines changed: 246 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,246 @@
1+
"""Commands for deny policies."""
2+
3+
import click
4+
5+
from ....core.api import orgs
6+
from ....core.pagination import paginate_results
7+
from ... import command, decorators, utils
8+
from ...exceptions import handle_api_exceptions
9+
from ...utils import fmt_datetime, maybe_spinner
10+
from .command import policy
11+
12+
13+
def print_deny_policies(policies):
14+
"""Print deny policies as a table."""
15+
headers = [
16+
"Name",
17+
"Description",
18+
"Package Query",
19+
"Enabled",
20+
"Created",
21+
"Updated",
22+
"Identifier",
23+
]
24+
25+
rows = [
26+
[
27+
click.style(policy["name"], fg="cyan"),
28+
click.style(policy["description"], fg="yellow"),
29+
click.style(policy.get("package_query_string", ""), fg="yellow"),
30+
click.style(
31+
"Yes" if policy.get("enabled") else "No",
32+
fg="green" if policy.get("enabled") else "red",
33+
),
34+
click.style(fmt_datetime(policy["created_at"]), fg="blue"),
35+
click.style(fmt_datetime(policy["updated_at"]), fg="blue"),
36+
click.style(policy["slug_perm"], fg="green"),
37+
]
38+
for policy in policies
39+
]
40+
41+
click.echo()
42+
utils.pretty_print_table(headers, rows)
43+
click.echo()
44+
45+
46+
@policy.group(cls=command.AliasGroup, name="deny", aliases=[])
47+
@decorators.common_cli_config_options
48+
@decorators.common_cli_output_options
49+
@decorators.common_api_auth_options
50+
@decorators.initialise_api
51+
@click.pass_context
52+
def deny_policy(*args, **kwargs):
53+
"""Manage deny policies for an organization."""
54+
55+
56+
@deny_policy.command(name="list", aliases=["ls"])
57+
@decorators.common_cli_config_options
58+
@decorators.common_cli_list_options
59+
@decorators.common_cli_output_options
60+
@decorators.common_api_auth_options
61+
@decorators.initialise_api
62+
@click.argument("owner")
63+
@click.pass_context
64+
def list_deny_policies(ctx, opts, owner, page, page_size, show_all):
65+
"""List deny policies for an organization."""
66+
use_stderr = opts.output != "pretty"
67+
click.echo("Getting deny policies ... ", nl=False, err=use_stderr)
68+
69+
context_msg = "Failed to get deny policies!"
70+
with handle_api_exceptions(ctx, opts=opts, context_msg=context_msg):
71+
with maybe_spinner(opts):
72+
data, page_info = paginate_results(
73+
orgs.list_deny_policies, show_all, page, page_size, owner=owner
74+
)
75+
76+
click.secho("OK", fg="green", err=use_stderr)
77+
78+
if utils.maybe_print_as_json(opts, data, page_info):
79+
return
80+
81+
print_deny_policies(data)
82+
83+
click.echo()
84+
85+
num_results = len(data)
86+
list_suffix = "deny polic%s" % ("y" if num_results == 1 else "ies")
87+
utils.pretty_print_list_info(
88+
num_results=num_results,
89+
page_info=None if show_all else page_info,
90+
suffix=list_suffix,
91+
show_all=show_all,
92+
)
93+
94+
95+
@deny_policy.command(name="create", aliases=["new"])
96+
@decorators.common_cli_config_options
97+
@decorators.common_cli_output_options
98+
@decorators.common_api_auth_options
99+
@decorators.initialise_api
100+
@click.argument("owner")
101+
@click.argument("policy_config_file", type=click.File("rb"))
102+
@click.pass_context
103+
def create_deny_policy(ctx, opts, owner, policy_config_file):
104+
"""Create a deny policy for an organization."""
105+
import json
106+
107+
use_stderr = opts.output != "pretty"
108+
policy_config = json.load(policy_config_file)
109+
110+
policy_name = policy_config.get("name")
111+
if not policy_name:
112+
raise click.BadParameter(
113+
"Name is required for creating a deny policy.", param="name"
114+
)
115+
116+
click.secho(
117+
"Creating %(name)s deny policy for the %(owner)s namespace ..."
118+
% {
119+
"name": click.style(policy_name, bold=True),
120+
"owner": click.style(owner, bold=True),
121+
},
122+
nl=False,
123+
err=use_stderr,
124+
)
125+
126+
context_msg = "Failed to create the deny policy!"
127+
with handle_api_exceptions(ctx, opts=opts, context_msg=context_msg):
128+
with maybe_spinner(opts):
129+
data = orgs.create_deny_policy(owner=owner, policy_config=policy_config)
130+
131+
click.secho("OK", fg="green", err=use_stderr)
132+
133+
if utils.maybe_print_as_json(opts, [data]):
134+
return
135+
136+
print_deny_policies([data])
137+
138+
139+
@deny_policy.command(name="get")
140+
@decorators.common_cli_config_options
141+
@decorators.common_cli_output_options
142+
@decorators.common_api_auth_options
143+
@decorators.initialise_api
144+
@click.argument("owner")
145+
@click.argument("identifier")
146+
@click.pass_context
147+
def get_deny_policy(ctx, opts, owner, identifier):
148+
"""Get a deny policy for an organization."""
149+
use_stderr = opts.output != "pretty"
150+
click.echo("Getting deny policy ... ", nl=False, err=use_stderr)
151+
152+
context_msg = "Failed to get deny policy!"
153+
with handle_api_exceptions(ctx, opts=opts, context_msg=context_msg):
154+
with maybe_spinner(opts):
155+
data = orgs.get_deny_policy(owner=owner, slug_perm=identifier)
156+
157+
click.secho("OK", fg="green", err=use_stderr)
158+
159+
if utils.maybe_print_as_json(opts, [data]):
160+
return
161+
162+
print_deny_policies([data])
163+
164+
165+
@deny_policy.command(name="update", aliases=["set"])
166+
@decorators.common_cli_config_options
167+
@decorators.common_cli_output_options
168+
@decorators.common_api_auth_options
169+
@decorators.initialise_api
170+
@click.argument("owner")
171+
@click.argument("identifier")
172+
@click.argument("policy_config_file", type=click.File("rb"))
173+
@click.pass_context
174+
def update_deny_policy(ctx, opts, owner, identifier, policy_config_file):
175+
"""Update a deny policy for an organization."""
176+
import json
177+
178+
use_stderr = opts.output != "pretty"
179+
policy_config = json.load(policy_config_file)
180+
181+
click.secho(
182+
"Updating %(identifier)s deny policy in the %(owner)s namespace ..."
183+
% {
184+
"identifier": click.style(identifier, bold=True),
185+
"owner": click.style(owner, bold=True),
186+
},
187+
nl=False,
188+
err=use_stderr,
189+
)
190+
191+
context_msg = "Failed to update the deny policy!"
192+
with handle_api_exceptions(ctx, opts=opts, context_msg=context_msg):
193+
with maybe_spinner(opts):
194+
data = orgs.update_deny_policy(
195+
owner=owner, slug_perm=identifier, policy_config=policy_config
196+
)
197+
198+
click.secho("OK", fg="green", err=use_stderr)
199+
200+
if utils.maybe_print_as_json(opts, [data]):
201+
return
202+
203+
print_deny_policies([data])
204+
205+
206+
@deny_policy.command(name="delete", aliases=["rm"])
207+
@decorators.common_cli_config_options
208+
@decorators.common_cli_output_options
209+
@decorators.common_api_auth_options
210+
@decorators.initialise_api
211+
@click.argument("owner")
212+
@click.argument("identifier")
213+
@click.option(
214+
"-y",
215+
"--yes",
216+
default=False,
217+
is_flag=True,
218+
help="Assume yes as default answer to questions (this is dangerous!)",
219+
)
220+
@click.pass_context
221+
def delete_deny_policy(ctx, opts, owner, identifier, yes):
222+
"""Delete a deny policy for an organization."""
223+
delete_args = {
224+
"namespace": click.style(owner, bold=True),
225+
"identifier": click.style(identifier, bold=True),
226+
}
227+
228+
prompt = (
229+
"delete the %(identifier)s deny policy from the %(namespace)s namespace"
230+
% delete_args
231+
)
232+
233+
if not utils.confirm_operation(prompt, assume_yes=yes):
234+
return
235+
236+
click.secho(
237+
"Deleting %(identifier)s from the %(namespace)s namespace ... " % delete_args,
238+
nl=False,
239+
)
240+
241+
context_msg = "Failed to delete the deny policy!"
242+
with handle_api_exceptions(ctx, opts=opts, context_msg=context_msg):
243+
with maybe_spinner(opts):
244+
orgs.delete_deny_policy(owner=owner, slug_perm=identifier)
245+
246+
click.secho("OK", fg="green")

0 commit comments

Comments
 (0)