Skip to content

Commit

Permalink
# This is a combination of 17 commits.
Browse files Browse the repository at this point in the history
# This is the 1st commit message:

fix build errors and create deploy file

# This is the commit message #2:

remove vercel from deploy yml

# This is the commit message #3:

fix build error related to vercel front end

# This is the commit message #4:

configure zapehr secrets for telemed intake zambdas

# This is the commit message #5:

fix deploy zambdas for demo env

# This is the commit message #6:

fix package json

# This is the commit message #7:

fix zapehr env error

# This is the commit message #8:

checkout the env variables from secrets repo

# This is the commit message #9:

update the private ssh keys

# This is the commit message #10:

configure deploy zambdas

# This is the commit message #11:

fix typo

# This is the commit message #12:

fix the directory for the build

# This is the commit message #13:

add build

# This is the commit message #14:

configure build with the deploy

# This is the commit message #15:

configure build file without pushd

# This is the commit message #16:

create separate build file for intake

# This is the commit message #17:

schedule types fix
  • Loading branch information
saewitz committed Jun 21, 2024
1 parent a9505a2 commit 4e31672
Show file tree
Hide file tree
Showing 7 changed files with 383 additions and 27 deletions.
45 changes: 35 additions & 10 deletions .github/workflows/build-and-deploy-telemed-intake.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ jobs:
uses: webfactory/[email protected]
with:
ssh-private-key: |
${{ secrets.OTTEHR_PRIVATE_SSH_KEY }}
${{ secrets.OTTEHR_DEMO_PRIVATE_KEY }}
${{ secrets.BUMP_VERSION }}
- uses: actions/checkout@v3
Expand All @@ -61,15 +61,15 @@ jobs:
with:
version: 8

- name: Create secrets file from github secrets
id: create-json
uses: jsdaniell/[email protected]
- name: Check out secrets repo to grab the env file.
uses: actions/checkout@v3
with:
name: 'secrets.json'
json: ${{ secrets.OTTEHR_SECRETS_JSON }}
repository: masslight/ottehr-secrets
ssh-key: ${{ secrets.OTTEHR_DEMO_PRIVATE_KEY }}
path: 'secrets'

