Database Packaging - #1121
Conversation
isc-jili
left a comment
There was a problem hiding this comment.
Looks great @isc-dchui ! I left some comments!
| } | ||
|
|
||
| /// Remaps the namespace's routines to newDBName, then dismounts the old routines DB. | ||
| /// Remap-before-dismount avoids a window where the namespace has no mounted routines DB. |
There was a problem hiding this comment.
Small nit: given that there is a nearly identical comment less than 10 lines down, suggest either removing that comment or removing this section from method description
| // Resolve all module dirs before switching namespace (^UnitTestRoot only in original NS) | ||
| set depDir = ..GetModuleDir("db-packaging", "dependency-module") | ||
| set simpleDir = ..GetModuleDir("db-packaging", "simple-module") | ||
| set depsDir = ..GetModuleDir("db-packaging", "module-with-deps") |
There was a problem hiding this comment.
nit: suggest renaming depDir and depsDir to be more distinctive as currently their names are a bit confusing
| // Extract and verify contents | ||
| set extractDir = ##class(%File).NormalizeDirectory(..PackageOutputDir _ "extract") | ||
| do ##class(%File).CreateDirectory(extractDir) | ||
| set sc = ##class(%IPM.General.Archive).Extract(packageFile, extractDir, .extractOutput) |
There was a problem hiding this comment.
Question: is there any info that needs to be checked in extractOutput? I don't see it being checked below. If it doesn't need to get checked, is there any value in storing it in a variable then?
| set sc = ##class(%IPM.Main).Shell("package-database simple-db-module -path " _ ..PackageOutputDir) | ||
|
|
||
| // Restore original module.xml unconditionally before asserting, so a failure doesn't leave | ||
| // the shared NS in a broken state for subsequent tests |
There was a problem hiding this comment.
Question: if we want to restore module.xml unconditionally, would it be safer to have the following in a try/catch block in case the package-database fails?
There was a problem hiding this comment.
Good call. Added a try/catch failsafe
| set $namespace = installNS2 | ||
|
|
||
| set sc = ##class(%IPM.Main).Shell("load " _ tamperedPackage _ " -swap-db") | ||
| do $$$AssertStatusNotOK(sc, "Tampered IRIS.DAT rejected by checksum validation") |
There was a problem hiding this comment.
Would it be more accurate to check for a substring of the expected error that is thrown as opposed to just the status not being OK? Since what if the status is not OK for a different reason than expected?
| write:verbose !, "Computing SHA-256 checksum..." | ||
| set irisDAT = tempDBPath _ "IRIS.DAT" | ||
| set checksumHex = ..ComputeSHA256Hex(irisDAT) | ||
| write !, " Checksum: ", checksumHex |
There was a problem hiding this comment.
Question: I'm noticing for print statements some are verbose and some are always printed. Just want to double check that this is the intent.
It seems that currently this is the state of print statements:
- invoke warnings: always printed
- checksum: always printed
- package path: always printed
- many progress steps: verbose-only
There was a problem hiding this comment.
Right. I think the checksum and package path being always printed is helpful for the user in general
| @@ -0,0 +1,1050 @@ | |||
| Include %syPrompt | |||
There was a problem hiding this comment.
Does anything in this class use %syPrompt?
There was a problem hiding this comment.
The $$$SuccessResponse macro used to check the result of ##class(%Library.Prompt).GetYesNo() is defined in %syPrompt
| Method ExportGeneratedResources( | ||
| ns As %String, | ||
| includeTestResources As %Boolean, | ||
| ByRef params, |
There was a problem hiding this comment.
Nit: ExportGeneratedResources() takes ByRef params, but I don’t think the method actually uses it?
There was a problem hiding this comment.
Good catch, removed it from the parameters
| /// Throws on any failure. Does not modify any state. | ||
| Method ValidateBeforeSwap( | ||
| packageDir As %String, | ||
| ns As %String) [ Private ] |
There was a problem hiding this comment.
nit: param ns seems to be unused
There was a problem hiding this comment.
Removed from parameters
| /// Dismounts the temp DB, remaps the namespace back to oldDBName, remounts it, | ||
| /// and removes the temp database config entry. | ||
| /// Logs warnings on partial failures but always attempts full restoration. | ||
| ClassMethod RestoreRoutinesDB( |
There was a problem hiding this comment.
The new methods in this new class could all benefit from params documented in the method comments as well!
There was a problem hiding this comment.
Added a bunch more documentation!
bba26d8 to
216223e
Compare
|
|
||
| build: | ||
| timeout-minutes: 45 | ||
| timeout-minutes: 60 |
There was a problem hiding this comment.
Just curious-- why the timeout-minutes changes?
There was a problem hiding this comment.
The new tests add enough time that we're running into the timeouts accidentally. There's an issue (#1195) to see if we can improve this overall
| <parameter name="module" required="true" description="Name of module to package as database" /> | ||
| <modifier name="path" aliases="p" dataAlias="Path" value="true" description="Output directory for the .tgz file." /> | ||
| <modifier name="use-current-db" dataAlias="UseCurrentDB" dataValue="1" description="Package the namespace's current routines DB instead of creating a fresh one." /> | ||
| <modifier name="include-test-resources" dataAlias="IncludeTestResources" dataValue="1" description="Include test resources (Scope=test or Scope=verify) in the package. Excluded by default." /> |
There was a problem hiding this comment.
I still vote calling this -dev instead of -include-test-resources to keep the modifier names consistent across commands and minimize confusion, but I won't die on that hill
There was a problem hiding this comment.
Nit: update the description to indicate that source packaging is the default for the "package" command
| // Identical to "package": route to the Package phase using current lifecycle | ||
| set tCommandInfo = "package" | ||
| do ..RunOnePhase(.tCommandInfo) | ||
| } elseif (tCommandInfo = "package-database") { |
There was a problem hiding this comment.
@isc-dchui I remember that adding package-** commands was part of your original design, but I can't remember why we went with that instead of flags on the existing package command so that it continues to be considered a module-action rather than separate commands. I'm not sure I have a strong preference either way, design-wise, but I'm noticing that the flow through the Shell is different.
There was a problem hiding this comment.
It's because of the lifecycle phases. A separate command allows it to dispatch to the %IPM.Lifecycle.Database class directly
| } | ||
| set tModule.VersionString = ..Module.VersionString | ||
| if (..Module.Deployed) { | ||
| // Use the lifecycle's PACKAGING parameter to determine the publish type, not the module's |
There was a problem hiding this comment.
This is kind of confusing. Thoughts on changing the wording of this parameter to BuildPackaging or something like that?
… and packaging property
isc-kiyer
left a comment
There was a problem hiding this comment.
@isc-dchui few small notes. I love the description of the MR! Could you add some more information for each of the "Key Details"? In particular, for the following:
- Can you explain why generated resources need special treatment? When packaging, wouldn't they be in the routine db already?
- What is the reason for exporting the module manifests and reimporting them? Could that not be accomplished by setting up a global mapping/copying over the IPM globals from the globals db to the routine db during packaging? Then its one less step at deployment time.
| // then it should have a package mapping of this specific resource to its namespace database | ||
| $$$ThrowOnError(..OnConfigureMappings(.pParams)) | ||
| } | ||
| if ..ResourceReference.Generated { |
There was a problem hiding this comment.
That's just dead code. If it's generated, it gets marked as handled at the bottom of the method anyway.
| <modifier name="path" aliases="p" dataAlias="Path" value="true" description="Output directory for the .tgz file." /> | ||
| <modifier name="use-current-db" dataAlias="UseCurrentDB" dataValue="1" description="Package the namespace's current routines DB instead of creating a fresh one." /> | ||
| <modifier name="include-test-resources" dataAlias="IncludeTestResources" dataValue="1" description="Include test resources (Scope=test or Scope=verify) in the package. Excluded by default." /> | ||
| <modifier name="export-python-deps" dataAlias="ExportPythonDependencies" dataValue="1" description="Include Python wheels (from <PythonWheel> resources) in the database package. On by default." /> |
There was a problem hiding this comment.
Nit: could have this be value="true" instead? We need the concept of enums ideally to restrict values but that's out of scope for now so could simply set value true instead of 2 modifiers for it
| <description> | ||
| Creates an IRIS.DAT database package bundled in a .tgz containing IRIS.DAT, module.xml (with SHA-256 checksum), and dependencies.xml. | ||
| </description> | ||
| <parameter name="module" required="true" description="Name of module to package as database" /> |
There was a problem hiding this comment.
Should indicate that this module should not be a dependent of any other module (something we should check and throw an error for). We should also probably check for DB packaging that every module other than this one has at least 1 dependent because if not, you are packaging things that aren't intended.
| pPhase As %String, | ||
| ByRef pParams) As %Status | ||
| { | ||
| if $get(pParams("SkipInvokes")) { quit $$$OK } |
There was a problem hiding this comment.
What is this for? Is there a SkipInvokes option at the command line level?
| { | ||
| // Install context: IRIS.DAT already in place — no source to reload. | ||
| // Also skip <Invoke> elements in all subsequent phases: they ran during packaging. | ||
| // The new IRIS.DAT isn't mounted until %Activate, so any invoke referencing packaged |
There was a problem hiding this comment.
Why do we delay mounting of the IRIS.DAT? I feel like it should be mounted as early as possible (Reload is probably fine). Invokes should remain then as some may have to do with actions at deployment time. This is another important reason we need different install modes (dev, build and deploy)
| write:verbose !, "Validating IPM mapping..." | ||
| do ..ValidateIPMNotInRoutinesDB() | ||
|
|
||
| // Warn about <Invoke> elements that won't run during database install. |
There was a problem hiding this comment.
Hmm so today this could result in some of our Payer Services solutions always issuing warnings because we don't yet have support for different build modes and during packaging time, there are certain reload invokes.
General question though as to why this needs to be done in activate and not in the Reload phase itself? What are the pros/cons of earlier vs later? I think the way we import deployed code is currently in reload.
|
|
||
| if useCurrentDB { | ||
| // -use-current-db: use the existing routines DB directly, no remapping | ||
| // Note: globals mapping is unchanged throughout — IPM metadata stays in globals DB |
There was a problem hiding this comment.
Wouldn't it be better to have IPM metadata be put into routine db? Or I guess we achieve this by exporting the module manifests and loading them into the ns after mounting the IRIS.DAT on install from db packaged?
There was a problem hiding this comment.
Right, the export/import is the mechanism and it avoids some trickiness around namespace/database mappings and possibly conflicting IPM data
| { | ||
| new $namespace | ||
| set $namespace = "%SYS" | ||
| $$$ThrowOnError(##class(Config.Namespaces).Get(ns, .nsProps)) |
There was a problem hiding this comment.
Nit: instead of needing a ns switch, we can use ##class(%SYS.Namespace).GetAllNSInfo()
|
|
||
| // get metadata from annotations | ||
| set metadata = ..GetPackageMetadata(..Location, name, tag, "", client) | ||
| if (metadata = "") { |
There was a problem hiding this comment.
What is the metadata value if the tag doesn't exist in the tag list? So that it's not equal to ""
| ByRef pDependencyGraph, | ||
| pLog As %IPM.General.AbstractHistory = "") As %Status | ||
| pLog As %IPM.General.AbstractHistory = "", | ||
| pIPMPackaging As %String = "") As %Status |
There was a problem hiding this comment.
For consistency with other params the ordering would make more sense to have tReference.IPMPackaging following tReference.PlatformVersion and before params, dep graph, and log
Description
Resolves #986
Overview
Key Details
swap-db, only installs source-packaged modules. If none exist, but database-packaged module(s) exist, informs user. When installing withswap-db, only installs database-packaged modules.com.intersystems.ipm.packagingmanifest annotation to "database" (for backwards compatibility, source packaged modules will not have this annotation) and use<module-version>_database__<IRIS-version>tagTesting
load -swap-db→ classes callable, module in list and historylist-include-test-resourcesincludes Scope=test (TestHelper) and UnitTest (Test) classes; default packaging excludes both-use-current-dbcompletes without error and produces a package filedeps/directory each fail validationupdate -path -swap-db; Packaging changes to "database"; backup confirms DB swap occurredlist; backup preserved; compiled classes gone from namespace-no-export-python-deps; lune importable after install. module-with-requirements: packaging wheel included by default, excluded with flag<Invoke After="Compile">skipped during database install;<Invoke After="Activate">runs; global markers confirm each%Activateoverride-swap-db) → Packaging=module; database install (-swap-db) → Packaging=database; main-with-deps install resolves dep as source; after unpublishing source tag, install without-swap-dbfails with hint to use-swap-dbpackage-databaseoverwrites a stale IRIS version in<SystemRequirements>with the current versionNot Yet Handled
Checklist
mainbranch rebased or merged.zpm test -only) and integration tests (zpm verify -only) pass.