Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

env config func update with api versioning #56

Merged
merged 4 commits into from
Oct 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions deploy/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,58 @@ The React application is deployed using AWS CloudFront and S3, utilizing a custo

For each account, the React assets are built and then pushed to a designated S3 bucket using AWS CodeBuild and Lambda functions. Specifically, a Lambda function uploads the assets to S3 and subsequently triggers CodeBuild. CodeBuild then compiles the React application and uploads the built assets back to S3.

## Deployment Strategy

The deployment strategy is to deploy the React application to the toolchain account, and then use AWS CodePipeline to deploy the application to the respective accounts.
![ORCA UI DUAL PIPELINE](../docs/orca-ui-dual-pipeline.png)

## env config lambda

The env config lambda is used to update the env.js file in the S3 bucket. Env Config Lmabda [here](./lambda/env_config_and_cdn_refresh.py)

Normally, the lambda function is invoked by the CodeBuild project. This is done by adding a code build action in the CodeBuild project to invoke the lambda function.

If you want to invoke the lambda function manually, you can use the following command (without payload):

```sh
aws lambda invoke \
--function-name CodeBuildEnvConfigLambdaBeta \
response.json
```

if you wanna invoke manually with payload to update the api version, you can use the following command:

```sh
aws lambda invoke \
--function-name CodeBuildEnvConfigLambdaBeta \
--payload '{"metadata_api_version": "v2"}' \
response.json
```

Update multiple API versions

```sh
aws lambda invoke \
--function-name CodeBuildEnvConfigLambdaBeta \
--payload '{
"metadata_api_version": "v2",
"workflow_api_version": "v2",
"sequence_run_api_version": "v1",
"file_api_version": "v2"
}' \
response.json
```

invoke with a specific AWS profile:

```sh
aws lambda invoke \
--profile your-profile-name \
--function-name CodeBuildEnvConfigLambdaBeta \
--payload '{"metadata_api_version": "v2"}' \
response.json
```

## Development

Change to the deploy directory
Expand Down
6 changes: 3 additions & 3 deletions deploy/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@ export const cloudFrontBucketNameConfig: Record<AppStage, string> = {
};

export const configLambdaNameConfig: Record<AppStage, string> = {
[AppStage.BETA]: 'TriggerCodeBuildLambdaBeta',
[AppStage.GAMMA]: 'TriggerCodeBuildLambdaGamma',
[AppStage.PROD]: 'TriggerCodeBuildLambdaProd',
[AppStage.BETA]: 'CodeBuildEnvConfigLambdaBeta',
[AppStage.GAMMA]: 'CodeBuildEnvConfigLambdaGamma',
[AppStage.PROD]: 'CodeBuildEnvConfigLambdaProd',
};

