Skip to content

Commit f68aec1

Browse files
committed
Add new genesis vars set command to simplify vars managment
The command `genesis vars set` allows to create a couple of var/value to simplify vars management. Signed-off-by: Anton Kremenetsky <anton.kremenetsky@gmail.com>
1 parent 37d19bb commit f68aec1

File tree

5 files changed

+229
-45
lines changed

5 files changed

+229
-45
lines changed

docs/cli/vars_set.md

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
# vars_set
2+
3+
Create variable if missing and set its value by creating a new value record
4+
5+
## Usage
6+
7+
```console
8+
Usage: genesis vars set [OPTIONS] VAR_UUID_OR_NAME VALUE
9+
```
10+
11+
## Options
12+
13+
- `var_uuid_or_name` (REQUIRED):
14+
- Type: text
15+
- Default: `sentinel.unset`
16+
- Usage: `var_uuid_or_name`
17+
18+
- `value` (REQUIRED):
19+
- Type: text
20+
- Default: `sentinel.unset`
21+
- Usage: `value`
22+
23+
- `project_id` (REQUIRED):
24+
- Type: uuid
25+
- Default: `sentinel.unset`
26+
- Usage: `-p, --project-id`
27+
28+
UUID of the project in which to deploy the variable
29+
30+
- `name`:
31+
- Type: text
32+
- Default: `none`
33+
- Usage: `--name`
34+
35+
Name of the variable to create if it does not exist
36+
37+
- `description`:
38+
- Type: text
39+
- Default: `none`
40+
- Usage: `--description`
41+
42+
Description of the variable to create if it does not exist
43+
44+
- `rotate`:
45+
- Type: boolean
46+
- Default: `false`
47+
- Usage: `--rotate`
48+
49+
Delete all existing values for the variable before creating the new one
50+
51+
- `help`:
52+
- Type: boolean
53+
- Default: `false`
54+
- Usage: `--help`
55+
56+
Show this message and exit.
57+
58+
## CLI Help
59+
60+
```console
61+
Usage: genesis vars set [OPTIONS] VAR_UUID_OR_NAME VALUE
62+
63+
Create variable if missing and set its value by creating a new value record
64+
65+
Options:
66+
-p, --project-id UUID UUID of the project in which to deploy the variable
67+
[required]
68+
--name TEXT Name of the variable to create if it does not exist
69+
--description TEXT Description of the variable to create if it does not
70+
exist
71+
--rotate Delete all existing values for the variable before
72+
creating the new one
73+
--help Show this message and exit.
74+
```

