Skip to content

Commit 7ceb0ee

Browse files
committed
Change command style for easier use & update docs
1 parent 48de64d commit 7ceb0ee

5 files changed

Lines changed: 172 additions & 96 deletions

File tree

hack/gcp-secret-manager/commands/create.py

Lines changed: 28 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,9 @@
1616
get_secrets_from_index,
1717
update_index_secret,
1818
validate_collection,
19-
validate_secret_name,
19+
validate_path,
2020
validate_secret_source,
21+
ensure_authentication,
2122
)
2223

2324
# Metadata keys used when creating secrets:
@@ -41,14 +42,7 @@
4142
type=str,
4243
callback=validate_collection,
4344
)
44-
@click.option(
45-
"-s",
46-
"--secret",
47-
required=True,
48-
help="Name of the secret.",
49-
type=str,
50-
callback=validate_secret_name,
51-
)
45+
@click.argument("path", required=True, callback=validate_path)
5246
@click.option(
5347
"-f",
5448
"--from-file",
@@ -59,9 +53,16 @@
5953
@click.option(
6054
"-l", "--from-literal", default="", help="Secret data as string input.", type=str
6155
)
62-
def create(collection: str, secret: str, from_file: str, from_literal: str):
63-
"""Create a new secret in the specified collection."""
56+
def create(collection: str, path: str, from_file: str, from_literal: str):
57+
"""Create a new secret in the specified collection.
58+
59+
The secret PATH should be in the format 'group/field' where:
60+
- group: Organizes related secrets (can be hierarchical: 'aws/prod')
61+
- field: The specific secret name (e.g., 'username', 'password')
6462
63+
Example: secret-manager create -c my-collection aws/password -l "secret value"
64+
"""
65+
ensure_authentication()
6566
validate_secret_source(from_file, from_literal)
6667

6768
if not check_if_collection_exists(collection):
@@ -71,15 +72,20 @@ def create(collection: str, secret: str, from_file: str, from_literal: str):
7172
"See: https://docs.ci.openshift.org/docs/how-tos/adding-a-new-secret-to-ci/"
7273
)
7374
client = secretmanager.SecretManagerServiceClient()
74-
secret_name = get_secret_name(collection, secret)
7575

76-
# Check if secret exists in either index or GCP
7776
index_secrets = get_secrets_from_index(client, collection)
78-
if secret in index_secrets:
79-
raise click.ClickException(f"Secret named '{secret}' already exists.")
77+
path_normalized = path.replace("/", "__")
78+
if path_normalized in index_secrets:
79+
raise click.ClickException(
80+
f"Secret '{path}' already exists."
81+
)
82+
83+
secret_id_normalized = get_secret_name(collection, path)
8084
try:
81-
client.get_secret(name=client.secret_path(PROJECT_ID, secret_name))
82-
raise click.ClickException(f"Secret named '{secret}' already exists.")
85+
client.get_secret(name=client.secret_path(PROJECT_ID, secret_id_normalized))
86+
raise click.ClickException(
87+
f"Secret '{path}' already exists."
88+
)
8389
except NotFound:
8490
pass # Secret doesn't exist in GCP - this is good
8591

@@ -95,7 +101,7 @@ def create(collection: str, secret: str, from_file: str, from_literal: str):
95101
gcp_secret = client.create_secret(
96102
request={
97103
"parent": f"projects/{PROJECT_ID}",
98-
"secret_id": secret_name,
104+
"secret_id": secret_id_normalized,
99105
"secret": {
100106
"replication": {"automatic": {}},
101107
"labels": labels,
@@ -107,12 +113,12 @@ def create(collection: str, secret: str, from_file: str, from_literal: str):
107113
parent=gcp_secret.name,
108114
payload=SecretPayload(data=create_payload(from_file, from_literal)),
109115
)
110-
update_index_secret(client, collection, index_secrets + [secret])
111-
click.echo(f"Secret '{secret}' created")
116+
update_index_secret(client, collection, index_secrets + [path_normalized])
117+
click.echo(f"Secret '{path}' created successfully.")
112118
except Exception as e:
113119
raise click.ClickException(
114-
f"Failed to create secret '{secret}': {e}. "
115-
f"If the secret is in an inconsistent state, run 'delete -c {collection} -s {secret}', then try again."
120+
f"Failed to create secret '{path}': {e}. "
121+
f"If the secret is in an inconsistent state, run 'delete -c {collection} {path}', then try again."
116122
) from e
117123

118124

hack/gcp-secret-manager/commands/delete.py

Lines changed: 15 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
get_secrets_from_index,
1212
update_index_secret,
1313
validate_collection,
14-
validate_secret_name,
14+
validate_path,
1515
)
1616

1717

@@ -24,40 +24,34 @@
2424
type=str,
2525
callback=validate_collection,
2626
)
27-
@click.option(
28-
"-s",
29-
"--secret",
30-
required=True,
31-
help="Name of the secret.",
32-
type=str,
33-
callback=validate_secret_name,
34-
)
35-
def delete(collection: str, secret: str):
36-
"""Delete a secret from the specified collection."""
27+
@click.argument("path", required=True, callback=validate_path)
28+
def delete(collection: str, path: str):
29+
"""Delete a secret from the specified collection.
30+
31+
The secret PATH format is 'group/field' (e.g., 'aws/password').
32+
"""
3733

