aio-kong 3.6.0__tar.gz → 3.7.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,4 @@
1
- Copyright (c) 2023 Quantmind
1
+ Copyright (c) 2018-2025 Quantmind
2
2
 
3
3
  Redistribution and use in source and binary forms, with or without modification,
4
4
  are permitted provided that the following conditions are met:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: aio-kong
3
- Version: 3.6.0
3
+ Version: 3.7.1
4
4
  Summary: Asynchronous Kong Client
5
5
  License: BSD-3-Clause
6
6
  Author: Luca Sbardella
@@ -16,6 +16,7 @@ Provides-Extra: cli
16
16
  Requires-Dist: PyYAML (>=6.0) ; extra == "cli"
17
17
  Requires-Dist: aiohttp (>=3.9.5)
18
18
  Requires-Dist: click (>=8.1.3) ; extra == "cli"
19
+ Requires-Dist: rich (>=13.7.1) ; extra == "cli"
19
20
  Project-URL: Issues, https://github.com/quantmind/aio-kong/issues
20
21
  Project-URL: Repository, https://github.com/quantmind/aio-kong
21
22
  Description-Content-Type: text/markdown
@@ -109,7 +110,7 @@ pip install aio-kong[cli]
109
110
  and to run the cli tool, you can use the following command:
110
111
 
111
112
  ```
112
- kongfig --yaml config.yaml
113
+ kongfig --help
113
114
  ```
114
115
 
115
116
  [kong]: https://github.com/Kong/kong
@@ -1,3 +1,3 @@
1
1
  """Asynchronous Kong client"""
2
2
 