export const getAppStackConfig = (appStage: AppStage): ApplicationStackProps => {
Expand Down
71 changes: 0 additions & 71 deletions deploy/lambda/env_config.py

This file was deleted.

108 changes: 108 additions & 0 deletions deploy/lambda/env_config_and_cdn_refresh.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@

import os
import boto3
import json

ssm = boto3.client('ssm')
s3 = boto3.client('s3')
cloudfront = boto3.client('cloudfront')

def get_ssm_parameter(name, with_decryption=False):
"""Fetch a SSM parameter"""
try:
response = ssm.get_parameter(Name=name, WithDecryption=with_decryption)
return response['Parameter']['Value']
except Exception as e:
print(f"Error fetching SSM parameter {name}: {e}")
return None


def update_api_versions(event):
"""Update API versions from event with validation and logging"""
if not event or not isinstance(event, dict):
print("No update event data received, skipping API version updates")
return

api_version_mappings = {
'metadata_api_version': 'VITE_METADATA_API_VERSION',
'workflow_api_version': 'VITE_WORKFLOW_API_VERSION',
'sequence_run_api_version': 'VITE_SEQUENCE_RUN_API_VERSION',
'file_api_version': 'VITE_FILE_API_VERSION'
}

# Check if any version keys exist in the event
if not any(key in event for key in api_version_mappings):
print("No API version updates found in event")
return

# update the environment variables
for event_key, env_key in api_version_mappings.items():
version = event.get(event_key)
if version and isinstance(version, str):
os.environ[env_key] = version
print(f"Updated {env_key} to {version}")

def handler(event, context):
"""Handler for the lambda function"""

# read the event to update the api version
update_api_versions(event)

bucket_name = os.environ['BUCKET_NAME']
cloudfront_distribution_id = os.environ['CLOUDFRONT_DISTRIBUTION_ID']

# List of SSM parameters to fetch
env_vars = {
'VITE_COG_APP_CLIENT_ID': get_ssm_parameter('/orcaui/cog_app_client_id_stage'),
'VITE_OAUTH_REDIRECT_IN': get_ssm_parameter('/orcaui/oauth_redirect_in_stage'),
'VITE_OAUTH_REDIRECT_OUT': get_ssm_parameter('/orcaui/oauth_redirect_out_stage'),
'VITE_COG_USER_POOL_ID': get_ssm_parameter('/data_portal/client/cog_user_pool_id'),
'VITE_COG_IDENTITY_POOL_ID': get_ssm_parameter('/data_portal/client/cog_identity_pool_id'),
'VITE_OAUTH_DOMAIN': get_ssm_parameter('/data_portal/client/oauth_domain'),
'VITE_UNSPLASH_CLIENT_ID': get_ssm_parameter('/data_portal/unsplash/client_id'),

'VITE_REGION': os.environ['VITE_REGION'],
'VITE_METADATA_URL': os.environ['VITE_METADATA_URL'],
'VITE_WORKFLOW_URL': os.environ['VITE_WORKFLOW_URL'],
'VITE_SEQUENCE_RUN_URL': os.environ['VITE_SEQUENCE_RUN_URL'],
'VITE_FILE_URL': os.environ['VITE_FILE_URL'],

# API Version
'VITE_METADATA_API_VERSION': os.environ.get('VITE_METADATA_API_VERSION', None),
'VITE_WORKFLOW_API_VERSION': os.environ.get('VITE_WORKFLOW_API_VERSION', None),
'VITE_SEQUENCE_RUN_API_VERSION': os.environ.get('VITE_SEQUENCE_RUN_API_VERSION', None),
'VITE_FILE_API_VERSION': os.environ.get('VITE_FILE_API_VERSION', None),
}
# Remove null values
env_vars = {k: v for k, v in env_vars.items() if v is not None}

env_js_content = f"window.config = {json.dumps(env_vars, indent=2)}"


try:
s3.put_object(Bucket=bucket_name, Key='env.js', Body=env_js_content, ContentType='text/javascript')

# invalidate cloudfront distribution for all files (clear cache)
cloudfront.create_invalidation(
DistributionId=cloudfront_distribution_id,
InvalidationBatch={
'Paths': {
'Quantity': 1,
'Items': ['/*']
},
'CallerReference': str(context.aws_request_id)
}
)

return {
'statusCode': 200,
'body': f" env.js uploaded to {bucket_name}, and CloudFront cache invalidated for {cloudfront_distribution_id}"
}
except Exception as e:
# Log the error and return a failure response
print("Error:")
print(e)
return {
'statusCode': 500,
'body': f"Failed to upload env.js to {bucket_name}. {e}"
}
2 changes: 1 addition & 1 deletion deploy/lib/application-stack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ export class ApplicationStack extends Stack {
functionName: props.configLambdaName,
code: Code.fromAsset(path.join(__dirname, '..', 'lambda')),
timeout: Duration.minutes(10),
handler: 'env_config.handler',
handler: 'env_config_and_cdn_refresh.handler',
logRetention: RetentionDays.ONE_WEEK,
runtime: Runtime.PYTHON_3_12,
architecture: Architecture.ARM_64,
Expand Down
Binary file added docs/orca-ui-dual-pipeline.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
14 changes: 12 additions & 2 deletions src/api/sequenceRun.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,27 @@ import createClient from 'openapi-fetch';
import type { paths, components } from './types/sequence-run';
import { useSuspenseQuery, useQuery } from '@tanstack/react-query';
import { authMiddleware, UseSuspenseQueryOptions, UseQueryOptions } from './utils';
import { env } from '@/utils/commonUtils';

const client = createClient<paths>({ baseUrl: config.apiEndpoint.sequenceRun });
client.use(authMiddleware);

const apiVersion = env.VITE_SEQUENCE_RUN_API_VERSION;

function getVersionedPath<K extends keyof paths>(path: K): K {
if (!apiVersion) return path;
return path.replace('/api/v1/', `/api/${apiVersion}/`) as K;
}

export function createSequenceRunFetchingHook<K extends keyof paths>(path: K) {
return function ({ params, reactQuery }: UseSuspenseQueryOptions<paths[typeof path]['get']>) {
const versionedPath = getVersionedPath(path);
return useSuspenseQuery({
...reactQuery,
queryKey: [path, params],
queryFn: async ({ signal }) => {
// @ts-expect-error: params is dynamic type type for openapi-fetch
const { data } = await client.GET(path, { params, signal });
const { data } = await client.GET(versionedPath, { params, signal });
return data;
},
});
Expand All @@ -23,12 +32,13 @@ export function createSequenceRunFetchingHook<K extends keyof paths>(path: K) {

export function createSequenceRunQueryHook<K extends keyof paths>(path: K) {
return function ({ params, reactQuery }: UseQueryOptions<paths[typeof path]['get']>) {
const versionedPath = getVersionedPath(path);
return useQuery({
...reactQuery,
queryKey: [path, params],
queryFn: async ({ signal }) => {
// @ts-expect-error: params is dynamic type type for openapi-fetch
const { data } = await client.GET(path, { params, signal });
const { data } = await client.GET(versionedPath, { params, signal });
return data;
},
});
Expand Down
Loading