Search

Clear filter
Announcement
Anastasia Dyubaylo · Jun 22, 2021

InterSystems AI Contest Kick-Off Webinar

Hi Community, We're pleased to invite all the developers to the upcoming InterSystems AI Contest Kick-Off Webinar! The topic of this webinar is dedicated to the InterSystems AI programming contest. During the webinar, we will demo how to load data into IRIS, how to deal with it using ODBC/JDBC and REST, and how to use special AI/ML features of IRIS: IntegratedML, DataRobot, R Gateway, Embedded Python, PMML. Date & Time: Monday, June 28 — 11:00 AM EDT Speakers: 🗣 @Aleksandar.Kovacevic, InterSystems Sales Engineer🗣 @Théophile.Thierry, InterSystems Intern🗣 @Robert.Kuszewski, Product Manager - Developer Experience, InterSystems 🗣 @Evgeny.Shvarov, InterSystems Developer Ecosystem Manager Join the webinar to find out all the details about this competition and ask your questions to our speakers! ✅ REGISTER TODAY! Hey Developers, The recording of this webinar is available on InterSystems Developers YouTube! Please welcome: ⏯ InterSystems AI Contest Kick-Off Webinar Big applause to our speakers! 👏🏼
Question
Muhammad Waseem · Jun 14, 2021

Visual studio .Net over InterSystems studio

Just want to know what is the reason to user Visual Studio .Net editor over InterSystems studio. Thanks I prefer editing in Visual Studio Code for two main reasons: It's available on Windows, Mac, and Linux. So I always have it available. It's a great editor for all my code - ObjectScript, JavaScript, Python, Java. https://intersystems-community.github.io/vscode-objectscript/introduction/ The reason for that will be that it is Cross Platform my friend. Visual Studio Code is supported on all the available platforms as at present. And also it has a compatibility to to edit, run and debug any version of the code that you have chosen. Ex: F#, C#, VB, Python etc. Hope this was useful and i have made it simple and clear. Thanks. We use both depending on the situation. VS Code is more modern and certainly better for Git and Merge tools than Studio but it doesn't have all the abilities of Studio that I've found, global storage properties etc. We've found VS Code doesn't handle physical files as well as Studio at least for InterSystems so its unfriendly when using CSPs that are version controlled.
Question
Rahul Srivastava · Jun 5, 2021

InterSystems IRIS Installation custom layout structure

Hello Respected members, I am new with InterSystems IRIS , want to setup the environment on RHEL 7.5 with multiple lun layouts (separate LVM for DB , Journal, and WIJ), I would like to get some help in setting up the same, as far as I know, the installation will go through on a single directory ( I did not see any option to chose path/directory for DB's or WIJ /journals), hence after installation I would like to move my DB's to separate LVM and WIJ on another LVM set Please suggest or share the steps for the same document if any for the same, that will be really helpful, Thanks Regards RS 1. Install InterSystems IRIS. 2. Shut it down. 3. Open <iris>/iris.cpf, there you will find: [Databases] USER=C:\InterSystems\IRIS\Mgr\user\ [Journal] AlternateDirectory=C:\InterSystems\IRIS\Mgr\journal\ CurrentDirectory=C:\InterSystems\IRIS\Mgr\journal\ [config] wijdir=C:\InterSystems\IRIS\Mgr\WIJ 4. Edit this file as you wish and move the corresponding directories. 5. Start InterSystems IRIS. @Eduard.Lebedyuk Thank you so much for providing this information I would like to know if I can move only user database or default databases as well?? Like irislib, irisaudit, irislocaldata and other under mgr folder Please suggest if I can do this if yes then what sort of permission do I need to change for new directory path and what all files I need to copy at new location?? I want to setup my layout as given below Database LUNs = 8 x 512GB- - - > vg_iris_db Journal LUNs = 2 x 512GB - - > vg_iris_jrn WIJ LUNs = 2 x 256GB - - > vg_iris_wij Install and IRISTEMP LUNs = 2 x 128GB - - - > vg_iris_sys Please suggest if modification in iris.cpf file can do this all operation Thanks & Regards RS I would like to know if I can move only user database or default databases as well?? All databases can be moved what sort of permission do I need to change for new directory path irisusr should have RW access to the folder. The easiest way is to check what access (owner, group, permissions) is set for existing files/folders and then recreate the same in a new location. and what all files I need to copy at new location?? IRIS.DAT and stream subfolder if it exist. Please suggest if modification in iris.cpf file can do this all operation Absolutely. @Eduard.Lebedyuk Thank you so much for providing the detailed information. I am able to deploy the IRIS Instance with custom layout which I wanted to One thing I noticed that after installation I was able to see the instance running from command line but I was unable to access management portal with the URL that comes after installation, anything I missed or need to change to make it accessible? Please suggest Thanks RS Do you get Unable to connect or something else? What's the output of iris list? Hello Rahul, I'd check your messages.log to see messages regarding the private webserver. Then I'd check the OS to see if anything is holding your configured web server port. This could be worth a new post, or contacting the WRC. I am getting below error if I tried to access the URL : This site can’t be reached scsps2391082001’s server IP address could not be found. Below is the output from iris list [root@scsps2391082001 prd]# iris list Configuration 'PROD' (default) directory: /prd versionid: 2020.1.1.408.0com datadir: /prd conf file: iris.cpf (SuperServer port = 51773, WebServer = 52773) status: running, since Thu Jun 10 05:45:18 2021 state: ok product: InterSystems IRIS[root@scsps2391082001 prd]# Below are the Installation details Please review the installation options:------------------------------------------------------------------Instance name: PRODDestination directory: /prdInterSystems IRIS version to install: 2020.1.1.408.0comInstallation type: DevelopmentUnicode support: YInitial Security settings: NormalUser who owns instance: rootGroup allowed to start and stop instance: rootEffective group for InterSystems IRIS processes: irisusrEffective user for InterSystems IRIS SuperServer: irisusrSuperServer port: 51773WebServer port: 52773JDBC Gateway port: 53773Web Gateway: using built-in web server------------------------------------------------------------------ Confirm InterSystems IRIS installation <Yes>? Starting installationStarting up InterSystems IRIS for loading...../bin/irisinstall -s . -B -c c -C /prd/iris.cpf*PROD -W 1 -g2Starting Control ProcessAllocated 159MB shared memory: 2MB global buffers, 35MB routine buffersIRIS startup successful.System locale setting is 'en_US.UTF-8'This copy of InterSystems IRIS has been licensed for use exclusively by:InterSystems IRIS CommunityCopyright (c) 1986-2021 by InterSystems CorporationAny other use is a violation of your license agreement ^^/prd/mgr/> ^^/prd/mgr/>Start of IRIS initialization Loading system routines Updating system TEMP and LOCALDATA databases Installing National Language support Setting IRISTEMP default collation to IRIS standard (5) Loading system classes Updating Security database Loading system source code Building system indices Updating Audit database Updating Journal directory Updating User database Updating Interoperability databases Scheduling inventory scan IRIS initialization complete in 2 minutes See the iboot.log file for a record of the installation. Starting up InterSystems IRIS...Once this completes, users may access InterSystems IRISStarting PRODUsing 'iris.cpf' configuration file Starting Control ProcessAutomatically configuring buffersAllocated 925MB shared memory: 725MB global buffers, 35MB routine buffersCreating a WIJ file to hold 99 megabytes of dataThis copy of InterSystems IRIS has been licensed for use exclusively by:InterSystems IRIS CommunityCopyright (c) 1986-2021 by InterSystems CorporationAny other use is a violation of your license agreement You can point your browser to http://scsps2391082001:52773/csp/sys/UtilHome.cspto access the management portal. Let me know if anything else I need to provide it as a details ? Regards RS Thanks @Vic.Sun I will create a new post and will share the messages.log entry there Thank you all for providing the inputs for my queries Regards RS Hello Rahul - I didn't see your new post yet but this message is pretty clear. The problem is at the OS level in resolving the hostname, scsps2391082001. If you replace the hostname in your URL with the actual IP of your server, it should work fine. If you want the hostname to work, I'd work with your OS/networking team to set that up.
Announcement
Anastasia Dyubaylo · Jun 21, 2021

Let's Chat: Join InterSystems Developers on Discord!

Hey Community, You've probably already heard about Discord and many of you are already chatting there. And now we invite you to become closer to the world of InterSystems technology and join the social club of our developers! Use a super-fast way to communicate with each other: 💥 InterSystems Developers Discord Channel 💥 On DC Discord Server, you'll find many InterSystems-related channels and discussions, as well as channels for everyday life conversations. Just join our club to get closer to the world of InterSystems developers! See you on the InterSystems Developers Discord ✌️ If you have any suggestions for improving our Discord Server or want to create a new channel on a specific topic – please feel free to share your thoughts in the comments. Hey everyone, To stay in touch, let's continue our tech talks on InterSystems Developers Discord Server! See you there ✌️
Announcement
Kristina Lauer · Jun 16, 2021

InterSystems IRIS® FHIR® Accelerator Learning Path

Try the new InterSystems IRIS® FHIR® Accelerator Learning Path from Online Learning. Learn the basics of this fully managed service, which allows for the storage and sharing of healthcare data via a secure and scalable FHIR® repository. Get an overview of the FHIR Accelerator Service—and see how to deploy it—by following this curated collection of multimedia resources. Target audience: Software developers and other technical audiences.
Announcement
Anastasia Dyubaylo · Jul 7, 2021

Video: What is the InterSystems IRIS FHIR Server?

Hi Developers, Learn about the InterSystems IRIS FHIR Server, the fully managed FHIR data solution that empowers FHIR application developers to focus on building life-changing healthcare applications: ⏯ What is the InterSystems IRIS FHIR Accelerator Service? Subscribe to InterSystems Developers YouTube and stay tuned!
Announcement
Anastasia Dyubaylo · Mar 8, 2021

InterSystems Grand Prix Contest: CONGRATS THE WINNERS!

Hey everyone, The InterSystems Grand Prix Contest is over. It was an incredible competition with a record number of participating apps and developers! Thank you all for participating! And now it's time to announce the winners! A storm of applause goes to these developers and their applications: 🏆 Experts Nomination - winners were determined by a specially selected jury: 🥇 1st place and $6,000 go to the vscode-intersystems-iris project by @Dmitry.Maslennikov 🥈 2nd place and $3,000 go to the iris-rad-studio project by @Henrique.GonçalvesDias and @José.Pereira 🥉 3rd place and $2,000 go to the HealthInfoQueryLayer project by @Botai.Zhang 🏆 Community Nomination - an application that received the most votes in total: 🥇 1st place and $3,000 go to the HealthInfoQueryLayer project by @Botai.Zhang 🥈 2nd place and $1,500 go to the Dictionary comparison scheme of cache database project by @Weiwei.Yang 🥉 3rd place and $500 go to the vscode-intersystems-iris project by @Dmitry.Maslennikov And... This time, we would also like to reward the developers who took 4-10 places in the Expert nomination! 10,000 points on Global Masters go to these apps and their developers: 🏅 4th place: iris-image-index-demo by @José.Pereira 🏅 5th place: Terminal Multi-Line Command Editor​​​​ by @Robert.Cemper1003 🏅 6th place: Dictionary comparison scheme of cache database by @Weiwei.Yang 🏅 7th place: Create a unified hospital data extraction scheme based on IRIS for Health by @Deming.Xu 🏅 8th place: iris4health-fhir-analytics by @José.Pereira 🏅 9th place: iris-fhir-portal by @Henrique.GonçalvesDias 🏅 10th place: ObjectScript Kernel by @Nikita.Mullin Congratulations to all the winners and participants! Thank you all for your attention to the contest and the efforts you pay in our mega competition! Congratulations to all winners! Congratulations to all winners! Congratulations to all winners! Congratulations to all winners! Congratulations! And congratulations to all the participants - that's awesome work, great applications! Big congratulations to you and @José.Pereira!!! 🤩 Hey everyone, As always, we really appreciate the contribution of all participants to the Grand Prix competition. And this time, we would also like to reward the developers who took 4-10 places in the Expert nomination! 10,000 points on Global Masters go to these apps and their developers: 🏅 4th place: iris-image-index-demo by @José.Pereira 🏅 5th place: Terminal Multi-Line Command Editor​​​​ by @Robert.Cemper1003 🏅 6th place: Dictionary comparison scheme of cache database by @Weiwei.Yang 🏅 7th place: Create a unified hospital data extraction scheme based on IRIS for Health by @Deming.Xu 🏅 8th place: iris4health-fhir-analytics by @José.Pereira 🏅 9th place: iris-fhir-portal by @Henrique.GonçalvesDias 🏅 10th place: ObjectScript Kernel by @Nikita.Mullin Thank you all! And our big congratulations! That's good news! and a real surprise! I appreciate it highly Congratulations to all winners! Thanks @Anastasia.Dyubaylo It's our pleasure to create apps that people enjoy Hey developers! We also want to thank the rest of the participants and their cool applications that were in the InterSystems Grand Prix Contest: interoperability-integratedml-adapter , iris-integratedml-monitor-example and iris-analytics-notebook by @José Roberto Pereira IRIS-REST-API-DATABASEMANAGER by @Lucas.Bourré IRIS Interoperability Message Viewer , iris-history-monitor , npm-iris and isc-generate-db by @Henrique Dias Wsock-Embedded-Py , The adopted Bitmap , Using ZPM for Node.js and WebSocket Client JS with IRIS Native API as Docker Micro Server by @Robert Cemper iris-for-money by @Oliver.Wilms springboot-iris-crud by @Yuri.Gomes Airplane React, Material UI, and REST API by @FlávioLúcio.NavesJúnior appmsw-telestat , zapm , apptools-admin and isc-apptools-lockdown by @MikhailenkoSergey realworld-intersystems-iris , isc-tar and BlocksExplorer by @Dmitry.Maslennikov RESTFUL_API_For_Hotel_OverBooking_System by @jingqi.LIu cmPurgeBackup by @Alexey.Maslov fhir-chatbot , iris-ml-suite and iris-python-suite by @Renato.Banzai IRIS import manager by @Oleh.Dontsov ISC DEV by @Gevorg.Arutiunian Questy by Alexey Nechaev M-commands instead of Excel formulas in cells by @alex.kosinets units by @Dmitrii.Kuznetsov We are waiting for your applications in the next contests🔥🔥🔥 Congratulations to all winners! Congratulations to all Congratulations to all winners ! Well done guys! I'm very proud to had been participated! Glad to see how these contests are stimulating so many people to do their best! I see those apps as ground to create awesome projects in 2021! Indeed @Robert.Cemper1003! Thank you @Anastasia.Dyubaylo ! :) Very happy to had the chance of sharing a project with @Henrique.GonçalvesDias. More brains and hands working together it's better! Congratulations to the winners for their commitment, dedication and for the work presented. certainly, that initiatives like this contribute in a unique way to the expansion of technology and all its potential.
Question
Yi Han · Mar 4, 2021

In which industries are InterSystems products used more?

InterSystems has been widely used in medical and health fields. Which other industries have high market share? What's the difference between InterSystems Trakcare and InterSystems Healthshare? we also expanding into Finance, Logistics, Government etc. Trakcare is our package HIS/EMR solution: https://www.intersystems.com/cn/products/trakcare/ HealthShare is a product family to support unified care record. For more info pls connect with our local sales team. @Jun.Qian. THX! Metallurgy, construction, clothing industry, peat extraction, trade, logistics. Also as universal system for financial and accounting for any enterprise.
Article
Guillaume Rongier · Mar 26, 2021

IAM (InterSystems API Manager), Zero to Hero

![alt](https://raw.githubusercontent.com/grongierisc/iam-training/training/misc/img/introduction.png "Credit : https://blog.octo.com/kong-le-gorille-de-lapi-management-vu-de-pres/") This article contains the materials, examples, exercises to learn the basic concepts of IAM. You have all resources available on this git : https://github.com/grongierisc/iam-training. Solutions are in [training branch](https://github.com/grongierisc/iam-training/tree/training). This article will cover the following points : - [1. Introduction](#1-introduction) - [1.1. What is IAM ?](#11-what-is-iam-) - [1.2. What is an API Management ?](#12-what-is-an-api-management-) - [1.3. IAM Portal](#13-iam-portal) - [1.4. Flow of this training](#14-flow-of-this-training) - [2. Installation](#2-installation) - [2.1. What do you need to install?](#21-what-do-you-need-to-install) - [2.2. How IAM works with IRIS](#22-how-iam-works-with-iris) - [2.3. Setup](#23-setup) - [2.4. Install IAM](#24-install-iam) - [2.4.1. Iris Image](#241-iris-image) - [2.4.2. IAM Image](#242-iam-image) - [2.4.3. Update the docker file](#243-update-the-docker-file) - [2.4.4. Update the docker-compose](#244-update-the-docker-compose) - [2.4.5. Option : add IRIS_PASSWARD as .env](#245-option--add-iris_passward-as-env) - [2.4.6. Test it !](#246-test-it-) - [3. First Service/Route](#3-first-serviceroute) - [3.1. Create a service](#31-create-a-service) - [3.2. Create a route](#32-create-a-route) - [3.3. Test it !](#33-test-it-) - [4. Second, go further with plugin](#4-second-go-further-with-plugin) - [4.1. Add a plugin to the service](#41-add-a-plugin-to-the-service) - [4.2. Test it !](#42-test-it-) - [5. Third, add our own authentication](#5-third-add-our-own-authentication) - [5.1. Add consumers](#51-add-consumers) - [5.2. Add Basic auth plugin](#52-add-basic-auth-plugin) - [5.3. Add ACL Plugin](#53-add-acl-plugin) - [5.4. Configure USER with ACL and credentials](#54-configure-user-with-acl-and-credentials) - [5.5. Test it !](#55-test-it-) - [6. Exercice, Rate-Limiting](#6-exercice-rate-limiting) - [6.1. Solution](#61-solution) - [7. Dev Portal](#7-dev-portal) - [7.1. Overview](#71-overview) - [7.2. Enable it !](#72-enable-it-) - [7.3. Add your first spec](#73-add-your-first-spec) - [7.4. Test it !](#74-test-it-) - [7.5. Exercise](#75-exercise) - [7.5.1. Solution](#751-solution) - [8. Dev Portal, Part two, Authentication](#8-dev-portal-part-two-authentication) - [8.1. Enable Basic Auth](#81-enable-basic-auth) - [8.2. Limit access](#82-limit-access) - [8.2.1. Create a role](#821-create-a-role) - [8.2.2. Add role to Spec](#822-add-role-to-spec) - [8.2.3. Test it !](#823-test-it-) - [8.2.3.1. Register a new developer](#8231-register-a-new-developer) - [8.2.3.2. Approve this developer](#8232-approve-this-developer) - [8.2.3.3. Add role for this developer](#8233-add-role-for-this-developer) - [8.3. Add Oauth2 for developer](#83-add-oauth2-for-developer) - [8.3.1. First, remove basic auth](#831-first-remove-basic-auth) - [8.3.2. Second, add application-registration plugin](#832-second-add-application-registration-plugin) - [8.3.3. Link service and documentation](#833-link-service-and-documentation) - [8.3.3.1. Test it !](#8331-test-it-) - [9. Secure Management Portal](#9-secure-management-portal) - [9.1. Create an admin](#91-create-an-admin) - [9.2. Enable Basic Auth for Kong Manager](#92-enable-basic-auth-for-kong-manager) - [9.3. Use Kong Admin API with RBAC](#93-use-kong-admin-api-with-rbac) - [9.3.1. Create and admin user with a token](#931-create-and-admin-user-with-a-token) - [10. Plugins](#10-plugins) - [10.1. Import a community plugin](#101-import-a-community-plugin) - [10.1.1. Build a new Kong/IAM docker image with the community plugin](#1011-build-a-new-kongiam-docker-image-with-the-community-plugin) - [10.1.2. Test it !](#1012-test-it-) - [10.1.2.1. Use it !](#10121-use-it-) - [10.2. Create a new plugin](#102-create-a-new-plugin) - [10.2.1. File structure](#1021-file-structure) - [10.2.1.1. handler.lua](#10211-handlerlua) - [10.2.1.1.1. Example](#102111-example) - [10.2.1.2. schema.lua](#10212-schemalua) - [10.2.1.3. *.rockspec](#10213-rockspec) - [10.2.2. Build it](#1022-build-it) - [10.2.3. Tips](#1023-tips) - [11. CI/CD](#11-cicd) - [11.1. Create the postman collection](#111-create-the-postman-collection) - [11.1.1. Is IAM startup ?](#1111-is-iam-startup-) - [11.1.2. Delete old datas](#1112-delete-old-datas) - [11.1.3. Create Service/Route](#1113-create-serviceroute) - [11.1.3.1. Tips](#11131-tips) - [11.2. Run it with newman](#112-run-it-with-newman) # 1. Introduction ![alt](https://raw.githubusercontent.com/grongierisc/iam-training/training/misc/img/introduction.png "Credit : https://blog.octo.com/kong-le-gorille-de-lapi-management-vu-de-pres/") ## 1.1. What is IAM ? IAM stand for InterSystems API Manager, it's based on **Kong Enterprise Edition**. This mean you have access on top of Kong Open Source edition to : * Manager Portal * Developer Portal * Advance plugin * Oauth2 * Caching * ... ![alt](https://raw.githubusercontent.com/grongierisc/iam-training/training/misc/img/KongEEvsOSS.png) ## 1.2. What is an API Management ? API management is the process of creating and publishing web application programming interfaces (APIs), enforcing their usage policies, controlling access, nurturing the subscriber community, collecting and analyzing usage statistics, and reporting on performance. API Management components provide mechanisms and tools to support developer and subscriber community. ![alt](https://raw.githubusercontent.com/grongierisc/iam-training/training/misc/img/Api_Managment.png) ## 1.3. IAM Portal Kong and IAM are design as API first, this mean, everything done in Kong/IAM can be done by rest calls or the manager portal. During this article all example / exercise will present both this way: |IAM Portal|Rest API| |-----------------|--------------| |![alt](https://raw.githubusercontent.com/grongierisc/iam-training/training/misc/img/default_kong.png)|![alt](https://raw.githubusercontent.com/grongierisc/iam-training/training/misc/img/rest_call.png)| ## 1.4. Flow of this article The aim of this article is to use IAM as a proxy of an IRIS rest API. Definition of this rest API can be found here : ```url http://localhost:52773/swagger-ui/index.html#/ ``` or here ```url https://github.com/grongierisc/iam-training/blob/training/misc/spec.yml ``` Start this article with the main branch. At the end of the article, you should have the same result as the training branch. # 2. Installation ![alt](https://raw.githubusercontent.com/grongierisc/iam-training/training/misc/img/installation.png) ## 2.1. What do you need to install? * [Git](https://git-scm.com/downloads) * [Docker](https://www.docker.com/products/docker-desktop) (if you are using Windows, make sure you set your Docker installation to use "Linux containers"). * [Docker Compose](https://docs.docker.com/compose/install/) * [Visual Studio Code](https://code.visualstudio.com/download) + [InterSystems ObjectScript VSCode Extension](https://marketplace.visualstudio.com/items?itemName=daimor.vscode-objectscript) * InterSystems IRIS IAM enabled license file. * IAM Docker image ## 2.2. How IAM works with IRIS At Kong/IAM start, the container check for the Kong/IAM license with a curl call. The endpoint of this call is a rest API on the IRIS container. FYI : Kong license is embedded in IRIS one. ![alt](https://raw.githubusercontent.com/grongierisc/iam-training/training/misc/img/IAM_IRIS.png) ## 2.3. Setup Git clone this repository. git clone https://github.com/grongierisc/iam-training Run the initial rest API : docker-compose up Test it : ```url http://localhost:52773/swagger-ui/index.html#/ ``` Login/Password : SuperUser/SYS ## 2.4. Install IAM ### 2.4.1. Iris Image First you need to switch for the community edition to a licensed one. To do so, you need to setup your access to InterSystems Container Registry to download IRIS limited access images. Have a look at this [Introducing InterSystems Container Registry](https://community.intersystems.com/post/introducing-intersystems-container-registry) on [Developer Community](https://community.intersystems.com). * Log-in into https://containers.intersystems.com/ using your WRC credentials and get a *token*. * Set up docker login in your computer: ```bash docker login -u="user" -p="token" containers.intersystems.com ``` * Get InterSystems IRIS image: ```bash docker pull containers.intersystems.com/intersystems/irishealth:2020.4.0.524.0 ``` ### 2.4.2. IAM Image In [WRC Software Distribution](https://wrc.intersystems.com/wrc/coDistribution.csp): * Components > Download *IAM-1.5.0.9-4.tar.gz* file, unzip & untar and then load the image: ```bash docker load -i iam_image.tar ``` ### 2.4.3. Update the docker file Change IRIS community edition to a licensed one. * containers.intersystems.com/intersystems/irishealth:2020.4.0.524.0 * add iris.key in key folder Edit the dockerfile to add on top of it this part ```dockerfile ARG IMAGE=containers.intersystems.com/intersystems/irishealth:2020.4.0.524.0 # Frist stage FROM $IMAGE as iris-iam COPY key/iris.key /usr/irissys/mgr/iris.key COPY iris-iam.script /tmp/iris-iam.script RUN iris start IRIS \ && iris session IRIS < /tmp/iris-iam.script \ && iris stop IRIS quietly # Second stage FROM iris-iam ``` This part will create a multi-stage dockerfile. * the first stage is to enable IRIS to serve IAM license. * the second stage is for the REST API build Create a new file iris-iam.script to build a new IRIS Image to enable IAM endpoint and user. ```objectscript zn "%SYS" write "Create web application ...",! set webName = "/api/iam" set webProperties("Enabled") = 1 set status = ##class(Security.Applications).Modify(webName, .webProperties) write:'status $system.Status.DisplayError(status) write "Web application "_webName_" was updated!",! set userProperties("Enabled") = 1 set userName = "IAM" Do ##class(Security.Users).Modify(userName,.userProperties) write "User "_userName_" was updated!",! halt ``` ### 2.4.4. Update the docker-compose Update the docker-compose file to : * db * postgres database for IAM * iam-migration * bootstrap the database * iam * actual IAM instance * a volume for data persistent Add this part to the end of the docker-compose file. ```yml iam-migrations: image: intersystems/iam:1.5.0.9-4 command: kong migrations bootstrap up depends_on: - db environment: KONG_DATABASE: postgres KONG_PG_DATABASE: ${KONG_PG_DATABASE:-iam} KONG_PG_HOST: db KONG_PG_PASSWORD: ${KONG_PG_PASSWORD:-iam} KONG_PG_USER: ${KONG_PG_USER:-iam} KONG_CASSANDRA_CONTACT_POINTS: db KONG_PLUGINS: bundled,jwt-crafter ISC_IRIS_URL: IAM:${IRIS_PASSWORD}@iris:52773/api/iam/license restart: on-failure links: - db:db iam: image: intersystems/iam:1.5.0.9-4 depends_on: - db environment: KONG_ADMIN_ACCESS_LOG: /dev/stdout KONG_ADMIN_ERROR_LOG: /dev/stderr KONG_ADMIN_LISTEN: '0.0.0.0:8001' KONG_ANONYMOUS_REPORTS: 'off' KONG_CASSANDRA_CONTACT_POINTS: db KONG_DATABASE: postgres KONG_PG_DATABASE: ${KONG_PG_DATABASE:-iam} KONG_PG_HOST: db KONG_PG_PASSWORD: ${KONG_PG_PASSWORD:-iam} KONG_PG_USER: ${KONG_PG_USER:-iam} KONG_PROXY_ACCESS_LOG: /dev/stdout KONG_PROXY_ERROR_LOG: /dev/stderr KONG_PORTAL: 'on' KONG_PORTAL_GUI_PROTOCOL: http KONG_PORTAL_GUI_HOST: '127.0.0.1:8003' KONG_ADMIN_GUI_URL: http://localhost:8002 KONG_PLUGINS: bundled ISC_IRIS_URL: IAM:${IRIS_PASSWORD}@iris:52773/api/iam/license volumes: - ./iam:/iam links: - db:db ports: - target: 8000 published: 8000 protocol: tcp - target: 8001 published: 8001 protocol: tcp - target: 8002 published: 8002 protocol: tcp - target: 8003 published: 8003 protocol: tcp - target: 8004 published: 8004 protocol: tcp - target: 8443 published: 8443 protocol: tcp - target: 8444 published: 8444 protocol: tcp - target: 8445 published: 8445 protocol: tcp restart: on-failure db: image: postgres:9.6 environment: POSTGRES_DB: ${KONG_PG_DATABASE:-iam} POSTGRES_PASSWORD: ${KONG_PG_PASSWORD:-iam} POSTGRES_USER: ${KONG_PG_USER:-iam} volumes: - 'pgdata:/var/lib/postgresql/data' healthcheck: test: ["CMD", "pg_isready", "-U", "${KONG_PG_USER:-iam}"] interval: 30s timeout: 30s retries: 3 restart: on-failure stdin_open: true tty: true volumes: pgdata: ``` Add the .env file in root folder : ```env IRIS_PASSWORD=SYS ``` BTW : Here are the definition of Kong ports : |Port|Protocol|Description| |----|--------|-----------| |:8000|HTTP|Takes incoming HTTP traffic from Consumers, and forwards it to upstream Services.| |:8443|HTTPS|Takes incoming HTTPS traffic from Consumers, and forwards it to upstream Services.| |:8001|HTTP|Admin API. Listens for calls from the command line over HTTP.| |:8444|HTTPS|Admin API. Listens for calls from the command line over HTTPS.| |:8002|HTTP|Kong Manager (GUI). Listens for HTTP traffic.| |:8445|HTTPS|Kong Manager (GUI). Listens for HTTPS traffic.| |:8003|HTTP|Dev Portal. Listens for HTTP traffic, assuming Dev Portal is enabled.| |:8446|HTTPS|Dev Portal. Listens for HTTPS traffic, assuming Dev Portal is enabled.| |:8004|HTTP|Dev Portal /files traffic over HTTP, assuming the Dev Portal is enabled.| |:8447|HTTPS|Dev Portal /files traffic over HTTPS, assuming the Dev Portal is enabled.| ### 2.4.5. Option : add IRIS_PASSWARD as .env For ease of use (and may be security), you can use the .env file in the IRIS dockerfile. To do so, edit the docker-compose with this in the iris service part : ```yml build: context: . dockerfile: dockerfile args: - IRIS_PASSWORD=${IRIS_PASSWORD} ``` And the dockerfile (second or first stage of the build): ```dockerfile ARG IRIS_PASSWORD RUN echo "${IRIS_PASSWORD}" > /tmp/password.txt && /usr/irissys/dev/Container/changePassword.sh /tmp/password.txt ``` ### 2.4.6. Test it ! docker-compose -f "docker-compose.yml" up -d --build # 3. First Service/Route ![alt](https://raw.githubusercontent.com/grongierisc/iam-training/training/misc/img/service_route.png) Remember how Kong/IAM works ? ![alt](https://raw.githubusercontent.com/grongierisc/iam-training/training/misc/img/KongEEvsOSS.png) Here, we will build : * a service * for our crud API * a route * to access this service ## 3.1. Create a service IAM Portal Rest API # Create service curl -i -X POST \ --url http://localhost:8001/services/ \ --data 'name=crud' \ --data 'url=http://iris:52773/crud/' What do we see here, to create a service we simply need it's url. ## 3.2. Create a route IAM Portal Rest API # Create route curl -i -X POST \ --url http://localhost:8001/services/crud/routes \ --data 'name=crud-route' \ --data 'paths=/persons/*' \ --data 'strip_path=false' What do we see here, to create a route we need : * it's service name * a path where RegEx is allowed ## 3.3. Test it ! **Original API** # Legacy curl –i --location --request GET 'http://localhost:52773/crud/persons/all' \ --header 'Authorization: Basic U3VwZXJVc2VyOlNZUw==' **Proxy API** # KONG curl –i --location --request GET 'http://localhost:8000/persons/all' \ --header 'Authorization: Basic U3VwZXJVc2VyOlNZUw==' What do we see here : * Nothing new on legacy side. * On kong side : * We change the port * The path corresponds to the route * We still need to authenticate # 4. Second, go further with plugin To go further, we will try to auto-authenticate Kong to the IRIS endpoint. To do so, we will use and plugin, resquest-transformer. ![alt](https://raw.githubusercontent.com/grongierisc/iam-training/training/misc/img/auto_authenticate.png) ## 4.1. Add a plugin to the service IAM Portal Rest API # Create plugin curl -i -X POST \ --url http://localhost:8001/services/crud/plugins \ --data 'name=request-transformer' \ --data 'config.add.headers=Authorization:Basic U3VwZXJVc2VyOlNZUw==' \ --data 'config.replace.headers=Authorization:Basic U3VwZXJVc2VyOlNZUw==' ## 4.2. Test it ! # Legacy **Original API** curl –i --location --request GET 'http://localhost:52773/crud/persons/all' **Proxy API** # KONG curl –i --location --request GET 'http://localhost:8000/persons/all' What do we see here : * Error 401 on the original API * We reach the data without authentication # 5. Third, add our own authentication What we want to achieved here is to add our own authentication without any distuption of the original API. ![alt](https://raw.githubusercontent.com/grongierisc/iam-training/training/misc/img/custom_auth.png) ## 5.1. Add consumers IAM Portal Rest API # Add consumer anonymous curl -i -X POST \ --url http://localhost:8001/consumers/ \ --data "username=anonymous" \ --data "custom_id=anonymous" # Add consumer user curl -i -X POST \ --url http://localhost:8001/consumers/ \ --data "username=user" \ --data "custom_id=user" ## 5.2. Add Basic auth plugin IAM Portal Rest API # Enable basic auth for service curl -i -X POST http://localhost:8001/routes/crud-route/plugins \ --data "name=basic-auth" \ --data "config.anonymous=5cc8dee4-066d-492e-b2f8-bd77eb0a4c86" \ --data "config.hide_credentials=false" Where : * config.anonymous = uuid of anonymous consumer ## 5.3. Add ACL Plugin IAM Portal Rest API # Enable ACL curl -i -X POST http://localhost:8001/routes/crud-route/plugins \ --data "name=acl" \ --data "config.whitelist=user" ## 5.4. Configure USER with ACL and credentials IAM Portal Rest API # Add consumer group curl -i -X POST \ --url http://localhost:8001/consumers/user/acls \ --data "group=user" # Add consumer credentials curl -i -X POST http://localhost:8001/consumers/user/basic-auth \ --data "username=user" \ --data "password=user" ## 5.5. Test it ! **Original API** # Legacy curl –i --location --request GET 'http://localhost:52773/crud/persons/all' \ --header 'Authorization:Basic dXNlcjp1c2Vy' **Proxy API ** # KONG curl –i --location --request GET 'http://localhost:8000/persons/all' \ --header 'Authorization:Basic dXNlcjp1c2Vy' # 6. Exercice, Rate-Limiting 1. Enable Unauthenticated user 2. Limit rate by 2 calls per minutes to Unauthenticated user ## 6.1. Solution 1. Enable Unauthenticated user IAM Portal Rest API # Add consumer group curl -i -X POST \ --url http://localhost:8001/consumers/anonymous/acls \ --data "group=user" 2. Limit rate by 2 calls per minutes to Unauthenticated user IAM Portal Rest API # Add rate limit consumer curl -i -X POST \ --url http://localhost:8001/consumers/anonymous/plugins \ --data "name=rate-limiting" \ --data "config.limit_by=consumer" \ --data "config.minute=2" # 7. Dev Portal ![alt](https://raw.githubusercontent.com/grongierisc/iam-training/training/misc/img/dev_portal_intro.png) ## 7.1. Overview The Kong Developer Portal provides : * a single source of truth for all developers * intuitive content management for documentation * streamlined developer onboarding * role-based access control (RBAC) ![alt](https://raw.githubusercontent.com/grongierisc/iam-training/training/misc/img/dev_portal.png) ## 7.2. Enable it ! IAM Portal Rest API curl -X PATCH http://localhost:8001/workspaces/default --data "config.portal=true" ## 7.3. Add your first spec **IAM Portal** **Rest API** curl -X POST http://localhost:8001/default/files -F "path=specs/iam-training.yml" -F "contents=@misc/spec.yml" ## 7.4. Test it ! ```url http://localhost:8003/default/documentation/iam-training ``` What happen ? How-to solve it ? ## 7.5. Exercise 1. Add CORS plugin on route ### 7.5.1. Solution IAM Portal Rest API # Enable CORS curl -i -X POST http://localhost:8001/routes/crud-route/plugins \ --data "name=cors" # 8. Dev Portal, Part two, Authentication ## 8.1. Enable Basic Auth IAM Portal Session Config (JSON) { "cookie_secure": false, "cookie_name": "portal_session", "secret": "SYS", "storage": "kong" } Now authentication is enabled for the dev portal. ## 8.2. Limit access By default, all is accessible for unauthenticated user. We can create role to limit some access. For example, let restrict access to our CRUD API documentation. ### 8.2.1. Create a role IAM Portal Rest API # Enable role curl -i -X POST http://localhost:8001/default/developers/roles \ --data "name=dev" ### 8.2.2. Add role to Spec **IAM Portal** **Rest API** # Enable role curl 'http://localhost:8001/default/files/specs/iam-training.yml' -X PATCH -H 'Accept: application/json, text/plain, */*' --compressed -H 'Content-Type: application/json;charset=utf-8' -H 'Origin: http://localhost:8002' -H 'Referer: http://localhost:8002/default/portal/permissions/' --data-raw $'{"contents":"x-headmatter:\\n readable_by:\\n - dev\\nswagger: \'2.0\'\\ninfo:\\n title: InterSystems IRIS REST CRUD demo\\n description: Demo of a simple rest API on IRIS\\n version: \'0.1\'\\n contact:\\n email: apiteam@swagger.io\\n license:\\n name: Apache 2.0\\n url: \'http://www.apache.org/licenses/LICENSE-2.0.html\'\\nhost: \'localhost:8000\'\\nbasePath: /\\nschemes:\\n - http\\nsecurityDefinitions:\\n basicAuth:\\n type: basic\\nsecurity:\\n - basicAuth: []\\npaths:\\n /:\\n get:\\n description: \' PersonsREST general information \'\\n summary: \' Server Info \'\\n operationId: GetInfo\\n x-ISC_CORS: true\\n x-ISC_ServiceMethod: GetInfo\\n responses:\\n \'200\':\\n description: (Expected Result)\\n schema:\\n type: object\\n properties:\\n version:\\n type: string\\n default:\\n description: (Unexpected Error)\\n /persons/all:\\n get:\\n description: \' Retreive all the records of Sample.Person \'\\n summary: \' Get all records of Person class \'\\n operationId: GetAllPersons\\n x-ISC_ServiceMethod: GetAllPersons\\n responses:\\n \'200\':\\n description: (Expected Result)\\n schema:\\n type: array\\n items:\\n $ref: \'#/definitions/Person\'\\n default:\\n description: (Unexpected Error)\\n \'/persons/{id}\':\\n get:\\n description: \' Return one record fo Sample.Person \'\\n summary: \' GET method to return JSON for a given person id\'\\n operationId: GetPerson\\n x-ISC_ServiceMethod: GetPerson\\n parameters:\\n - name: id\\n in: path\\n required: true\\n type: string\\n responses:\\n \'200\':\\n description: (Expected Result)\\n schema:\\n $ref: \'#/definitions/Person\'\\n default:\\n description: (Unexpected Error)\\n put:\\n description: \' Update a record in Sample.Person with id \'\\n summary: \' Update a person with id\'\\n operationId: UpdatePerson\\n x-ISC_ServiceMethod: UpdatePerson\\n parameters:\\n - name: id\\n in: path\\n required: true\\n type: string\\n - name: payloadBody\\n in: body\\n description: Request body contents\\n required: false\\n schema:\\n type: string\\n responses:\\n \'200\':\\n description: (Expected Result)\\n default:\\n description: (Unexpected Error)\\n delete:\\n description: \' Delete a record with id in Sample.Person \'\\n summary: \' Delete a person with id\'\\n operationId: DeletePerson\\n x-ISC_ServiceMethod: DeletePerson\\n parameters:\\n - name: id\\n in: path\\n required: true\\n type: string\\n responses:\\n \'200\':\\n description: (Expected Result)\\n default:\\n description: (Unexpected Error)\\n /persons/:\\n post:\\n description: \' Creates a new Sample.Person record \'\\n summary: \' Create a person\'\\n operationId: CreatePerson\\n x-ISC_ServiceMethod: CreatePerson\\n parameters:\\n - name: payloadBody\\n in: body\\n description: Request body contents\\n required: false\\n schema:\\n type: string\\n responses:\\n \'200\':\\n description: (Expected Result)\\n default:\\n description: (Unexpected Error)\\ndefinitions:\\n Person:\\n type: object\\n properties:\\n Name:\\n type: string\\n Title:\\n type: string\\n Company:\\n type: string\\n Phone:\\n type: string\\n DOB:\\n type: string\\n format: date-time\\n"}' What's important here is this part : ```yml x-headmatter: readable_by: - dev ``` Refere to this documentation : [readable_by attribute](https://docs.konghq.com/enterprise/1.5.x/developer-portal/administration/developer-permissions/#readable_by-attribute) ### 8.2.3. Test it ! #### 8.2.3.1. Register a new developer ![video](https://raw.githubusercontent.com/grongierisc/iam-training/training/misc/video/new_dev_sign.gif) #### 8.2.3.2. Approve this developer ![video](https://raw.githubusercontent.com/grongierisc/iam-training/training/misc/video/approve_new_dev.gif) #### 8.2.3.3. Add role for this developer ![video](https://raw.githubusercontent.com/grongierisc/iam-training/training/misc/video/add_role_dev.gif) curl 'http://localhost:8001/default/developers/dev@dev.com' -X PATCH --compressed -H 'Content-Type: application/json;charset=utf-8' -H 'Cache-Control: no-cache' -H 'Origin: http://localhost:8002' -H 'DNT: 1' -H 'Connection: keep-alive' -H 'Referer: http://localhost:8002/default/portal/permissions/dev/update' -H 'Pragma: no-cache' --data-raw '{"roles":["dev"]}' ## 8.3. Add Oauth2 for developer In this part we will add an Oauth2 authentication for developers to use securely our crud API. This flow will provide self-registration from developer and grant them access to the crud API. ### 8.3.1. First, remove basic auth To do so, we will replace our basic auth to a bearToken one. First disable our basic auth/acl. **IAM Portal** **Rest API** # Disable ACL Plugin curl 'http://localhost:8001/default/routes/afefe836-b9be-49a8-927a-1324a8597a9c/plugins/3f2e605e-9cb6-454a-83ec-d1b1929b1d30' -X PATCH --compressed -H 'Content-Type: application/json;charset=utf-8' -H 'Cache-Control: no-cache' -H 'Origin: http://localhost:8002' -H 'DNT: 1' -H 'Connection: keep-alive' -H 'Referer: http://localhost:8002/default/plugins/acl/3f2e605e-9cb6-454a-83ec-d1b1929b1d30/update' -H 'Pragma: no-cache' --data-raw '{"enabled":false,"name":"acl","route":{"id":"afefe836-b9be-49a8-927a-1324a8597a9c"},"config":{"hide_groups_header":false,"whitelist":["user","dev","crud"]}}' ### 8.3.2. Second, add application-registration plugin IAM Portal Rest API # Create application-registration plugin curl -i -X POST \ --url http://localhost:8001/services/crud/plugins \ --data 'name=application-registration' \ --data 'config.auth_header_name=authorization' \ --data 'config.auto_approve=true' \ --data 'config.display_name=auth' \ --data 'config.enable_client_credentials=true' ### 8.3.3. Link service and documentation **IAM Porta** **Rest API** curl 'http://localhost:8001/default/document_objects' --compressed -H 'Content-Type: application/json;charset=utf-8' -H 'Cache-Control: no-cache' -H 'Origin: http://localhost:8002' -H 'DNT: 1' -H 'Connection: keep-alive' -H 'Referer: http://localhost:8002/default/services/create-documentation' -H 'Pragma: no-cache' --data-raw '{"service":{"id":"7bcef2e6-117c-487a-aab2-c7e57a0bf61a"},"path":"specs/iam-training.yml"}' #### 8.3.3.1. Test it ! From the dev portal logged as dev@dev.com, create a new application. ![alt](https://raw.githubusercontent.com/grongierisc/iam-training/training/misc/video/add_app_dev.gif) This will give you client_id and client_secret. Theses can be used in the swagger dev portal. Register this application to the crud service : ![alt](https://raw.githubusercontent.com/grongierisc/iam-training/training/misc/video/register_app_dev.gif) Get token: curl --insecure -X POST https://localhost:8443/persons/oauth2/token \ --data "grant_type=client_credentials" \ --data "client_id=2TXNvDqjeVMHydJbjv9t96lWTXOKAtU8" \ --data "client_secret=V6Vma6AtIvl04UYssz6gAxPc92eCF4KR" Use token : curl --insecure -X GET https://localhost:8443/persons/all \ --header "authorization: Bearer u5guWaYR3BjZ1KdwuBSC6C7udCYxj5vK" # 9. Secure Management Portal ## 9.1. Create an admin As we have bootstrap Kong without a seed password. We have to create an admin before enforcing RBAC. To do so: * Go to Teams * Invite admin * Set Mail * Set Username * Set role to super admin * Invite * Go to Invited Admin * View * Generate link ![alt](https://raw.githubusercontent.com/grongierisc/iam-training/training/misc/video/invite_admin.gif) ## 9.2. Enable Basic Auth for Kong Manager To enable this feature, we have to change the docker-compose file. Add this to the iam service, environment ```yml KONG_ENFORCE_RBAC: 'on' KONG_ADMIN_GUI_AUTH: 'basic-auth' KONG_ADMIN_GUI_SESSION_CONF: '{"secret":"${IRIS_PASSWORD}","storage":"kong","cookie_secure":false}' ``` Restart the container docker-compose down && docker-compose up -d Go to the invited admin link : ```url http://localhost:8002/register?email=test.test%40gmail.com&username=admin&token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2MTYzMzYzNzEsImlkIjoiY2JiZGE5Y2UtODQ3NS00MmM2LTk4ZjItNDgwZTI4MjQ4NWNkIn0.sFeOc_5UPIr3MdlQrgyGvmvIjRFvSn3nQjo2ph8GrJA ``` ## 9.3. Use Kong Admin API with RBAC As RBAC is set, we can't use kong admin api anymore : curl -s -X GET \ --url http://localhost:8001/routes Get this error : ```json {"message":"Invalid credentials. Token or User credentials required"} ``` ### 9.3.1. Create and admin user with a token * Go to Teams * RBAC Users * Add new user ![alt](https://raw.githubusercontent.com/grongierisc/iam-training/training/misc/img/user_rbac.png) curl -s -X GET \ --url http://localhost:8001/routes \ --header "Kong-Admin-Token: SYS" # 10. Plugins ![alt](https://raw.githubusercontent.com/grongierisc/iam-training/training/misc/img/plugins_intro.png) Kong come with high quality plugins. But, what if, we need plugin that are not embedded. If we want community plugins ? In this chapiter, we will talk about community plugins, how to import them. Then, we will see how-to build our own plugin. ## 10.1. Import a community plugin For this part, we will be using the jwt-crafter plugin. This plugin adds the possibility to generate a JWT token within Kong itself, eliminating the need for an upstream service doing the token generation. Here is the plugin : ``` https://github.com/grongierisc/kong-plugin-jwt-crafter ``` To install this plugin, as we are using the docker version, we have to build a new image who embed the plugin. ### 10.1.1. Build a new Kong/IAM docker image with the community plugin 1. Create a folder named iam at root of this git. 2. Create a dockerfile in this new folder 3. Create a folder named plugins 1. This is where we will add all our community plugins 4. Update the docker-compose file to enable the new plug in In the plugins folder, git clone our community plugin. git clone https://github.com/grongierisc/kong-plugin-jwt-crafter The dockerfile should look like this: ```dockerfile FROM intersystems/iam:1.5.0.9-4 USER root COPY ./plugins /custom/plugins RUN cd /custom/plugins/kong-plugin-jwt-crafter && luarocks make USER kong ``` What we see in this dockerfile ? Simply to install a community plugin, we have to move to its root folder (where the rockspec is) and call luarocks make. That's it. You have installed the plugin. For the docker-compose part : 1. Edit the iam iamge tag 1. intersystems/iam:1.5.0.9-4 -> intersystems/iam-custom:1.5.0.9-4 2. Add a build context ```yml build: context: iam dockerfile: dockerfile ``` 3. Enable the plugin in the environment variables ```yml KONG_PLUGINS: 'bundled,jwt-crafter' ``` Now build our new iam image : docker-compose build iam ### 10.1.2. Test it ! docker-compose up -d If you go to plugin -> new, at the bottom of the list you should see the jwt-crafter plugin. ![alt](https://raw.githubusercontent.com/grongierisc/iam-training/training/misc/img/jwt_crafter.png) #### 10.1.2.1. Use it ! 1. Create a new service : IAM Portal Rest API # Create service curl -i -X POST \ --url http://localhost:8001/services/ \ --data 'name=crud-persons' \ --data 'url=http://iris:52773/crud/persons/' 2. Create a route IAM Portal Rest API # Create route curl -i -X POST \ --url http://localhost:8001/services/crud-persons/routes \ --data 'name=crud-route-jwt' \ --data 'paths=/crud/persons/*' \ --data 'strip_path=true' 3. Re-use our auto-auth IAM Portal Rest API # Create plugin curl -i -X POST \ --url http://localhost:8001/services/crud-persons/plugins \ --data 'name=request-transformer' \ --data 'config.add.headers=Authorization:Basic U3VwZXJVc2VyOlNZUw==' \ --data 'config.replace.headers=Authorization:Basic U3VwZXJVc2VyOlNZUw==' Now we are set. The real use of jwt-crafter. ```bash # Add acl to route curl -i -X POST http://localhost:8001/routes/crud-route-jwt/plugins \ --data "name=acl" \ --data "config.whitelist=test" \ --data "config.hide_groups_header=false" # Create service curl -i -X POST \ --url http://localhost:8001/services/ \ --data 'name=jwt-login' \ --data 'url=http://neverinvoked/' # Create route curl -i -X POST \ --url http://localhost:8001/services/jwt-login/routes \ --data 'name=jwt-login-route' \ --data 'paths=/jwt/log-in' # Enable basic auth for service curl -i -X POST http://localhost:8001/routes/jwt-login-route/plugins \ --data "name=basic-auth" \ --data "config.hide_credentials=false" # Enable basic auth for service curl -i -X POST http://localhost:8001/routes/jwt-login-route/plugins \ --data "name=jwt-crafter" \ --data "config.expires_in=86400" # Add consumer curl -i -X POST \ --url http://localhost:8001/consumers/ \ --data "username=test" # Add consumer group curl -i -X POST \ --url http://localhost:8001/consumers/test/acls \ --data "group=test" # Add consumer credentials curl -i -X POST http://localhost:8001/consumers/test/basic-auth \ --data "username=test" \ --data "password=test" curl -i -X POST http://localhost:8001/consumers/test/jwt \ --data "key=test" \ --data "algorithm=HS256" # JWT plugins curl -i -X POST http://localhost:8001/routes/crud-route-jwt/plugins \ --data "name=jwt" ``` Test it ! ```bash # test:test is base64 encoded curl -H 'Authorization: basic dGVzdDp0ZXN0' localhost:8000/jwt/log-in ``` ```bash curl --location --request GET 'http://localhost:8000/crud/persons/all' \ --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW0iOiJ0ZXN0Iiwic3ViIjoiODJiNjcwZDgtNmY2OC00NDE5LWJiMmMtMmYxZjMxNTViN2E2Iiwicm9sIjpbInRlc3QiXSwiZXhwIjoxNjE2MjUyMTIwLCJpc3MiOiJ0ZXN0In0.g2jFqe0hDPumy8_gG7J3nYsuZ8KUz9SgZOecdBDhfns' ``` ## 10.2. Create a new plugin This is not the place to learn lua. But I'll give you some tips like how to quickly restart IAM to test our new development. ### 10.2.1. File structure ``` kong-plugin-helloworld ├── kong │ └── plugins │ └── helloworld │ ├── handler.lua │ └── schema.lua └── kong-plugin-helloworld-0.1.0-1.rockspec ``` By convention, kong plugins must be prefix by kong-plugin. In our example, the name of the plugin is helloworld. Three files are mandatory : * handler.lua: the core of your plugin. It is an interface to implement, in which each function will be run at the desired moment in the lifecycle of a request / connection. * schema.lua: your plugin probably has to retain some configuration entered by the user. This module holds the schema of that configuration and defines rules on it, so that the user can only enter valid configuration values. * *.rockspec: Rockspec: a package specification file A declarative Lua script, with rules on how to build and package rocks *.rockspec - a Lua file containing some tables. #### 10.2.1.1. handler.lua The plugins interface allows you to override any of the following methods in your handler.lua file to implement custom logic at various entry-points of the execution life-cycle of Kong: |Function name |Phase |Description| |----|----|----| |:init_worker() |init_worker |Executed upon every Nginx worker process’s startup.| |:certificate() |ssl_certificate |Executed during the SSL certificate serving phase of the SSL handshake.| |:rewrite() |rewrite |Executed for every request upon its reception from a client as a rewrite phase handler. NOTE in this phase neither the Service nor the Consumer have been identified, hence this handler will only be executed if the plugin was configured as a global plugin!| |:access() |access |Executed for every request from a client and before it is being proxied to the upstream service.| |:response() |access |Replaces both header_filter() and body_filter(). Executed after the whole response has been received from the upstream service, but before sending any part of it to the client.| |:header_filter() |header_filter |Executed when all response headers bytes have been received from the upstream service.| |:body_filter() |body_filter |Executed for each chunk of the response body received from the upstream service. Since the response is streamed back to the client, it can exceed the buffer size and be streamed chunk by chunk. hence this method can be called multiple times if the response is large. See the lua-nginx-module documentation for more details.| |:log() |log |Executed when the last response byte has been sent to the client.| ##### 10.2.1.1.1. Example ```lua local BasePlugin = require "kong.plugins.base_plugin" local HelloWorldHandler = BasePlugin:extend() function HelloWorldHandler:new() HelloWorldHandler.super.new(self, "helloworld") end function HelloWorldHandler:access(conf) HelloWorldHandler.super.access(self) if conf.say_hello then ngx.log(ngx.ERR, "============ Hello World! ============") ngx.header["Hello-World"] = "Hello World!!!" else ngx.log(ngx.ERR, "============ Bye World! ============") ngx.header["Hello-World"] = "Bye World!!!" end end return HelloWorldHandler ``` #### 10.2.1.2. schema.lua Simply the configuration file see in the portal. ```lua return { no_consumer = true, fields = { say_hello = { type = "boolean", default = true }, say_hello_body = { type = "boolean", default = true } } } ``` #### 10.2.1.3. *.rockspec ```rockspec package = "kong-plugin-helloworld" -- hint: rename, must match the info in the filename of this rockspec! -- as a convention; stick to the prefix: `kong-plugin-` version = "0.1.0-1" -- hint: renumber, must match the info in the filename of this rockspec! -- The version '0.1.0' is the source code version, the trailing '1' is the version of this rockspec. -- whenever the source version changes, the rockspec should be reset to 1. The rockspec version is only -- updated (incremented) when this file changes, but the source remains the same. -- TODO: This is the name to set in the Kong configuration `plugins` setting. -- Here we extract it from the package name. local pluginName = package:match("^kong%-plugin%-(.+)$") -- "myPlugin" supported_platforms = {"linux", "macosx"} source = { url = "https://github.com/grongierisc/iam-training", branch = "master", -- tag = "0.1.0" -- hint: "tag" could be used to match tag in the repository } description = { summary = "This a demo helloworld for Kong plugin", homepage = "https://github.com/grongierisc/iam-training", license = "Apache 2.0" } dependencies = { "lua >= 5.1" -- other dependencies should appear here } build = { type = "builtin", modules = { ["kong.plugins."..pluginName..".handler"] = "kong/plugins/"..pluginName.."/handler.lua", ["kong.plugins."..pluginName..".schema"] = "kong/plugins/"..pluginName.."/schema.lua", } } ``` ### 10.2.2. Build it We will be doing the same as here : [11.1.1. Build a new Kong/IAM docker image with the community plugin](#1111-build-a-new-kongiam-docker-image-with-the-community-plugin) But adapted to our plugin : Dockerfile : ```dockerfile FROM intersystems/iam:1.5.0.9-4 USER root COPY ./plugins /custom/plugins RUN cd /custom/plugins/kong-plugin-jwt-crafter && luarocks make RUN cd /custom/plugins/kong-plugin-helloworld && luarocks make #USER kong #Stay with root use, we will see why later ``` Enable the plugin in the environment variables ```yml KONG_PLUGINS: 'bundled,jwt-crafter,helloworld' ``` Now build our new iam image : docker-compose build iam Then docker-compose up and test it. ### 10.2.3. Tips To run the IAM container in "debug mode", to easily stop/restart it, modify the dockerfile to add/remove plugin and so on. You can stop iam service : docker-compose stop iam And start it in run mode with a shell : docker-compose run -p 8000:8000 -p 8001:8001 -p 8002:8002 iam sh In the container : ./docker-entrypoint.sh kong Happy coding :) # 11. CI/CD ![alt](https://raw.githubusercontent.com/grongierisc/iam-training/training/misc/img/cd_ci_intro.png) We are close to the end of this article. To finish let's talk about DevOps/CI/CD. The aim of this chapter is to give you some ideas about how to implement/script ci/cd for IAM/Kong. As Kong is API first, the idea is to script all the rest calls and play then on each environment. The easiest way to script rest calls is with postman and his best friend newman (command line version of postman). ## 11.1. Create the postman collection One thing handy with postman is its ability to run script before and after a rest call. We will use this functionality in most cases. ### 11.1.1. Is IAM startup ? Our first script will check if IAM is up and running. ![alt](https://raw.githubusercontent.com/grongierisc/iam-training/training/misc/img/postman_startup.png) ```javascript var iam_url = pm.environment.get("iam_url"); var iam_config_port = pm.environment.get("iam_config_port"); var url = "http://" + iam_url + ":" + iam_config_port + "/"; SenReq(20); async function SenReq(maxRequest) { var next_request = "end request"; const result = await SendRequest(maxRequest); console.log("result:",result); if(result == -1) { console.error("IAM starting .... failed !!!!"); } } function SendRequest(maxRequest) { return new Promise(resolve => { pm.sendRequest(url, function (err) { if (err) { if (maxRequest > 1) { setTimeout(function () {}, 5000); console.warn("IAM not started...retry..next retry in 5 sec"); SendRequest(maxRequest - 1); } else { console.error("IAM starting .... failed"); resolve(-1); } } else { console.log("IAM starting .... ok"); resolve(1); } } ); }); } ``` ### 11.1.2. Delete old datas ```javascript var iam_url=pm.environment.get("iam_url"); var iam_config_port=pm.environment.get("iam_config_port"); pm.sendRequest("http://"+iam_url+":"+iam_config_port+"/plugins", function (err, res) { if (err) { console.log("ERROR : ",err); } else { var body_json=res.json(); if(body_json.data) { for( i=0; i < body_json.data.length; i++) { // Example with a full fledged SDK Request route_id = body_json.data[i].id; const delete_route = { url: "http://"+iam_url+":"+iam_config_port+"/plugins/" + route_id, method: 'DELETE', }; pm.sendRequest(delete_route, function(err, res){ console.log(err ? err : res); }); } } } }); ``` Do the same for routes, services and consumers. This order is important beause you can't remove services with routes. ### 11.1.3. Create Service/Route Routes are dependent from services. For this type of cases we can use Test function of postman to retrieve data : ![alt](https://raw.githubusercontent.com/grongierisc/iam-training/training/misc/img/postman_form_data.png) Screen Script var id = pm.response.json().id; var name = pm.response.json().name; pm.globals.set("service_crud_id", id); pm.globals.set("service_crud_name", name); Here we save from the response the id and name of the new services. Then we can use it in the next route creation : Screen Script service_crud_name = pm.globals.get("service_crud_name"); Here we retrieve the global variable "service_crud_name". Then, use it in the actual call. Screen Script { "paths": [ "/persons/*" ], "protocols": [ "http" ], "name": "crud-persons", "strip_path": false, "service": { "name": "{{service_crud_name}}" } } #### 11.1.3.1. Tips * paylaod can be either json or form-data * form-data : ![alt](https://raw.githubusercontent.com/grongierisc/iam-training/training/misc/img/postman_form_data.png) * json : ![alt](https://raw.githubusercontent.com/grongierisc/iam-training/training/misc/img/postman_json.png) Easy way to get the json format, go to the manager portal, view, copy json : ![alt](https://raw.githubusercontent.com/grongierisc/iam-training/training/misc/img/postman_get_json.png) ## 11.2. Run it with newman docker run --rm -v "`pwd`/ci/":"/etc/newman" \ --network="iam-training_default" \ -t postman/newman run "DevOps_IAM.postman_collection.json" \ --environment="DevOps_IAM.postman_environment.json"
Announcement
Evgeny Shvarov · Mar 26, 2021

Bonus Points for InterSystems Developer Tools Contest

Hi Developers! Here're the technology bonuses for the InterSystems Developer Tools Contest that will give you extra points in the voting. Docker container usage - 2 points The application gets a 'Docker container' bonus if it uses InterSystems IRIS running in a docker container. Here is the simplest template to start from. ZPM Package deployment - 2 points You can collect the bonus if you build and publish the ZPM(ObjectScript Package Manager) package for your Full-Stack application so it could be deployed with: zpm "install your-multi-model-solution" command on IRIS with ZPM client installed. ZPM client. Documentation. Unit Testing - 2 points Applications that have Unit Testing for the InterSystems IRIS code will collect the bonus. Learn more about ObjectScript Unit Testing in Documentation and on Developer Community. Online Demo of your project - 3 pointsCollect 3 more bonus points if you provision your project to the cloud as an online demo. You can use this template or any other deployment option. Example. Learn more on deployment in Monday's Kick-Off webinar. Code quality analysis with zero bugs - 2 points Include the code quality Github action for code static control and make it show 0 bugs for ObjectScript. Learn more in Monday's Kick-Off webinar. Article on Developer Community - 2 points Post an article on Developer Community that describes features of your project. Collect 2 points for each article. Translations to different languages work too. Video on YouTube - 3 points Make the Youtube video that demonstrates your product in action and collect 3 bonus points per each. Example. The list of bonuses is subject to change. Stay tuned!
Article
Bob Binstock · Apr 26, 2021

Scaling Cloud Hosts and Reconfiguring InterSystems IRIS

Like hardware hosts, virtual hosts in public and private clouds can develop resource bottlenecks as workloads increase. If you are using and managing InterSystems IRIS instances deployed in public or private clouds, you may have encountered a situation in which addressing performance or other issues requires increasing the capacity of an instance's host (that is, vertically scaling). One common reason to scale is insufficient memory. As described in Memory Management and Scaling for InterSystems IRIS in the Scalability Guide, providing enough memory for all of the entities running on the host of an InterSystems IRIS instance under all normal operating circumstances is a critical factor in both performance and availability. In one common scenario, as the workload of an InterSystems IRIS instance increases, its working set becomes too large to be held by the instance's allocated database cache. This forces some queries to fall back to disk, greatly increasing the number of disk reads required and creating a major performance problem. Increasing the size of the cache solves that problem, but if doing so would leave insufficient memory remaining for other purposes, you also need to increase the total physical memory on the host to avoid pushing the bottleneck to another part of the system. Fortunately, scaling a virtual host is typically a lot easier than scaling hardware. This post discusses the two stages of the process: Scaling the cloud host's resources You can change the resource specification of a virtual host on AWS, GCP, and Azure, using the platform's command line, API, or portal. VMWare vSphere allows you to easily change a number of resource parameters for a VM through its vSphere Client interface.. Reconfiguring InterSystems IRIS to take advantage of the scaled resources There are a number of ways to reconfigure InterSystems IRIS to take advantage of scaled host resources. This document describes the use of the configuration merge feature, which merges new parameter values, specified in a merge file, into an instance's CPF. Configuration merge is an easy and effective method because it lets you address only the configuration settings you want to modify, make multiple changes to an instance's configuration in one operation, and easily make the same set of changes to multiple instances. The procedures described here are manual, but in production they would very likely be automated, for example using a script that would apply a specific merge file in an accessible location to a list of instances. Scaling Cloud Host Resources Public cloud platforms provide a range of resource templates to choose from that specify CPU, memory, network interfaces, and other resources for virtual hosts (storage is provisioned and sized separately). To resize a host, you change the template selected when the host was provisioned to one that specifies more of the resources you want to increase. On Amazon Web Services, the resource template is called an instance type; for example, the t3.large instance type specifies 2 CPUs and 8 GB of memory. On Google Cloud Platform it's a machine type, such as the e2-standard-2 (which also includes 2 CPUs and 8 GB), and on Microsoft Azure it's a size (the Standard_B2ms calls for the same 2 CPUs and 8 GB). By redefining the instance type, machine type, or size of an existing public cloud host, you can scale its resource specifications. In a VMware vSphere private cloud, you can use the vSphere Client interface to the vCenter Server management console to directly modify one or more individual resource settings of an existing virtual machine. (You can also simultaneously scale groups of hosts on each platform.) The following sections provide brief examples of resizing individual virtual hosts on the various platforms, with links to the documentation for all available methods. Please note that these methods (APIs, command line interfaces, and portal interfaces) are offered and maintained by the cloud vendors, and the examples included here are for informational purposes, to illustrate how easily you can adapt InterSystems IRIS to take advantage of increased resources AWS To modify the instance type of an AWS host (called an instance, not to be confused with an InterSystems IRIS instance), you can use the modify-instance-attribute CLI command, as shown in the following example: $ aws ec2 describe-instances --instance-ids i-01519f663af48a55e { "Instances": [ { "AmiLaunchIndex": 0, "ImageId": "ami-0abcdef1234567890, "InstanceId": "i-1234567890abcdef0, "InstanceType": "m5n.large", ... $ aws ec2 stop-instances --instance-ids i-01519f663af48a55e { "StoppingInstances": [ { "InstanceId": "i-1234567890abcdef0", ... $ aws ec2 describe-instances --instance-ids i-01519f663af48a55e { "Instances": [ { ... "State": { "Code": 80, "Name": "stopped" } ... $ aws ec2 modify-instance-attribute --instance-ids i-01519f663af48a55e \ --instance-type "{\"Value\": \"m5n.xlarge\"}" $ aws ec2 start-instances --instance-ids i-01519f663af48a55e { "StartingInstances": [ { "InstanceId": "i-1234567890abcdef0", "CurrentState": { "Code": 0, "Name": "pending" }, "PreviousState": { "Code": 80, "Name": "stopped" ... $ aws ec2 describe-instances --instance-ids i-01519f663af48a55e { "Instances": [ { "AmiLaunchIndex": 0, "ImageId": "ami-0abcdef1234567890, "InstanceId": "i-1234567890abcdef0, "InstanceType": "m5n.xlarge", ... You can also make this change using the ModifyInstanceAttribute AWS API call or the AWS EC2 console. GCP To modify the machine type of a GCP host (also known as an instance), you can use the gcloud CLI to stop, modify, and restart the instance. For example, you would use the following commands to change the machine type of an instance named scalingTest to n1-highmem-96: $ gcloud compute instances stop scalingTest $ gcloud compute instances set-machine-type scalingTest --machine-type n1-highmem-32 $ gcloud compute instances start scalingTest You can also make this change using the Google Cloud Console or GCP API. Azure When you use the Azure CLI to modify the size of a Linux VM , you can view a list of the sizes available on the hardware cluster where the VM is hosted using the list-vm-resize-options command, for example: az vm list-vm-resize-options --resource-group testingGroup --name scalingTest --output table You can then use the resize command to change the VM's size to one of the listed options, as shown. This command restarts the VM automatically. az vm resize --resource-group testingGroup --name scalingTest --size Standard_E32d_v4 If the size you want to change the VM to is not available, you can deallocate the VM, which can then be resized to any size supported by the region and restarted; the commands involved are illustrated below: az vm deallocate --resource-group testingGroup --name scalingTest az vm resize --resource-group testingGroup --name scalingTest --size Standard_M128s az vm start --resource-group testingGroup --name scalingTest You can resize a Windows VM on Azure using either the Azure portal or Powershell commands. vSphere To resize a VMware vSphere VM, do the following: Open vSphere Client or Web Client and display the VM inventory. Right-click the VM you want to modify and select Edit Settings. On the Virtual Hardware tab, Expand Memory and change the amount of RAM configured for the VM. Expand CPU and change the number of cores and optionally the number of cores per socket. Make any other desired changes to the hardware resources allocated to the VM. Reconfiguring InterSystems IRIS for Scaled Resources Once you have scaled the host, the next step is to reconfigure InterSystems IRIS to take advantage of the increased resources by changing one or more parameters in the instance's configuration parameter file (CPF). For example, to continue with the common scenario mentioned at the start of this post, now that you have increased the host's memory resources, you will want to take advantage of this by increasing the size of the InterSystems IRIS instance's database cache (which is done by changing the value of the globals parameter) so it can keep more data in memory. An easy way to make such a change, and far the easiest and most repeatable way to make multiple changes to an instance's configuration in one operation or make the same changes to multiple instances, is to use the configuration merge feature, which is available on UNIX® and Linux systems. As described in Using Configuration Merge to Deploy Customized InterSystems IRIS Instances in Running InterSystems Products in Containers and Using the Configuration Merge Feature in the Configuration Parameter File Reference, configuration merge lets you specify a merge file containing the settings you want merged into an instance's CPF immediately prior to a restart. (In release 2021.1 you'll be able to do this on a running instance without restarting it.) Not only is this more convenient than editing an instance's CPF directly, but it is highly repeatable across multiple instances, and supports reliable change management by enabling you to keep an accurate record of changes simply by versioning the configuration merge files you apply. To execute a configuration merge, you need to do the following: Create the merge file with the parameters you want to modify. Stage the merge file in a location accessible to the instance. If the instance you are modifying is in a container (which is likely on a cloud host), you can stage the file in the instance's durable %SYS directory (see Durable %SYS for Persistent Instance Data in Running InterSystems Products in Containers). Specify the merge file's location using the ISC_CPF_MERGE_FILE environment variable before restarting the instance. For example, continuing with the case of the database cache that needs updating, suppose you wanted a containerized instance's database cache size increased to 100 GB. The setting for this, in the [config] section of the CPF, would be globals=102400, which sets the database cache for 8-kilobyte blocks to 102,400 MB, or 100 GB. (As explained in the globals description in the Configuration Parameter File Reference, the parameter sets the size of the cache for multiple block sizes; if only one value is provided, however, it is applied to the 8-kilobyte block size, and 0 [zero] is assumed for the other sizes; globals=102400 is therefore the equivalent of globals=0,0,102400,0,0,0.) To make this change, you might do the following on the cloud host: 1. Create a configuration merge file, called for example mergefile2021.06.30.cpf, containing these lines: [config] globals=102400 2. Stage the merge file in the durable %SYS directory on the host's file system, which if you mounted the external volume /data as /external in the container and used the ISC_DATA_DIRECTORY variable to specify /external/iris_durable as the durable %SYS directory for the instance, would be /data/iris_durable. 3. Use the docker exec command on the host's command line to specify the variable and restart the instance with the iris command; if the instance's container is named iris and the instance is named IRIS, for example, the command would look like this: docker exec iris ISC_CPF_MERGE_FILE=/data/iris_durable/mergefile2021.06.30.cpf iris stop IRIS restart 4. When the instance is restarted, you can confirm the new globals setting with this command: docker exec iris grep globals /data/iris_durable/iris.cpf 💡 This article is considered as InterSystems Data Platform Best Practice.
Announcement
Anastasia Dyubaylo · Jul 19, 2021

InterSystems AI Programming Contest: Voting Time!

Hey Developers, This week is a voting week for the InterSystems AI programming contest! So, it's time to give your vote to the best solutions built with InterSystems IRIS. 🔥 You decide: VOTING IS HERE 🔥 How to vote? Details below. Experts nomination: This time, InterSystems experienced jury will choose the best apps to nominate the prizes in the Experts Nomination. Please welcome our experts: ⭐️ @Evgeny.Shvarov, Developer Ecosystem Manager⭐️ @Raj.Singh5479, Product Manager - Developer Experience⭐️ @Robert.Kuszewski, Product Manager - Developer Experience⭐️ @Thomas.Dyar, Product Specialist - Machine Learning⭐️ @Aleksandar.Kovacevic, Sales Engineer⭐️ @Eduard.Lebedyuk, Sales Engineer⭐️ @Sergey.Lukyanchikov, Sales Engineer⭐️ @Guillaume.Rongier7183, Sales Engineer Community nomination: For each user, a higher score is selected from two categories below: Conditions Place 1st 2nd 3rd If you have an article posted on DC and an app uploaded to Open Exchange (OEX) 9 6 3 If you have at least 1 article posted on DC or 1 app uploaded to OEX 6 4 2 If you make any valid contribution to DC (posted a comment/question, etc.) 3 2 1 Level Place 1st 2nd 3rd VIP Global Masters level or ISC Product Managers 15 10 5 Ambassador GM level 12 8 4 Expert GM level or DC Moderators 9 6 3 Specialist GM level 6 4 2 Advocate GM level or ISC Employees 3 2 1 Blind vote again! The number of votes for each app will be hidden from everyone. Once a day we will publish the leaderboard in the comments to this post. The order of projects on the Contest Page will be as follows: the earlier an application was submitted to the competition, the higher it will be in the list. P.S. Don't forget to subscribe to this post (click on the bell icon) to be notified of new comments. To take part in the voting, you need: Sign in to Open Exchange – DC credentials will work. Make any valid contribution to the Developer Community – answer or ask questions, write an article, contribute applications on Open Exchange – and you'll be able to vote. Check this post on the options to make helpful contributions to the Developer Community. If you changed your mind, cancel the choice and give your vote to another application! Support the application you like! Note: contest participants are allowed to fix the bugs and make improvements to their applications during the voting week, so don't miss and subscribe to application releases! OK! After the first day of the voting we have: Top 3 nominees by voting: cryptocurrency-rate-forecasting iris_satellite_plantation fhir-integratedml-example ➡️ Vote here! Experts, we are waiting for your votes! 🔥 Participants, improve & promote your solutions! Hey Developers! The second day of voting is over! Don't forget to vote for the best application! Good luck to the participants! Hi Devs! The voting continues! Who is gonna be the winner? Developers! The top nominees are changing! Who is now in the first place?🙀 Developers! The voting period is almost over! And here are the results at the moment: fhir-integratedml-example cryptocurrency-rate-forecasting ESKLP
Announcement
Anastasia Dyubaylo · Jul 26, 2021

Online Meetup with the InterSystems AI Contest Winners

Hi Community, We're pleased to invite you to the online meetup with the winners of the InterSystems AI contest! Date & Time: Friday, July 30, 2021 – 11:00 AM EDT What awaits you at this Virtual Meetup? Our winners' bios. Short demos on their applications. An open discussion about technologies being used. Q&A. Plans for the next contests. Our speakers: @José.Pereira, Business Intelligence Developer at Shift Consultoria e Sistemas Ltda @Henrique.GonçalvesDias, System Management Specialist / Database Administrator, Sao Paulo Federal Court @Oleh.Dontsov, Full Stack Developer, Tax Sketches SRO @Aleksandr.Kalinin6636, Programm Engineer at LLC "Escape" @Evgeny.Shvarov, InterSystems Developer Ecosystem Manager @Thomas.Dyar, InterSystems Product Specialist - Machine Learning @Raj.Singh5479, InterSystems Product Manager - Developer Experience @Robert.Kuszewski, InterSystems Product Manager - Developer Experience You will also have the opportunity to ask any questions to our developers in a special webinar chat. We will be happy to talk to you at our Virtual Meetup! ➡️ REGISTER TODAY! TODAY! Join our meetup at https://mologaconsulting.my.webex.com/meet/Community See you! Hey Developers, Watch the recording of the meetup on InterSystems Developers YouTube: ⏯ Online Meetup with the InterSystems AI Contest Winners Big applause to all the speakers! 👏🏼
Article
Lucas Enard · Sep 11, 2022

Sustainable Machine Learning for the InterSystems Interoperability Contest

Hello everyone, I’m a French student that just arrived in Prague for an academical exchange for my fifth year of engineering school and here is my participation in the interop contest. I hadn’t much time to code since I was moving from France to Prague and I’m participating alone, so I decided to make a project that’s more like a template rather than an application. I wanted to participate since my field (Data Science and AI) is not often linked with sustainability and the contest was a way for me to express myself in this important subject that is sustainability and Environnement. As you know, Artificial Intelligence is becoming more and more popular, with many well-known firms trying to follow the movement and sell tools to easily create machine learning models, train them and use them. All of this is practical and easy, but it comes with a cost, a financial one but also an Environnmental one. Training huge models again and again can take a lot of resources and produce a lot of CO2s. Supercomputers are running for days, and days and the size of the models are just exponentially increasing taking more space than ever. All these effort for some performance improvement that are not even sure to be find in some cases. Of course, this process is needed by many firms where even 0.1% of improvement in the accuracy of a model could save thousands of lives. That's why this template is designed for more common uses. However, as I had the opportunity to work with NLP (Natural Language Process) or Image Classification, I realized that some tools and models are already almost usable as is and can help us save hundreds of hours of training and therefore a lot of CO2s production and electricity consumption. That’s why I decided to create a template using InterSystems technologies to create an interoperable solution that answer some sustainability issues by allowing you to easily, in a few clicks, download pre-trained models from the internet ONCE, use them as much as you want for your own need, and of course, fine-tune these pre-trained models with new content available on an IRIS database and add content to the existing model. That way, in the template, we are taking an NLP model, trying it, then training it on data to create five new labels in the model to grade internet review. Therefore, by doing this, we end up with (If you have time and some compute power) a great model that can be used to predict the grade of internet review, that for free and for a small amount of CO2 produced. See [the GitHub](https://github.com/LucasEnard/Contest-Sustainability) and the Open Exchange post linked to this article. Or see the ReadMe here : # 1. Contest-Sustainability This is an template using InterSystems technologies to create an interoperable solution that answer some sustainability issues by allowing you to easily, in a few clicks, download pre-trained models from the internet ONCE, use them as much as you want for your own need, and of course, fine-tune these pre-trained models with new content available on an IRIS database and add content to the existing model. In this example we are taking an NLP model, trying it, then training it on data to create five new labels in the model to grade internet review. Saving in the process a lot of resources and CO2 emission. Here are some models as example you can try : https://huggingface.co/gpt2 https://huggingface.co/Jean-Baptiste/camembert-ner https://huggingface.co/bert-base-uncased https://huggingface.co/facebook/detr-resnet-50 https://huggingface.co/facebook/detr-resnet-50-panoptic TABLE OF CONTENT : - [1. Contest-Sustainability](#1-contest-sustainability) - [2. Installation](#2-installation) - [2.1. Starting the Production](#21-starting-the-production) - [2.2. Access the Production](#22-access-the-production) - [2.3. Closing the Production](#23-closing-the-production) - [3. How it works](#3-how-it-works) - [4. HuggingFace API](#4-huggingface-api) - [5. Use any model from the web](#5-use-any-model-from-the-web) - [5.1. FIRST CASE : YOU HAVE YOUR OWN MODEL](#51-first-case--you-have-your-own-model) - [5.2. SECOND CASE : YOU WANT TO DOWNLOAD A MODEL FROM HUGGINGFACE](#52-second-case--you-want-to-download-a-model-from-huggingface) - [5.2.1. Settings](#521-settings) - [5.2.2. Testing](#522-testing) - [6. Fine-tune the models](#6-fine-tune-the-models) - [6.1. Tunning the model](#61-tunning-the-model) - [6.1.1. Download the model](#611-download-the-model) - [6.1.2. Settings](#612-settings) - [6.1.3. Train the model](#613-train-the-model) - [6.1.4. Replace the model](#614-replace-the-model) - [6.2. Use the model](#62-use-the-model) - [6.2.1. Settings](#621-settings) - [6.2.2. Test the model](#622-test-the-model) - [7. Important note](#7-important-note) - [8. TroubleShooting](#8-troubleshooting) - [9. Conclusion](#9-conclusion) # 2. Installation ## 2.1. Starting the Production While in the contest-sustainability folder, open a terminal and enter : ``` docker-compose up ``` The very first time, it may take a few minutes to build the image correctly and install all the needed modules for Python. ## 2.2. Access the Production Following this link, access the production : [Access the Production](http://localhost:52795/csp/irisapp/EnsPortal.ProductionConfig.zen?RODUCTION=INFORMATION.QuickFixProduction) ## 2.3. Closing the Production ``` docker-compose down ``` # 3. How it works For now, some models may not work with this implementation since everything is automatically done, which means, no matter what model you input, we will try to make it work through `transformers` `pipeline` library. Pipeline is a powerful tool by the HuggingFace team that will scan the folder in which we downloaded the model, then understand what library it should use between PyTorch, Keras, Tensorflow or JAX to then load that model using `AutoModel`. From here, by inputting the task, the pipeline knows what to do with the model, tokenizer or even feature-extractor in this folder, and manage your input automatically, tokenize it, process it, pass it into the model, then give back the output in a decoded form usable directly by us. # 4. HuggingFace API Some people or some systems can not download models or use them due to some restriction, that's why it is possible to use the HuggingFace API and call some models directly throught this service. It is made easier for you here : You must first start the demo, using the green `Start` button or `Stop` and `Start` it again to apply your config changes. Then, by clicking on the operation `Python.HFOperation` of your choice, and selecting in the right tab `action`, you can `test` the demo. In this `test` window, select : Type of request : `Grongier.PEX.Message` For the `classname` you must enter : ``` msg.HFRequest ``` And for the `json`, here is an example of a call to GPT2 : ``` { "api_url":"https://api-inference.huggingface.co/models/gpt2", "payload":"Can you please let us know more details about your ", "api_key":"----------------------" } ``` Now you can click on `Visual Trace` to see in details what happened and see the logs. **NOTE** that you must have an API key from HuggingFace before using this Operation ( the api-keys are free, you just need to register to HF ) **NOTE** that you can change the url to try any other models from HuggingFace, you may need to change the payload. See as example: ![sending hf req](https://user-images.githubusercontent.com/77791586/182403526-0f6e97a0-2019-4d86-b1ae-38c56dfc8746.png) ![hf req](https://user-images.githubusercontent.com/77791586/182404662-b37b9489-c12c-47f8-98bd-18008c9a615e.jpg) ![hf resp](https://user-images.githubusercontent.com/77791586/182403515-7c6c2075-bdb6-46cd-9258-ac251844d591.png) # 5. Use any model from the web In the section we will teach you how to use almost any pre-trained model from the internet, HuggingFace or not in order to save some resource or simply use these model inside IRIS. ## 5.1. FIRST CASE : YOU HAVE YOUR OWN MODEL In this case, you must copy paste your model, with the config, the tokenizer.json etc inside a folder inside the model folder. Path : `src/model/yourmodelname/` From here you must create a new operation, call it as you wish, go to the parameters of this operation. Then go to `settings` in the right tab, then in the `Python` part, then in the `%settings` part. Here, you can enter or modify any parameters ( don't forget to press `apply` once your are done ). Here's the default configuration for this case : %settings ``` name=yourmodelname task=text-generation ``` **NOTE** that any settings that are not `name` or `model_url` will go into the PIPELINE settings. Now you can double-click on the operation and `start` it. You must see in the `Log` part the starting of your model. From here, we create a `PIPELINE` using transformers that uses your config file find in the folder as seen before. To call that pipeline, click on the operation, and select in the right tab `action`, you can `test` the demo. In this `test` window, select : Type of request : `Grongier.PEX.Message` For the `classname` you must enter : ``` msg.MLRequest ``` And for the `json`, you must enter every arguments needed by your model. Here is an example of a call to GPT2 : ``` { "text_inputs":"Unfortunately, the outcome", "max_length":100, "num_return_sequences":3 } ``` Click `Invoke Testing Service` and wait for the model to operate. See for example: ![sending ml req](https://user-images.githubusercontent.com/77791586/182402707-13ca90d0-ad5a-4934-8923-a58fe821e00e.png) Now you can click on `Visual Trace` to see in details what happened and see the logs. See for example : ![ml req](https://user-images.githubusercontent.com/77791586/182402878-e34b64de-351c-49c3-affe-023cd885e04b.png) ![ml resp](https://user-images.githubusercontent.com/77791586/182402932-4afd14fe-5f57-4b03-b0a6-1c6b74474015.png) ## 5.2. SECOND CASE : YOU WANT TO DOWNLOAD A MODEL FROM HUGGINGFACE In this case, you must find the URL of the model on HuggingFace. Find a model that does what you are looking for and use it without spending resources and using the InterSystems technologies. ### 5.2.1. Settings From here you must go to the parameters of the `Hugging`. Click on the `HuggingFace` operation of your choice then go to `settings` in the right tab, then in the `Python` part, then in the `%settings` part. Here, you can enter or modify any parameters ( don't forget to press `apply` once your are done ). Here's some example configuration for some models we found on HuggingFace : %settings for gpt2 ``` model_url=https://huggingface.co/gpt2 name=gpt2 task=text-generation ``` %settings for camembert-ner ``` name=camembert-ner model_url=https://huggingface.co/Jean-Baptiste/camembert-ner task=ner aggregation_strategy=simple ``` %settings for bert-base-uncased ``` name=bert-base-uncased model_url=https://huggingface.co/bert-base-uncased task=fill-mask ``` %settings for detr-resnet-50 ``` name=detr-resnet-50 model_url=https://huggingface.co/facebook/detr-resnet-50 task=object-detection ``` %settings for detr-resnet-50-protnic ``` name=detr-resnet-50-panoptic model_url=https://huggingface.co/facebook/detr-resnet-50-panoptic task=image-segmentation ``` **NOTE** that any settings that are not `name` or `model_url` will go into the PIPELINE settings, so in our second example, the camembert-ner pipeline requirers an `aggregation_strategy` and a `task` that are specified here while the gpt2 requirers only a `task`. See as example: ![settings ml ope2](https://user-images.githubusercontent.com/77791586/182403258-c24efb77-2696-4462-ae71-9184667ac9e4.png) Now you can double-click on the operation and `start` it. **You must see in the `Log` part the starting of your model and the downloading.** **NOTE** You can refresh those logs every x seconds to see the advancement with the downloads. ![dl in real time](https://user-images.githubusercontent.com/77791586/182403064-856724b5-876e-460e-a2b4-34eb63f44673.png) From here, we create a `PIPELINE` using transformers that uses your config file find in the folder as seen before. ### 5.2.2. Testing To call that pipeline, click on the operation , and select in the right tab `action`, you can `test` the demo. In this `test` window, select : Type of request : `Grongier.PEX.Message` For the `classname` you must enter : ``` msg.MLRequest ``` And for the `json`, you must enter every arguments needed by your model. Here is an example of a call to GPT2 : ``` { "text_inputs":"George Washington lived", "max_length":30, "num_return_sequences":3 } ``` Here is an example of a call to Camembert-ner : ``` { "inputs":"George Washington lived in washington" } ``` Here is an example of a call to bert-base-uncased : ``` { "inputs":"George Washington lived in [MASK]." } ``` Here is an example of a call to detr-resnet-50 using an online url : ``` { "url":"http://images.cocodataset.org/val2017/000000039769.jpg" } ``` Here is an example of a call to detr-resnet-50-panoptic using the url as a path: ``` { "url":"/irisdev/app/misc/000000039769.jpg" } ``` Click `Invoke Testing Service` and wait for the model to operate. Now you can click on `Visual Trace` to see in details what happened and see the logs. **NOTE** that once the model was downloaded once, the production won't download it again but get the cached files found at `src/model/TheModelName/`. If some files are missing, the Production will download them again. See as example: ![sending ml req](https://user-images.githubusercontent.com/77791586/182402707-13ca90d0-ad5a-4934-8923-a58fe821e00e.png) ![ml req](https://user-images.githubusercontent.com/77791586/182402878-e34b64de-351c-49c3-affe-023cd885e04b.png) ![ml resp](https://user-images.githubusercontent.com/77791586/182402932-4afd14fe-5f57-4b03-b0a6-1c6b74474015.png) See as example: ![sending ml req](https://user-images.githubusercontent.com/77791586/183036076-f0cb9512-573b-4723-aa70-64f575c8f563.png) ![ml resp](https://user-images.githubusercontent.com/77791586/183036060-2a2328f7-535e-4046-9d2c-02d6fa666362.png) # 6. Fine-tune the models In this part we are trying to fine-tune a model in order to repurpose an existing model and make it even better without using too much resources. ## 6.1. Tunning the model ### 6.1.1. Download the model In order to use this GitHub you need to have a model from HuggingFace compatible with pipeline to use and train, and have a dataset you want to train your model on. In order to help, we let you the possibility to use the Python script in `src/utils/download_bert.py`. It will download for you the `"https://huggingface.co/bert-base-cased"` model and put inside the `src/model/bert-base-cased` folder if it's not already here. Moreover we also give you a DataSet to train the bert model on, this dataset was already loaded inside the IRIS DataBase and nothing else needs to be done if you want to use it. ( You can access it going to the SQL part of the portal and in the iris database namespace then to the review table ) To use the script, if you are inside the container, you can execute it without worry, if you are in local, you may need to `pip3 install requests` and `pip3 install beautifulsoup4` See the output : ![Download OutPut](https://user-images.githubusercontent.com/77791586/185119729-defa55d2-7d11-408e-b57e-2c00eb7823d8.png) ### 6.1.2. Settings If you want to use the bert-base-cased model, and you did downloaded it using the script, nothing needs to be added to the settings and you can advance to the [train the model part](#43-train-the-model). If you want to use your own model, click on the `Python.TuningOperation`, and select in the right tab `Settings`, then `Python` then in the `%settings` part, enter the path to the model, the name of the folder and the number of label you want it trained on. Example : ``` path=/irisdev/app/src/model/ model_name=bert-base-cased num_labels=5 ``` ### 6.1.3. Train the model To train the model you must go the `Production` following this link : ``` http://localhost:52795/csp/irisapp/EnsPortal.ProductionConfig.zen?PRODUCTION=iris.Production ``` And connect using : ```SuperUser``` as username and ```SYS``` as password. To call the training, click on the `Python.TuningOperation`, and select in the right tab `Actions`, you can `Test` the demo. In this test window, select : Type of request : Grongier.PEX.Message For the classname you must enter : ``` msg.TrainRequest ``` And for the json, you must enter every arguments needed by the trainer to train. Here is an example that train on the first 20 rows ( This isn't a proper training but it is fast ): ``` { "columns":"ReviewLabel,ReviewText", "table":"iris.Review", "limit":20, "p_of_train":0.8, "output_dir":"/irisdev/app/src/model/checkpoints", "evaluation_strategy":"steps", "learning_rate":0.01, "num_train_epochs":1 } ``` As for example : ![Train request](https://user-images.githubusercontent.com/77791586/185121527-696becaa-8b3e-4535-8156-1d40423e622b.png) As you can see, you must enter - `table` to use. - `columns` to use ( first is the `label`, second is the `input` to be tokenized ) - `limit` of rows to take in ( if you don't precise a number of rows, all the data will be used ) - `p_of_train` the percentage of training data to take from the dataset and `1 - p_of_train` the percentage of testing data to take from the dataset. After that, the other parameters are up to you and can be anything according to `https://huggingface.co/docs/transformers/main_classes/trainer` parameters. **NOTE** that the batch size for training and testing is automatically calculated if not input in the request. ( It's the biggest divider of the number of rows that's less than the square root of the number of row and less than 32 ) Click Invoke Testing Service and close the testing window without waiting. Now access the `Python.TuningOperation`, and select in the right tab `log` ; Here you can see the advancement of the training and evaluations. Once it is over, you will see a log saying that the new model was saved in a temporary folder. Now access the `Python.TuningOperation`, and select in the right tab `message` and select the last one by clicking on it's header. Here you can see the advancement of the training and evaluations and at the end you can have access to the Metrics of the old and the new model for you to compare. ### 6.1.4. Replace the model **If you want to keep the old model**, nothing must be done, the old one will stay on the non-temporary folder and is still loaded for further training. **If you want to keep the new model**, you must click on the `Python.TuningOperation`, and select in the right tab `Actions` and test. In this test window, select : Type of request : Grongier.PEX.Message For the classname you must enter : ``` msg.OverrideRequest ``` And for the json, empty brackets: ``` {} ``` Click Invoke Testing Service and see the response message. The new model was moved from the temporary folder to the non-temporary folder. ## 6.2. Use the model Training a model is interesting but you can also try it out. ### 6.2.1. Settings If you want to use the bert-base-cased model, and you did downloaded it using the script, nothing needs to be added to the settings and you can advance to the [test the model part](#52-test-the-model). If you want to use your own model, click on the `Python.TuningOperation`, and select in the right tab `Settings`, then `Python` then in the `%settings` part, enter the parameter to add to the pipeline. ### 6.2.2. Test the model To test the model you must go the `Production` following this link : ``` http://localhost:52795/csp/irisapp/EnsPortal.ProductionConfig.zen?PRODUCTION=iris.Production ``` And connect using : ```SuperUser``` as username and ```SYS``` as password. To call the testing, click on the `Python.MLOperation`, and select in the right tab `Actions`, you can `Test` the demo. In this test window, select : Type of request : Grongier.PEX.Message For the classname you must enter : ``` msg.MLRequest ``` And for the json, you must enter every arguments needed by the model to work ``` { "inputs":"This was a really bad experience" } ``` Press `Call test services` and then watch the result. # 7. Important note Fine-tuning models can take a LOT of time and resources, however it will always consume less resource than training a model from scratch. You can already see that it's taking a long time and a lot of computer power to fine-tune the model so imagine the cost if you had to train it from scratch and start over multiple times to get the right results. # 8. TroubleShooting If you have issues, reading is the first advice we can give you, most errors are easily understood just by reading the logs as almost all errors will be captured by a try / catch and logged. If you need to install a new module, or Python dependence, open a terminal inside the container and enter for example : "pip install new-module" To open a terminal there are many ways, - If you use the InterSystems plugins, you can click in the below bar in VSCode, the one looking like `docker:iris:52795[IRISAPP]` and select `Open Shell in Docker`. - In any local terminal enter : `docker-compose exec -it iris bash` - From Docker-Desktop find the IRIS container and click on `Open in terminal` Some models may require some changes for the pipeline or the settings for example, it is your task to add in the settings and in the request the right information. # 9. Conclusion From here you should be able to use some models that you may need on IRIS and fine-tune them as you wish. This template is supposed to be modified to suits your need and was created as a base for any AI and IRIS project that has sustainability and interoperability in mind. Due to the lack of time I was unable to add an API that was supposed to use a Director to directly communicate with the production and allow the users to make request to the models. However, if you are still interested in an IRIS API using this Python module you can check [my GitHub](https://github.com/LucasEnard/) or you can directly go to [my example of API in Python for IRIS](https://github.com/LucasEnard/iris-python-flask-api-template). Link to my DC profile : https://community.intersystems.com/user/lucas-enard-0 Best of luck for the contest ! Thank you so much!! Hi @Lucas.Enard2487! Thanks for such a great contribution to the community! I have a question: where can I obtain an API key for GPT2 to run the test: { "api_url":"https://api-inference.huggingface.co/models/gpt2", "payload":"Can you please let us know more details about your ", "api_key":"----------------------" } Hello, Thanks for trying my template. You need to register through the HuggingFace website and get an API key for free.
Announcement
Anastasia Dyubaylo · Sep 29, 2022

InterSystems Developer Ecosystem Summer News 2022

Hello and welcome to the Developer Ecosystem Summer News! This summer was full of exciting events and activities in the InterSystems Developer Ecosystem. In case you missed something, we've prepared for you a selection of the hottest news and topics to read! For your convenience, here we gathered everything worth noting that happened this last season at a glance. Read on! News 🎉 Incredible Milestones on Dev Community: 10K posts, 11K members, 5M reads 💡 InterSystems Ideas announced 🆕 Brand New "About Us" Page 🆕 DC rubric: "InterSystems Memes" (+part 1) 🆕 DC rubric: Key Questions ⛱ Inquisitive Summer on DC: ask questions - get double points ☕️ Dev Random Coffee on Global Masters: join our new networking activity 🔗 DC in French > < Global Masters API integration released Contests & Events 🔥 InterSystems Global Summit 2022 Main announcement Live from the Summit: part 1, part 2 InterSystems Developers at the Summit: how it was? Watch the Keynotes 🏆 InterSystems Full Stack Contest 2022 Rules & prizes Kick-off Webinar Winners announcement Meetup with winners 🏆 InterSystems Tech Article Contest: Python Edition Rules & prizes New contest bonuses introduced Winners announcements 🧩 Code of Golf challenge ⏯ Webinar organized by DC: Scaling InterSystems FHIR Server on Amazon Web Services with ECP 👩‍💻 Women's health - FemTech panel hosted by InterSystems 👋 InterSystems Developer Meetup on Python Interoperability in Boston Latest Releases ⬇️ InterSystems IRIS, IRIS for Health, & HealthShare Health Connect 2022.2 developer previews Preview 1 Preview 2 Preview 3 Preview 4 Preview 5 Preview 6 ⬇️ InterSystems IRIS, IRIS for Health, & HealthShare Health Connect 2022.1 ⬇️ InterSystems API Manager 2.8.1 🆕 Developer Community Release, July 2022 🆕 UX/UI change on Open Exchange 🆕 New Open Exchange feature: ObjectScript Quality status Best Practices & Key Questions 🔥 Best Practices of Summer 2022 DB Migration using SQLgateway SystemPerformance Utility (pka pButtons) API (and REST API) [and sample UI] Ensemble Orphaned Messages Formation on InterSystems' interoperability framework using ONLY Python Mastering the %SYSTEM.Encryption class Method to recompile classes and routines after a major upgrade ❓ Key Questions of Summer 2022: July, August People to Know About 👋 Meet new DC Moderator: Niangang Huang 🌟 Global Masters of Summer 2022: June, July, August Job Opportunities 💼 Sr. Application Development Analyst - The Ohio State University Wexner Medical Center 💼 InterSystems IRIS Developer - French Speaking - based in Paris - France 💼 Potential HealthShare opportunity 💼 Senior Software Developer - International Healthcare 💼 Looking for a Fully Remote InterSystems IRIS Developer So... Here is our take on the most interesting and important things! What were your highlights from this summer/winter? Share them in the comments section and let's remember the joy we've had! Proud of our Dev Ecosystem Team! Thanks, @Anastasia.Dyubaylo for sharing! My favorites are Random Coffee, New About page, and Key Questions! And of course the Memes! Who says that summer is a quiet time - not in the InterSystems Developer Community! hahaha))) indeed! Proud of our milestone achievements! Great numbers, aren't they? ;) For now: 🎉🎉🎉