Hi Developers!
Many of you publish your InterSystems ObjectScript libraries on Open Exchange and Github.
But what do you do to ease the usage and collaboration to your project for developers?
In this article, I want to introduce the way how to introduce an easy way to launch and contribute to any ObjectScript project just by copying a standard set of files to your repository.
Let's go!
TLDR - copy these files from the repository into your repository:
.dockerignore
.gitattributes
.gitignore
And you get the standard way to launch and collaborate to your project. Below is the long article on how and why this works.
NB: In this article, we will consider projects which are runnable on InterSystems IRIS 2019.1 and newer.
Choosing the launch environment for InterSystems IRIS projects
Usually, we want a developer to try the project/library and be sure that this will be fast and safe exercise.
IMHO the ideal approach to launch anything new fast and safe is the Docker container which gives a developer a guarantee that anything he/she launches, imports, compiles and calculates is safe for the host machine and no system or code would be destroyed or spoiled. If something goes wrong you just stop and remove the container. If the application takes an enormous amount of disk space - you wipe out it with the container and your space is back. If an application spoils the database configuration - you just delete the container with spoiled configuration. Simple and safe like that.
Docker container gives you safety and standardization.
The simplest way to run vanilla InterSystems IRIS Docker container is to run an IRIS Community Edition image:
1. Install Docker desktop
2. Run in OS terminal the following:
docker run --rm -p 52773:52773 --init --name my-iris store/intersystems/iris-community:2020.1.0.199.0
3. Then open Management portal in your host browser on:
http://localhost:52773/csp/sys/UtilHome.csp
4. Or open a terminal to IRIS:
docker exec -it my-iris iris session IRIS
5. Stop IRIS container when you don't need it:
docker stop my-iris
OK! We run IRIS in a docker container. But you want a developer to install your code into IRIS and maybe make some settings. This is what we will discuss below.
Importing ObjectScript files
The simplest InterSystems ObjectScript project can contain a set of ObjectScript files like classes, routines, macro, and globals. Check the article on the naming convention and proposed folder structure.
The question is how to import all this code into an IRIS container?
Here is the momennt where Dockerfile helps us which we can use to take the vanilla IRIS container and import all the code from a repository to IRIS and do some settings with IRIS if we need. We need to add a Dockerfile in the repo.
Let's examine the Dockerfile from ObjectScript template repo:
ARG IMAGE=store/intersystems/irishealth:2019.3.0.308.0-community ARG IMAGE=store/intersystems/iris-community:2019.3.0.309.0 ARG IMAGE=store/intersystems/iris-community:2019.4.0.379.0 ARG IMAGE=store/intersystems/iris-community:2020.1.0.199.0 FROM $IMAGE USER root WORKDIR /opt/irisapp RUN chown ${ISC_PACKAGE_MGRUSER}:${ISC_PACKAGE_IRISGROUP} /opt/irisapp USER irisowner COPY Installer.cls . COPY src src COPY iris.script /tmp/iris.script # run iris and initial RUN iris start IRIS \ && iris session IRIS < /tmp/iris.script
First ARG lines set the $IMAGE variable - which we will use then in FROM. This is suitable to test/run the code in different IRIS versions switching them just by what is the last line before FROM to change the $IMAGE variable.
Here we have:
ARG IMAGE=store/intersystems/iris-community:2020.1.0.199.0 FROM $IMAGE
This means that we are taking IRIS 2020 Community Edition build 199.
We want to import the code from the repository - that means we need to copy the files from a repository into a docker container. The lines below help to do that:
USER root WORKDIR /opt/irisapp RUN chown ${ISC_PACKAGE_MGRUSER}:${ISC_PACKAGE_IRISGROUP} /opt/irisapp USER irisowner COPY Installer.cls . COPY src src
USER root - here we switch user to a root to create a folder and copy files in docker.
WORKDIR /opt/irisapp - in this line we setup the workdir in which we will copy files.
RUN chown ${ISC_PACKAGE_MGRUSER}:${ISC_PACKAGE_IRISGROUP} /opt/irisapp - here we give the rights to irisowner user and group which are run IRIS.
USER irisowner - switching user from root to irisowner
COPY Installer.cls . - coping Installer.cls to a root of workdir. Don't miss the dot, here.
COPY src src - copy source files from src folder in the repo to src folder in workdir in the docker.
In the next block we run the initial script, where we call installer and ObjectScript code:
COPY iris.script /tmp/iris.script # run iris and initial RUN iris start IRIS \ && iris session IRIS < /tmp/iris.script
COPY iris.script / - we copy iris.script into the root directory. It contains ObjectScript we want to call to setup the container.
RUN iris start IRIS\ - start IRIS
&& iris session IRIS < /tmp/iris.script - start IRIS terminal and input the initial ObjectScript to it.
Fine! We have the Dockerfile, which imports files in docker. But we faced two other files: installer.cls and iris.script Let's examine it.
Class App.Installer
{
XData setup
{
<Manifest>
<Default Name="SourceDir" Value="#{$system.Process.CurrentDirectory()}src"/>
<Default Name="Namespace" Value="IRISAPP"/>
<Default Name="app" Value="irisapp" />
<Namespace Name="${Namespace}" Code="${Namespace}" Data="${Namespace}" Create="yes" Ensemble="no">
<Configuration>
<Database Name="${Namespace}" Dir="/opt/${app}/data" Create="yes" Resource="%DB_${Namespace}"/>
<Import File="${SourceDir}" Flags="ck" Recurse="1"/>
</Configuration>
<CSPApplication Url="/csp/${app}" Directory="${cspdir}${app}" ServeFiles="1" Recurse="1" MatchRoles=":%DB_${Namespace}" AuthenticationMethods="32"
/>
</Namespace>
</Manifest>
}
ClassMethod setup(ByRef pVars, pLogLevel As %Integer = 3, pInstaller As %Installer.Installer, pLogger As %Installer.AbstractLogger) As %Status [ CodeMode = objectgenerator, Internal ]
{
#; Let XGL document generate code for this method.
Quit ##class(%Installer.Manifest).%Generate(%compiledclass, %code, "setup")
}
}
Frankly, we do not need Installer.cls to import files. This could be done with one line. But often besides importing code we need to setup the CSP app, introduce security settings, create databases and namespaces.
In this Installer.cls we create a new database and namespace with the name IRISAPP and create the default /csp/irisapp application for this namespace.
All this we perform in <Namespace> element:
<Namespace Name="${Namespace}" Code="${Namespace}" Data="${Namespace}" Create="yes" Ensemble="no"> <Configuration> <Database Name="${Namespace}" Dir="/opt/${app}/data" Create="yes" Resource="%DB_${Namespace}"/> <Import File="${SourceDir}" Flags="ck" Recurse="1"/> </Configuration> <CSPApplication Url="/csp/${app}" Directory="${cspdir}${app}" ServeFiles="1" Recurse="1" MatchRoles=":%DB_${Namespace}" AuthenticationMethods="32" /> </Namespace>
And we import files all the files from SourceDir with Import tag:
<Import File="${SourceDir}" Flags="ck" Recurse="1"/>
SourceDir here is a variable, which is set to the current directory/src folder:
<Default Name="SourceDir" Value="#{$system.Process.CurrentDirectory()}src"/>
Installer.cls with these settings gives us confidence, that we create a clear new database IRISAPP in which we import arbitrary ObjectScript code from src folder.
Here you are welcome to provide any initial ObjectScript setup code you want to start your IRIS container.
E.g. here we load and run installer.cls and then we make UserPasswords forever just to avoid the first request to change the password cause we don't need this prompt for development.
; run installer to create namespace
do $SYSTEM.OBJ.Load("/opt/irisapp/Installer.cls", "ck")
set sc = ##class(App.Installer).setup() zn "%SYS"
Do ##class(Security.Users).UnExpireUserPasswords("*") ; call your initial methods here
halt
Why do we need docker-compose.yml - couldn't we just build and run the image just with Dockerfile? Yes, we could. But docker-compose.yml simplifies the life.
Usually, docker-compose.yml is used to launch several docker images connected to one network.
docker-compose.yml could be used to also make launches of one docker image easier when we deal with a lot of parameters. You can use it to pass parameters to docker, such as ports mapping, volumes, VSCode connection parameters.
version: '3.6' services: iris: build: context: . dockerfile: Dockerfile restart: always ports: - 51773 - 52773 - 53773 volumes: - ~/iris.key:/usr/irissys/mgr/iris.key - ./:/irisdev/app
Here we declare service iris, which uses docker file Dockerfile and which exposes the following ports of IRIS: 51773, 52773, 53773. Also this service maps two volumes: iris.key from home directory of host machine to IRIS folder where it is expected and it maps the root folder of source code to /irisdev/app folder.
Docker-compose gives us the shorter and unified command to build and run the image whatever parameters you setup in docker compose.
in any case, the command to build and launch the image is:
$ docker-compose up -d
and to open IRIS terminal:
$ docker-compose exec iris iris session iris Node: 05a09e256d6b, Instance: IRIS USER>
Also, docker-compose.yml helps to set up the connection for VSCode ObjectScript plugin.
The part, which relates to ObjectScript addon connection settings is this:
{ "objectscript.conn" :{ "ns": "IRISAPP", "active": true, "docker-compose": { "service": "iris", "internalPort": 52773 } } }
Here we see the settings, which are different from default settings of VSCode ObjectScript plugin.
Here we say, that we want to connect to IRISAPP namespace (which we create with Installer.cls):
"ns": "IRISAPP",
and there is a docker-compose setting, which tells, that in docker-compose file inside service "iris" VSCode will connect to the port, which 52773 is mapped to:
"docker-compose": { "service": "iris", "internalPort": 52773 }
If we check, what we have for 52773 we see that this is the mapped port is not defined for 52773:
ports: - 51773 - 52773 - 53773
This means that a random available on a host machine port will be taken and VSCode will connect to this IRIS on docker via random port automatically.
This is a very handy feature, cause it gives you the option to run any amount of docker images with IRIS on random ports and having VSCode connected to them automatically.
What about other files?
We also have:
.dockerignore - file which you can use to filter host machine files you don't want to be copied into docker image you build. Usually .git and .DS_Store are mandatory lines.
.gitattributes - attributes for git, which unify line endings for ObjectScript files in sources. This is very useful if the repo is collaborated by Windows and Mac/Ubuntu owners.
.gitignore - files, which you don't want git to track the changes history for. Typically some hidden OS level files, like .DS_Store.
Fine!
How to make your repository docker-runnable and collaboration friendly?
1. Clone this repository.
2. Copy all this files:
.dockerignore
.gitattributes
.gitignore
to your repository.
Change this line in Dockerfile to match the directory with ObjectScript in the repo you want to import into IRIS (or don't change if you have it in /src folder).
That's it. And everyone (and you too) will have your code imported into IRIS in a new IRISAPP namespace.
How will people launch your project
the algorithm to execute any ObjectScript project in IRIS would be:
1. Git clone the project locally
2. Run the project:
$ docker-compose up -d
$ docker-compose exec iris iris session iris Node: 05a09e256d6b, Instance: IRIS USER>zn "IRISAPP"
How would any the developer contribute to your project
1. Fork the repository and git clone the forked repo locally
2. Open the folder in VSCode (they also need Docker and ObjectScript extensions are installed in VSCode)
3. Right-click on docker-compose.yml->Restart - VSCode ObjectScript will automatically connect and be ready to edit/compile/debug
4. Commit, Push and Pull request changes to your repository
Here is the short gif on how this works:
That's it! Happy coding!
Link to Management portal from is corrected:
http://localhost:52773/csp/UtilHome.cspRight link is: http://localhost:52773/csp/sys/UtilHome.csp
Thanks @Artem.Reva
could you please explain command
i understand exec, but what is "iris iris session iris" ?
The first
iris
after exec is the service name from docker-compose.yml, then goes command which has to be executed.iris
command is a replacement forccontrol
from Cache/Ensemblesession
- subcommand foriris
toolAnd the latest
iris
as the instance name inside for IRIS inside the containerIs putting all this in the main directory of the repository necessary?
I believe the two git files (.gitignore and .gitattributes) need to be there. But perhaps all files related to docker can be put in a "Docker" directory to avoid adding so many files to the main directory.
My main fear is people seeing all these files and not knowing where to start.
Hi Peter!
Thanks for the question.
In addition to .gitignore and .gitattributes
.vscode/settings.json should be in the root too ( @Dmitry Maslennikov please correct me if I'm wrong).
All the rest:
Dockerfile
docker-compose.yml
Installer.cls
irissession.sh
Could live in a dedicated folder.
BUT! We use Dockerfile to COPY installer.cls and source files from the repo to the image we build and Dockerfile sees the files which sit in the same folder or in subfolders. Specialists, please correct me here if I'm wrong.
So Dockerfile could possibly live inside the source folder - not sure this is what you want to achieve.
there are some possible issues to have docker related files in a dedicated folder.
When you would like to start an environment with docker-compose, you can do it with a command like this.
but it will work only if the docker-compose.yml file name has not changed and it lays right in the current folder.
if you change its location or name, you will have to specify the new place
became not so simple, right?
Ok, next about Dockerfile.
when you build docker image, you have to specify context. So, the command below, just uses file Dockerfile in the current folder, and uses current folder as a context for build.
To build docker image with Dockerfile placed somewhere else, you should specify it, suppose you still would like to have current folder as context.
any other files in the root, such as Installer.cls, irissession.sh or any other files which should be used during docker build have to be available from specified context folder. And you can't specify more than one context. So, any of those files should have some parent folder at least, and why not the root of a project.
with docker-compose.yml, we forget about docker build command, but we still have to care about docker-compose
When I try this on my laptop I am getting the following error: The terminal process terminated with exit code: 1
Looking at the terminal output everything seems normal up to that point. Have you encountered this issue before?
David
Hi David!
Could you please commit it and push to the repo on GitHub and I'll try on my laptop?
If not possible - join the discord.
please provide more information from log
Hi David, could you check it with the latest beta version?
it happens.
If you want to open IRIS terminal you can try the following:
Evgeny;
This did work so thanks for the tip!
David
Hi All!
Update the article to introduce iris.script approach - dockerfile is smaller, objectscirpt is clearer.
💡 This article is considered as InterSystems Data Platform Best Practice.
I have followed these steps both from the GitHub readme file and this more detailed article and I'm having an issue.
To start, however, know I was able to follow the instructions on the the DockerHub page for IRIS and I got my container running and VS Code connected and working. Since I got that working, I decided to better understand starting with this template.
I downloaded the objectscript-docker-template from GitHub to my local drive K:\objectscript-docker-template. I ran the commands to install and run the IRIS container in Git Bash and I see it running in Docker Desktop. I can go in the container's command line and type 'iris TERMINAL IRIS' and I'm brought to the USER namespace.
Back in the project location, I run 'docker-compose up -d' and I get a series of errors:
$ docker-compose up -d
Creating objectscript-docker-template_iris_1 ... error
ERROR: for objectscript-docker-template_iris_1 Cannot create container for service iris: status code not OK but 500: {"Message":"Unhandled exception: Filesharing has been cancelled","StackTrace":" at Docker.ApiServices.Mounting.FileSharing.<DoShareAsync>d__6.MoveNext() in C:\\workspaces\\stable-2.3.x\\src . . .
It goes on and on so I won't paste the whole thing.
If I stop the Docker container it's also deleted.
Has anyone had a similar issue or can see right off the bat what I might be doing wrong?
Thanks,
Mike
Hi Mike! It looks like that your docker space is over.
Call
and see if this fixes the problem.
Caution! This will delete unused containers on your laptop. make sure you don't have sensitive data or uncommitted code in it. Usually you don't
You may face the access issue, check the docker settings that you correctly provided a list of resources that can be shared.
@Evgeny Shvarov - "Check the article on the naming and proposed folder structure."
Is this supposed to link to a specific article?
In a recent example I posted, I had the need to extend the naming and proposed folder structure
It was obvious if you were reading the downloaded repository.
The related article was an advertisement and a "heads up" that it just wasn't the default structure as usual.
Hi Ben!
Thanks for the heads up. We have several so far:
Thanks! I see you updated the article with links. Very helpful :)