3
- __version__ = "3.6.0"
3
+ __version__ = "3.7.1"
@@ -0,0 +1,203 @@
1
+ import asyncio
2
+ import json
3
+ from typing import Any, cast
4
+
5
+ import click
6
+ import yaml as _yaml
7
+ from rich.console import Console
8
+ from rich.table import Table
9
+
10
+ from . import __version__
11
+ from .client import Kong, KongError, default_admin_url
12
+ from .components import KongEntity
13
+ from .consumers import Consumer
14
+ from .utils import local_ip
15
+
16
+ admin_url = click.option(
17
+ "--url", default=default_admin_url(), help="Kong Admin URL", show_default=True
18
+ )
19
+
20
+ as_json = click.option("--json", default=False, is_flag=True, help="Output as JSON")
21
+
22
+
23
+ @click.group()
24
+ @click.version_option(version=__version__)
25
+ def kong() -> None:
26
+ """Kong CLI - Manage Kong Gateway configurations."""
27
+ pass
28
+
29
+
30
+ @kong.command()
31
+ @click.argument(
32
+ "yaml",
33
+ type=click.File("r"),
34
+ )
35
+ @click.option(
36
+ "--clear",
37
+ default=False,
38
+ is_flag=True,
39
+ help="Clear objects not in configuration",
40
+ )
41
+ @admin_url
42
+ def yaml(
43
+ yaml: click.File,
44
+ clear: bool,
45
+ url: str,
46
+ ) -> None:
47
+ "Upload a configuration from a yaml file"
48
+ asyncio.run(_yml(yaml, clear, url))
49
+
50
+
51
+ @kong.command()
52
+ def ip() -> None:
53
+ "Show local IP address"
54
+ click.echo(local_ip())
55
+
56
+
57
+ @kong.command()
58
+ @admin_url
59
+ @as_json
60
+ def services(url: str, json: bool) -> None:
61
+ "Display services"
62
+ asyncio.run(_services(url, as_json=json))
63
+
64
+
65
+ @kong.command()
66
+ @click.argument("service")
67
+ @admin_url
68
+ @as_json
69
+ def routes(service: str, url: str, json: bool) -> None:
70
+ "Display routes for a service"
71
+ asyncio.run(_routes(service, url, as_json=json))
72
+
73
+
74
+ @kong.command()
75
+ @admin_url
76
+ @as_json
77
+ def consumers(url: str, json: bool) -> None:
78
+ "Display consumers"
79
+ asyncio.run(_consumers(url, as_json=json))
80
+
81
+
82
+ @kong.command()
83
+ @click.argument("consumer")
84
+ @admin_url
85
+ def key_auth(consumer: str, url: str) -> None:
86
+ "Create or display an authentication key for a consumer"
87
+ asyncio.run(_auth_key(consumer, url))
88
+
89
+
90
+ async def _yml(yaml: Any, clear: bool, url: str) -> None:
91
+ async with Kong(url=url) as cli:
92
+ try:
93
+ result = await cli.apply_json(_yaml.safe_load(yaml), clear=clear)
94
+ display_json(result)
95
+ except KongError as exc:
96
+ raise click.ClickException(str(exc)) from None
97
+
98
+
99
+ async def _services(url: str, as_json: bool = False) -> None:
100
+ async with Kong(url=url) as cli:
101
+ try:
102
+ services = await cli.services.get_full_list()
103
+ except KongError as exc:
104
+ raise click.ClickException(str(exc)) from None
105
+ if as_json:
106
+ display_json(services)
107
+ return
108
+ table = Table(title="Services")
109
+ columns = ["Name", "Host", "Port", "Protocol", "Path", "Tags", "ID"]
110
+ for column in columns:
111
+ table.add_column(column)
112
+ for s in sorted(services, key=lambda s: s.name):
113
+ table.add_row(*[str_value(s.data, column) for column in columns])
114
+ console = Console()
115
+ console.print(table)
116
+
117
+
118
+ async def _routes(service: str, url: str, as_json: bool = False) -> None:
119
+ async with Kong(url=url) as cli:
120
+ try:
121
+ svc = await cli.services.get(service)
122
+ routes = await svc.routes.get_full_list()
123
+ except KongError as exc:
124
+ raise click.ClickException(str(exc)) from None
125
+ if as_json:
126
+ display_json(routes)
127
+ return
128
+ table = Table(title="Services")
129
+ columns = [
130
+ "Name",
131
+ "Hosts",
132
+ "Protocols",
133
+ "Path",
134
+ "Tags",
135
+ "Strip Path",
136
+ "Preserve Host",
137
+ "ID",
138
+ ]
139
+ for column in columns:
140
+ table.add_column(column)
141
+ for s in sorted(routes, key=lambda s: s.name):
142
+ table.add_row(*[str_value(s.data, column) for column in columns])
143
+ console = Console()
144
+ console.print(table)
145
+
146
+
147
+ async def _consumers(url: str, as_json: bool = False) -> None:
148
+ async with Kong(url=url) as cli:
149
+ try:
150
+ consumers = await cli.consumers.get_full_list()
151
+ except KongError as exc:
152
+ raise click.ClickException(str(exc)) from None
153
+ if as_json:
154
+ display_json(consumers)
155
+ return
156
+ table = Table(title="Consumers")
157
+ columns = ["Username", "ID", "Custom ID", "Tags"]
158
+ for column in columns:
159
+ table.add_column(column)
160
+ for c in sorted(consumers, key=lambda s: s.name):
161
+ table.add_row(*[str_value(c.data, column) for column in columns])
162
+ console = Console()
163
+ console.print(table)
164
+
165
+
166
+ async def _auth_key(consumer: str, admin_url: str) -> None:
167
+ async with Kong(url=admin_url) as cli:
168
+ try:
169
+ c = cast(Consumer, await cli.consumers.get(consumer))
170
+ keys = await c.keyauths.get_list()
171
+ if keys:
172
+ key = keys[0]
173
+ else:
174
+ key = await c.keyauths.create()
175
+ display_json(key)
176
+ except KongError as exc:
177
+ raise click.ClickException(str(exc)) from None
178
+
179
+
180
+ def str_value(data: dict, key: str) -> str:
181
+ key = key.lower().replace(" ", "_")
182
+ value = data.get(key)
183
+ if value is None:
184
+ return ""
185
+ elif isinstance(value, bool):
186
+ return ":white_check_mark:" if value else "[red]:x:"
187
+ elif isinstance(value, list):
188
+ return ", ".join(str(v) for v in value)
189
+ else:
190
+ return str(value)
191
+
192
+
193
+ def display_json(data: Any) -> None:
194
+ """Display data as JSON."""
195
+ click.echo(json.dumps(data, indent=2, default=kong_entity))
196
+
197
+
198
+ def kong_entity(obj: Any) -> Any:
199
+ if isinstance(obj, KongEntity):
200
+ return obj.data
201
+ raise TypeError(
202
+ f"Cannot serialize {type(obj).__name__} to JSON"
203
+ ) # pragma: no cover
@@ -9,7 +9,7 @@ from aiohttp import ClientResponse, ClientSession
9
9
  from . import __version__