3834
ensure_authentication()
3935
client = secretmanager.SecretManagerServiceClient()
4036

4137
index_secrets = get_secrets_from_index(client, collection)
42-
43-
# Remove from index if present (if missing from index, don't fail)
44-
if secret in index_secrets:
45-
index_secrets.remove(secret)
38+
secret_id_normalized = path.replace("/", "__")
39+
if secret_id_normalized in index_secrets:
40+
index_secrets.remove(secret_id_normalized)
4641
update_index_secret(client, collection, index_secrets)
4742

48-
# Remove from GSM if present (don't fail if missing)
4943
try:
50-
click.echo(f"Deleting secret '{secret}'...")
44+
click.echo(f"Deleting secret '{path}'...")
5145
client.delete_secret(
52-
name=client.secret_path(PROJECT_ID, get_secret_name(collection, secret))
46+
name=client.secret_path(PROJECT_ID, get_secret_name(collection, path))
5347
)
5448
except NotFound:
5549
raise click.ClickException(
56-
f"Secret '{secret}' does not exist within the collection."
50+
f"Secret '{path}' does not exist within the collection."
5751
)
5852
except Exception as e:
5953
raise click.ClickException(
60-
f"Failed to delete secret '{secret}': {e}. Please retry the delete operation."
54+
f"Failed to delete secret '{path}': {e}. Please retry the delete operation."
6155
)
6256

63-
click.echo(f"Secret '{secret}' successfully deleted.")
57+
click.echo(f"Secret '{path}' successfully deleted.")

hack/gcp-secret-manager/commands/list.py

Lines changed: 38 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -30,19 +30,21 @@
3030
callback=validate_collection,
3131
)
3232
@click.option(
33-
"-g",
34-
"--group",
33+
"--rover-group",
3534
default="",
36-
help="Use this option to list all secret collections for a group.",
35+
help="Use this option to list all secret collections for a rover group.",
3736
)
38-
def list_secrets(output: str, collection: str, group: str):
37+
def list_secrets(output: str, collection: str, rover_group: str):
3938
"""
40-
List secrets from the specified collection.
41-
If no collection is provided, lists all secret collections.
39+
List secrets.
40+
41+
Without options: Lists all secret collections that exist.
42+
With -c {COLLECTION}: Lists all secrets in group/field format.
43+
With --rover-group {GROUP}: Lists collections accessible to that rover group.
4244
"""
43-
if collection != "" and group != "":
45+
if collection != "" and rover_group != "":
4446
raise click.ClickException(
45-
"--collection and --group cannot both be set at the same time"
47+
"--collection and --rover-group cannot both be set at the same time"
4648
)
4749

4850
if collection != "":
@@ -51,8 +53,8 @@ def list_secrets(output: str, collection: str, group: str):
5153
return
5254

5355
collections_dict = get_group_collections()
54-
if group != "":
55-
list_collections_for_group(collections_dict, group, output)
56+
if rover_group != "":
57+
list_collections_for_rover_group(collections_dict, rover_group, output)
5658
else:
5759
list_all_collections(collections_dict, output)
5860

@@ -67,26 +69,43 @@ def list_all_collections(collections_dict: Dict, output: str):
6769
click.echo(f"- {collection}")
6870

6971

70-
def list_collections_for_group(
71-
collections_dict: Dict[str, List[str]], group: str, output: str
72+
def list_collections_for_rover_group(
73+
collections_dict: Dict[str, List[str]], rover_group: str, output: str
7274
):
73-
if group and group not in collections_dict:
74-
click.echo(f"Group '{group}' has no secret collections")
75+
"""Lists collections for a specified rover group."""
76+
77+
if rover_group and rover_group not in collections_dict:
78+
click.echo(f"Rover group '{rover_group}' has no secret collections")
7579
return
7680

7781
if output == "json":
78-
click.echo(json.dumps(collections_dict[group], indent=2))
82+
click.echo(json.dumps(collections_dict[rover_group], indent=2))
7983
else:
80-
for collection in collections_dict[group]:
84+
for collection in collections_dict[rover_group]:
8185
click.echo(f"{collection}")
8286

8387

8488
def list_secrets_for_collection(collection: str, output: str):
8589
secret_list = get_secrets_from_index(
8690
secretmanager.SecretManagerServiceClient(), collection
8791
)
92+
93+
paths = [secret.replace("__", "/") for secret in secret_list]
94+
paths.sort()
95+
8896
if output == "json":
89-
click.echo(json.dumps(secret_list, indent=2))
97+
click.echo(json.dumps(paths, indent=2))
9098
else:
91-
for secret in secret_list:
92-
click.echo(secret)
99+
if not paths:
100+
click.echo("(no secrets)")
101+
return
102+
103+
last_top_group = None
104+
for path in paths:
105+
top_group = path.split("/")[0] if "/" in path else ""
106+
107+
if last_top_group is not None and top_group != last_top_group:
108+
click.echo()
109+
110+
click.echo(path)
111+
last_top_group = top_group

hack/gcp-secret-manager/commands/update.py

Lines changed: 22 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@
1111
get_secret_name,
1212
get_secrets_from_index,
1313
validate_collection,
14-
validate_secret_name,
15-
validate_secret_source,
14+
validate_path,
15+
validate_secret_source, ensure_authentication,
1616
)
1717

1818

@@ -25,14 +25,7 @@
2525
type=str,
2626
callback=validate_collection,
2727
)
28-
@click.option(
29-
"-s",
30-
"--secret",
31-
required=True,
32-
help="Name of the secret.",
33-
type=str,
34-
callback=validate_secret_name,
35-
)
28+
@click.argument("path", required=True, callback=validate_path)
3629
@click.option(
3730
"-f",
3831
"--from-file",
@@ -43,19 +36,21 @@
4336
@click.option(
4437
"-l", "--from-literal", default="", help="Secret data as string input.", type=str
4538
)
46-
def update(collection: str, secret: str, from_file: str, from_literal: str):
47-
"""Update an existing secret."""
39+
def update(collection: str, path: str, from_file: str, from_literal: str):
40+
"""Update an existing secret.
4841
49-
validate_secret_source(from_file, from_literal)
42+
The secret PATH format is 'group/field' (e.g., 'aws/password').
43+
"""
5044

45+
ensure_authentication()
46+
validate_secret_source(from_file, from_literal)
5147
client = secretmanager.SecretManagerServiceClient()
52-
secret_name = get_secret_name(collection, secret)
53-
full_secret_path = client.secret_path(PROJECT_ID, secret_name)
5448

5549
# Check if secret exists in both index and GSM
50+
path_normalized = path.replace("/", "__")
5651
index_secrets = get_secrets_from_index(client, collection)
57-
secret_in_index = secret in index_secrets
58-
52+
secret_in_index = path_normalized in index_secrets
53+
full_secret_path = client.secret_path(PROJECT_ID, get_secret_name(collection, path))
5954
try:
6055
client.get_secret(request={"name": full_secret_path})
6156
secret_in_gsm = True
@@ -65,31 +60,33 @@ def update(collection: str, secret: str, from_file: str, from_literal: str):
6560
# Simple existence checks - tell user to delete/recreate if inconsistent
6661
if not secret_in_index and not secret_in_gsm:
6762
raise click.ClickException(
68-
f"Secret '{secret}' does not exist in collection '{collection}'."
63+
f"Secret '{path}' does not exist in collection '{collection}'."
6964
)
7065

7166
if secret_in_index and not secret_in_gsm:
7267
raise click.ClickException(
73-
f"Secret '{secret}' is in inconsistent state. "
74-
f"Run 'delete -c {collection} -s {secret}', then 'create' to fix."
68+
f"Secret '{path}' is in inconsistent state. "
69+
f"Run 'delete -c {collection} {path}', then 'create' to fix."
7570
)
7671

7772
if not secret_in_index and secret_in_gsm:
7873
raise click.ClickException(
79-
f"Secret '{secret}' is in inconsistent state (GSM only). "
80-
f"Run 'delete -c {collection} -s {secret}', then 'create' to fix."
74+
f"Secret '{path}' is in inconsistent state (GSM only). "
75+
f"Run 'delete -c {collection} {path}', then 'create' to fix."
8176
)
8277

83-
# Secret exists in both places - proceed with update
78+
# Secret exists in both places - proceed with the update
8479
try:
8580
client.add_secret_version(
8681
parent=full_secret_path,
8782
payload=SecretPayload(data=create_payload(from_file, from_literal)),
8883
)
89-
click.echo(f"Secret '{secret}' updated successfully.")
84+
click.echo(f"Secret '{path}' updated successfully.")
9085
except PermissionDenied:
9186
raise click.ClickException(
9287
f"You don't have permission to update secrets in collection '{collection}'"
9388
)
9489
except Exception as e:
95-
raise click.ClickException(f"Failed to update secret '{secret}': {e}.")
90+
raise click.ClickException(
91+
f"Failed to update secret '{path}': {e}."
92+
)

0 commit comments

Comments
 (0)