diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..e2869829 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,14 @@ +# docker volume data folders +**/data + +# cli client +goinstant + +# vscode config +.vscode + +# Docs +docs + +# Dependency directories +node_modules/ diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 00000000..e5d5e5b9 --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1,21 @@ +{ + "arrowParens": "always", + "bracketSpacing": true, + "endOfLine": "lf", + "htmlWhitespaceSensitivity": "css", + "insertPragma": false, + "jsxBracketSameLine": false, + "jsxSingleQuote": false, + "printWidth": 80, + "proseWrap": "preserve", + "quoteProps": "as-needed", + "requirePragma": false, + "semi": false, + "singleQuote": true, + "tabWidth": 2, + "trailingComma": "none", + "useTabs": false, + "vueIndentScriptAndStyle": false, + "filepath": "/home/ryan/git/instant/instant.ts", + "parser": "typescript" +} diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..8c7e2914 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,27 @@ +FROM node:lts + +WORKDIR /instant + +# install curl +RUN apt-get update; apt-get install -y curl + +# install kubectl +RUN curl -LO https://storage.googleapis.com/kubernetes-release/release/`curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt`/bin/linux/amd64/kubectl +RUN chmod +x ./kubectl +RUN mv ./kubectl /usr/local/bin/kubectl + +# install docker engine +RUN curl -sSL https://get.docker.com/ | sh + +# install docker-compose binary +RUN curl -L "https://github.com/docker/compose/releases/download/1.25.5/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose +RUN chmod +x /usr/local/bin/docker-compose + +# install node deps +ADD package.json . +ADD yarn.lock . +RUN yarn + +ADD . . + +ENTRYPOINT [ "yarn", "instant" ] diff --git a/README.md b/README.md index 138904da..6ec9eeef 100644 --- a/README.md +++ b/README.md @@ -13,27 +13,33 @@ Navigate to the main folder to execute the commands. To set the Instant OpenHIE services run the following command: ```sh -./instant.sh init docker +yarn +yarn docker:build +yarn docker:instant init -t docker ``` To tear down the deployments use the opposing command: ```bash -./instant.sh down docker +yarn docker:instant down -t docker ``` To start up the services after a tear down, use the following command: ```bash -./instant.sh up docker +yarn docker:instant up -t docker ``` To completely remove all project components use the following option: ```bash -./instant.sh destroy docker +yarn docker:instant destroy -t docker ``` +Each command also takes a list of package IDs to operate on. If this is left out then all packages are run by default. + +E.g only run `core` package: `yarn docker:instant init -t docker core` + ## Kubernetes A kubernetes deployment can either be to AWS using [eksctl](https://docs.aws.amazon.com/eks/latest/userguide/getting-started-eksctl.html) and [kubectl](https://kubernetes.io/docs/tasks/tools/install-kubectl/) or locally using [minikube](https://kubernetes.io/docs/setup/learning-environment/minikube/) and `kubectl`. @@ -45,23 +51,29 @@ Navigate to the main folder to execute the commands. To set the Instant OpenHIE services run the following command: ```sh -./instant.sh init k8s +yarn +yarn docker:build +yarn docker:instant init -t k8s ``` -To tear down the deployments use the following command: +To tear down the deployments, use the following command: ```bash -./instant.sh down k8s +yarn docker:instant down -t k8s ``` To start up the services after a tear down, use the following command: ```bash -./instant.sh up k8s +yarn docker:instant up -t k8s ``` -To completely remove all project components use the following option: +To completely remove all project components, use the following option: ```bash -./instant.sh destroy k8s +yarn docker:instant destroy -t k8s ``` + +Each command also takes a list of package IDs to operate on. If this is left out then all packages are run by default. + +E.g only run `core` package: `yarn docker:instant init -t k8s core` diff --git a/client/docker/compose.sh b/client/docker/compose.sh index 6d8ca57f..a54c3a20 100644 --- a/client/docker/compose.sh +++ b/client/docker/compose.sh @@ -1,28 +1,28 @@ composeFilePath=$( cd "$(dirname "${BASH_SOURCE[0]}")" ; pwd -P ) if [ "$1" == "init" ]; then - docker-compose -f "$composeFilePath"/docker-compose.yml up -d fhir es + docker-compose -p instant -f "$composeFilePath"/docker-compose.yml up -d fhir es # Set up the openhim # "$composeFilePath"/initiateReplicaSet.sh # Wait sleep 100 - docker-compose -f "$composeFilePath"/docker-compose.yml up -d opencr + docker-compose -p instant -f "$composeFilePath"/docker-compose.yml up -d opencr elif [ "$1" == "up" ]; then - docker-compose -f "$composeFilePath"/docker-compose.yml up -d fhir es + docker-compose -p instant -f "$composeFilePath"/docker-compose.yml up -d fhir es # Wait sleep 20 - docker-compose -f "$composeFilePath"/docker-compose.yml up -d opencr + docker-compose -p instant -f "$composeFilePath"/docker-compose.yml up -d opencr elif [ "$1" == "down" ]; then - docker-compose -f "$composeFilePath"/docker-compose.yml stop + docker-compose -p instant -f "$composeFilePath"/docker-compose.yml stop elif [ "$1" == "destroy" ]; then - docker-compose -f "$composeFilePath"/docker-compose.yml down -v + docker-compose -p instant -f "$composeFilePath"/docker-compose.yml down -v else echo "Valid options are: init, up, down, or destroy" diff --git a/core/docker/compose.sh b/core/docker/compose.sh index f964b756..bbcc4f90 100755 --- a/core/docker/compose.sh +++ b/core/docker/compose.sh @@ -1,23 +1,23 @@ composeFilePath=$( cd "$(dirname "${BASH_SOURCE[0]}")" ; pwd -P ) if [ "$1" == "init" ]; then - docker-compose -f "$composeFilePath"/docker-compose-mongo.yml up -d + docker-compose -p instant -f "$composeFilePath"/docker-compose-mongo.yml up -d # Set up the replica set "$composeFilePath"/initiateReplicaSet.sh - docker-compose -f "$composeFilePath"/docker-compose.yml -f "$composeFilePath"/docker-compose.dev.yml -f "$composeFilePath"/importer/docker-compose.config.yml up -d + docker-compose -p instant -f "$composeFilePath"/docker-compose.yml -f "$composeFilePath"/docker-compose.dev.yml -f "$composeFilePath"/importer/docker-compose.config.yml up -d elif [ "$1" == "up" ]; then - docker-compose -f "$composeFilePath"/docker-compose-mongo.yml up -d + docker-compose -p instant -f "$composeFilePath"/docker-compose-mongo.yml up -d # Wait for mongo replica set to be set up sleep 20 - docker-compose -f "$composeFilePath"/docker-compose.yml up -d + docker-compose -p instant -f "$composeFilePath"/docker-compose.yml up -d elif [ "$1" == "down" ]; then - docker-compose -f "$composeFilePath"/docker-compose-mongo.yml -f "$composeFilePath"/docker-compose.yml -f "$composeFilePath"/docker-compose.dev.yml -f "$composeFilePath"/importer/docker-compose.config.yml stop + docker-compose -p instant -f "$composeFilePath"/docker-compose-mongo.yml -f "$composeFilePath"/docker-compose.yml -f "$composeFilePath"/docker-compose.dev.yml -f "$composeFilePath"/importer/docker-compose.config.yml stop elif [ "$1" == "destroy" ]; then - docker-compose -f "$composeFilePath"/docker-compose-mongo.yml -f "$composeFilePath"/docker-compose.yml -f "$composeFilePath"/docker-compose.dev.yml -f "$composeFilePath"/importer/docker-compose.config.yml down -v + docker-compose -p instant -f "$composeFilePath"/docker-compose-mongo.yml -f "$composeFilePath"/docker-compose.yml -f "$composeFilePath"/docker-compose.dev.yml -f "$composeFilePath"/importer/docker-compose.config.yml down -v else echo "Valid options are: init, up, down, or destroy" fi diff --git a/core/docker/docker-compose-mongo.yml b/core/docker/docker-compose-mongo.yml index baf35b03..4103c0eb 100644 --- a/core/docker/docker-compose-mongo.yml +++ b/core/docker/docker-compose-mongo.yml @@ -5,7 +5,7 @@ services: image: mongo:4.2 container_name: mongo-1 volumes: - - './data/mongo1:/data/db' + - 'openhim-mongo1:/data/db' command: - --replSet - mongo-set @@ -14,7 +14,7 @@ services: image: mongo:4.2 container_name: mongo-2 volumes: - - './data/mongo2:/data/db' + - 'openhim-mongo2:/data/db' command: - --replSet - mongo-set @@ -23,7 +23,12 @@ services: image: mongo:4.2 container_name: mongo-3 volumes: - - './data/mongo3:/data/db' + - 'openhim-mongo3:/data/db' command: - --replSet - mongo-set + +volumes: + openhim-mongo1: + openhim-mongo2: + openhim-mongo3: diff --git a/core/docker/docker-compose.dev.yml b/core/docker/docker-compose.dev.yml index 5661eec7..db068f96 100644 --- a/core/docker/docker-compose.dev.yml +++ b/core/docker/docker-compose.dev.yml @@ -2,10 +2,8 @@ version: '3.3' services: mongo-1: - image: mongo:4.2 container_name: mongo-1 - volumes: - - './data/mongo1:/data/db' + image: mongo:4.2 ports: - "27017:27017" diff --git a/core/docker/docker-compose.yml b/core/docker/docker-compose.yml index c2d4fb52..0d2a89e0 100644 --- a/core/docker/docker-compose.yml +++ b/core/docker/docker-compose.yml @@ -26,9 +26,11 @@ services: container_name: hapi-fhir image: hapiproject/hapi:v4.1.0 environment: - - JAVA_OPTS='-Dhapi.properties=/usr/local/tomcat/conf/hapi.properties' + - JAVA_OPTS='-Dhapi.properties=/instant/core/docker/hapi.properties' volumes: - - ./hapi.properties:/usr/local/tomcat/conf/hapi.properties + - type: volume + source: instant + target: /instant depends_on: - mysql @@ -42,4 +44,9 @@ services: MYSQL_PASSWORD: 'instant101' MYSQL_ROOT_PASSWORD: 'instant101' volumes: - - './data/mysql:/var/lib/mysql' + - 'hapi-mysql:/var/lib/mysql' + +volumes: + hapi-mysql: + instant: + external: true diff --git a/core/docker/importer/.dockerignore b/core/docker/importer/.dockerignore deleted file mode 100644 index eac3aa82..00000000 --- a/core/docker/importer/.dockerignore +++ /dev/null @@ -1,2 +0,0 @@ -Dockerfile -volume/ \ No newline at end of file diff --git a/core/docker/importer/docker-compose.config.yml b/core/docker/importer/docker-compose.config.yml index 5b3bc4b9..43871dc2 100644 --- a/core/docker/importer/docker-compose.config.yml +++ b/core/docker/importer/docker-compose.config.yml @@ -14,11 +14,15 @@ services: # Reject unauthorised is only needed if the OpenHIM's SSL is not setup NODE_TLS_REJECT_UNAUTHORIZED: 0 volumes: - - type: bind - source: ./importer/volume - target: /importer + - type: volume + source: instant + target: /instant # This command will only attempt to import the OpenHIM config when the heartbeat responds with a 2xx - command: sh -c "wait-on -t 60000 https-get://core:8080/heartbeat && node openhimConfig.js" + command: sh -c "wait-on -t 60000 https-get://core:8080/heartbeat && node /instant/core/docker/importer/volume/openhimConfig.js" # ensure all relevant services are running before executing the importer depends_on: - core + +volumes: + instant: + external: true diff --git a/core/gen-k8s-config.sh b/core/gen-k8s-config.sh deleted file mode 100755 index 158bfe31..00000000 --- a/core/gen-k8s-config.sh +++ /dev/null @@ -1 +0,0 @@ -kompose convert -f docker/docker-compose.config.yml -o kubernetes/importer diff --git a/core/instant.json b/core/instant.json new file mode 100644 index 00000000..13dfab9c --- /dev/null +++ b/core/instant.json @@ -0,0 +1,6 @@ +{ + "id": "core", + "name": "Core package", + "description": "", + "version": "" +} diff --git a/core/kubernetes/main/k8s.sh b/core/kubernetes/main/k8s.sh index 8a96cc7f..01259e55 100755 --- a/core/kubernetes/main/k8s.sh +++ b/core/kubernetes/main/k8s.sh @@ -68,7 +68,7 @@ cloud_setup () { } local_setup () { - minikubeIP=$(minikube ip) + minikubeIP=$(kubectl config view -o=jsonpath='{.clusters[?(@.name=="minikube")].cluster.server}' | awk '{ split($0,A,/:\/*/) ; print A[2] }') openhimCoreMediatorSSLPort=$(kubectl get service openhim-core-service -o=jsonpath={.spec.ports[0].nodePort}) openhimCoreTransactionPort=$(kubectl get service openhim-core-service -o=jsonpath={.spec.ports[2].nodePort}) openhimCoreTransactionSSLPort=$(kubectl get service openhim-core-service -o=jsonpath={.spec.ports[1].nodePort}) @@ -123,6 +123,8 @@ if [ "$1" == "init" ]; then print_services_url printf ">>> The OpenHIM Console Url will take a few minutes to become active <<<\n\n" + + bash "$k8sMainRootFilePath"/../importer/k8s.sh up elif [ "$1" == "up" ]; then kubectl apply -k $k8sMainRootFilePath diff --git a/docs/docs/how-to/creating-packages.mdx b/docs/docs/how-to/creating-packages.mdx index 45c15a7e..7ef7a03f 100644 --- a/docs/docs/how-to/creating-packages.mdx +++ b/docs/docs/how-to/creating-packages.mdx @@ -17,7 +17,7 @@ Packages are a way of allowing a group of applications to be setup and configure 1. `docker-compose.*.yml` files to setup and configure the necessary applications in Docker Compose 2. Deployment and service resource files (and any other necessary resource files) to setup and configure the necessary applications in Kubernetes -3. A `meta.json` file that holds metadata about the package +3. A `instant.json` file that holds metadata about the package 4. Bash scripts that accept a particular set of commands (up, down or destroy) and execute these Docker Compose and Kubernetes infrastructure files and any other necessary processing and configuration to perform the required command. ## Docker Compose files @@ -34,7 +34,7 @@ All Kubernetes files should by convention be contained in a `./kubernetes` direc Config containers can be executed as job resources with an [init container](https://kubernetes.io/docs/concepts/workloads/pods/init-containers/). These temporary containers only configure the main application once the service has started up. -## meta.json +## instant.json This file should be in the root directory of the package and provide metadata about the package itself along with any dependencies: @@ -53,11 +53,12 @@ This file should be in the root directory of the package and provide metadata ab Two bash scripts are required in each package: * `./docker/compose.sh` - to configure, start and stop the applications using Docker Compose -* `./kubernetes/k8s.sh` - to configure, start and stop the applications using Kubernetes +* `./kubernetes/main/k8s.sh` - to configure, start and stop the applications using Kubernetes Each of these scripts should accept one of the following commands (i.e. `./compose.sh `): -* `up` - start all the applications in this package and performs any necessary pre-processing of the infrastructure files +* `init` - start all the applications in this package and performs any necessary pre-processing of the infrastructure files +* `up` - start all the applications in this package * `down` - stop all the applications in this package * `destroy` - delete all the application containers in this package and all their stored data @@ -66,14 +67,19 @@ For example, a `compose.sh` script could look like this: ```sh composeFilePath=$( cd "$(dirname "${BASH_SOURCE[0]}")" ; pwd -P ) -if [ "$1" == "up" ]; then +if [ "$1" == "init" ]; then + # execute other setup scripts or commands here + # ... + + docker-compose -f "$composeFilePath"/docker-compose.yml -f "$composeFilePath"/importer/docker-compose.config.yml up -d +elif [ "$1" == "up" ]; then docker-compose -f "$composeFilePath"/docker-compose.yml -f "$composeFilePath"/importer/docker-compose.config.yml up -d elif [ "$1" == "down" ]; then docker-compose -f "$composeFilePath"/docker-compose.yml -f "$composeFilePath"/importer/docker-compose.config.yml stop elif [ "$1" == "destroy" ]; then docker-compose -f "$composeFilePath"/docker-compose.yml -f "$composeFilePath"/importer/docker-compose.config.yml down -v else - echo "Valid options are: up, down, or destroy" + echo "Valid options are: init, up, down, or destroy" fi ``` @@ -84,7 +90,14 @@ A `k8s.sh` script could look like this: k8sMainRootFilePath=$( cd "$(dirname "${BASH_SOURCE[0]}")" ; pwd -P ) -if [ "$1" == "up" ]; then +if [ "$1" == "init" ]; then + # execute other setup scripts or commands here + # ... + + # Create the namespace + kubectl apply -f $k8sMainRootFilePath/healthworkforce-namespace.yaml + kubectl apply -k $k8sMainRootFilePath +elif [ "$1" == "up" ]; then # Create the namespace kubectl apply -f $k8sMainRootFilePath/healthworkforce-namespace.yaml kubectl apply -k $k8sMainRootFilePath @@ -101,4 +114,4 @@ The Instant OpenHIE executable will look for these scripts and ensure that they ## How to execute your new package -TBD, this part is still in development and will be updated once it is implemented. +Mount your package into the openhie/instant container when it is run to start the packages. It will automatically detect packages folders in the `/instant` folder and will execute them as described above. For a package to be detected it must have an `instant.json` file in the root folder of the package. diff --git a/docs/docs/introduction/getting-started.mdx b/docs/docs/introduction/getting-started.mdx index 14b77e28..936b6747 100644 --- a/docs/docs/introduction/getting-started.mdx +++ b/docs/docs/introduction/getting-started.mdx @@ -15,19 +15,36 @@ import TabItem from '@theme/TabItem'; The Instant OpenHIE architecture, codebase, and documentation are under active development and are subject to change. While we encourage adoption and extension of the Instant OpenHIE framework, we do not consider this ready for production use at this stage. ::: -**Prerequisites** +## Prerequisites + +Instant OpenHIE is, by design, easily downloaded and run by anyone on any platform. It works on Linux, macOS and Windows. + +To get started, install Docker, Docker Compose and Kubectl for Kubernetes support. Instant OpenHIE uses Docker Compose to manage all the necessary services and applications. The links above will help guide the set up on your platform. + +For Windows and macOS: +- [Docker Desktop](https://www.docker.com/products/docker-desktop) + +For Linux-based operating systems: - [Docker engine](https://docs.docker.com/install/) - [Docker compose](https://docs.docker.com/compose/install/) +- [Minikube](https://kubernetes.io/docs/tasks/tools/install-minikube/) + +## Configuring Docker for Desktop (Windows and macOS only) + +Once Docker Desktop has been installed you will need to configure a few things. To do this click the docker icon in the task bar and select 'Dashboard'. Next, open the setting using the gear icon. Then, configure the following: -Instant OpenHIE is, by design, easily downloaded and run by anyone on any platform. +1. Under 'Resources' increase memory to 4.00 GB +1. Under 'Kubernetes' select 'Enable Kubernetes' +1. Click Apply & Restart - this may take some time +1. Once the above is done enter setting again and select 'Filesharing'. Next, add the `C:\Users\\.kube` folder and select 'Apply & Restart' -To get started, install Docker and Docker Compose. Instant OpenHIE uses Docker Compose to manage all the necessary services and applications. The links above will help guide the set up on your platform. +Now, you are ready to continue. ## Use the Interactive App (recommended) 1. Download the latest Instant OpenHIE executable from our [GitHub releases page](https://github.com/openhie/instant/releases). Find the executable in the assets section of the latest release for your platform. -1. Move the Instant OpenHIE download somewhere memorable. Run the executable while in its containing directory to get the command-line interface: +1. Move the Instant OpenHIE download somewhere memorable. Open up the terminal or (command prompt in Windows) and run the executable to get the command-line interface: ```sh ./goinstant-linux @@ -41,20 +58,36 @@ To get started, install Docker and Docker Compose. Instant OpenHIE uses Docker C Quit ``` -1. Use the arrow keys to navigate the interface and choose '**Start Instant OpenHIE**'. + ```sh + goinstant.exe + + Use the arrow keys to navigate: j k l h + ? Choose Start Instant OpenHIE if this is your first time: + > Start Instant OpenHIE + Stop Instant OpenHIE + Debug + Help + Quit + ``` + +1. Use the arrow keys (on windows you need to use j k l h) to navigate the interface and choose '**Start Instant OpenHIE**'. - This will start a download that may take some time. After the download, the script will start up the default packages. The script will then open a web page with links to the started services/application as well as their corresponding documentation. + This will start the default OpenHIE packages under docker. After the images download, the script will start up the packages and a web page will open with links to the started services/application as well as their corresponding documentation. - > All available packages are listed here in the sidebar under **Existing Packages**. Each package includes several services/applications. + > All available packages are listed here in the sidebar under **Existing Packages**. Each package includes several services/applications. These will be expanded over time. 1. Congratulations, you have successfully started up Instant OpenHIE. Follow the instructions on the web page to stop the services when you are done. -## Run the Bash Scripts Directly (for automation and development) +## Advanced: Run the startup Scripts Directly (for automation and development) To start all the default Instant OpenHIE packages, select a deployment platform below. > Have a look at the existing packages on the sidebar to see what is currently available. +:::caution +If you update the openhie/instant docker image to a new version you will need to remove the `instant` docker volume to see some of the updates as files are cached in this volume to allow sharing with other containers. +::: + -**Before proceeding**, make sure you are in the main `/instant` directory. This directory contains the `instant.sh` script. +If you have the Instant [OpenHIE codebase](https://github.com/openhie/instant) checked-out make sure you are in the main `/instant` directory. 1. To start up the system, execute the following command: ```sh - ./instant.sh up docker + yarn + yarn docker:instant init -t docker ``` 1. To stop the running containers, execute the following: ```sh - ./instant.sh down docker + yarn docker:instant down -t docker ``` 1. Remove the containers with the command below. However, make sure you **stop all the containers before trying to delete** them. @@ -83,35 +117,44 @@ To start all the default Instant OpenHIE packages, select a deployment platform > **This action will also delete all volumes** created by the containers. ```sh - ./instant.sh destroy docker + yarn docker:instant destroy -t docker ``` +Each command also takes a list of package IDs to operate on. If this is left out then all packages are run by default. + +If you do not have the code checked-out then you can still startup the environment with the correct command. You can find the docker commands to run in the scripts section of [this file](https://github.com/openhie/instant/blob/master/package.json) in the codebase. Add the action you would like to perform (init, up, down, destroy) to the end of that command. + -**Before proceeding**, make sure you are in the main `/instant` directory. This directory contains the `instant.sh` script. +For this to work you will need to have Kubernetes enabled in Docker for Desktop or minikube installed and running for Linux. -1. To start up the system, execute the following command: +If you have the Instant [OpenHIE codebase](https://github.com/openhie/instant) checked-out make sure you are in the main `/instant` directory. - > This command will output urls from which you can access your Instant OpenHIE instance. +1. To start up the system, execute the following command: ```sh - ./instant.sh up k8s + yarn + yarn docker:instant init -t kubernetes ``` -1. To delete all the deployment related pods, run the command below. - - > This command will leave services, and volumes intact. +1. To stop the running containers, execute the following: ```sh - ./instant.sh down k8s + yarn docker:instant down -t kubernetes ``` -1. To purge the entire Instant OpenHIE system, run the command below. +1. Remove the containers with the command below. However, make sure you **stop all the containers before trying to delete** them. + + > **This action will also delete all volumes** created by the containers. ```sh - ./instant.sh destroy k8s + yarn docker:instant destroy -t kubernetes ``` +Each command also takes a list of package IDs to operate on. If this is left out then all packages are run by default. + +If you do not have the code checked-out then you can still startup the environment with the correct command. You can find the docker commands to run in the scripts section of [this file](https://github.com/openhie/instant/blob/master/package.json) in the codebase. Add the action you would like to perform (init, up, down, destroy) to the end of that command and make sure to add the `-t kubernetes` option after the action. + diff --git a/facility/docker/compose.sh b/facility/docker/compose.sh index 11dbc46e..b8eeef5b 100644 --- a/facility/docker/compose.sh +++ b/facility/docker/compose.sh @@ -1,13 +1,13 @@ composeFilePath=$( cd "$(dirname "${BASH_SOURCE[0]}")" ; pwd -P ) if [ "$1" == "init" ]; then - docker-compose -f "$composeFilePath"/docker-compose.yml up -d + docker-compose -p instant -f "$composeFilePath"/docker-compose.yml up -d # Set up the openhim # "$composeFilePath"/initiateReplicaSet.sh elif [ "$1" == "up" ]; then - docker-compose -f "$composeFilePath"/docker-compose.yml up -d + docker-compose -p instant -f "$composeFilePath"/docker-compose.yml up -d # Wait # sleep 20 @@ -15,10 +15,10 @@ elif [ "$1" == "up" ]; then # do something else needed elif [ "$1" == "down" ]; then - docker-compose -f "$composeFilePath"/docker-compose.yml stop + docker-compose -p instant -f "$composeFilePath"/docker-compose.yml stop elif [ "$1" == "destroy" ]; then - docker-compose -f "$composeFilePath"/docker-compose.yml down -v + docker-compose -p instant -f "$composeFilePath"/docker-compose.yml down -v else echo "Valid options are: init, up, down, or destroy" diff --git a/goinstant/goinstant.go b/goinstant/goinstant.go index d60d12ec..9ceb3a40 100644 --- a/goinstant/goinstant.go +++ b/goinstant/goinstant.go @@ -9,7 +9,6 @@ import ( "os" "os/exec" "runtime" - "strings" "github.com/docker/docker/api/types" "github.com/docker/docker/client" @@ -86,13 +85,22 @@ func composeGet(url string) string { return (string(body)) } -func composeUp(composeFile string) { +// TODO: This will need to be run when we destory the stack +func clearInstantVolume() { + cmd := exec.Command("docker", "volume", "rm", "-f", "instant") + cmd.Run() +} + +func composeUp() { + home, err := os.UserHomeDir() + if err != nil { + fmt.Println("Error: ", err) + } fmt.Println("Running on", runtime.GOOS) switch runtime.GOOS { case "linux", "darwin": - cmd := exec.Command("docker-compose", "-f", "-", "up", "-d") - cmd.Stdin = strings.NewReader(composeFile) + cmd := exec.Command("docker", "run", "--rm", "-v", "/var/run/docker.sock:/var/run/docker.sock", "-v", home+"/.kube/config:/root/.kube/config:ro", "-v", home+"/.minikube:/home/$USER/.minikube:ro", "--mount=type=volume,src=instant,dst=/instant", "--network", "host", "openhie/instant:latest", "init", "-t", "docker") cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr err := cmd.Run() @@ -100,8 +108,7 @@ func composeUp(composeFile string) { log.Fatalf("cmd.Run() failed with %s\n", err) } case "windows": - cmd := exec.Command("cmd", "/C", "docker-compose", "-f", "-", "up", "-d") - cmd.Stdin = strings.NewReader(composeFile) + cmd := exec.Command("cmd", "/C", "docker", "run", "--rm", "-v", "/var/run/docker.sock:/var/run/docker.sock", "-v", home+"\\.kube:/root/.kube/config:ro", "--mount=type=volume,src=instant,dst=/instant", "openhie/instant:latest", "init", "-t", "docker") cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr if err := cmd.Run(); err != nil { @@ -117,12 +124,16 @@ func composeUp(composeFile string) { openBrowser("http://localhost:27517") } -func composeDown(composeFile string) { +func composeDown() { + home, err := os.UserHomeDir() + if err != nil { + fmt.Println("Error: ", err) + } + fmt.Println("Running on", runtime.GOOS) switch runtime.GOOS { case "linux", "darwin": - cmd := exec.Command("docker-compose", "-f", "-", "down") - cmd.Stdin = strings.NewReader(composeFile) + cmd := exec.Command("docker", "run", "--rm", "-v", "/var/run/docker.sock:/var/run/docker.sock", "-v", home+"/.kube/config:/root/.kube/config:ro", "-v", home+"/.minikube:/home/$USER/.minikube:ro", "--mount=type=volume,src=instant,dst=/instant", "--network", "host", "openhie/instant:latest", "down", "-t", "docker") cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr err := cmd.Run() @@ -130,8 +141,7 @@ func composeDown(composeFile string) { log.Fatalf("cmd.Run() failed with %s\n", err) } case "windows": - cmd := exec.Command("cmd", "/C", "docker-compose", "-f", "-", "down") - cmd.Stdin = strings.NewReader(composeFile) + cmd := exec.Command("cmd", "/C", "docker", "run", "--rm", "-v", "/var/run/docker.sock:/var/run/docker.sock", "-v", home+"\\.kube:/root/.kube/config:ro", "--mount=type=volume,src=instant,dst=/instant", "openhie/instant:latest", "down", "-t", "docker") cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr if err := cmd.Run(); err != nil { @@ -139,7 +149,6 @@ func composeDown(composeFile string) { } default: fmt.Println("What operating system is this?", runtime.GOOS) - } } @@ -163,13 +172,10 @@ func main() { } fmt.Printf("You chose %q\n", result) - stack := "https://raw.github.com/openhie/instant/master/core/docker/docker-compose.yml" - switch result { case "Start Instant OpenHIE": debug() - stuff := composeGet(stack) - composeUp(stuff) + composeUp() box := packr.New("someBoxName", "./templates") http.Handle("/", http.FileServer(box)) @@ -177,8 +183,7 @@ func main() { http.ListenAndServe(":27517", nil) case "Stop Instant OpenHIE": - stuff := composeGet(stack) - composeDown(stuff) + composeDown() case "Debug": debug() case "Help": diff --git a/healthworkforce/docker/compose.sh b/healthworkforce/docker/compose.sh index ae75d27f..42355fd5 100755 --- a/healthworkforce/docker/compose.sh +++ b/healthworkforce/docker/compose.sh @@ -1,13 +1,13 @@ composeFilePath=$( cd "$(dirname "${BASH_SOURCE[0]}")" ; pwd -P ) if [ "$1" == "init" ]; then - docker-compose -f "$composeFilePath"/docker-compose.yml -f "$composeFilePath"/docker-compose.dev.yml -f "$composeFilePath"/importer/docker-compose.config.yml up -d + docker-compose -p instant -f "$composeFilePath"/docker-compose.yml -f "$composeFilePath"/docker-compose.dev.yml -f "$composeFilePath"/importer/docker-compose.config.yml up -d elif [ "$1" == "up" ]; then - docker-compose -f "$composeFilePath"/docker-compose.yml -f "$composeFilePath"/docker-compose.dev.yml up -d + docker-compose -p instant -f "$composeFilePath"/docker-compose.yml -f "$composeFilePath"/docker-compose.dev.yml up -d elif [ "$1" == "down" ]; then - docker-compose -f "$composeFilePath"/docker-compose.yml -f "$composeFilePath"/docker-compose.dev.yml -f "$composeFilePath"/importer/docker-compose.config.yml stop + docker-compose -p instant -f "$composeFilePath"/docker-compose.yml -f "$composeFilePath"/docker-compose.dev.yml -f "$composeFilePath"/importer/docker-compose.config.yml stop elif [ "$1" == "destroy" ]; then - docker-compose -f "$composeFilePath"/docker-compose.yml -f "$composeFilePath"/docker-compose.dev.yml -f "$composeFilePath"/importer/docker-compose.config.yml down + docker-compose -p instant -f "$composeFilePath"/docker-compose.yml -f "$composeFilePath"/docker-compose.dev.yml -f "$composeFilePath"/importer/docker-compose.config.yml down else echo "Valid options are: init, up, down, or destroy" fi diff --git a/healthworkforce/docker/importer/docker-compose.config.yml b/healthworkforce/docker/importer/docker-compose.config.yml index 43d0e84b..ff473d3c 100644 --- a/healthworkforce/docker/importer/docker-compose.config.yml +++ b/healthworkforce/docker/importer/docker-compose.config.yml @@ -14,11 +14,11 @@ services: # Reject unauthorised is only needed if the OpenHIM's SSL is not setup NODE_TLS_REJECT_UNAUTHORIZED: 0 volumes: - - type: bind - source: ./importer/volume - target: /importer + - type: volume + source: instant + target: /instant # This command will only attempt to import the OpenHIM config when the heartbeat responds with a 2xx - command: sh -c "wait-on -t 60000 https-get://core:8080/heartbeat && node openhimConfig.js" + command: sh -c "wait-on -t 60000 https-get://core:8080/heartbeat && node /instant/healthworkforce/docker/importer/volume/openhimConfig.js" # container for executing config import scripts for setting up initial config mcsd-config-importer: @@ -29,8 +29,12 @@ services: MEDIATOR_HOSTNAME: 'mcsd-mediator' MEDIATOR_API_PORT: 3003 volumes: - - type: bind - source: ./importer/volume - target: /importer + - type: volume + source: instant + target: /instant # This command will only attempt to import the mcsd mediator config when the uptime endpoint responds with 200 - command: sh -c "wait-on -t 60000 http-get://mcsd-mediator:3003/uptime && sleep 1 && node endpoint.js" + command: sh -c "wait-on -t 60000 http-get://mcsd-mediator:3003/uptime && sleep 1 && node /instant/healthworkforce/docker/importer/volume/endpoint.js" + +volumes: + instant: + external: true diff --git a/healthworkforce/instant.json b/healthworkforce/instant.json new file mode 100644 index 00000000..6e6ed46c --- /dev/null +++ b/healthworkforce/instant.json @@ -0,0 +1,6 @@ +{ + "id": "mcsd", + "name": "mCSD use case package", + "description": "", + "version": "" +} diff --git a/healthworkforce/kubernetes/main/k8s.sh b/healthworkforce/kubernetes/main/k8s.sh index 942a7544..9baa607d 100755 --- a/healthworkforce/kubernetes/main/k8s.sh +++ b/healthworkforce/kubernetes/main/k8s.sh @@ -2,7 +2,10 @@ k8sMainRootFilePath=$( cd "$(dirname "${BASH_SOURCE[0]}")" ; pwd -P ) -if [ "$1" == "up" ]; then +if [ "$1" == "init" ]; then + kubectl apply -k $k8sMainRootFilePath + bash "$k8sMainRootFilePath"/../importer/k8s.sh up +elif [ "$1" == "up" ]; then kubectl apply -k $k8sMainRootFilePath elif [ "$1" == "down" ]; then kubectl delete deployment mapper-deployment diff --git a/instant.sh b/instant.sh deleted file mode 100755 index 728c243a..00000000 --- a/instant.sh +++ /dev/null @@ -1,67 +0,0 @@ -#!/bin/bash - -COMMAND=$1 -TARGET=$2 - -if ! [[ "$TARGET" =~ ^(docker|kubernetes|k8s)$ ]]; then - TARGET=docker - echo "Defaulting to docker as a target, target either not specified or invalid" -fi - -if [ "$TARGET" == "docker" ]; then - if [ "$COMMAND" == "init" ]; then - ./core/docker/compose.sh init - sleep 15 # give some time for the core services to start up - ./healthworkforce/docker/compose.sh init - elif [ "$COMMAND" == "up" ]; then - ./core/docker/compose.sh up - ./healthworkforce/docker/compose.sh up - elif [ "$COMMAND" == "down" ]; then - ./core/docker/compose.sh down - ./healthworkforce/docker/compose.sh down - elif [ "$COMMAND" == "destroy" ]; then - ./core/docker/compose.sh destroy - ./healthworkforce/docker/compose.sh destroy - elif [ "$COMMAND" == "test" ]; then - ./core/test.sh localhost:5000 - ./healthworkforce/test.sh localhost:5000 - else - echo "Valid options are: init, up, down, test or destroy" - fi -fi - - -if [ "$TARGET" == "kubernetes" ] || [ "$TARGET" == "k8s" ]; then - envContextName=$(kubectl config get-contexts | grep '*' | awk '{print $2}') - printf "\n\n>>> Applying to the '${envContextName}' context <<<\n\n\n" - - if [ "$COMMAND" == "init" ]; then - ./core/kubernetes/main/k8s.sh init - ./core/kubernetes/importer/k8s.sh up - ./healthworkforce/kubernetes/main/k8s.sh up - ./healthworkforce/kubernetes/importer/k8s.sh up - elif [ "$COMMAND" == "up" ]; then - ./core/kubernetes/main/k8s.sh up - ./healthworkforce/kubernetes/main/k8s.sh up - elif [ "$COMMAND" == "down" ]; then - ./core/kubernetes/main/k8s.sh down - ./healthworkforce/kubernetes/main/k8s.sh down - elif [ "$COMMAND" == "destroy" ]; then - ./core/kubernetes/main/k8s.sh destroy - ./healthworkforce/kubernetes/main/k8s.sh destroy - elif [ "$COMMAND" == "test" ]; then - openhimCoreHostname=$(kubectl get service openhim-core-service -o=jsonpath="{.status.loadBalancer.ingress[*]['hostname', 'ip']}") - openhimCoreTransactionSSLPort=$(kubectl get service openhim-core-service -o=jsonpath={.spec.ports[1].port}) - hostnameLength=$(expr length "$openhimCoreHostname") - - if [ "$hostnameLength" -le 0 ]; then - openhimCoreHostname=$(minikube ip) - openhimCoreTransactionSSLPort=$(kubectl get service openhim-core-service -o=jsonpath={.spec.ports[1].nodePort}) - fi - - ./core/test.sh $openhimCoreHostname:$openhimCoreTransactionSSLPort - ./healthworkforce/test.sh $openhimCoreHostname:$openhimCoreTransactionSSLPort - else - echo "Valid options are: init, up, down, test or destroy" - fi -fi diff --git a/instant.ts b/instant.ts new file mode 100644 index 00000000..01483c5c --- /dev/null +++ b/instant.ts @@ -0,0 +1,179 @@ +'use strict' + +import * as commandLineArgs from 'command-line-args' +import * as glob from 'glob' +import * as fs from 'fs' +import * as child from 'child_process' +import * as util from 'util' + +const exec = util.promisify(child.exec) + +interface PackageInfo { + metadata: { + id: string + name: string + description: string + version: string + dependencies: string[] + } + path: string +} + +interface PackagesMap { + [packageID: string]: PackageInfo +} + +function getInstantOHIEPackages(): PackagesMap { + const packages: PackagesMap = {} + const paths = glob.sync('*/instant.json') + + for (const path of paths) { + const metadata = JSON.parse(fs.readFileSync(path).toString()) + packages[metadata.id] = { + metadata, + path: path.replace('instant.json', '') + } + } + + return packages +} + +async function runBashScript(path: string, filename: string, args: string[]) { + const cmd = `bash ${path}${filename} ${args.join(' ')}` + console.log(`Executing: ${cmd}`) + + try { + const promise = exec(cmd) + if (promise.child) { + promise.child.stdout.on('data', (data) => console.log(data)) + promise.child.stderr.on('data', (data) => console.error(data)) + } + await promise + } catch (err) { + console.error(`Error: Script ${filename} returned an error`) + console.log(err.stdout) + console.log(err.stderr) + } +} + +// Main script execution +;(async () => { + const allPackages = getInstantOHIEPackages() + console.log( + `Found ${Object.keys(allPackages).length} packages: ${Object.values( + allPackages + ) + .map((p) => p.metadata.id) + .join(', ')}` + ) + + const main = commandLineArgs( + [ + { + name: 'command', + defaultOption: true + } + ], + { + stopAtFirstUnknown: true + } + ) + + let argv = main._unknown || [] + + // main commands + if (['init', 'up', 'down', 'destroy'].includes(main.command)) { + const mainOptions = commandLineArgs( + [ + { + name: 'target', + alias: 't', + defaultValue: 'docker' + } + ], + { argv, stopAtFirstUnknown: true } + ) + + console.log(`Target environment is: ${mainOptions.target}`) + + argv = mainOptions._unknown || [] + let chosenPackageIds = argv + + if ( + !chosenPackageIds.every((id) => Object.keys(allPackages).includes(id)) + ) { + throw new Error('Unknown package id') + } + + if (chosenPackageIds.length < 1) { + chosenPackageIds = Object.keys(allPackages) + } + + console.log( + `Selected package IDs to operate on: ${chosenPackageIds.join(', ')}` + ) + + switch (mainOptions.target) { + case 'docker': + for (const id of chosenPackageIds) { + await runBashScript(`${allPackages[id].path}docker/`, 'compose.sh', [ + main.command + ]) + } + break + case 'k8s': + case 'kubernetes': + for (const id of chosenPackageIds) { + await runBashScript( + `${allPackages[id].path}kubernetes/main/`, + 'k8s.sh', + [main.command] + ) + } + break + default: + throw new Error("Unknown value given for option 'target'") + } + } + + // test command + if (main.command === 'test') { + const testOptions = commandLineArgs( + [ + { + name: 'host', + alias: 'h', + defaultValue: 'localhost' + }, + { + name: 'port', + alias: 'p', + defaultValue: '5000' + } + ], + { argv, stopAtFirstUnknown: true } + ) + + argv = testOptions._unknown || [] + let chosenPackageIds = argv + + if ( + !chosenPackageIds.every((id) => Object.keys(allPackages).includes(id)) + ) { + throw new Error('Unknown package id') + } + + if (chosenPackageIds.length < 1) { + chosenPackageIds = Object.keys(allPackages) + } + + console.log(`Running tests for packages: ${chosenPackageIds.join(', ')}`) + console.log(`Using host: ${testOptions.host}:${testOptions.port}`) + + for (const id of chosenPackageIds) { + await runBashScript(`${allPackages[id].path}`, 'test.sh', [ + `${testOptions.host}:${testOptions.port}` + ]) + } + } +})() diff --git a/package.json b/package.json new file mode 100644 index 00000000..cca56860 --- /dev/null +++ b/package.json @@ -0,0 +1,26 @@ +{ + "name": "instant", + "version": "0.0.1", + "main": "instant.ts", + "repository": "git@github.com:openhie/instant.git", + "author": "Jembi and IntraHealth", + "license": "Apache-2.0", + "dependencies": { + "@types/command-line-args": "^5.0.0", + "@types/glob": "^7.1.3", + "@types/node": "^14.0.22", + "command-line-args": "^5.1.1", + "glob": "^7.1.6", + "ts-node": "^8.10.2", + "typescript": "^3.9.6" + }, + "scripts": { + "instant": "ts-node instant.ts", + "docker:instant": "docker volume rm instant ; docker run -it --rm -v /var/run/docker.sock:/var/run/docker.sock -v ~/.kube/config:/root/.kube/config:ro -v ~/.minikube:/home/$USER/.minikube:ro --mount='type=volume,src=instant,dst=/instant' --network host openhie/instant:latest", + "docker:instant:win": "docker volume rm instant & docker run --rm -v /var/run/docker.sock:/var/run/docker.sock -v %USERPROFILE%\\.kube:/root/.kube/config:ro --mount='type=volume,src=instant,dst=/instant' --network host openhie/instant:latest", + "docker:build": "docker build -t openhie/instant:latest ." + }, + "devDependencies": { + "prettier": "^2.0.5" + } +} diff --git a/tests/README.md b/tests/README.md deleted file mode 100644 index e69de29b..00000000 diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 00000000..7d20853c --- /dev/null +++ b/yarn.lock @@ -0,0 +1,189 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@types/command-line-args@^5.0.0": + version "5.0.0" + resolved "https://registry.yarnpkg.com/@types/command-line-args/-/command-line-args-5.0.0.tgz#484e704d20dbb8754a8f091eee45cdd22bcff28c" + integrity sha512-4eOPXyn5DmP64MCMF8ePDvdlvlzt2a+F8ZaVjqmh2yFCpGjc1kI3kGnCFYX9SCsGTjQcWIyVZ86IHCEyjy/MNg== + +"@types/glob@^7.1.3": + version "7.1.3" + resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.1.3.tgz#e6ba80f36b7daad2c685acd9266382e68985c183" + integrity sha512-SEYeGAIQIQX8NN6LDKprLjbrd5dARM5EXsd8GI/A5l0apYI1fGMWgPHSe4ZKL4eozlAyI+doUE9XbYS4xCkQ1w== + dependencies: + "@types/minimatch" "*" + "@types/node" "*" + +"@types/minimatch@*": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d" + integrity sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA== + +"@types/node@*", "@types/node@^14.0.22": + version "14.0.22" + resolved "https://registry.yarnpkg.com/@types/node/-/node-14.0.22.tgz#23ea4d88189cec7d58f9e6b66f786b215eb61bdc" + integrity sha512-emeGcJvdiZ4Z3ohbmw93E/64jRzUHAItSHt8nF7M4TGgQTiWqFVGB8KNpLGFmUHmHLvjvBgFwVlqNcq+VuGv9g== + +arg@^4.1.0: + version "4.1.3" + resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" + integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== + +array-back@^3.0.1: + version "3.1.0" + resolved "https://registry.yarnpkg.com/array-back/-/array-back-3.1.0.tgz#b8859d7a508871c9a7b2cf42f99428f65e96bfb0" + integrity sha512-TkuxA4UCOvxuDK6NZYXCalszEzj+TLszyASooky+i742l9TqsOdYCMJJupxRic61hwquNtppB3hgcuq9SVSH1Q== + +balanced-match@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" + integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +buffer-from@^1.0.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" + integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A== + +command-line-args@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/command-line-args/-/command-line-args-5.1.1.tgz#88e793e5bb3ceb30754a86863f0401ac92fd369a" + integrity sha512-hL/eG8lrll1Qy1ezvkant+trihbGnaKaeEjj6Scyr3DN+RC7iQ5Rz84IeLERfAWDGo0HBSNAakczwgCilDXnWg== + dependencies: + array-back "^3.0.1" + find-replace "^3.0.0" + lodash.camelcase "^4.3.0" + typical "^4.0.0" + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= + +diff@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" + integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== + +find-replace@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/find-replace/-/find-replace-3.0.0.tgz#3e7e23d3b05167a76f770c9fbd5258b0def68c38" + integrity sha512-6Tb2myMioCAgv5kfvP5/PkZZ/ntTpVK39fHY7WkWBgvbeE+VHd/tZuZ4mrC+bxh4cfOZeYKVPaJIZtZXV7GNCQ== + dependencies: + array-back "^3.0.1" + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= + +glob@^7.1.6: + version "7.1.6" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" + integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +lodash.camelcase@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6" + integrity sha1-soqmKIorn8ZRA1x3EfZathkDMaY= + +make-error@^1.1.1: + version "1.3.6" + resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" + integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== + +minimatch@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" + integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== + dependencies: + brace-expansion "^1.1.7" + +once@^1.3.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= + dependencies: + wrappy "1" + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= + +prettier@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.0.5.tgz#d6d56282455243f2f92cc1716692c08aa31522d4" + integrity sha512-7PtVymN48hGcO4fGjybyBSIWDsLU4H4XlvOHfq91pz9kkGlonzwTfYkaIEwiRg/dAJF9YlbsduBAgtYLi+8cFg== + +source-map-support@^0.5.17: + version "0.5.19" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.19.tgz#a98b62f86dcaf4f67399648c085291ab9e8fed61" + integrity sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw== + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" + +source-map@^0.6.0: + version "0.6.1" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + +ts-node@^8.10.2: + version "8.10.2" + resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-8.10.2.tgz#eee03764633b1234ddd37f8db9ec10b75ec7fb8d" + integrity sha512-ISJJGgkIpDdBhWVu3jufsWpK3Rzo7bdiIXJjQc0ynKxVOVcg2oIrf2H2cejminGrptVc6q6/uynAHNCuWGbpVA== + dependencies: + arg "^4.1.0" + diff "^4.0.1" + make-error "^1.1.1" + source-map-support "^0.5.17" + yn "3.1.1" + +typescript@^3.9.6: + version "3.9.6" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.6.tgz#8f3e0198a34c3ae17091b35571d3afd31999365a" + integrity sha512-Pspx3oKAPJtjNwE92YS05HQoY7z2SFyOpHo9MqJor3BXAGNaPUs83CuVp9VISFkSjyRfiTpmKuAYGJB7S7hOxw== + +typical@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/typical/-/typical-4.0.0.tgz#cbeaff3b9d7ae1e2bbfaf5a4e6f11eccfde94fc4" + integrity sha512-VAH4IvQ7BDFYglMd7BPRDfLgxZZX4O4TFcRDA6EN5X7erNJJq+McIEp8np9aVtxrCJ6qx4GTYVfOWNjcqwZgRw== + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= + +yn@3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" + integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==