genesis_devtools/cmd/secret/certificate/commands.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,9 @@ def delete_certificate_cmd(
8383
certificate_lib.delete_certificate(client, uuid)
8484

8585

86-
@certificates_group.command("add", help="Add a new certificate to the Genesis installation")
86+
@certificates_group.command(
87+
"add", help="Add a new certificate to the Genesis installation"
88+
)
8789
@click.pass_context
8890
@click.option(
8991
"-u",

genesis_devtools/cmd/values/commands.py

Lines changed: 2 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
# under the License.
1616
from __future__ import annotations
1717

18-
import json
1918
import typing as tp
2019
import uuid as sys_uuid
2120

@@ -146,7 +145,7 @@ def add_value_cmd(
146145
"project_id": str(project_id),
147146
"name": name,
148147
"description": description,
149-
"value": _convert_to_nearest_type(value),
148+
"value": utils.convert_to_nearest_type(value),
150149
}
151150

152151
# Validate variable UUID if provided
@@ -221,7 +220,7 @@ def update_value_cmd(
221220
if description is not None:
222221
data["description"] = description
223222
if value is not None:
224-
data["value"] = _convert_to_nearest_type(value)
223+
data["value"] = utils.convert_to_nearest_type(value)
225224
if variable is not None:
226225
data["variable"] = variable
227226
value_resp = value_lib.update_value(client, uuid, data)
@@ -254,44 +253,3 @@ def _print_values(values: tp.List[dict]) -> None:
254253
)
255254

256255
click.echo(table)
257-
258-
259-
def _convert_to_nearest_type(value: str) -> bool | int | float | list | dict | str:
260-
"""
261-
Convert input string to the nearest appropriate type.
262-
263-
Args:
264-
value: String value to convert
265-
266-
Returns:
267-
Converted value in appropriate type (bool, int, float, list, dict, or str)
268-
"""
269-
# Try boolean
270-
if value.lower() in ("true", "false"):
271-
return value.lower() == "true"
272-
273-
# Try integer
274-
try:
275-
return int(value)
276-
except ValueError:
277-
pass
278-
279-
# Try float
280-
try:
281-
return float(value)
282-
except ValueError:
283-
pass
284-
285-
# Try list (JSON array or object)
286-
if (value.startswith("[") and value.endswith("]")) or (
287-
value.startswith("[") and value.endswith("]")
288-
):
289-
try:
290-
parsed = json.loads(value)
291-
if isinstance(parsed, (list, dict)):
292-
return parsed
293-
except (ValueError, TypeError):
294-
pass
295-
296-
# Return as string if no other type matches
297-
return value

genesis_devtools/cmd/vars/commands.py

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,12 @@
2020

2121
import click
2222
import prettytable
23+
from bazooka import exceptions as bazooka_exc
2324

2425
from gcl_sdk.clients.http import base as http_client
2526

27+
from genesis_devtools import constants as c
28+
from genesis_devtools.clients import value as value_lib
2629
from genesis_devtools.clients import variable as variable_lib
2730
from genesis_devtools.common import utils
2831

@@ -62,6 +65,123 @@ def show_variable_cmd(
6265
_print_variables([variable])
6366

6467

68+
@variables_group.command(
69+
"set",
70+
help="Create variable if missing and set its value by creating a new value record",
71+
)
72+
@click.argument(
73+
"var_uuid_or_name",
74+
type=str,
75+
required=True,
76+
)
77+
@click.argument(
78+
"value",
79+
type=click.STRING,
80+
required=True,
81+
)
82+
@click.option(
83+
"-p",
84+
"--project-id",
85+
type=click.UUID,
86+
required=True,
87+
help="UUID of the project in which to deploy the variable",
88+
)
89+
@click.option(
90+
"--name",
91+
type=str,
92+
default=None,
93+
help="Name of the variable to create if it does not exist",
94+
)
95+
@click.option(
96+
"--description",
97+
type=str,
98+
default=None,
99+
help="Description of the variable to create if it does not exist",
100+
)
101+
@click.option(
102+
"--rotate",
103+
is_flag=True,
104+
default=False,
105+
help="Delete all existing values for the variable before creating the new one",
106+
)
107+
@click.pass_context
108+
def set_variable_cmd(
109+
ctx: click.Context,
110+
var_uuid_or_name: str,
111+
value: str,
112+
project_id: sys_uuid.UUID | None,
113+
name: str | None,
114+
description: str | None,
115+
rotate: bool,
116+
) -> None:
117+
client: http_client.CollectionBaseClient = ctx.obj.client
118+
119+
var_uuid: str | None = None
120+
variable: dict[str, tp.Any] | None = None
121+
122+
if utils.is_valid_uuid(var_uuid_or_name):
123+
var_uuid = var_uuid_or_name
124+
try:
125+
variable = variable_lib.get_variable(client, sys_uuid.UUID(var_uuid))
126+
except bazooka_exc.NotFoundError as exc:
127+
raise click.ClickException(
128+
f"Variable with UUID {var_uuid} not found"
129+
) from exc
130+
else:
131+
variables = variable_lib.list_variables(client, name=var_uuid_or_name)
132+
if len(variables) > 1:
133+
raise click.ClickException(
134+
f"Multiple variables with name {var_uuid_or_name} found; use UUID"
135+
)
136+
if len(variables) == 1:
137+
variable = variables[0]
138+
var_uuid = variable["uuid"]
139+
140+
if variable is None or var_uuid is None:
141+
root_ctx = ctx.find_root()
142+
project_id = project_id or root_ctx.params.get("project_id")
143+
if project_id is None:
144+
raise click.ClickException(
145+
"Unable to create variable: project id is not set. "
146+
"Provide it via '--project-id' (or set 'project_id' in config)."
147+
)
148+
149+
variable_name = name if name is not None else var_uuid_or_name
150+
variable_description = description if description is not None else ""
151+
152+
created = variable_lib.add_variable(
153+
client,
154+
{
155+
"uuid": str(sys_uuid.uuid4()),
156+
"project_id": str(project_id),
157+
"name": variable_name,
158+
"description": variable_description,
159+
"setter": {"kind": "selector", "selector_strategy": "latest"},
160+
},
161+
)
162+
variable = created
163+
var_uuid = created["uuid"]
164+
165+
if rotate:
166+
variable_ref = f"{c.VARIABLE_COLLECTION}{var_uuid}"
167+
existing_values = value_lib.list_values(client, variable=variable_ref)
168+
for existing in existing_values:
169+
value_lib.delete_value(client, sys_uuid.UUID(existing["uuid"]))
170+
171+
data = {
172+
"uuid": str(sys_uuid.uuid4()),
173+
"project_id": variable["project_id"],
174+
"name": f"{variable['name']}_value",
175+
"description": "",
176+
"value": utils.convert_to_nearest_type(value),
177+
"variable": f"{c.VARIABLE_COLLECTION}{var_uuid}",
178+
}
179+
value_lib.add_value(client, data)
180+
181+
variable = variable_lib.get_variable(client, sys_uuid.UUID(var_uuid))
182+
_print_variables([variable])
183+
184+
65185
@variables_group.command("delete", help="Delete variable")
66186
@click.argument(
67187
"uuid",

genesis_devtools/common/utils.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,9 @@
1313
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
1414
# License for the specific language governing permissions and limitations
1515
# under the License.
16+
from __future__ import annotations
1617

18+
import json
1719
import os
1820
import typing as tp
1921
import uuid
@@ -32,4 +34,32 @@ def get_project_path() -> str:
3234
return os.sep.join(__file__.split(os.sep)[:-3])
3335

3436

37+
def convert_to_nearest_type(value: str) -> bool | int | float | list | dict | str:
38+
lower_value = value.lower()
39+
if lower_value in ("true", "false"):
40+
return lower_value == "true"
41+
42+
try:
43+
return int(value)
44+
except ValueError:
45+
pass
46+
47+
try:
48+
return float(value)
49+
except ValueError:
50+
pass
51+
52+
if (value.startswith("[") and value.endswith("]")) or (
53+
value.startswith("{") and value.endswith("}")
54+
):
55+
try:
56+
parsed = json.loads(value)
57+
if isinstance(parsed, (list, dict)):
58+
return parsed
59+
except (ValueError, TypeError):
60+
pass
61+
62+
return value
63+
64+
3565
PROJECT_PATH = get_project_path()

0 commit comments

Comments
 (0)