10
10
  from .acls import Acl, Acls
11
11
  from .certificates import Certificate, Certificates
12
- from .components import KongError, KongResponseError
12
+ from .components import CrudComponent, KongError, KongResponseError
13
13
  from .consumers import Consumer, Consumers
14
14
  from .plugins import Plugin, Plugins
15
15
  from .routes import Route, Routes
@@ -23,22 +23,24 @@ DEFAULT_USER_AGENT = (
23
23
  )
24
24
 
25
25
 
26
+ def default_admin_url() -> str:
27
+ """Return the default Kong admin URL."""
28
+ return os.getenv("KONG_ADMIN_URL", os.getenv("KONG_URL", "http://127.0.0.1:8001"))
29
+
30
+
26
31
  class Kong:
27
32
  """Kong client"""
28
33
 
29
- url: str = os.getenv(
30
- "KONG_ADMIN_URL", os.getenv("KONG_URL", "http://127.0.0.1:8001")
31
- )
32
34
  content_type: str = "application/json, text/*; q=0.5"
33
35
 
34
36
  def __init__(
35
37
  self,
36
- url: str = "",
38
+ url: str | None = None,
37
39
  session: ClientSession | None = None,
38
40
  request_kwargs: dict | None = None,
39
41
  user_agent: str = DEFAULT_USER_AGENT,
40
42
  ) -> None:
41
- self.url = url or self.url
43
+ self.url = url or default_admin_url()
42
44
  self.session = session
43
45
  self.user_agent = user_agent
44
46
  self.request_kwargs = request_kwargs or {}
@@ -107,7 +109,7 @@ class Kong:
107
109
  if not isinstance(data, list):
108
110
  data = [data]
109
111
  o = getattr(self, name)
110
- if not o:
112
+ if not isinstance(o, CrudComponent):
111
113
  raise KongError("Kong object %s not available" % name)
112
114
  result[name] = await o.apply_json(data, clear=clear)
113
115
  return result
@@ -3,7 +3,7 @@ from typing import cast
3
3
  from .components import UUID, CrudComponent, JsonType, KongError
4
4
  from .plugins import KongEntityWithPlugins
5
5
  from .routes import Route, Routes
6
- from .utils import local_ip
6
+ from .utils import local_ip, uid
7
7
 
8
8
  REMOVE = frozenset(("absent", "remove"))
9
9
  LOCAL_HOST = frozenset(("localhost", "127.0.0.1"))
@@ -20,12 +20,16 @@ class Service(KongEntityWithPlugins):
20
20
  def host(self) -> str:
21
21
  return self.data.get("host", "")
22
22
 
23
+ @property
24
+ def protocol(self) -> str:
25
+ return self.data.get("protocol", "")
26
+
23
27
 
24
28
  class Services(CrudComponent[Service]):
25
29
  """Kong Services"""
26
30
 
27
31
  async def delete(self, id_: str | UUID) -> bool:
