-
Notifications
You must be signed in to change notification settings - Fork 0
Conversation
c4be4b1
to
5ccd832
Compare
7e0c108
to
e634727
Compare
b724d50
to
513d683
Compare
Had a meeting with @eaon about this work today, and just documenting some of our conversation here. istm there are 3 main somewhat independent areas for review:
I feel comfortable in the first two areas, but would appreciate the additional eye of someone more familiar with packaging and building to look at the rpm build and test process (maybe a @legoktm 😄 )? For next steps: @eaon and have set aside some time next week to pair and document test scenarios, particularly for the second point (identifying edge cases/more robust migration testing). |
47f1cf8
to
ffdefe2
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Did a first pass of just visual review, will poke at the packaging/rpm/etc. stuff tomorrow.
9918505
to
4d5e043
Compare
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
61f3c14
to
f292f1f
Compare
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
96edb7b
to
1430d0f
Compare
be0f387
to
7c3a2f3
Compare
This comment was marked as outdated.
This comment was marked as outdated.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My main concern about the use of inheritance is that it's just difficult to trace what is happening, given any action. You have to be aware of the entire hierarchy, and what each class does for each step.
versus something like:
def remove(path: Path):
snapshot(path)
if path.exists():
try:
path.unlink()
except Exception as e:
raise Rollback(e)
Yes, there will probably be some duplication because of it, but it's very straightforward to understand what's happening and in what order.
files/migrations.py
Outdated
rc = None | ||
try: | ||
log.info(f"Running migration for {self}") | ||
process = subprocess.Popen([str(self.path)]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since these are Python files, what's the advantage gained from shelling out? I know earlier in the PR these were bash scripts, but now shelling out doesn't seem necessary.
I think it would be cleaner if each migration file returned a list of steps to run, and then the main migration entrypoint ran the necessary ones.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Very good question that I missed to think about when you originally commented… and you're right that's basically just organic growth, it would certainly make logging less icky as well. However I don't quite see a good way to do this yet, do you have any suggestions?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Refactored to use importlib
which seemed like the least worst option to continue to use the version file name convention.
migrations/steps.py
Outdated
return self.path.exists() == self.check_exists | ||
return True | ||
|
||
def snapshot(self, tmpdir): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm skeptical how useful snapshotting will be. For example, adding/moving/deleting a file will fail because of some I/O error (disk full, permissions, etc.) or a logic issue (code has the wrong name).
In the first case, if we're having I/O errors, copying or moving the tree has no better chance at success and really a lot more ways to go wrong. It could be useful in the second case, but then it seems better to have a global snapshot, rather than per-step ones.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Apparently I lost a response to this message, but for posterity: as discussed in person, I agree with your view here, the implementation for simple file operations is more of a showcase, as the primary target of this functionality would be for making potential destructive changes to VMs and templates, where this functionality would be much more appropriate.
Just chatted with @eaon today about testing + documenting. I suggested either additions to the README that explain the process of writing a migration, or a separate README for this purpose in the migrations directory. I'm happy to help write up any docs that need writing. My understanding of the main points (to get this readme/explainer started/make sure I understand correctly) is:
lmk if you think I'm missing any key info! |
That's pretty much it! Small nits:
|
9348da7
to
e8746e3
Compare
This is a test-balloon for `securedrop-workstation`, where we need complex migrations that don't rely on having to install every package incrementally, but allows for upgrading to the newest package directly. When the package is upgraded, a version number of the previous installed version is read and migrations that form the delta between the target version are run in succession. To allow the use of this pattern immediately, the version number is also checked on installation, letting us deploy the package with a`%post` scriptlet that ensures an "initial migration" is run. `%post` scriptlets are always expected to run successfully, and even if scriplets fail, the package installation isn't marked as failed. Hence, later down the line we'll need logic in the updater itself to check for the expected version, and respond appropriately if a migration failed.
e8746e3
to
cab66d9
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK! I hope the number of comments isn't overwhelming, like half of them are pretty minor/trivial issues.
I did not yet get to reviewing the test framework and the tests themselves, will try to do that tomorrow.
files/migration_steps.py
Outdated
return self.__repr__() | ||
|
||
|
||
def path_remove(path): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: Typically I'd name functions as verb-noun, so remove_path
, validate_path
, etc.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Aye, having a style guide for that sort of thing might be good 😄 My approach to this is typically to start names of things with the part that is shared, sort of sharedbase_variant[_detail]
etc. Not sure if there's a name for that though. Totally open to renaming them
- Added all the type hints I think - Removed subclassing for Migration - Added some docstrings - Removed unidiomatic uses of exit - Move per-step tmpdir customisation to MigrationSteps tmpdir method - Don't ship migration directory's README.md - Replace error f-strings with logging.exception
7aa739e
to
144afc4
Compare
RPM's %files macro's -f argument is apparently incompatible with reproducible builds, so we need to be explicit about what is (not) included when Also, RPM spec linter is set to disallow macro expansion in comments, so instruct developers to account for this when uncommenting.
144afc4
to
3666984
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Overall the tests look good, the main remaining issue is the types being passed to the MigrationStep constructors being Path or str.
Also a general nit is that the read_text()/write_text() functions are nicer than .open.read()
and .open("w").write()
:-)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One very minor documentation update is needed, otherwise it looks ready to me!!
- More (and better) documentation for tests - Consistent API for file migration steps - Moved migration test infrastructure from fixtures to utils - Changed return codes to be more informative - Use pathlib's write_text/read_text where possible - Move validation exception from failure.py to its own migration test - Use unique class names for migration tests - Slight improvements for a variety of tests
9f4cb6d
to
f7c52a5
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Awesome work!
This is equivalent to merging in freedomofpress/securedrop-updater@803c329b8a7323053d3345f3cdf6efc9a6b0308b7 without freedomofpress/securedrop-updater#2 and freedomofpress/securedrop-updater#32. Since freedomofpress/securedrop-updater originally diverged from freedomofpress/securedrop-workstation, squashing the merge avoids duplicating that shared history. commit c6179d5 Merge: f73b6bf 803c329 Author: Cory Francis Myers <cory@freedom.press> Date: Tue Apr 9 15:38:41 2024 -0700 Merge branch 'no-migrations' into reabsorb-updater-no-migrations commit 803c329b8a7323053d3345f3cdf6efc9a6b0308b7 Author: Michael Z <michael@freedom.press> Date: Mon Mar 13 16:30:08 2023 +0100 Catch up with securedrop-workstation#844 Open SD client with the desktop file introduced in securedrop-client#1600 ...
This is equivalent to merging in freedomofpress/securedrop-updater@e3930ae without freedomofpress/securedrop-updater#2 and freedomofpress/securedrop-updater#32. Since freedomofpress/securedrop-updater originally diverged from freedomofpress/securedrop-workstation, squashing the merge avoids duplicating that shared history. commit c6179d5 Merge: f73b6bf 803c329 Author: Cory Francis Myers <cory@freedom.press> Date: Tue Apr 9 15:38:41 2024 -0700 Merge branch 'no-migrations' into reabsorb-updater-no-migrations commit 803c329b8a7323053d3345f3cdf6efc9a6b0308b7 Author: Michael Z <michael@freedom.press> Date: Mon Mar 13 16:30:08 2023 +0100 Catch up with securedrop-workstation#844 Open SD client with the desktop file introduced in securedrop-client#1600 ...
Description
Implements freedomofpress/securedrop-workstation#673, a way to ensure all migrations that would be necessary to run are executed in succession, even if there are multiple, and that at time of package install/upgrade. It is a test balloon 🎈 intended to be ported to
securedrop-workstation
after the next release.This is work also goes towards Qubes OS R4.2 support, which will have its own updater policy system, which would otherwise be incompatible with our current upgrade/migrate strategy.
Note: this PR only contains the migration engine and its respective tests, it does not contain any actual migrations. This will follow in a separate PR.
Review
migrations/README.md
is helpful enough to start writing new migrationsTesting
make test
(in a dev environment)No dom0 testing necessary.