Skip to content

Commit f9413f3

Browse files
committed
Adapt change endpoints to new schema
1 parent 635213b commit f9413f3

8 files changed

Lines changed: 73 additions & 27 deletions

File tree

src/foxops/database/repositories/change/model.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ class ChangeInDB(BaseModel):
3030

3131
merge_request_id: str | None
3232
merge_request_branch_name: str | None
33+
initialized_by: int | None
34+
3335
model_config = ConfigDict(from_attributes=True)
3436

3537
@classmethod

src/foxops/database/repositories/change/repository.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ async def create_change(
4040
template_data_full: str,
4141
merge_request_id: str | None = None,
4242
merge_request_branch_name: str | None = None,
43+
initialized_by: int | None = None,
4344
) -> ChangeInDB:
4445
"""
4546
Create a new change for the given incarnation with the given "revision" number.
@@ -67,6 +68,7 @@ async def create_change(
6768
commit_pushed=commit_pushed,
6869
merge_request_id=merge_request_id,
6970
merge_request_branch_name=merge_request_branch_name,
71+
initialized_by=initialized_by,
7072
)
7173
.returning(*change.columns)
7274
)

src/foxops/models/change.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
from foxops.engine import TemplateData
66
from foxops.hosters.types import MergeRequestStatus
7+
from foxops.models.user import User
78

89

910
class Change(BaseModel):
@@ -21,6 +22,8 @@ class Change(BaseModel):
2122
created_at: datetime
2223
commit_sha: str
2324

25+
initialized_by: User | None = None
26+
2427

2528
class ChangeWithMergeRequest(Change):
2629
merge_request_id: str

src/foxops/routers/changes.py

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,12 @@
77

88
from foxops.database.repositories.change.errors import ChangeNotFoundError
99
from foxops.database.repositories.change.model import ChangeType as DatabaseChangeType
10-
from foxops.dependencies import get_change_service
10+
from foxops.dependencies import authorization, get_change_service
1111
from foxops.engine import TemplateData
1212
from foxops.hosters.types import MergeRequestStatus
1313
from foxops.models.change import Change, ChangeWithMergeRequest
14+
from foxops.models.user import User
15+
from foxops.services.authorization import AuthorizationService
1416
from foxops.services.change import CannotRepairChangeException, ChangeService
1517

1618
router = APIRouter()
@@ -73,12 +75,15 @@ class ChangeDetails(BaseModel):
7375
created_at: datetime
7476
commit_sha: str
7577

78+
initialized_by: User | None = None
79+
7680
merge_request_id: str | None = None
7781
merge_request_branch_name: str | None = None
7882
merge_request_status: MergeRequestStatus | None = None
7983

8084
@classmethod
8185
def from_service_object(cls, obj: Change | ChangeWithMergeRequest) -> Self:
86+
print(obj)
8287
match obj:
8388
case ChangeWithMergeRequest():
8489
return cls(type=ChangeType.MERGE_REQUEST, **obj.model_dump())
@@ -93,19 +98,31 @@ async def create_change(
9398
incarnation_id: int,
9499
request: CreateChangeRequest,
95100
change_service: ChangeService = Depends(get_change_service),
101+
authorization_serive: AuthorizationService = Depends(authorization),
96102
) -> ChangeDetails:
97103
match request.change_type:
98104
case CreateChangeType.DIRECT:
99105
change = await change_service.create_change_direct(
100-
incarnation_id, request.requested_version, request.requested_data
106+
incarnation_id,
107+
request.requested_version,
108+
request.requested_data,
109+
initialized_by=authorization_serive.current_user.id,
101110
)
102111
case CreateChangeType.MERGE_REQUEST_MANUAL:
103112
change = await change_service.create_change_merge_request(
104-
incarnation_id, request.requested_version, request.requested_data, automerge=False
113+
incarnation_id,
114+
request.requested_version,
115+
request.requested_data,
116+
automerge=False,
117+
initialized_by=authorization_serive.current_user.id,
105118
)
106119
case CreateChangeType.MERGE_REQUEST_AUTOMERGE:
107120
change = await change_service.create_change_merge_request(
108-
incarnation_id, request.requested_version, request.requested_data, automerge=True
121+
incarnation_id,
122+
request.requested_version,
123+
request.requested_data,
124+
automerge=True,
125+
initialized_by=authorization_serive.current_user.id,
109126
)
110127
case _:
111128
raise NotImplementedError(f"Unknown change type {request.change_type}")

src/foxops/routers/incarnations.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -220,10 +220,14 @@ async def reset_incarnation(
220220
incarnation_service: IncarnationService = Depends(get_incarnation_service),
221221
change_service: ChangeService = Depends(get_change_service),
222222
hoster: Hoster = Depends(get_hoster),
223+
authorization_service: AuthorizationService = Depends(authorization),
223224
):
224225
try:
225226
change = await change_service.reset_incarnation(
226-
incarnation_id, request.requested_version, request.requested_data
227+
incarnation_id,
228+
request.requested_version,
229+
request.requested_data,
230+
initialized_by=authorization_service.current_user.id,
227231
)
228232
except ProvidedTemplateDataInvalidError as e:
229233
response.status_code = status.HTTP_400_BAD_REQUEST
@@ -257,6 +261,7 @@ async def _create_change(
257261
patch: bool,
258262
response: Response,
259263
change_service: ChangeService,
264+
initialized_by: int,
260265
) -> IncarnationWithDetails | ApiError:
261266
try:
262267
await change_service.create_change_merge_request(
@@ -265,6 +270,7 @@ async def _create_change(
265270
requested_data=requested_data,
266271
automerge=automerge,
267272
patch=patch,
273+
initialized_by=initialized_by,
268274
)
269275
except ProvidedTemplateDataInvalidError as e:
270276
response.status_code = status.HTTP_400_BAD_REQUEST
@@ -334,6 +340,7 @@ async def update_incarnation(
334340
request: UpdateIncarnationRequest,
335341
change_service: ChangeService = Depends(get_change_service),
336342
incarnation_service: IncarnationService = Depends(get_incarnation_service),
343+
authorization_serive: AuthorizationService = Depends(authorization),
337344
):
338345
"""Updates the incarnation to the given version and data.
339346
@@ -378,6 +385,7 @@ async def update_incarnation(
378385
patch=False,
379386
response=response,
380387
change_service=change_service,
388+
initialized_by=authorization_serive.current_user.id,
381389
)
382390

383391

@@ -442,6 +450,7 @@ async def patch_incarnation(
442450
request: PatchIncarnationRequest,
443451
change_service: ChangeService = Depends(get_change_service),
444452
incarnation_service: IncarnationService = Depends(get_incarnation_service),
453+
authorization_serive: AuthorizationService = Depends(authorization),
445454
):
446455
"""Updates the incarnation to the given version and data.
447456
@@ -492,6 +501,7 @@ async def patch_incarnation(
492501
patch=True,
493502
response=response,
494503
change_service=change_service,
504+
initialized_by=authorization_serive.current_user.id,
495505
)
496506
else:
497507
try:

src/foxops/services/change.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,9 @@ async def create_incarnation(
206206

207207
return await self.get_change(change.id)
208208

209-
async def reset_incarnation(self, incarnation_id: int, version: str, data: TemplateData) -> ChangeWithMergeRequest:
209+
async def reset_incarnation(
210+
self, incarnation_id: int, version: str, data: TemplateData, initialized_by: int | None = None
211+
) -> ChangeWithMergeRequest:
210212
"""
211213
Resets an incarnation by removing all customizations that were done to it
212214
... and bring it back to a pristine state as if it was just created freshly from the template.
@@ -256,6 +258,7 @@ async def reset_incarnation(self, incarnation_id: int, version: str, data: Templ
256258
requested_data=json.dumps(incarnation_state.template_data),
257259
template_data_full=json.dumps(incarnation_state.template_data_full),
258260
merge_request_branch_name=reset_branch_name,
261+
initialized_by=initialized_by,
259262
)
260263

261264
await self._push_change_commit_and_update_database(incarnation_git, change_in_db.id)
@@ -280,7 +283,7 @@ async def reset_incarnation(self, incarnation_id: int, version: str, data: Templ
280283
return await self.get_change_with_merge_request(change_in_db.id)
281284

282285
async def create_change_direct(
283-
self, incarnation_id: int, requested_version: str, requested_data: TemplateData
286+
self, incarnation_id: int, requested_version: str, requested_data: TemplateData, initialized_by: int
284287
) -> Change:
285288
"""
286289
Perform a DIRECT change on the given incarnation.
@@ -315,6 +318,7 @@ async def create_change_direct(
315318
requested_version=env.to_version,
316319
requested_data=json.dumps(env.to_data),
317320
template_data_full=json.dumps(env.to_data_full),
321+
initialized_by=initialized_by,
318322
)
319323

320324
# if some failure happens after this point, the database object can be cleaned
@@ -328,6 +332,7 @@ async def create_change_merge_request(
328332
incarnation_id: int,
329333
requested_version: str | None,
330334
requested_data: TemplateData,
335+
initialized_by: int | None = None,
331336
automerge: bool = False,
332337
patch: bool = False,
333338
) -> ChangeWithMergeRequest:
@@ -354,6 +359,7 @@ async def create_change_merge_request(
354359
requested_data=json.dumps(env.to_data),
355360
template_data_full=json.dumps(env.to_data_full),
356361
merge_request_branch_name=env.branch_name,
362+
initialized_by=initialized_by,
357363
)
358364

359365
await self._push_change_commit_and_update_database(env.incarnation_repository, change_in_db.id)
@@ -454,6 +460,11 @@ async def get_change(self, change_id: int) -> Change:
454460
"""
455461

456462
change = await self._change_repository.get_change(change_id)
463+
464+
initialized_by = None
465+
if change.initialized_by is not None:
466+
initialized_by = await self._user_repository.get_by_id(change.initialized_by)
467+
457468
if not change.commit_pushed:
458469
raise IncompleteChange(
459470
"the given change is in an incomplete state (commit_pushed=False). "
@@ -470,6 +481,7 @@ async def get_change(self, change_id: int) -> Change:
470481
template_data_full=json.loads(change.template_data_full),
471482
created_at=change.created_at,
472483
commit_sha=change.commit_sha,
484+
initialized_by=initialized_by,
473485
)
474486

475487
async def get_change_with_merge_request(self, change_id: int) -> ChangeWithMergeRequest:

tests/routers/test_incarnations.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -230,4 +230,4 @@ async def test_api_get_diff_of_non_existing_incarnation_returns_not_found(
230230
response = await api_client.get("/incarnations/1/diff")
231231

232232
assert response.status_code == HTTPStatus.NOT_FOUND
233-
assert response.json() == {"message": "could not find incarnation in DB with id: 1"}
233+
assert response.json() == {"message": "Incarnation with id '1' not found."}

tests/services/test_change.py

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -281,11 +281,11 @@ async def test_create_incarnation_fails_if_there_is_already_one_at_the_target(
281281

282282

283283
async def test_create_change_direct_succeeds_when_updating_the_template_version(
284-
change_service: ChangeService, initialized_incarnation: Incarnation
284+
change_service: ChangeService, initialized_incarnation: Incarnation, user: User
285285
):
286286
# WHEN
287287
change = await change_service.create_change_direct(
288-
initialized_incarnation.id, requested_version="v1.1.0", requested_data={}
288+
initialized_incarnation.id, requested_version="v1.1.0", requested_data={}, initialized_by=user.id
289289
)
290290

291291
# THEN
@@ -302,11 +302,11 @@ async def test_create_change_direct_succeeds_when_updating_the_template_version(
302302

303303

304304
async def test_create_change_direct_succeeds_and_makes_new_template_variables_visible_in_the_foxops_api(
305-
change_service: ChangeService, initialized_incarnation: Incarnation
305+
change_service: ChangeService, initialized_incarnation: Incarnation, user: User
306306
):
307307
# WHEN
308308
change = await change_service.create_change_direct(
309-
initialized_incarnation.id, requested_version="v1.3.0", requested_data={}
309+
initialized_incarnation.id, requested_version="v1.3.0", requested_data={}, initialized_by=user.id
310310
)
311311

312312
# THEN
@@ -324,10 +324,11 @@ async def test_create_change_direct_succeeds_when_updating_to_the_same_branch_na
324324
local_hoster: LocalHoster,
325325
initialized_incarnation: Incarnation,
326326
git_repo_template: str,
327+
user: User,
327328
):
328329
# GIVEN
329330
initial_change = await change_service.create_change_direct(
330-
initialized_incarnation.id, requested_version="main", requested_data={}
331+
initialized_incarnation.id, requested_version="main", requested_data={}, initialized_by=user.id
331332
)
332333
async with local_hoster.cloned_repository(git_repo_template) as repo:
333334
(repo.directory / "template" / "README.md").write_text("Hello, world - even more!")
@@ -336,7 +337,7 @@ async def test_create_change_direct_succeeds_when_updating_to_the_same_branch_na
336337

337338
# WHEN
338339
new_change = await change_service.create_change_direct(
339-
initialized_incarnation.id, requested_version="main", requested_data={}
340+
initialized_incarnation.id, requested_version="main", requested_data={}, initialized_by=user.id
340341
)
341342

342343
# THEN
@@ -352,9 +353,7 @@ async def test_create_change_direct_succeeds_when_updating_to_the_same_branch_na
352353

353354

354355
async def test_create_change_direct_succeeds_when_the_previous_change_was_not_merged(
355-
change_service: ChangeService,
356-
initialized_incarnation: Incarnation,
357-
local_hoster: LocalHoster,
356+
change_service: ChangeService, initialized_incarnation: Incarnation, local_hoster: LocalHoster, user: User
358357
):
359358
# GIVEN
360359
unmerged_change = await change_service.create_change_merge_request(
@@ -369,29 +368,30 @@ async def test_create_change_direct_succeeds_when_the_previous_change_was_not_me
369368
# WHEN
370369
local_hoster.close_merge_request(initialized_incarnation.incarnation_repository, unmerged_change.merge_request_id)
371370
change = await change_service.create_change_direct(
372-
initialized_incarnation.id, requested_version="v1.2.0", requested_data={}
371+
initialized_incarnation.id, requested_version="v1.2.0", requested_data={}, initialized_by=user.id
373372
)
374373

375374
# THEN
376375
assert change.commit_sha != previous_commit_sha
377376

378377

379378
async def test_create_change_direct_succeeds_with_reverting_a_variable_back_to_its_default_value_if_not_explicitly_specified(
380-
change_service: ChangeService,
381-
initialized_incarnation: Incarnation,
382-
local_hoster: LocalHoster,
379+
change_service: ChangeService, initialized_incarnation: Incarnation, local_hoster: LocalHoster, user: User
383380
):
384381
# GIVEN
385382
# ... the incarnation is at a version that requires a variable - and the variable was explicitly set
386383
change = await change_service.create_change_direct(
387-
initialized_incarnation.id, requested_version="v1.3.0", requested_data={"author": "John Doe"}
384+
initialized_incarnation.id,
385+
requested_version="v1.3.0",
386+
requested_data={"author": "John Doe"},
387+
initialized_by=user.id,
388388
)
389389
assert change.template_data_full["author"] == "John Doe"
390390

391391
# WHEN
392392
# ... creating a change that no longer specifies the variable
393393
change = await change_service.create_change_direct(
394-
initialized_incarnation.id, requested_version="v1.3.0", requested_data={}
394+
initialized_incarnation.id, requested_version="v1.3.0", requested_data={}, initialized_by=user.id
395395
)
396396

397397
# THEN
@@ -514,11 +514,11 @@ async def test_list_changes(
514514

515515

516516
async def test_update_incomplete_change_recovers_from_unpushed_direct_change(
517-
local_hoster: LocalHoster, change_service: ChangeService, initialized_incarnation: Incarnation
517+
local_hoster: LocalHoster, change_service: ChangeService, initialized_incarnation: Incarnation, user: User
518518
):
519519
# GIVEN
520520
change = await change_service.create_change_direct(
521-
initialized_incarnation.id, requested_version="v1.1.0", requested_data={}
521+
initialized_incarnation.id, requested_version="v1.1.0", requested_data={}, initialized_by=user.id
522522
)
523523

524524
# remove latest commit from repo
@@ -542,11 +542,11 @@ async def test_update_incomplete_change_recovers_from_unpushed_direct_change(
542542

543543

544544
async def test_update_incomplete_change_recovers_from_pushed_direct_change(
545-
local_hoster: LocalHoster, change_service: ChangeService, initialized_incarnation: Incarnation
545+
local_hoster: LocalHoster, change_service: ChangeService, initialized_incarnation: Incarnation, user: User
546546
):
547547
# GIVEN
548548
change = await change_service.create_change_direct(
549-
initialized_incarnation.id, requested_version="v1.1.0", requested_data={}
549+
initialized_incarnation.id, requested_version="v1.1.0", requested_data={}, initialized_by=user.id
550550
)
551551

552552
# update database to reflect the missing update about the pushed change

0 commit comments

Comments
 (0)