28
- srv = cast(Service, self.wrap({"id": id_}))
32
+ srv = cast(Service, self.wrap({"id": uid(id_)}))
29
33
  await srv.routes.delete_all()
30
34
  await srv.plugins.delete_all()
31
35
  return await super().delete(id_)
@@ -1,19 +1,20 @@
1
1
  [project]
2
2
  name = "aio-kong"
3
- version = "3.6.0"
3
+ version = "3.7.1"
4
4
  description = "Asynchronous Kong Client"
5
5
  authors = [{ name = "Luca Sbardella", email = "luca@quantmind.com" }]
6
6
  license = "BSD-3-Clause"
7
7
  readme = "readme.md"
8
8
  requires-python = ">=3.10,<4.0"
9
9
  dependencies = [
10
- "aiohttp>=3.9.5",
10
+ "aiohttp >= 3.9.5"
11
11
  ]
12
12
 
13
13
  [project.optional-dependencies]
14
14
  cli = [
15
- "PyYAML>=6.0",
16
- "click>=8.1.3"
15
+ "PyYAML >= 6.0",
16
+ "click >= 8.1.3",
17
+ "rich >= 13.7.1"
17
18
  ]
18
19
 
19
20
  [project.scripts]
@@ -87,7 +87,7 @@ pip install aio-kong[cli]
87
87
  and to run the cli tool, you can use the following command:
88
88
 
89
89
  ```
90
- kongfig --yaml config.yaml
90
+ kongfig --help
91
91
  ```
92
92
 
93
93
  [kong]: https://github.com/Kong/kong
@@ -1,69 +0,0 @@
1
- import asyncio
2
- import json
3
- from typing import Any, cast
4
-
5
- import click
6
- import yaml as _yaml
7
-
8
- from . import __version__
9
- from .client import Kong, KongError
10
- from .consumers import Consumer
11
- from .utils import local_ip
12
-
13
-
14
- @click.command()
15
- @click.option("--version", is_flag=True, default=False, help="Display version and exit")
16
- @click.option("--ip", is_flag=True, default=False, help="Show local IP address")
17
- @click.option(
18
- "--key-auth", help="Create or display an authentication key for a consumer"
19
- )
20
- @click.option("--yaml", type=click.File("r"), help="Yaml configuration to upload")
21
- @click.option(
22
- "--clear", default=False, is_flag=True, help="Clear objects not in configuration"
23
- )
24
- @click.pass_context
25
- def kong(
26
- ctx: click.Context,
27
- version: bool,
28
- ip: bool,
29
- key_auth: str,
30
- yaml: click.File | None,
31
- clear: bool,
32
- ) -> None:
33
- if version:
34
- click.echo(__version__)
35
- elif ip:
36
- click.echo(local_ip())
37
- elif key_auth:
38
- _run(_auth_key(key_auth))
39
- elif yaml:
40
- _run(_yml(yaml, clear))
41
- else:
42
- click.echo(ctx.get_help())
43
-
44
-
45
- def _run(coro: Any) -> None:
46
- asyncio.get_event_loop().run_until_complete(coro)
47
-
48
-
49
- async def _yml(yaml: Any, clear: bool) -> None:
50
- async with Kong() as cli:
51
- try:
52
- result = await cli.apply_json(_yaml.safe_load(yaml), clear=clear)
53
- click.echo(json.dumps(result, indent=4))
54
- except KongError as exc:
55
- raise click.ClickException(str(exc)) from None
56
-
57
-
58
- async def _auth_key(consumer: str) -> None:
59
- async with Kong() as cli:
60
- try:
61
- c = cast(Consumer, await cli.consumers.get(consumer))
62
- keys = await c.keyauths.get_list()
63
- if keys:
64
- key = keys[0]
65
- else:
66
- key = await c.keyauths.create()
67
- click.echo(json.dumps(key.data, indent=4))
68
- except KongError as exc:
69
- raise click.ClickException(str(exc)) from None
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes