Skip to content

Commit 38b3987

Browse files
Update Processor pattern used in secret updation
1 parent 268c9cc commit 38b3987

File tree

11 files changed

+266
-205
lines changed

11 files changed

+266
-205
lines changed

executor/migrations/0046_secret_secret_executor_se_key_60cc82_idx_and_more.py

+2-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Generated by Django 4.1.13 on 2025-03-04 12:00
1+
# Generated by Django 4.1.13 on 2025-03-05 09:24
22

33
from django.conf import settings
44
from django.db import migrations, models
@@ -10,8 +10,8 @@
1010
class Migration(migrations.Migration):
1111

1212
dependencies = [
13-
('accounts', '0003_accountuseroauth2sessioncodestore'),
1413
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
14+
('accounts', '0003_accountuseroauth2sessioncodestore'),
1515
('executor', '0045_upgrade_step_relation_conditions'),
1616
]
1717

@@ -20,7 +20,6 @@ class Migration(migrations.Migration):
2020
name='Secret',
2121
fields=[
2222
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
23-
('name', models.CharField(help_text='Human-readable name', max_length=255)),
2423
('key', models.CharField(help_text='Reference key for the secret', max_length=255)),
2524
('value', encrypted_model_fields.fields.EncryptedTextField()),
2625
('created_at', models.DateTimeField(auto_now_add=True)),

executor/models.py

-1
Original file line numberDiff line numberDiff line change
@@ -676,7 +676,6 @@ def proto(self) -> PlaybookStepRelationExecutionLogProto:
676676

677677
class Secret(models.Model):
678678
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
679-
name = models.CharField(max_length=255, help_text="Human-readable name")
680679
key = models.CharField(max_length=255, help_text="Reference key for the secret")
681680
value = EncryptedTextField()
682681
account = models.ForeignKey('accounts.Account', on_delete=models.CASCADE)

executor/secrets/crud/__init__.py

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import logging
2+
3+
from executor.models import Secret
4+
from protos.secrets.api_pb2 import UpdateSecretOp
5+
from utils.update_processor_mixin import UpdateProcessorMixin
6+
7+
logger = logging.getLogger(__name__)
8+
9+
10+
class SecretsUpdateProcessor(UpdateProcessorMixin):
11+
update_op_cls = UpdateSecretOp
12+
13+
@staticmethod
14+
def update_secret(elem: Secret, update_op: UpdateSecretOp.UpdateSecret) -> Secret:
15+
"""Update a secret's description and/or value"""
16+
update_fields = ['updated_at']
17+
18+
# Update description if provided and has a value
19+
if hasattr(update_op, 'description') and update_op.description.value:
20+
elem.description = update_op.description.value
21+
update_fields.append('description')
22+
23+
# Update value if provided and has a value
24+
if hasattr(update_op, 'value') and update_op.value.value:
25+
elem.value = update_op.value.value
26+
update_fields.append('value')
27+
28+
logger.info(f"Updating secret {elem.key} with fields: {update_fields}, {len(update_fields)}")
29+
if len(update_fields) > 1: # Only save if we have fields to update
30+
try:
31+
elem.save(update_fields=update_fields)
32+
except Exception as ex:
33+
logger.exception(f"Error occurred updating secret {elem.key}")
34+
raise Exception(f"Error updating secret: {str(ex)}")
35+
36+
return elem
37+
38+
@staticmethod
39+
def update_secret_status(elem: Secret, update_op: UpdateSecretOp.UpdateSecretStatus) -> Secret:
40+
"""Update a secret's active status (soft delete)"""
41+
is_active = update_op.is_active.value
42+
43+
# Can't reactivate a secret that's been deactivated
44+
if not elem.is_active and is_active:
45+
raise Exception(f"Secret {elem.key} cannot be reactivated")
46+
47+
# No change needed
48+
if elem.is_active == is_active:
49+
return elem
50+
51+
elem.is_active = is_active
52+
try:
53+
elem.save(update_fields=['is_active', 'updated_at'])
54+
except Exception as ex:
55+
logger.exception(f"Error occurred updating secret status for {elem.key}")
56+
raise Exception(f"Error updating secret status: {str(ex)}")
57+
return elem
58+
59+
60+
secrets_update_processor = SecretsUpdateProcessor()

executor/secrets/urls.py

+1-2
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,5 @@
55
path('list/', views.secrets_list, name='secrets_list'),
66
path('get/', views.secret_get, name='secret_get'),
77
path('create/', views.secret_create, name='secret_create'),
8-
path('update/', views.secret_update, name='secret_update'),
9-
path('delete/', views.secret_delete, name='secret_delete'),
8+
path('update/', views.secret_update, name='secret_update')
109
]

executor/secrets/views.py

+50-72
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@
1616
GetSecretRequest, GetSecretResponse,
1717
CreateSecretRequest, CreateSecretResponse,
1818
UpdateSecretRequest, UpdateSecretResponse,
19-
DeleteSecretRequest, DeleteSecretResponse,
20-
Secret as SecretProto
19+
Secret as SecretProto,
20+
UpdateSecretOp
2121
)
2222

2323
logger = logging.getLogger(__name__)
@@ -34,7 +34,6 @@ def _secret_to_proto(secret: Secret) -> SecretProto:
3434
"""Convert a Secret model to a Secret proto"""
3535
return SecretProto(
3636
id=StringValue(value=str(secret.id)),
37-
name=StringValue(value=secret.name),
3837
key=StringValue(value=secret.key),
3938
masked_value=StringValue(value=_mask_secret_value(secret.value)),
4039
description=StringValue(value=secret.description or ""),
@@ -99,18 +98,17 @@ def secret_create(request_message: CreateSecretRequest) -> Union[CreateSecretRes
9998
"""Create a new secret"""
10099
account: Account = get_request_account()
101100
user = get_request_user()
102-
103-
name = request_message.name.value
101+
104102
key = request_message.key.value
105103
value = request_message.value.value
106104
description = request_message.description.value
107105

108106
# Validate required fields
109-
if not name or not key or not value:
107+
if not key or not value:
110108
return CreateSecretResponse(
111109
meta=get_meta(),
112110
success=BoolValue(value=False),
113-
message=Message(title="Invalid Request", description="Name, key, and value are required")
111+
message=Message(title="Invalid Request", description="Key, and value are required")
114112
)
115113

116114
# Check if key already exists for this account
@@ -125,7 +123,6 @@ def secret_create(request_message: CreateSecretRequest) -> Union[CreateSecretRes
125123
try:
126124
secret = Secret.objects.create(
127125
account=account,
128-
name=name,
129126
key=key,
130127
value=value,
131128
description=description,
@@ -151,94 +148,75 @@ def secret_create(request_message: CreateSecretRequest) -> Union[CreateSecretRes
151148

152149
@web_api(UpdateSecretRequest)
153150
def secret_update(request_message: UpdateSecretRequest) -> Union[UpdateSecretResponse, HttpResponse]:
154-
"""Update a secret's name or description (not the value)"""
151+
"""Update a secret using operations"""
155152
account: Account = get_request_account()
156153
user = get_request_user()
157154

158-
secret_id = request_message.secret_id.value
159-
name = request_message.name.value
160-
description = request_message.description.value
161-
key = request_message.key.value
162-
163-
if not secret_id:
164-
return UpdateSecretResponse(
165-
meta=get_meta(),
166-
success=BoolValue(value=False),
167-
message=Message(title="Invalid Request", description="Secret ID is required")
168-
)
155+
update_secret_ops = request_message.update_secret_ops
169156

170-
try:
171-
secret = Secret.objects.get(id=secret_id, account=account, is_active=True)
172-
173-
# Update fields if provided
174-
if name:
175-
secret.name = name
176-
if description is not None: # Allow empty description
177-
secret.description = description
178-
if key:
179-
secret.key = key
180-
secret.last_updated_by = user
181-
secret.save()
182-
183-
return UpdateSecretResponse(
184-
meta=get_meta(),
185-
success=BoolValue(value=True),
186-
message=Message(title="Success", description="Secret updated successfully"),
187-
secret=_secret_to_proto(secret)
188-
)
189-
except Secret.DoesNotExist:
157+
if not update_secret_ops:
190158
return UpdateSecretResponse(
191159
meta=get_meta(),
192160
success=BoolValue(value=False),
193-
message=Message(title="Not Found", description="Secret not found")
161+
message=Message(title="Invalid Request", description="No update operations provided")
194162
)
195-
except Exception as e:
196-
logger.error(f"Error updating secret: {str(e)}")
163+
164+
# All operations should reference the same secret
165+
secret_ids = set(op.secret_id.value for op in update_secret_ops)
166+
if len(secret_ids) != 1:
197167
return UpdateSecretResponse(
198168
meta=get_meta(),
199169
success=BoolValue(value=False),
200-
message=Message(title="Error", description="Failed to update secret")
170+
message=Message(title="Invalid Request", description="All operations must reference the same secret")
201171
)
202-
203-
204-
@web_api(DeleteSecretRequest)
205-
def secret_delete(request_message: DeleteSecretRequest) -> Union[DeleteSecretResponse, HttpResponse]:
206-
"""Soft delete a secret by setting is_active to False"""
207-
account: Account = get_request_account()
208-
user = get_request_user()
209172

210-
secret_id = request_message.secret_id.value
211-
212-
if not secret_id:
213-
return DeleteSecretResponse(
214-
meta=get_meta(),
215-
success=BoolValue(value=False),
216-
message=Message(title="Invalid Request", description="Secret ID is required")
217-
)
173+
secret_id = list(secret_ids)[0]
218174

219175
try:
220176
secret = Secret.objects.get(id=secret_id, account=account, is_active=True)
221177

222-
# Soft delete by setting is_active to False
223-
secret.is_active = False
178+
# Store the original user for later restoration
179+
original_last_updated_by = secret.last_updated_by
180+
181+
# Set the user who is making the update
224182
secret.last_updated_by = user
225-
secret.save()
183+
secret.save(update_fields=['last_updated_by'])
226184

227-
return DeleteSecretResponse(
228-
meta=get_meta(),
229-
success=BoolValue(value=True),
230-
message=Message(title="Success", description="Secret deleted successfully")
231-
)
185+
try:
186+
# Apply all update operations
187+
from executor.secrets.crud.secrets_update_processor import secrets_update_processor
188+
secrets_update_processor.update(secret, update_secret_ops)
189+
190+
# Get the updated secret
191+
updated_secret = Secret.objects.get(id=secret_id)
192+
193+
return UpdateSecretResponse(
194+
meta=get_meta(),
195+
success=BoolValue(value=True),
196+
message=Message(title="Success", description="Secret updated successfully"),
197+
secret=_secret_to_proto(updated_secret)
198+
)
199+
except Exception as e:
200+
# Restore the original user if update fails
201+
secret.last_updated_by = original_last_updated_by
202+
secret.save(update_fields=['last_updated_by'])
203+
204+
logger.error(f"Error updating secret: {str(e)}")
205+
return UpdateSecretResponse(
206+
meta=get_meta(),
207+
success=BoolValue(value=False),
208+
message=Message(title="Error", description=str(e))
209+
)
232210
except Secret.DoesNotExist:
233-
return DeleteSecretResponse(
211+
return UpdateSecretResponse(
234212
meta=get_meta(),
235213
success=BoolValue(value=False),
236214
message=Message(title="Not Found", description="Secret not found")
237215
)
238216
except Exception as e:
239-
logger.error(f"Error deleting secret: {str(e)}")
240-
return DeleteSecretResponse(
217+
logger.error(f"Error updating secret: {str(e)}")
218+
return UpdateSecretResponse(
241219
meta=get_meta(),
242220
success=BoolValue(value=False),
243-
message=Message(title="Error", description="Failed to delete secret")
244-
)
221+
message=Message(title="Error", description="Failed to update secret")
222+
)

playbooks/urls.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,10 @@
2727
path('executor/', include('executor.urls')),
2828
path('executor/workflows/', include('executor.workflows.urls')),
2929
path('executor/engine/', include('executor.engine_manager.urls')),
30+
path('executor/secrets/', include('executor.secrets.urls')),
3031
path('pb/', include('executor.urls')),
3132
path('management/', include('management.urls')),
3233
path('media/', include('media.urls')),
33-
path('secrets/', include('executor.secrets.urls')),
3434
path('', include('django_prometheus.urls')),
3535
path('', views.index),
3636
]

protos/secrets/api.proto

+36-28
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,14 @@ import "protos/base.proto";
66

77
message Secret {
88
google.protobuf.StringValue id = 1;
9-
google.protobuf.StringValue name = 2;
10-
google.protobuf.StringValue key = 3;
11-
google.protobuf.StringValue masked_value = 4;
12-
google.protobuf.StringValue description = 5;
13-
google.protobuf.StringValue creator = 6;
14-
google.protobuf.StringValue last_updated_by = 7;
15-
int64 created_at = 8;
16-
int64 updated_at = 9;
17-
bool is_active = 10;
9+
google.protobuf.StringValue key = 2;
10+
google.protobuf.StringValue masked_value = 3;
11+
google.protobuf.StringValue description = 4;
12+
google.protobuf.StringValue creator = 5;
13+
google.protobuf.StringValue last_updated_by = 6;
14+
int64 created_at = 7;
15+
int64 updated_at = 8;
16+
bool is_active = 9;
1817
}
1918

2019
message GetSecretsRequest {
@@ -42,10 +41,9 @@ message GetSecretResponse {
4241

4342
message CreateSecretRequest {
4443
Meta meta = 1;
45-
google.protobuf.StringValue name = 2;
46-
google.protobuf.StringValue key = 3;
47-
google.protobuf.StringValue value = 4;
48-
google.protobuf.StringValue description = 5;
44+
google.protobuf.StringValue key = 2;
45+
google.protobuf.StringValue value = 3;
46+
google.protobuf.StringValue description = 4;
4947
}
5048

5149
message CreateSecretResponse {
@@ -55,12 +53,33 @@ message CreateSecretResponse {
5553
Secret secret = 4;
5654
}
5755

56+
message UpdateSecretOp {
57+
enum Op {
58+
UNKNOWN = 0;
59+
UPDATE_SECRET = 1;
60+
UPDATE_SECRET_STATUS = 2;
61+
}
62+
63+
message UpdateSecret {
64+
google.protobuf.StringValue description = 1;
65+
google.protobuf.StringValue value = 2;
66+
}
67+
68+
message UpdateSecretStatus {
69+
google.protobuf.BoolValue is_active = 1;
70+
}
71+
72+
Op op = 1;
73+
google.protobuf.StringValue secret_id = 2;
74+
oneof update {
75+
UpdateSecret update_secret = 3;
76+
UpdateSecretStatus update_secret_status = 4;
77+
}
78+
}
79+
5880
message UpdateSecretRequest {
5981
Meta meta = 1;
60-
google.protobuf.StringValue secret_id = 2;
61-
google.protobuf.StringValue name = 3;
62-
google.protobuf.StringValue description = 4;
63-
google.protobuf.StringValue key = 5;
82+
repeated UpdateSecretOp update_secret_ops = 2;
6483
}
6584

6685
message UpdateSecretResponse {
@@ -69,14 +88,3 @@ message UpdateSecretResponse {
6988
Message message = 3;
7089
Secret secret = 4;
7190
}
72-
73-
message DeleteSecretRequest {
74-
Meta meta = 1;
75-
google.protobuf.StringValue secret_id = 2;
76-
}
77-
78-
message DeleteSecretResponse {
79-
Meta meta = 1;
80-
google.protobuf.BoolValue success = 2;
81-
Message message = 3;
82-
}

0 commit comments

Comments
 (0)