- name: Move env file into .env folder
run: mkdir -p ${{ env.ZAMBDAS_LOCATION }}/.env/ && mv secrets.json ${{ env.ZAMBDAS_LOCATION }}/.env/${{ github.event.inputs.environment }}.json
- name: Move secrets file into the .env directory.
run: mkdir -p ${{ env.ZAMBDAS_LOCATION }}/.env/ && cp secrets/telemed-intake/* ${{ env.ZAMBDAS_LOCATION }}/.env/

- name: Copy env file to local.env to support serverless package command
run: cp ${{ env.ZAMBDAS_LOCATION }}/.env/${{ github.event.inputs.environment }}.json ${{ env.ZAMBDAS_LOCATION }}/.env/local.json
Expand Down Expand Up @@ -97,11 +97,14 @@ jobs:
- name: Install dependencies
run: pnpm install

- name: Build app
run: pnpm build:telemed-intake

- name: Setup zapehr secrets
run: cd ${{ env.ZAMBDAS_LOCATION }} && pnpm setup-zapehr-secrets:${{ github.event.inputs.environment }}

- name: Deploy zambdas
run: cd ${{ env.ZAMBDAS_LOCATION }} && pnpm deploy-zambas:${{ github.event.inputs.environment }}
run: cd ${{ env.ZAMBDAS_LOCATION }} && pnpm deploy-zambdas:${{ github.event.inputs.environment }}

- name: Configure AWS CLI
uses: aws-actions/configure-aws-credentials@v1
Expand All @@ -112,8 +115,9 @@ jobs:

- name: Deploy to AWS S3
run: |
aws s3 sync ./build s3://telemed.ottehr.com --delete
aws s3 sync ./packages/telemed-intake/app/build s3://telemed.ottehr.com --delete
<<<<<<< HEAD
<<<<<<< HEAD
<<<<<<< HEAD
=======
Expand Down Expand Up @@ -143,3 +147,24 @@ jobs:
{ "title": "Branch", "value": "${{ env.BRANCH }}", "short": true },
{ "title": "Environment", "value": "${{ env.ENVIRONMENT }}", "short": true },
{ "title": "Version", "value": "${{ env.PACKAGE_VERSION }}", "short": true }]
=======
# - name: Notify Slack
# if: always()
# uses: edge/[email protected]
# env:
# SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
# BRANCH: ${{ github.ref }}
# PACKAGE_VERSION: ${{ steps.package-version.outputs.current-version }}
# with:
# channel: '#ottehr-notifications'
# status: ${{ job.status }}
# success_text: '${{ env.GITHUB_WORKFLOW }} (Run #${{ env.GITHUB_RUN_NUMBER }}) deploy completed successfully'
# failure_text: '${{ env.GITHUB_WORKFLOW }} (Run #${{ env.GITHUB_RUN_NUMBER }}) deploy failed'
# cancelled_text: '${{ env.GITHUB_WORKFLOW }} (Run #${{ env.GITHUB_RUN_NUMBER }}) deploy was cancelled'
# fields: |
# [{ "title": "Service", "value": "${{ env.SLACK_NOTIFICATION_SERVICE_NAME }}", "short": true },
# { "title": "Action URL", "value": "${{ env.GITHUB_SERVER_URL }}/${{ env.GITHUB_REPOSITORY }}/actions/runs/${{ env.GITHUB_RUN_ID }}"},
# { "title": "Branch", "value": "${{ env.BRANCH }}", "short": true },
# { "title": "Environment", "value": "${{ env.ENVIRONMENT }}", "short": true },
# { "title": "Version", "value": "${{ env.PACKAGE_VERSION }}", "short": true }]
>>>>>>> bf2b1b4 (checkout the env variables from secrets repo)
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"scripts": {
"test": "pnpm recursive run test",
"build": "sh ./scripts/build.sh",
"build:telemed-intake": "sh ./scripts/build-telemed-intake.sh",
"lint": "eslint packages/",
"start": "pnpm start:local",
"ehr:start": "pnpm --filter 'ottehr-ehr-*' --stream=true start:local",
Expand Down
2 changes: 1 addition & 1 deletion packages/telemed-intake/zambdas/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"start:staging": "ENV=staging npm run start-skeleton",
"start-skeleton": "sls offline --stage ${ENV} --httpPort 3000",
"setup-zapehr-secrets:dev": "ENV=dev ts-node scripts/configure-zapehr-secrets.ts",
"deploy-zambdas": "npm run package && ts-node scripts/deploy-zambdas.ts",
"deploy-zambdas:dev": "ENV=dev pnpm run package && ts-node scripts/deploy-zambdas.ts",
"setup-default-locations": "ts-node scripts/setup-default-locations.ts",
"update-permissions-for-users": "ts-node scripts/update-permissions-for-users.ts",
"build": "tsc && sls package --stage dev",
Expand Down
293 changes: 293 additions & 0 deletions packages/telemed-intake/zambdas/scripts/deploy-zambdas.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,293 @@
import { BatchInputDeleteRequest, BatchInputPostRequest } from '@zapehr/sdk';
import { Subscription } from 'fhir/r4';
import fs from 'fs';
import { getM2MClientToken } from '../src/shared';
import { createZambdaClient, performEffectWithEnvFile } from './common';
import { createFhirClient } from 'ottehr-utils';

interface SubscriptionDetils {
criteria: string;
reason: string;
event?: 'create' | 'update';
}

interface DeployZambda {
type: 'http_open' | 'http_auth' | 'subscription' | 'cron';
subscriptionDetils?: SubscriptionDetils[];
schedule?: {
start?: string;
end?: string;
expression: string;
};
environments?: string[];
}

const ZAMBDAS: { [name: string]: DeployZambda } = {
'GET-PATIENTS': {
type: 'http_auth',
},
'GET-PAPERWORK': {
type: 'http_auth',
},
'CREATE-PAPERWORK': {
type: 'http_auth',
},
'UPDATE-PAPERWORK': {
type: 'http_auth',
},
'CREATE-APPOINTMENT': {
type: 'http_auth',
},
'GET-APPOINTMENTS': {
type: 'http_auth',
},
'GET-TELEMED-STATES': {
type: 'http_open',
},
'CANCEL-APPOINTMENT': {
type: 'http_auth',
},
'GET-WAIT-STATUS': {
type: 'http_open',
},
'JOIN-CALL': {
type: 'http_open',
},
'VIDEO-CHAT-INVITES-CREATE': {
type: 'http_auth',
},
'VIDEO-CHAT-INVITES-CANCEL': {
type: 'http_auth',
},
'VIDEO-CHAT-INVITES-LIST': {
type: 'http_auth',
},
'GET-PRESIGNED-FILE-URL': {
type: 'http_open',
},
};

const updateZambdas = async (config: any): Promise<void> => {
const token = await getM2MClientToken(config);
const zambdaClient = await createZambdaClient(config);

console.log('Getting list of zambdas');
const currentZambdas = await zambdaClient.getAllZambdas();

// First check if any zambdas are not found
for await (const zambda of Object.keys(ZAMBDAS)) {
const currentZambda = ZAMBDAS[zambda];
if (currentZambda.environments && !currentZambda.environments.includes(config.ENVIRONMENT)) {
console.log(`\nZambda ${zambda} is not run in ${config.ENVIRONMENT}`);
continue;
}

let currentDeployedZambda = currentZambdas.find(
(tempZambda) => tempZambda.name === `telemed-${zambda.toLowerCase()}`,
);

if (currentDeployedZambda) {
console.log(`\nZambda ${zambda} is found with ID ${currentDeployedZambda.id}`);
} else {
console.log(`\nZambda ${zambda} is not found, creating it`);
currentDeployedZambda = await zambdaClient.createZambda({
name: `telemed-${zambda.toLowerCase()}`,
});
console.log(`Zambda ${zambda} with ID ${currentDeployedZambda.id}`);
}

await updateProjectZambda(currentDeployedZambda.id, zambda, currentZambda, config, token);
}
};

async function updateProjectZambda(
zambdaId: string,
zambdaName: string,
zambda: DeployZambda,
config: any,
token: string,
): Promise<void> {
const projectApiUrl = 'https://project-api.zapehr.com/v1';
// todo use zambda client https://github.com/masslight/zapehr/issues/2586
const endpoint = `${projectApiUrl}/zambda/${zambdaId}/s3-upload`;

console.log(`Getting S3 upload URL for zambda ${zambdaName}`);
const zapehrResponse = await fetch(endpoint, {
method: 'post',
headers: {
Authorization: `Bearer ${token}`,
'x-zapehr-project-id': config.PROJECT_ID,
},
});

if (!zapehrResponse.ok) {
const zapehrResponseJson = await zapehrResponse.json();
console.log(
`status, ${zapehrResponse.status}, status text, ${
zapehrResponse.statusText
}, zapehrResponseJson, ${JSON.stringify(zapehrResponseJson)}`,
);
throw Error('An error occurred during the zapEHR Zambda S3 URL request');
}
const s3Url = (await zapehrResponse.json())['signedUrl'];
console.log(`Got S3 upload URL for zambda ${zambdaName}`);

console.log('Uploading zip file to S3');
// zip file names are lowercase with dashes
const zipFile = zambdaName.toLowerCase().replace(/_/g, '-');
const file = fs.readFileSync(`.dist/${zipFile}.zip`);
const awsResponse = await fetch(s3Url, {
method: 'put',
body: file,
});

if (!awsResponse.ok) {
const awsResponseJson = await awsResponse.json();
console.log(
`status, ${awsResponse.status}, status text, ${awsResponse.statusText}, awsResponseJson, ${JSON.stringify(
awsResponseJson,
)}`,
);
throw Error('An error occurred during the AWS upload zip file request');
}
console.log('Uploaded zip file to S3');

console.log('Updating zambda');
//TODO: change this code back to zambdaClient.updateZambda() function, this is temporary fix
const updateZambda = await fetch(`${projectApiUrl}/zambda/${zambdaId}`, {
method: 'PATCH',
headers: {
authorization: `Bearer ${token}`,
},
body: JSON.stringify({
triggerMethod: zambda.type,
schedule: zambda.schedule,
}),
});
if (updateZambda.status !== 200) {
throw new Error(`Error updating the zambda ${JSON.stringify(await updateZambda.json())}`);
}
console.log('Updated zambda');

if (zambda.type === 'subscription') {
if (zambda.subscriptionDetils === undefined) {
console.log('Zambda is subscription type but does not have details on the subscription');
return;
}
const endpoint = `zapehr-lambda:${zambdaId}`;
const fhirClient = await createFhirClient(config);
const subscriptionsSearch: Subscription[] = await fhirClient.searchResources({
resourceType: 'Subscription',
searchParams: [
{
name: 'url',
value: endpoint,
},
{
name: 'status',
value: 'active',
},
],
});
console.log(`${subscriptionsSearch.length} existing subscriptions found`);

const EXTENSION_URL = 'http://zapehr.com/fhir/extension/SubscriptionTriggerEvent';

const createSubscriptionRequests: BatchInputPostRequest[] = [];
const deleteSubscriptionRequests: BatchInputDeleteRequest[] = [];

// check existing subscriptions against current subscription details to determin if any should be deleted
// if any events are changing, delete
// if any existing criteria doesn't exist in the details array defined above, delete
const subscriptionsNotChanging = subscriptionsSearch.reduce((acc: Subscription[], existingSubscription) => {
const existingSubscriptionEvent = existingSubscription.extension?.find(
(ext) => ext.url === EXTENSION_URL,
)?.valueString;
const subscriptionMatch = zambda.subscriptionDetils?.find((zambdaSubscritionDetail) => {
const eventMatch = existingSubscriptionEvent === zambdaSubscritionDetail.event;
const criteriaMatch = zambdaSubscritionDetail.criteria === existingSubscription.criteria;
return eventMatch && criteriaMatch;
});
if (subscriptionMatch) {
console.log(
`subscription with criteria: '${subscriptionMatch.criteria}' and event: '${subscriptionMatch.event}' is not changing`,
);
acc.push(existingSubscription);
} else {
console.log(
`subscription with criteria: '${existingSubscription.criteria}' and event: '${existingSubscriptionEvent}' is being deleted since the criteria/event is not contained in the updated subscription details array`,
);
const deleteRequest: BatchInputDeleteRequest = {
method: 'DELETE',
url: `/Subscription/${existingSubscription.id}`,
};
deleteSubscriptionRequests.push(deleteRequest);
}
return acc;
}, []);

// check current subscription details again existing subscriptions to determin if any should be created
zambda.subscriptionDetils.forEach((subscriptionDetail) => {
// if the subscription detail is found in subscriptions not chaning, do nothing
const foundSubscription = subscriptionsNotChanging.find(
(subscription) => subscription.criteria === subscriptionDetail.criteria,
);
// else create it
if (!foundSubscription) {
console.log(
`Creating subscription with criteria: '${subscriptionDetail.criteria}' and event: '${subscriptionDetail.event}'`,
);
const extension = [];
if (subscriptionDetail?.event) {
extension.push({
url: EXTENSION_URL,
valueString: subscriptionDetail.event,
});
}
const subscriptionResource: Subscription = {
resourceType: 'Subscription',
status: 'active',
reason: subscriptionDetail.reason,
criteria: subscriptionDetail.criteria,
channel: {
type: 'rest-hook',
endpoint: endpoint,
},
extension: extension,
};
const subscriptionRequest: BatchInputPostRequest = {
method: 'POST',
url: '/Subscription',
resource: subscriptionResource,
};
createSubscriptionRequests.push(subscriptionRequest);
}
});
if (createSubscriptionRequests.length > 0 || deleteSubscriptionRequests.length > 0) {
console.log('making subscription transaction request');
await fhirClient.transactionRequest({
requests: [...createSubscriptionRequests, ...deleteSubscriptionRequests],
});
}
console.log(`Created ${createSubscriptionRequests.length} subscriptions`);
console.log(`Deleted ${deleteSubscriptionRequests.length} subscriptions`);
console.log(`${subscriptionsNotChanging.length} subscriptions are not changing`);
}
}

if (process.argv.length < 3) {
console.log(
'You must provide an environment and an api as command-line arguments, e.g.: npm run deploy-zambdas testing',
);
process.exit();
}

// So we can use await
const main = async (): Promise<void> => {
await performEffectWithEnvFile(updateZambdas);
};

main().catch((error) => {
console.log('error', error);
throw error;
});
Loading

0 comments on commit 4e31672

Please sign in to comment.