From 3943f0f525b268d9cffb57c53db19cab785358b0 Mon Sep 17 00:00:00 2001 From: Pravin Barton <9560941+isc-pbarton@users.noreply.github.com> Date: Fri, 10 Jan 2025 14:28:24 -0500 Subject: [PATCH 1/2] enh: API method to load a production directory in a CD context without Git configured --- cls/SourceControl/Git/API.cls | 8 +++ cls/SourceControl/Git/Util/Production.cls | 72 +++++++++++++++++++ .../SourceControl/Git/Util/Production.cls | 16 +++++ .../cls/UnitTest/SampleProduction.cls | 16 +++++ .../ProdStgs-UnitTest_SampleProduction.xml | 32 +++++++++ .../UnitTest_SampleProduction/Stgs-b949C.xml | 32 +++++++++ .../UnitTest_SampleProduction/Stgs-c949C.xml | 32 +++++++++ 7 files changed, 208 insertions(+) create mode 100644 test/_resources/cls/UnitTest/SampleProduction.cls create mode 100644 test/_resources/ptd/UnitTest_SampleProduction/ProdStgs-UnitTest_SampleProduction.xml create mode 100644 test/_resources/ptd/UnitTest_SampleProduction/Stgs-b949C.xml create mode 100644 test/_resources/ptd/UnitTest_SampleProduction/Stgs-c949C.xml diff --git a/cls/SourceControl/Git/API.cls b/cls/SourceControl/Git/API.cls index e77ec05f..d3dab115 100644 --- a/cls/SourceControl/Git/API.cls +++ b/cls/SourceControl/Git/API.cls @@ -82,4 +82,12 @@ ClassMethod BaselineProductions() do ##class(SourceControl.Git.Util.Production).BaselineProductions() } +/// Given the path to a directory that contains production items, this method will import them all +/// and delete any custom items from the production configuration that do not exist in the directory. +/// This method may be called on a namespace that is not configured with Embedded Git for source control. +ClassMethod LoadProductionsFromDirectory(pDirectoryName, Output pFailedItems) As %Status +{ + return ##class(SourceControl.Git.Util.Production).LoadProductionsFromDirectory(pDirectoryName, .pFailedItems) +} + } diff --git a/cls/SourceControl/Git/Util/Production.cls b/cls/SourceControl/Git/Util/Production.cls index fd7f7341..c7d5c794 100644 --- a/cls/SourceControl/Git/Util/Production.cls +++ b/cls/SourceControl/Git/Util/Production.cls @@ -70,4 +70,76 @@ ClassMethod ItemIsPTD(externalName) As %Boolean return 0 } +/// Given the path to a directory that contains production items, this method will import them all +/// and delete any custom items from the production configuration that do not exist in the directory. +/// This method may be called on a namespace that is not configured with Embedded Git for source control. +ClassMethod LoadProductionsFromDirectory(pDirectoryName, Output pFailedItems) As %Status +{ + set st = $$$OK + try { + set rs = ##class(%File).FileSetFunc(pDirectoryName,,,1) + throw:rs.%SQLCODE<0 ##class(%Exception.SQL).CreateFromSQLCODE(rs.%SQLCODE,rs.%Message) + kill itemsOnDisk + while rs.%Next() { + continue:rs.Type'="D" + set rs2 = ##class(%File).FileSetFunc(rs.Name,"*.xml",,0) + throw:rs2.%SQLCODE<0 ##class(%Exception.SQL).CreateFromSQLCODE(rs2.%SQLCODE,rs2.%Message) + while rs2.%Next() { + set filePath = rs2.Name + $$$ThrowOnError(##class(SourceControl.Git.Production).ParseExternalName(filePath,.internalName)) + set itemName = "", itemClassName = "", productionName = "" + do ##class(SourceControl.Git.Production).ParseInternalName(internalName,,,.itemName,.itemClassName,.productionName) + quit:productionName="" + if (itemName'="") && (itemClassName'="") { + set itemsOnDisk(productionName, itemName, itemClassName) = 1 + } + // if production does not exist, create it + if '$isobject(##class(Ens.Config.Production).%OpenId(productionName)) { + $$$ThrowOnError(##class(SourceControl.Git.Production).CreateProduction(productionName)) + } + set st = ##class(SourceControl.Git.Production).ImportPTD(filePath, productionName) + if $$$ISERR(st) { + set pFailedItems(filePath) = st + } + } + } + // handle deletes by iterating through XDATA of each production class. for every config item that is not in itemsOnDisk, delete it. + set key = $order(itemsOnDisk("")) + while (key '= "") { + set classDef = ##class(%Dictionary.ClassDefinition).%OpenId(key) + if $isobject(classDef) { + set productionXData = $$$NULLOREF + for i=1:1:classDef.XDatas.Count() { + set xdata = classDef.XDatas.GetAt(i) + if xdata.Name = "ProductionDefinition" { + set productionXData = xdata + quit + } + } + if $isobject(productionXData) { + $$$ThrowOnError(##class(%XML.XPATH.Document).CreateFromStream(productionXData.Data,.xdoc)) + $$$ThrowOnError(xdoc.EvaluateExpression("/Production","Item/@Name | Item/@ClassName",.results)) + for i=1:2:results.Count() { + set itemName = results.GetAt(i).Value + set itemClassName = results.GetAt(i+1).Value + if (itemName'="") && (itemClassName'="") && '$get(itemsOnDisk(key,itemName, itemClassName)) { + write !, "Removing item from production ", key, ": ", itemName, ":", itemClassName + set internalName = ##class(SourceControl.Git.Production).CreateInternalName(key,itemName, itemClassName) + set st = ##class(SourceControl.Git.Production).RemoveItem(internalName) + if $$$ISERR(st) { + set pFailedItems(itemName, itemClassName) = st + } + } + } + } + } + set key = $order(itemsOnDisk(key)) + } + } catch err { + set st = err.AsStatus() + } + if $data(pFailedItems) set st = $$$ADDSC($$$ERROR($$$GeneralError,"Some items failed to deploy."),st) + return st +} + } diff --git a/test/UnitTest/SourceControl/Git/Util/Production.cls b/test/UnitTest/SourceControl/Git/Util/Production.cls index 2ccbf11f..f9729b37 100644 --- a/test/UnitTest/SourceControl/Git/Util/Production.cls +++ b/test/UnitTest/SourceControl/Git/Util/Production.cls @@ -15,6 +15,22 @@ Method TestItemIsPTD() do $$$AssertTrue(##class(SourceControl.Git.Util.Production).ItemIsPTD("ptd2\test.xml")) } +Method TestLoadProductionsFromDirectory() +{ + // load a production from a class file under resources + set packageRoot = ##class(SourceControl.Git.PackageManagerContext).ForInternalName("git-source-control.zpm").Package.Root + $$$ThrowOnError($System.OBJ.Load(packageRoot_"test/_resources/cls/UnitTest/SampleProduction.cls","ck")) + // call LoadProductionsFromDirectory on a directory under resources/ptd + do $$$AssertStatusOK(##class(SourceControl.Git.Util.Production).LoadProductionsFromDirectory(packageRoot_"test/_resources/ptd")) + // confirm items were deleted and added + set itemA = ##class(Ens.Config.Production).OpenItemByConfigName("UnitTest.SampleProduction||a") + do $$$AssertNotTrue($isobject(itemA),"item a was deleted") + set itemB = ##class(Ens.Config.Production).OpenItemByConfigName("UnitTest.SampleProduction||b") + do $$$AssertEquals(itemB.Settings.GetAt(1).Value,71) + set itemB = ##class(Ens.Config.Production).OpenItemByConfigName("UnitTest.SampleProduction||c") + do $$$AssertTrue($isobject(itemB),"item a was created") +} + Method OnBeforeAllTests() As %Status { merge ..Mappings = @##class(SourceControl.Git.Utils).MappingsNode() diff --git a/test/_resources/cls/UnitTest/SampleProduction.cls b/test/_resources/cls/UnitTest/SampleProduction.cls new file mode 100644 index 00000000..2f507841 --- /dev/null +++ b/test/_resources/cls/UnitTest/SampleProduction.cls @@ -0,0 +1,16 @@ +Class UnitTest.SampleProduction Extends Ens.Production +{ + +XData ProductionDefinition +{ + + + 60 + + + 61 + + +} + +} diff --git a/test/_resources/ptd/UnitTest_SampleProduction/ProdStgs-UnitTest_SampleProduction.xml b/test/_resources/ptd/UnitTest_SampleProduction/ProdStgs-UnitTest_SampleProduction.xml new file mode 100644 index 00000000..ad951464 --- /dev/null +++ b/test/_resources/ptd/UnitTest_SampleProduction/ProdStgs-UnitTest_SampleProduction.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + +UnitTest.SampleProduction +1841-01-01 00:00:00.000 + + + + +ProductionSettings-UnitTest_SampleProduction +ProductionSettings:UnitTest.SampleProduction.PTD + + + + +]]> + + + + +]]> + diff --git a/test/_resources/ptd/UnitTest_SampleProduction/Stgs-b949C.xml b/test/_resources/ptd/UnitTest_SampleProduction/Stgs-b949C.xml new file mode 100644 index 00000000..0bfa76b2 --- /dev/null +++ b/test/_resources/ptd/UnitTest_SampleProduction/Stgs-b949C.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + +UnitTest.SampleProduction +1841-01-01 00:00:00.000 + + + + +Settings-b +Settings:b.PTD + + + + +]]> + + + +71 +]]> + diff --git a/test/_resources/ptd/UnitTest_SampleProduction/Stgs-c949C.xml b/test/_resources/ptd/UnitTest_SampleProduction/Stgs-c949C.xml new file mode 100644 index 00000000..e66637cc --- /dev/null +++ b/test/_resources/ptd/UnitTest_SampleProduction/Stgs-c949C.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + +UnitTest.SampleProduction +1841-01-01 00:00:00.000 + + + + +Settings-c +Settings:c.PTD + + + + +]]> + + + + +]]> + From 70eb962971cac94c7284e212cfd6dcd11e615543 Mon Sep 17 00:00:00 2001 From: Pravin Barton <9560941+isc-pbarton@users.noreply.github.com> Date: Mon, 13 Jan 2025 10:33:57 -0500 Subject: [PATCH 2/2] chore: update CHANGELOG and module version --- CHANGELOG.md | 5 +++++ module.xml | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 94231c17..08528980 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,11 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [2.10.0] - Unreleased + +### Added +- LoadProductionsFromDirectory method to help custom deployment scripts load decomposed productions from the repository (#670) + ## [2.9.0] - 2025-01-09 ### Added diff --git a/module.xml b/module.xml index 7702e998..14518da8 100644 --- a/module.xml +++ b/module.xml @@ -3,7 +3,7 @@ git-source-control - 2.9.0 + 2.10.0 Server-side source control extension for use of Git on InterSystems platforms git source control studio vscode module