├── sc-list.txt ├── icm ├── definitions.json ├── load_ci_icm.script ├── Dockerfile ├── defaults.json └── .gitlab-ci.yml ├── isc ├── git │ ├── hook │ │ ├── Global.cls │ │ ├── Local.cls │ │ ├── Abstract.cls │ │ └── Manager.cls │ ├── Settings.cls │ ├── Diff.cls │ └── GitLab.cls └── util │ ├── LogUtils.cls │ └── OSUtils.cls ├── docker ├── Dockerfile ├── load_ci.script └── .gitlab-ci.yml ├── LICENSE ├── .gitlab-ci.yml └── README.md /sc-list.txt: -------------------------------------------------------------------------------- 1 | isc.pkg 2 | -------------------------------------------------------------------------------- /icm/definitions.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "Role": "DM", 4 | "Count": "1", 5 | "ISCLicense": "/icmdata/iris.key" 6 | } 7 | ] 8 | -------------------------------------------------------------------------------- /isc/git/hook/Global.cls: -------------------------------------------------------------------------------- 1 | /// Subclass this class to create a hook 2 | /// that would be executed each time the CI is ran. 3 | Class isc.git.hook.Global Extends isc.git.hook.Abstract 4 | { 5 | 6 | } 7 | 8 | -------------------------------------------------------------------------------- /icm/load_ci_icm.script: -------------------------------------------------------------------------------- 1 | 2 | set dir = ##class(%File).NormalizeDirectory($system.Util.GetEnviron("CI_PROJECT_DIR")) 3 | do ##class(%SYSTEM.OBJ).Load(dir _ "Installer/Global.cls","cdk") 4 | do ##class(Installer.Global).init() 5 | halt -------------------------------------------------------------------------------- /isc/git/hook/Local.cls: -------------------------------------------------------------------------------- 1 | Class isc.git.hook.Local Extends isc.git.hook.Abstract 2 | { 3 | 4 | /// Code executed after main code load/compile. 5 | /// Do not modify. 6 | ClassMethod after() As %Status 7 | { 8 | try { 9 | set sc = ..onAfter() 10 | do ##class(isc.git.hook.Manager).add($classname()) 11 | } catch ex { 12 | set sc = ex.AsStatus() 13 | } 14 | quit sc 15 | } 16 | 17 | } 18 | 19 | -------------------------------------------------------------------------------- /icm/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM intersystems/iris:2018.1.1-released 2 | 3 | ENV SRC_DIR=/tmp/src 4 | ENV CI_DIR=$SRC_DIR/ci 5 | ENV CI_PROJECT_DIR=$SRC_DIR 6 | 7 | COPY ./ $SRC_DIR 8 | 9 | RUN cp $CI_DIR/iris.key $ISC_PACKAGE_INSTALLDIR/mgr/ \ 10 | && cp $CI_DIR/GitLab.xml $ISC_PACKAGE_INSTALLDIR/mgr/ \ 11 | && $ISC_PACKAGE_INSTALLDIR/dev/Cloud/ICM/changePassword.sh $CI_DIR/pwd.txt \ 12 | && iris start $ISC_PACKAGE_INSTANCENAME \ 13 | && irissession $ISC_PACKAGE_INSTANCENAME -U USER < $CI_DIR/load_ci.script \ 14 | && iris stop $ISC_PACKAGE_INSTANCENAME quietly -------------------------------------------------------------------------------- /docker/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM docker.intersystems.com/intersystems/iris:2018.1.1.611.0 2 | 3 | ENV SRC_DIR=/tmp/src 4 | ENV CI_DIR=$SRC_DIR/ci 5 | ENV CI_PROJECT_DIR=$SRC_DIR 6 | 7 | COPY ./ $SRC_DIR 8 | 9 | RUN cp $CI_DIR/iris.key $ISC_PACKAGE_INSTALLDIR/mgr/ \ 10 | && cp $CI_DIR/GitLab.xml $ISC_PACKAGE_INSTALLDIR/mgr/ \ 11 | && $ISC_PACKAGE_INSTALLDIR/dev/Cloud/ICM/changePassword.sh $CI_DIR/pwd.txt \ 12 | && iris start $ISC_PACKAGE_INSTANCENAME \ 13 | && irissession $ISC_PACKAGE_INSTANCENAME -U%SYS < $CI_DIR/load_ci.script \ 14 | && iris stop $ISC_PACKAGE_INSTANCENAME quietly -------------------------------------------------------------------------------- /isc/util/LogUtils.cls: -------------------------------------------------------------------------------- 1 | Class isc.util.LogUtils 2 | { 3 | 4 | ClassMethod logVar(var = "", name As %String = "") As %String 5 | { 6 | do ..log("Variable " _ name) 7 | zw var 8 | /*if $isObject(var) { 9 | zw var 10 | } elseif $listValid(var) { 11 | write $lts(var, ", ") 12 | } else { 13 | write var 14 | }*/ 15 | } 16 | 17 | ClassMethod logException(ex As %Exception.AbstractException) 18 | { 19 | do ..logStatus(ex.AsStatus()) 20 | } 21 | 22 | ClassMethod logStatus(sc As %Status) 23 | { 24 | do ..log($System.Status.GetErrorText(sc)) 25 | } 26 | 27 | ClassMethod log(msg As %String) 28 | { 29 | write !, $$$FormatText("[%1] %2", $zdatetime($ztimestamp, 3, 1, 3), msg), ! 30 | } 31 | 32 | } 33 | 34 | -------------------------------------------------------------------------------- /icm/defaults.json: -------------------------------------------------------------------------------- 1 | { 2 | "Provider": "GCP", 3 | "Label": "gsdemo2", 4 | "Tag": "TEST", 5 | "SystemMode": "TEST", 6 | "DataVolumeSize": "10", 7 | "SSHUser": "sample", 8 | "SSHPublicKey": "/icmdata/ssh/insecure.pub", 9 | "SSHPrivateKey": "/icmdata/ssh/insecure", 10 | "DockerImage": "eduard93/icmdemo:master", 11 | "DockerUsername": "", 12 | "DockerPassword": "", 13 | "TLSKeyDir": "/icmdata/tls", 14 | "Credentials": "/icmdata/gcp2.json", 15 | "Project": "elebedyu-test", 16 | "MachineType": "n1-standard-1", 17 | "Region": "us-east1", 18 | "Zone": "us-east1-b", 19 | "Image": "rhel-cloud/rhel-7-v20170719", 20 | "ISCPassword": "SYS", 21 | "Mirror": "false" 22 | } 23 | -------------------------------------------------------------------------------- /docker/load_ci.script: -------------------------------------------------------------------------------- 1 | 2 | set sc = ##Class(Security.System).Get("SYSTEM",.Properties) 3 | write:('sc) $System.Status.GetErrorText(sc) 4 | set AutheEnabled = Properties("AutheEnabled") 5 | set AutheEnabled = $zb(+AutheEnabled,16,7) 6 | set Properties("AutheEnabled") = AutheEnabled 7 | set sc = ##Class(Security.System).Modify("SYSTEM",.Properties) 8 | write:('sc) $System.Status.GetErrorText(sc) 9 | zn "USER" 10 | do ##class(%SYSTEM.OBJ).Load(##class(%File).ManagerDirectory() _ "GitLab.xml","cdk") 11 | do ##class(isc.git.Settings).setSetting("hooks", "MyApp/Hooks/") 12 | do ##class(isc.git.Settings).setSetting("tests", "MyApp/Tests/") 13 | write $system.Process.CurrentDirectory(),!,$system.Process.UserName(),!,##class(isc.git.GitLab).getDir() 14 | do ##class(isc.git.GitLab).load() 15 | halt -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 InterSystems 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /isc/git/hook/Abstract.cls: -------------------------------------------------------------------------------- 1 | /// Base hook class. 2 | /// You should not subclass directly. 3 | /// Extend Global or Local hook classes 4 | Class isc.git.hook.Abstract 5 | { 6 | 7 | /// Code executed before main code load/compile. 8 | /// Do not modify. 9 | ClassMethod before() As %Status 10 | { 11 | try { 12 | set sc = ..onBefore() 13 | } catch ex { 14 | set sc = ex.AsStatus() 15 | } 16 | quit sc 17 | } 18 | 19 | /// Code executed before main code load/compile. 20 | /// Overwrite this method. 21 | ClassMethod onBefore() As %Status 22 | { 23 | quit $$$OK 24 | } 25 | 26 | /// Code executed after main code load/compile. 27 | /// Do not modify. 28 | ClassMethod after() As %Status 29 | { 30 | try { 31 | set sc = ..onAfter() 32 | } catch ex { 33 | set sc = ex.AsStatus() 34 | } 35 | quit sc 36 | } 37 | 38 | /// Code executed before main code load/compile. 39 | /// Overwrite this method. 40 | ClassMethod onAfter() As %Status 41 | { 42 | quit $$$OK 43 | } 44 | 45 | /// Code executed during rollback. 46 | /// Do not modify. 47 | ClassMethod rollback() As %Status 48 | { 49 | try { 50 | set sc = ..onRollback() 51 | } catch ex { 52 | set sc = ex.AsStatus() 53 | } 54 | quit sc 55 | } 56 | 57 | /// Code executed during rollback. 58 | /// Overwrite this method. 59 | ClassMethod onRollback() As %Status 60 | { 61 | quit $$$OK 62 | } 63 | 64 | } 65 | 66 | -------------------------------------------------------------------------------- /icm/.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | stages: 2 | - build 3 | - publish 4 | - run 5 | - test 6 | - deploy 7 | 8 | build image: 9 | stage: build 10 | tags: 11 | - master 12 | script: 13 | - cp -r /InterSystems/mount ci 14 | - cd ci 15 | - echo 'SuperUser' | cat - pwd.txt load_ci_icm.script > temp.txt 16 | - mv temp.txt load_ci.script 17 | - cd .. 18 | - docker build --build-arg CI_PROJECT_DIR -t eduard93/icmdemo:$CI_COMMIT_REF_NAME . 19 | 20 | publish image: 21 | stage: publish 22 | tags: 23 | - master 24 | script: 25 | - docker login -u eduard93 -p ${DOCKERPASSWORD} 26 | - docker push eduard93/icmdemo:$CI_COMMIT_REF_NAME 27 | 28 | run image: 29 | stage: run 30 | environment: 31 | name: $CI_COMMIT_REF_NAME 32 | tags: 33 | - master 34 | script: 35 | - docker exec icm sh -c "cd /icmdata/test && icm upgrade -image eduard93/icmdemo:$CI_COMMIT_REF_NAME" 36 | 37 | test image: 38 | stage: test 39 | tags: 40 | - master 41 | script: 42 | - docker exec icm sh -c "cd /icmdata/test && icm session -namespace USER -command 'do \$classmethod(\"%UnitTest.Manager\",\"RunTest\",\"MyApp/Tests\",\"/nodelete\")' | tee /dev/stderr | grep 'All PASSED' && exit 0 || exit 1" 43 | 44 | deploy image: 45 | stage: deploy 46 | environment: 47 | name: $CI_COMMIT_REF_NAME 48 | tags: 49 | - master 50 | script: 51 | - docker exec icm sh -c "cd /icmdata/live && icm upgrade -image eduard93/icmdemo:$CI_COMMIT_REF_NAME" -------------------------------------------------------------------------------- /docker/.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | stages: 2 | - build 3 | - destroy 4 | - run 5 | - test 6 | - publish 7 | 8 | build image: 9 | stage: build 10 | tags: 11 | - test 12 | script: 13 | - cp -r /InterSystems/mount ci 14 | - cd ci 15 | - echo 'SuperUser' | cat - pwd.txt load_ci.script > temp.txt 16 | - mv temp.txt load_ci.script 17 | - cd .. 18 | - docker build --build-arg CI_PROJECT_DIR=$CI_PROJECT_DIR -t docker.eduard.win/test/docker:$CI_COMMIT_REF_NAME . 19 | 20 | destroy old: 21 | stage: destroy 22 | tags: 23 | - test 24 | script: 25 | - docker stop iris-$CI_COMMIT_REF_NAME || true 26 | - docker rm -f iris-$CI_COMMIT_REF_NAME || true 27 | 28 | 29 | 30 | run image: 31 | stage: run 32 | environment: 33 | name: $CI_COMMIT_REF_NAME 34 | url: http://$CI_COMMIT_REF_SLUG.docker.eduard.win/index.html 35 | tags: 36 | - test 37 | script: 38 | - docker run -d 39 | --expose 52773 40 | --env VIRTUAL_HOST=$CI_COMMIT_REF_SLUG.docker.eduard.win 41 | --name iris-$CI_COMMIT_REF_NAME 42 | docker.eduard.win/test/docker:$CI_COMMIT_REF_NAME 43 | --log $ISC_PACKAGE_INSTALLDIR/mgr/messages.log 44 | 45 | test image: 46 | stage: test 47 | tags: 48 | - test 49 | script: 50 | - docker exec iris-$CI_COMMIT_REF_NAME irissession iris -U USER "##class(isc.git.GitLab).test()" 51 | 52 | publish image: 53 | stage: publish 54 | tags: 55 | - test 56 | script: 57 | - docker login docker.eduard.win -u dev -p 123 58 | - docker push docker.eduard.win/test/docker:$CI_COMMIT_REF_NAME -------------------------------------------------------------------------------- /isc/git/Settings.cls: -------------------------------------------------------------------------------- 1 | Include %syPrompt 2 | 3 | Class isc.git.Settings 4 | { 5 | 6 | /// List of extensions relevant to code load 7 | Parameter EXT As List = {$lb("xml", "cls", "csp", "csr", "mac", "int", "bas", "inc", "gbl", "prj", "obj", "pkg", "gof", "dfi", "pivot", "dashboard")}; 8 | 9 | Parameter URL = "http://127.0.0.1:57772"; 10 | 11 | Parameter GLVN = "^isc.git.Settings"; 12 | 13 | /// Get setting 14 | /// Получить настойку 15 | /// write ##class(isc.git.Settings).getSetting("ext") 16 | ClassMethod getSetting(name As %String) As %String [ CodeMode = expression ] 17 | { 18 | $get(@..#GLVN@($zcvt(name, "l")), $parameter(,$zcvt(name, "U"))) 19 | } 20 | 21 | /// Get setting 22 | /// Установить настройку 23 | /// write ##class(isc.git.Settings).setSetting("ext") 24 | ClassMethod setSetting(name As %String = "", value As %String = "") As %Status 25 | { 26 | #dim sc As %Status = $$$OK 27 | 28 | if name = "tests" { 29 | // Path relative from the repo root to test suite 30 | } elseif name = "ext" { 31 | set:'$listValid(value) sc = $$$ERROR($$$GeneralError, "Extension list should be in $lb format.") 32 | } elseif name = "commit" { 33 | // TO-DO commit validation. 34 | } elseif name = "delete" { 35 | set:$length(value, ":")'=2 sc = $$$ERROR($$$GeneralError, "Delete should be in a format: 'class:method'") 36 | } elseif name = "url" { 37 | // TO-DO url validation. 38 | // "http://127.0.0.1:57772" 39 | } elseif name = "hooks" { 40 | // Path relative from the repo root to hooks 41 | } else { 42 | set sc = $$$ERROR($$$GeneralError, $$$FormatText("Setting '%1' does not exist", name)) 43 | } 44 | 45 | set:$$$ISOK(sc) @..#GLVN@($zcvt(name, "l")) = value 46 | return sc 47 | } 48 | 49 | } 50 | 51 | -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | stages: 2 | - load 3 | - test 4 | - package 5 | - deploy 6 | 7 | .env_test: &env_test 8 | environment: 9 | name: test 10 | url: http://test.eduard.win 11 | only: 12 | - master 13 | tags: 14 | - test 15 | 16 | .env_preprod: &env_preprod 17 | when: manual 18 | environment: 19 | name: preprod 20 | url: http://preprod.eduard.win 21 | only: 22 | - preprod 23 | tags: 24 | - preprod 25 | 26 | .env_prod: &env_prod 27 | environment: 28 | name: prod 29 | url: http://prod.eduard.win 30 | only: 31 | - prod 32 | tags: 33 | - prod 34 | 35 | .script_load: &script_load 36 | stage: load 37 | script: csession ensemble "##class(isc.git.GitLab).loadDiff()" 38 | artifacts: 39 | paths: 40 | - diff.xml 41 | 42 | .script_test: &script_test 43 | stage: test 44 | script: csession ensemble "##class(isc.git.GitLab).test()" 45 | artifacts: 46 | paths: 47 | - tests.html 48 | 49 | .script_package_client: &script_package_client 50 | stage: package 51 | script: envsubst < client/index.html > index.html 52 | artifacts: 53 | paths: 54 | - index.html 55 | 56 | .script_package_server: &script_package_server 57 | stage: package 58 | script: csession ensemble "##class(isc.git.GitLab).package()" 59 | artifacts: 60 | paths: 61 | - full.xml 62 | 63 | .script_deploy_client: &script_deploy_client 64 | stage: deploy 65 | script: cp -f index.html /var/www/html/index.html 66 | 67 | load test: 68 | <<: *env_test 69 | <<: *script_load 70 | 71 | test test: 72 | <<: *env_test 73 | <<: *script_test 74 | 75 | package client test: 76 | <<: *env_test 77 | <<: *script_package_client 78 | 79 | package server test: 80 | <<: *env_test 81 | <<: *script_package_server 82 | 83 | deploy test: 84 | <<: *env_test 85 | <<: *script_deploy_client 86 | 87 | load preprod: 88 | <<: *env_preprod 89 | <<: *script_load 90 | 91 | package client preprod: 92 | <<: *env_preprod 93 | <<: *script_package_client 94 | 95 | deploy preprod: 96 | <<: *env_preprod 97 | <<: *script_deploy_client 98 | 99 | load prod: 100 | <<: *env_prod 101 | <<: *script_load 102 | 103 | package client prod: 104 | <<: *env_prod 105 | <<: *script_package_client 106 | 107 | deploy prod: 108 | <<: *env_prod 109 | <<: *script_deploy_client -------------------------------------------------------------------------------- /isc/git/hook/Manager.cls: -------------------------------------------------------------------------------- 1 | /// Manages hooks 2 | /// Class to store local hooks execution 3 | Class isc.git.hook.Manager Extends isc.util.LogUtils 4 | { 5 | 6 | Parameter GLVN = "^isc.git.Hooks"; 7 | 8 | ClassMethod add(class As %Dictionary.CacheClassname) 9 | { 10 | set @..#GLVN@(class) = "" 11 | } 12 | 13 | ClassMethod isDone(class As %Dictionary.CacheClassname) As %Boolean [ CodeMode = expression ] 14 | { 15 | $data(@..#GLVN@(class)) 16 | } 17 | 18 | /// do ##class(isc.git.hook.Manager).execute() 19 | ClassMethod execute(directory As %String = "", ByRef hooks As %String = "", method As %String(VALUELIST=",before,after,rollback")) As %Status 20 | { 21 | try { 22 | #dim sc As %Status = $$$OK 23 | do:directory'="" ..load(directory, .loadedHooks) 24 | merge hooks = loadedHooks 25 | 26 | if (method="before") { 27 | 28 | /// Execute global hooks 29 | do ..executeInternal(.hooks, method, "isc.git.hook.Global") 30 | 31 | /// Execute local hooks 32 | do ..executeInternal(.hooks, method, "isc.git.hook.Local") 33 | } elseif (method="after") { 34 | /// Execute local hooks 35 | do ..executeInternal(.hooks, method, "isc.git.hook.Local") 36 | 37 | /// Execute global hooks 38 | do ..executeInternal(.hooks, method, "isc.git.hook.Global") 39 | } 40 | } catch ex { 41 | set sc = ex.AsStatus() 42 | } 43 | quit sc 44 | } 45 | 46 | ClassMethod executeInternal(ByRef hooks As %String = "", method As %String(VALUELIST=",before,after"), type As %String(VALUELIST=",isc.git.hook.Global,isc.git.hook.Local")) 47 | { 48 | /// Execute hooks 49 | set key = "" 50 | for { 51 | set key=$order(hooks(key)) 52 | quit:key="" 53 | continue:$p(key, ".", *)'="cls" 54 | set class = $p(key, ".", 1, *-1) 55 | continue:'$classmethod(class, "%IsA", type) 56 | continue:class=type 57 | continue:((type="isc.git.hook.Local") && (..isDone(class))) 58 | 59 | do ..log("Executing hook class: " _ class) 60 | 61 | $$$TOE(sc, $classmethod(class, method)) 62 | do:((type="isc.git.hook.Local") && (method="after")) ..add(class) 63 | } 64 | } 65 | 66 | /// do ##class(isc.git.hook.Manager).load(,.h) 67 | ClassMethod load(directory As %String, Output hooks As %String) 68 | { 69 | do ..log("Importing hooks dir " _ directory) 70 | set hooks = "" 71 | do $system.OBJ.ImportDir(directory, ##class(isc.git.GitLab).getExtWildcard(), "cukb /displaylog=0", .errors, 1, .hooks) 72 | throw:$get(errors,0)'=0 ##class(%Exception.General).%New("Hooks load error") 73 | } 74 | 75 | } 76 | 77 | -------------------------------------------------------------------------------- /isc/git/Diff.cls: -------------------------------------------------------------------------------- 1 | Class isc.git.Diff Extends isc.util.OSUtils 2 | { 3 | 4 | /// Get diff between two points in repository 5 | /// repo - repository root directory 6 | /// sha1, commitEnd - poins of history in repository 7 | /// modified - list of modified files 8 | /// added - list of added files 9 | /// deleted - list of deleted files 10 | /// 11 | /// Internal diff statuses: 12 | /// M modified - File has been modified 13 | /// C copy-edit - File has been copied and modified //3-arg form 14 | /// R rename-edit - File has been renamed and modified //3-arg form 15 | /// A added - File has been added 16 | /// D deleted - File has been deleted 17 | /// U unmerged - File has conflicts after a merge 18 | /// 19 | /// do ##class(isc.git.Diff).buildDiff("C:\\temp\GitLab\", "HEAD~10", "HEAD", .modified, .added, .deleted) 20 | ClassMethod buildDiff(repo As %String, commitBegin As %String, commitEnd As %String, Output modified As %List, Output added As %List, Output deleted As %List) 21 | { 22 | #include %occCPTJSgen 23 | set (modified, added, deleted) = "" 24 | $$$TOE(sc, ..createFile(.tempFile)) 25 | do $system.Process.CurrentDirectory(repo) 26 | $$$TOE(sc, ..execute($$$FormatText("git diff --name-status %1 %2 > %3 2>&1", commitBegin, commitEnd, tempFile))) 27 | $$$TOE(sc, ..fileToString(tempFile, .diffRaw)) 28 | for i=1:1:$length(diffRaw, $c(10)) { 29 | set element = $piece(diffRaw, $c(10), i) 30 | set status = $e($piece(element, $$$TAB)) 31 | set file = $piece(element, $$$TAB, 2) 32 | 33 | if ((element="") || ('..isRelevantFile(repo, file))) { 34 | continue 35 | } elseif ($length(element, $$$TAB)=2) { 36 | if ((status="M") || (status="U")) { 37 | set modified = modified _ $lb(file) 38 | } elseif (status="A") { 39 | set added = added _ $lb(file) 40 | } elseif (status="D") { 41 | set deleted = deleted _ $lb(file) 42 | } else { 43 | throw ##class(%Exception.General).%New("INVALID DIFF STATUS: " _ status) 44 | } 45 | } elseif ($length(element, $$$TAB)=3) { 46 | set newFile = $piece(element, $c(9), 3) 47 | if (status="C") { 48 | set added = added _ $lb(newFile) 49 | } elseif (status="R") { 50 | set added = added _ $lb(newFile) 51 | set deleted = deleted _ $lb(file) 52 | } else { 53 | throw ##class(%Exception.General).%New("INVALID DIFF STATUS: " _ status) 54 | } 55 | } else { 56 | throw ##class(%Exception.General).%New("INVALID DIFF LINE: " _ element) 57 | } 58 | } 59 | } 60 | 61 | /// Determine if the file is neede for git diff 62 | ClassMethod isRelevantFile(dir As %String, file As %String) As %Boolean 63 | { 64 | set ext = $select($length(file, ".")=1:"", 1:$piece(file, ".", *)) 65 | quit $lf(##class(isc.git.Settings).getSetting("ext"), ext)>0 66 | } 67 | 68 | } 69 | 70 | -------------------------------------------------------------------------------- /isc/util/OSUtils.cls: -------------------------------------------------------------------------------- 1 | Class isc.util.OSUtils 2 | { 3 | 4 | /// Create file name 5 | /// If name os empty then random file would be created in a Temp directrory 6 | /// If name is an extension then new filename would be created 7 | /// If name is a filename, then this file would be created 8 | /// stream - %Stream.FileBinary pointing to this file 9 | /// content - write something into a file. Can be a stream or a string 10 | /// 11 | /// Создать файл name 12 | /// Если name не задан, то возвращается имя созданного файла (в папке Temp). 13 | /// Если name - расширение, то возвращается имя созданного файла (в папке Temp) с заданным расширением. 14 | /// stream - стрим файла 15 | /// content - строка или stream который записывается в файл 16 | ClassMethod createFile(ByRef name As %String = "", Output stream As %Stream.FileBinary, content As %String) As %Status 17 | { 18 | #dim sc As %Status = $$$OK 19 | 20 | if name="" { 21 | set name = ##class(%File).TempFilename() 22 | } elseif $length(name, ".")=1 { 23 | set name = ##class(%File).TempFilename(name) 24 | } 25 | 26 | set stream = ##class(%Stream.FileBinary).%New() 27 | set sc = stream.LinkToFile(name) 28 | 29 | if $data(content) { 30 | if $isObject(content) { 31 | set sc = stream.CopyFrom(content) 32 | } else { 33 | set sc = stream.Write(content) 34 | } 35 | quit:$$$ISERR(sc) sc 36 | set sc = stream.%Save() 37 | do stream.Rewind() 38 | } 39 | 40 | quit sc 41 | } 42 | 43 | /// Read file into string. Delete original file 44 | /// Прочитать файл в строку 45 | ClassMethod fileToString(name As %String, Output content As %String, delete As %Boolean = {$$$YES}) As %Status 46 | { 47 | #dim sc As %Status = $$$OK 48 | set stream = ##class(%Stream.FileBinary).%New() 49 | set sc = stream.LinkToFile(name) 50 | 51 | set content = stream.Read($$$MaxStringLength) 52 | 53 | if delete { 54 | kill stream 55 | set sc = ..deleteFile(name) 56 | } 57 | 58 | quit sc 59 | } 60 | 61 | /// Delete file 62 | /// Удалить файл 63 | ClassMethod deleteFile(name As %String) As %Status 64 | { 65 | #dim sc As %Status = $$$OK 66 | set success = ##class(%File).Delete(name, .code) 67 | set:success'=$$$YES sc = $$$ERROR($$$GeneralError, $$$FormatText("Error deleting file %1 with code %2", name, code)) 68 | quit sc 69 | } 70 | 71 | /// Execute OS command 72 | /// Выполнить команду ОС 73 | ClassMethod execute(cmd, debug As %Boolean = {$$$NO}) As %Status 74 | { 75 | #dim sc As %Status = $$$OK 76 | set code = "" 77 | //set out = "" 78 | write:debug !, "cmd: ", cmd 79 | //set sc = ##class(%Net.Remote.Utility).RunCommandViaZF(cmd, , .out, timeout, $$$YES, .code) 80 | set code = $zf(-1, cmd) 81 | write:debug !,"code: ", code 82 | 83 | if code'=0 { 84 | set sc1 = $$$ERROR($$$GeneralError, $$$FormatText("ОС command: `%1` exited with status: `%2`", cmd, code)) 85 | set sc = $$$ADDSC(sc, sc1) 86 | } 87 | return sc 88 | } 89 | 90 | } 91 | 92 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GitLab 2 | 3 | Various GitLab hooks. For more information please read a series of articles on Continuous Delivery of your InterSystems solution using GitLab ([index](https://community.intersystems.com/post/continuous-delivery-your-intersystems-solution-using-gitlab-index)): 4 | 5 | - [Part I: Git](https://community.intersystems.com/post/continuous-delivery-your-intersystems-solution-using-gitlab-part-i-git) 6 | - [Part II: GitLab workflow](https://community.intersystems.com/post/continuous-delivery-your-intersystems-solution-using-gitlab-part-ii-gitlab-workflow) 7 | - [Part III: GitLab installation and configuration](https://community.intersystems.com/post/continuous-delivery-your-intersystems-solution-using-gitlab-part-iii-gitlab-installation-and) 8 | - [Part IV: CD configuration](https://community.intersystems.com/post/continuous-delivery-your-intersystems-solution-using-gitlab-part-iv-cd-configuration) 9 | - [Part V: Why containers?](https://community.intersystems.com/post/continuous-delivery-your-intersystems-solution-using-gitlab-part-v-why-containers) 10 | - [Part VI: Containers infrastructure](https://community.intersystems.com/post/continuous-delivery-your-intersystems-solution-using-gitlab-part-vi-containers-infrastructure) 11 | - [Part VII: CD using containers](https://community.intersystems.com/post/continuous-delivery-your-intersystems-solution-using-gitlab-part-vii-cd-using-containers) 12 | - [Part VIII: CD using ICM](https://community.intersystems.com/post/continuous-delivery-your-intersystems-solution-using-gitlab-part-viii-cd-using-icm) 13 | # Installation 14 | 15 | Load and compile classes. 16 | 17 | ## Development 18 | 19 | Development is done on [Cache-Tort-Git UDL fork](https://github.com/MakarovS96/cache-tort-git). 20 | 21 | ## Use 22 | 23 | Set settings via: `write ##class(isc.git.Settings).setSetting("setting", "value")` 24 | 25 | Available settings: 26 | 27 | | Setting | Sample Value | Description | 28 | |---------|------------------------|-------------------------------------------------------------------------------------------------| 29 | | ext | $lb("xml") | List of files extensions to load and compile. | 30 | | tests | MyApp/Tests | Path relative from the repo root to the test suite. | 31 | | commit | | Do not set. Current commit hash. | 32 | | hooks | MyApp/Hooks/ | Path relative from the repo root to the hooks. | 33 | | delete | Package.Class:Method | Code called to delete files from project. Should accept one argument - list of files to delete. | 34 | | url | http://127.0.0.1:57772 | Server root. | 35 | 36 | 37 | ## Hooks 38 | 39 | There are two types of hooks available: 40 | 41 | - **Global** - executed each time the CI is run. Extend `isc.git.hook.Global`. 42 | - **Local** - executed once. Extend `isc.git.hook.Local`. 43 | 44 | Hooks of the same type are executed in collation order. To create a hook extend either or `isc.git.hook.Global` or `isc.git.hook.Local` and implement `onBefore` and/or `onAfter` methods. 45 | 46 | ### Execution order: 47 | 48 | 1. Global hooks, before. 49 | 2. Local hooks, before. 50 | 3. Code load and compile. 51 | 4. Local hooks, after. 52 | 5. Global hooks, after. 53 | 54 | ## Tips & Tricks 55 | 56 | Various tricks for GitLab CI. 57 | 58 | ### Namespaces 59 | 60 | Use `CI_COMMIT_REF_NAME` (resolves to branch) environment variable to use several namespaces on one server. 61 | 62 | I.e. `csession ensemble -U ${CI_COMMIT_REF_NAME} "##class(isc.git.GitLab).loadDiff()"` 63 | -------------------------------------------------------------------------------- /isc/git/GitLab.cls: -------------------------------------------------------------------------------- 1 | Class isc.git.GitLab Extends isc.util.LogUtils 2 | { 3 | 4 | ClassMethod getDir() [ CodeMode = expression ] 5 | { 6 | ##class(%File).NormalizeDirectory($system.Util.GetEnviron("CI_PROJECT_DIR")) 7 | } 8 | 9 | /// For CI build - get current commit 10 | ClassMethod getCommit() [ CodeMode = expression ] 11 | { 12 | $system.Util.GetEnviron("CI_COMMIT_SHA") 13 | } 14 | 15 | /// Do a full load 16 | /// do ##class(isc.git.GitLab).load() 17 | ClassMethod load() 18 | { 19 | try { 20 | set dir = ..getDir() 21 | do ..executeHooks(.hooks, "before") 22 | 23 | do ..log("Importing dir " _ dir) 24 | do $system.OBJ.ImportDir(dir, ..getExtWildcard(), "c", .errors, 1) 25 | throw:$get(errors,0)'=0 ##class(%Exception.General).%New("Load error") 26 | 27 | do ..executeHooks(.hooks, "after") 28 | 29 | $$$TOE(sc, ##class(isc.git.Settings).setSetting("commit", ..getCommit())) 30 | 31 | halt 32 | } catch ex { 33 | write !,$System.Status.GetErrorText(ex.AsStatus()),! 34 | do $system.Process.Terminate(, 1) 35 | } 36 | } 37 | 38 | /// Do a diff load 39 | /// do ##class(isc.git.GitLab).loadDiff() 40 | ClassMethod loadDiff() 41 | { 42 | try { 43 | #dim sc,sc1 As %Status = $$$OK 44 | set oldCommit = ##class(isc.git.Settings).getSetting("commit") 45 | if (oldCommit="") { 46 | do ..log("Previous commit not found. Doing full load.") 47 | do ..load() 48 | halt 49 | } else { 50 | set dir = ..getDir() 51 | do ..executeHooks(.hooks, "before") 52 | 53 | set newCommit = ..getCommit() 54 | do ..log("Importing dir " _ dir) 55 | do ..log($$$FormatText("Loading diff between %1 and %2", oldCommit, newCommit)) 56 | } 57 | 58 | do ##class(isc.git.Diff).buildDiff(dir, oldCommit, newCommit, .modified, .added, .deleted) 59 | 60 | set modified = modified _ added 61 | do ..logVar(modified, "modified") 62 | set items = "" 63 | 64 | for i=1:1:$ll(modified) { 65 | set file = dir _ $lg(modified, i) 66 | set sc = $$$ADDSC(sc, $system.OBJ.Load(file,"", .errors, .item,,,,"UTF8")) 67 | merge items = item 68 | } 69 | 70 | do ..logVar(.items, "items") 71 | set sc = $$$ADDSC(sc, $system.OBJ.CompileList(.items, "cuk /checkuptodate=expandedonly", .errors)) 72 | 73 | // To-Do delete 74 | set deleteCode = ##class(isc.git.Settings).getSetting("delete") 75 | if (($ll(deleted)>0) && (deleteCode '="")) { 76 | do $classmethod($p(deleteCode, ":"), $p(deleteCode, ":", 2), deleted) 77 | } 78 | 79 | throw:$$$ISERR(sc) ##class(%Exception.StatusException).CreateFromStatus(sc) 80 | throw:$get(errors,0)'=0 ##class(%Exception.General).%New("Load error") 81 | 82 | do ..executeHooks(.hooks, "after") 83 | 84 | $$$TOE(sc, ##class(isc.git.Settings).setSetting("commit", ..getCommit())) 85 | 86 | $$$TOE(sc, $system.OBJ.Export(.items, dir _ "diff.xml")) 87 | 88 | halt 89 | } catch ex { 90 | do ..logException(ex) 91 | do $system.Process.Terminate(, 1) 92 | } 93 | } 94 | 95 | ClassMethod executeHooks(Output hooks As %String, method As %String(VALUELIST=",before,after,rollback")) 96 | { 97 | set hooksDir = ##class(isc.git.Settings).getSetting("hooks") 98 | if (hooksDir'="") { 99 | do ..log("Running init hooks: " _ method) 100 | 101 | if method = "before" { 102 | set dir = ..getDir() 103 | $$$TOE(sc, ##class(isc.git.hook.Manager).execute(dir _ hooksDir, .hooks, "before")) 104 | } elseif method = "after" { 105 | $$$TOE(sc, ##class(isc.git.hook.Manager).execute(, .hooks, "after")) 106 | } 107 | } else { 108 | do ..log("No hooks") 109 | } 110 | } 111 | 112 | /// do ##class(isc.git.GitLab).test() 113 | ClassMethod test() 114 | { 115 | try { 116 | set tests = ##class(isc.git.Settings).getSetting("tests") 117 | if (tests'="") { 118 | set dir = ..getDir() 119 | set ^UnitTestRoot = dir 120 | 121 | $$$TOE(sc, ##class(%UnitTest.Manager).RunTest(tests, "/nodelete")) 122 | $$$TOE(sc, ..writeTestHTML()) 123 | throw:'..isLastTestOk() ##class(%Exception.General).%New("Tests error") 124 | } 125 | halt 126 | } catch ex { 127 | do ..logException(ex) 128 | do $system.Process.Terminate(, 1) 129 | } 130 | } 131 | 132 | /// do ##class(GitLab.Main).package() 133 | ClassMethod package() 134 | { 135 | try { 136 | set dir = ..getDir() 137 | // TODO 138 | do $system.OBJ.ExportAllClasses(dir _ "full.xml", , .errors) 139 | throw:$g(errors,0)'=0 ##class(%Exception.General).%New("Package error") 140 | halt 141 | } catch ex { 142 | do ..logException(ex) 143 | do $system.Process.Terminate(, 1) 144 | } 145 | } 146 | 147 | ClassMethod writeTestHTML() 148 | { 149 | set text = ##class(%Dictionary.XDataDefinition).IDKEYOpen($classname(), "html").Data.Read() 150 | set text = $replace(text, "!!!", ..getURL()) 151 | 152 | set file = ##class(%Stream.FileCharacter).%New() 153 | set name = ..getDir() _ "tests.html" 154 | do file.LinkToFile(name) 155 | do file.Write(text) 156 | quit file.%Save() 157 | } 158 | 159 | ClassMethod getURL() 160 | { 161 | set url = ##class(isc.git.Settings).getSetting("url") 162 | set url = url _ $system.CSP.GetDefaultApp("%SYS") 163 | set url = url_"/%25UnitTest.Portal.Indices.cls?Index="_ $g(^UnitTest.Result, 1) _ "&$NAMESPACE=" _ $zconvert($namespace,"O","URL") 164 | quit url 165 | } 166 | 167 | /// Get extensions as wildcard for import 168 | ClassMethod getExtWildcard() As %String 169 | { 170 | set extList = ##class(isc.git.Settings).getSetting("ext") 171 | set ext = "*." _ $lts(##class(isc.git.Settings).getSetting("ext"), ";*.") 172 | quit ext 173 | } 174 | 175 | /// w ##class(GitLab.Main).isLastTestOk() 176 | ClassMethod isLastTestOk() As %Boolean 177 | { 178 | set in = ##class(%UnitTest.Result.TestInstance).%OpenId(^UnitTest.Result) 179 | for i=1:1:in.TestSuites.Count() { 180 | #dim suite As %UnitTest.Result.TestSuite 181 | set suite = in.TestSuites.GetAt(i) 182 | return:suite.Status=0 $$$NO 183 | } 184 | quit $$$YES 185 | } 186 | 187 | XData html 188 | { 189 | 190 |
191 | 192 | 193 | 196 | 197 | 198 | If you are not redirected automatically, follow this link to tests. 199 | 200 | 201 | } 202 | 203 | } 204 | 205 | --------------------------------------------------------------------------------