Nesta série de artigos, quero apresentar e discutir várias abordagens possíveis para o desenvolvimento de software com tecnologias da InterSystems e do GitLab. Vou cobrir tópicos como: * Git básico * Fluxo Git (processo de desenvolvimento) * Instalação do GitLab * Fluxo de trabalho do GitLab * Entrega contínua * Instalação e configuração do GitLab * **CI/CD do GitLab** No [primeiro artigo](https://pt.community.intersystems.com/post/entrega-cont%C3%ADnua-de-sua-solu%C3%A7%C3%A3o-intersystems-usando-gitlab-%E2%80%93-parte-i-git), abordamos os fundamentos do Git, por que um entendimento de alto nível dos conceitos do Git é importante para o desenvolvimento de software moderno e como o Git pode ser usado para desenvolver software. No [segundo artigo](https://pt.community.intersystems.com/post/entrega-cont%C3%ADnua-de-sua-solu%C3%A7%C3%A3o-intersystems-usando-gitlab-%E2%80%93-parte-ii-fluxo-de-trabalho-do), abordamos o fluxo de trabalho do GitLab: um processo inteiro do ciclo de vida do software e a entrega contínua. No [terceiro artigo](https://pt.community.intersystems.com/post/entrega-cont%C3%ADnua-de-sua-solu%C3%A7%C3%A3o-intersystems-usando-gitlab-%E2%80%93-parte-iii-instala%C3%A7%C3%A3o-e), abordamos a instalação e configuração do GitLab e a conexão dos seus ambientes a ele Neste artigo, finalmente, vamos escrever uma configuração de CD. ## Plano ### Ambientes Em primeiro lugar, precisamos de vários ambientes e branches que correspondam a eles:
Environment | Branch | Delivery | Who can commit | Who can merge |
---|---|---|---|---|
Test | master | Automatic | Developers Owners | Developers Owners |
Preprod | preprod | Automatic | No one | Owners |
Prod | prod | Semiautomatic (press button to deliver) | No one |
Owners |
stages: - load - test - package - deploy### Scripts #### Carregamento Em seguida, vamos definir os scripts. [Documentos de scripts](https://docs.gitlab.com/ee/ci/yaml/README.html). Primeiro, vamos definir um script load server que carrega o código do lado do servidor:
load server: environment: name: test url: http://test.hostname.com only: - master tags: - test stage: load script: csession IRIS "##class(isc.git.GitLab).load()"O que acontece aqui? * load server é o nome de um script * em seguida, descrevemos o ambiente em que esse script é executado * only: master — informa ao GitLab que esse script só deve ser executado quando houver um commit para o master branch * tags: test especifica que esse script só deve ser executado em um runner com a tag test * stage especifica o estágio para um script * script define o código para executar. No nosso caso, chamamos o classmethod load da classe isc.git.GitLab > **Observação importante** > > Para InterSystems IRIS, troque _csession_ por _iris session_. > > Para Windows, use: _irisdb -s ../mgr -U TEST "##class(isc.git.GitLab).load()_ Agora, vamos escrever a classe isc.git.GitLab correspondente. Todos os pontos de entrada nessa classe ficam desta forma: ClassMethod method() { try { // code halt } catch ex { write !,$System.Status.GetErrorText(ex.AsStatus()),! do $system.Process.Terminate(, 1) } } Observe que esse método pode terminar de duas maneiras: * interrompendo o processo atual — que é registrado no GitLab como uma conclusão bem-sucedida * chamando $system.Process.Terminate — que termina o processo de maneira anormal e o GitLab registra isso como um erro Dito isso, aqui está nosso código de carregamento: /// Do a full load /// do ##class(isc.git.GitLab).load() ClassMethod load() { try { set dir = ..getDir() do ..log("Importing dir " _ dir) do $system.OBJ.ImportDir(dir, ..getExtWildcard(), "c", .errors, 1) throw:$get(errors,0)'=0 ##class(%Exception.General).%New("Load error") halt } catch ex { write !,$System.Status.GetErrorText(ex.AsStatus()),! do $system.Process.Terminate(, 1) } } Dois métodos de utilitários são chamados: * getExtWildcard — para obter uma lista das extensões de arquivo relevantes * getDir — para obter o diretório do repositório Como podemos obter o diretório? Quando o GitLab executa um script, primeiro, ele especifica várias [variáveis de ambiente](https://docs.gitlab.com/ce/ci/variables/README.html). Uma delas é a CI_PROJECT_DIR — o caminho completo onde o repositório é clonado e onde o job é executado. Ele pode ser obtido facilmente no nosso método getDir : ClassMethod getDir() [ CodeMode = expression ] { ##class(%File).NormalizeDirectory($system.Util.GetEnviron("CI_PROJECT_DIR")) } ####Testes Aqui está o script de teste:
load test: environment: name: test url: http://test.hostname.com only: - master tags: - test stage: test script: csession IRIS "##class(isc.git.GitLab).test()" artifacts: paths: - tests.htmlO que mudou? O nome e o código do script, é claro, mas o artefato também foi adicionado. Um artefato é uma lista de arquivos e diretórios que são anexados a um job depois que ele é concluído com sucesso. No nosso caso, depois que os testes forem concluídos, podemos gerar a página HTML redirecionando para os resultados dos testes e disponibilizá-la a partir do GitLab. Observe que há bastante copiar e colar do estágio de carregamento — o ambiente é o mesmo, partes do script, como ambientes, podem ser rotuladas separadamente e anexadas a um script. Vamos definir o ambiente de teste:
.env_test: &env_test environment: name: test url: http://test.hostname.com only: - master tags: - testAgora, nosso script de teste fica assim:
load test: <<: *env_test script: csession IRIS "##class(isc.git.GitLab).test()" artifacts: paths: - tests.htmlEm seguida, vamos executar os testes usando o [framework UnitTest](http://docs.intersystems.com/latest/csp/docbook/DocBook.UI.Page.cls?KEY=TUNT). /// do ##class(isc.git.GitLab).test() ClassMethod test() { try { set tests = ##class(isc.git.Settings).getSetting("tests") if (tests'="") { set dir = ..getDir() set ^UnitTestRoot = dir $$$TOE(sc, ##class(%UnitTest.Manager).RunTest(tests, "/nodelete")) $$$TOE(sc, ..writeTestHTML()) throw:'..isLastTestOk() ##class(%Exception.General).%New("Tests error") } halt } catch ex { do ..logException(ex) do $system.Process.Terminate(, 1) } } A definição do teste, nesse caso, é um caminho relativo à raiz do repositório onde os testes de unidade são armazenados. Se estiver vazio, pulamos testes. O método writeTestHTML é usado para gerar o html com um redirecionamento para os resultados dos testes:
ClassMethod writeTestHTML()
{
set text = ##class(%Dictionary.XDataDefinition).IDKEYOpen($classname(), "html").Data.Read()
set text = $replace(text, "!!!", ..getURL())
set file = ##class(%Stream.FileCharacter).%New()
set name = ..getDir() _ "tests.html"
do file.LinkToFile(name)
do file.Write(text)
quit file.%Save()
}
ClassMethod getURL()
{
set url = ##class(isc.git.Settings).getSetting("url")
set url = url _ $system.CSP.GetDefaultApp("%SYS")
set url = url_"/%25UnitTest.Portal.Indices.cls?Index="_ $g(^UnitTest.Result, 1) _ "&$NAMESPACE=" _ $zconvert($namespace,"O","URL")
quit url
}
ClassMethod isLastTestOk() As %Boolean
{
set in = ##class(%UnitTest.Result.TestInstance).%OpenId(^UnitTest.Result)
for i=1:1:in.TestSuites.Count() {
#dim suite As %UnitTest.Result.TestSuite
set suite = in.TestSuites.GetAt(i)
return:suite.Status=0 $$$NO
}
quit $$$YES
}
XData html
{
<html lang="en-US">
<head>
<meta charset="UTF-8"/>
<meta http-equiv="refresh" content="0; url=!!!"/>
<script type="text/javascript">
window.location.href = "!!!"
</script>
</head>
<body>
If you are not redirected automatically, follow this <a href='!!!'>link to tests</a>.
</body>
</html>
}
#### Pacote
Nosso cliente é uma página HTML simples:
<html> <head> <script type="text/javascript"> function initializePage() { var xhr = new XMLHttpRequest(); var url = "${CI_ENVIRONMENT_URL}:57772/MyApp/version"; xhr.open("GET", url, true); xhr.send(); xhr.onloadend = function (data) { document.getElementById("version").innerHTML = "Version: " + this.response; }; var xhr = new XMLHttpRequest(); var url = "${CI_ENVIRONMENT_URL}:57772/MyApp/author"; xhr.open("GET", url, true); xhr.send(); xhr.onloadend = function (data) { document.getElementById("author").innerHTML = "Author: " + this.response; }; } </script> </head> <body onload="initializePage()"> <div id = "version"></div> <div id = "author"></div> </body> </html>E, para criá-la, precisamos substituir ${CI_ENVIRONMENT_URL} pelo seu valor. Claro, um aplicativo real provavelmente exigiria npm, mas esse é apenas um exemplo. Aqui está o script:
package client: <<: *env_test stage: package script: envsubst < client/index.html > index.html artifacts: paths: - index.html#### Implantação Por fim, implantamos nosso cliente ao copiar index.html para o diretório raiz do servidor web.
deploy client: <<: *env_test stage: deploy script: cp -f index.html /var/www/html/index.htmlÉ isso! ### Vários ambientes O que fazer se você precisar executar o mesmo script (semelhante) em vários ambientes? Partes do script também podem ser rótulos, então aqui está uma configuração de exemplo que carrega o código em ambientes de teste e pré-produção:
stages: - load - test .env_test: &env_test environment: name: test url: http://test.hostname.com only: - master tags: - test .env_preprod: &env_preprod environment: name: preprod url: http://preprod.hostname.com only: - preprod tags: - preprod .script_load: &script_load stage: load script: csession IRIS "##class(isc.git.GitLab).loadDiff()" load test: <<: *env_test <<: *script_load load preprod: <<: *env_preprod <<: *script_loadAssim, podemos fugir de copiar e colar o código. Veja a configuração de CD completa [aqui](https://github.com/intersystems-ru/GitLab/blob/master/.gitlab-ci.yml). Ela segue o plano original de mover código entre os ambientes de teste, pré-produção e produção. ## Conclusão A entrega contínua pode ser configurada para automatizar qualquer fluxo de trabalho de desenvolvimento necessário. ## Links * [Repositório de hooks (e configuração de exemplo)](https://github.com/intersystems-ru/GitLab) * [Repositório de teste](http://gitlab.eduard.win/test/testProject) * [Documentos de scripts](https://docs.gitlab.com/ee/ci/yaml/README.html) * [Variáveis de ambiente disponíveis](https://docs.gitlab.com/ce/ci/variables/README.html) ## O que vem a seguir No próximo artigo, vamos criar a configuração de CD que usa o contêiner Docker do InterSystems IRIS.