Skip to content

Commit

Permalink
Val -> Prod (#772)
Browse files Browse the repository at this point in the history
  • Loading branch information
jessabean authored Sep 30, 2024
2 parents 5a44b68 + 45e8834 commit 11a3ac3
Show file tree
Hide file tree
Showing 86 changed files with 1,762 additions and 1,223 deletions.
53 changes: 3 additions & 50 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ on:
- "!skipci*"

concurrency:
group: ${{ github.workflow }}-${{ github.ref_name }}
group: ${{ github.ref_name }}

permissions:
id-token: write
Expand Down Expand Up @@ -108,38 +108,8 @@ jobs:
echo "application_endpoint=$APPLICATION_ENDPOINT" >> $GITHUB_OUTPUT
echo "## Application Endpoint" >> $GITHUB_STEP_SUMMARY
echo "<$APPLICATION_ENDPOINT>" >> $GITHUB_STEP_SUMMARY
- id: api_name
run: |
API_NAME=$(./scripts/output.sh app-api ApiGatewayRestApiName $STAGE_PREFIX$branch_name)
echo "api_name=$API_NAME" >> $GITHUB_OUTPUT
- id: branch_name
run: |
echo "branch_name=$branch_name" >> $GITHUB_OUTPUT
- id: cognito_user_pool_client_id
run: |
COGNITO_USER_POOL_CLIENT_ID=$(./scripts/output.sh ui-auth UserPoolClientId $STAGE_PREFIX$branch_name)
echo "cognito_user_pool_client_id=$COGNITO_USER_POOL_CLIENT_ID" >> $GITHUB_OUTPUT
- id: cognito_user_pool_id_encrypted
run: |
COGNITO_USER_POOL_ID=$(./scripts/output.sh ui-auth UserPoolId $STAGE_PREFIX$branch_name)
COGNITO_USER_POOL_ID_ENCRYPTED=$(echo -n "$COGNITO_USER_POOL_ID" | openssl enc -e -aes-256-cbc -a -pbkdf2 -iter 10000 -k "${{ secrets.CODE_CLIMATE_ID }}")
echo "cognito_user_pool_id_encrypted<<EOF" >> $GITHUB_OUTPUT
echo $COGNITO_USER_POOL_ID_ENCRYPTED >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
- id: region_encrypted
run: |
REGION=$(./scripts/output.sh app-api Region $STAGE_PREFIX$branch_name)
REGION_ENCRYPTED=$(echo -n "$REGION" | openssl enc -e -aes-256-cbc -a -pbkdf2 -iter 10000 -k "${{ secrets.CODE_CLIMATE_ID }}")
echo "region_encrypted<<EOF" >> $GITHUB_OUTPUT
echo $REGION_ENCRYPTED >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
outputs:
api_name: ${{ steps.api_name.outputs.api_name }}
application_endpoint: ${{ steps.endpoint.outputs.application_endpoint }}
branch_name: ${{ steps.branch_name.outputs.branch_name }}
cognito_user_pool_client_id: ${{ steps.cognito_user_pool_client_id.outputs.cognito_user_pool_client_id }}
cognito_user_pool_id_encrypted: ${{ steps.cognito_user_pool_id_encrypted.outputs.cognito_user_pool_id_encrypted }}
region_encrypted: ${{ steps.region_encrypted.outputs.region_encrypted }}
BRANCH_SPECIFIC_VARNAME_AWS_DEFAULT_REGION: ${{ steps.set_names.outputs.BRANCH_SPECIFIC_VARNAME_AWS_DEFAULT_REGION }}
BRANCH_SPECIFIC_VARNAME_AWS_OIDC_ROLE_TO_ASSUME: ${{ steps.set_names.outputs.BRANCH_SPECIFIC_VARNAME_AWS_OIDC_ROLE_TO_ASSUME }}

Expand Down Expand Up @@ -307,6 +277,8 @@ jobs:
needs:
- deploy
- register-runner
- e2e-test
- a11y-tests
if: ${{ always() && !cancelled() && needs.deploy.result == 'success' && github.ref_name != 'production' }}
timeout-minutes: 60
runs-on: ubuntu-latest
Expand All @@ -320,22 +292,6 @@ jobs:
- uses: actions/setup-node@v4
with:
node-version-file: ".nvmrc"
- name: Decrypt cognito_user_pool_id
run: |
COGNITO_USER_POOL_ID_ENCRYPTED=${{ needs.deploy.outputs.cognito_user_pool_id_encrypted }}
COGNITO_USER_POOL_ID_DECRYPTED=$(echo "$COGNITO_USER_POOL_ID_ENCRYPTED" | openssl base64 -d)
COGNITO_USER_POOL_ID=$(echo -n "$COGNITO_USER_POOL_ID_DECRYPTED" | openssl enc -d -aes-256-cbc -pbkdf2 -iter 10000 -k "${{ secrets.CODE_CLIMATE_ID }}")
echo "cognito_user_pool_id<<EOF" >> $GITHUB_ENV
echo $COGNITO_USER_POOL_ID >> $GITHUB_ENV
echo "EOF" >> $GITHUB_ENV
- name: Decrypt region
run: |
REGION_ENCRYPTED=${{ needs.deploy.outputs.region_encrypted }}
REGION_DECRYPTED=$(echo "$REGION_ENCRYPTED" | openssl base64 -d)
REGION=$(echo -n "$REGION_DECRYPTED" | openssl enc -d -aes-256-cbc -pbkdf2 -iter 10000 -k "${{ secrets.CODE_CLIMATE_ID }}")
echo "region<<EOF" >> $GITHUB_ENV
echo $REGION >> $GITHUB_ENV
echo "EOF" >> $GITHUB_ENV
- name: yarn install
run: yarn install
- name: Install Playwright Browsers
Expand All @@ -344,10 +300,7 @@ jobs:
run: yarn playwright test
continue-on-error: true
env:
API_URL: https://${{ needs.deploy.outputs.api_name }}.execute-api.${{ env.region }}.amazonaws.com/${{ needs.deploy.outputs.branch_name }}
BASE_URL: ${{ needs.deploy.outputs.application_endpoint }}
COGNITO_USER_POOL_CLIENT_ID: ${{ needs.deploy.outputs.cognito_user_pool_client_id }}
COGNITO_USER_POOL_ID: ${{ env.cognito_user_pool_id }}
CYPRESS_STATE_USER_EMAIL: ${{ secrets.CYPRESS_STATE_USER_EMAIL }}
CYPRESS_STATE_USER_PASSWORD: ${{ secrets.CYPRESS_STATE_USER_PASSWORD }}
CYPRESS_ADMIN_USER_EMAIL: ${{ secrets.CYPRESS_ADMIN_USER_EMAIL }}
Expand Down
17 changes: 17 additions & 0 deletions .github/workflows/destroy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ on:
description: "Name of the environment to destroy:"
required: true

concurrency:
group: ${{ inputs.environment || github.event.ref }}

permissions:
id-token: write
contents: read
Expand Down Expand Up @@ -63,3 +66,17 @@ jobs:
run: |
./run destroy --stage $STAGE_PREFIX$branch_name --verify false --service app-api
./run destroy --stage $STAGE_PREFIX$branch_name --verify false
# Notify the integrations channel when a destroy action fails
notify_on_destroy_failure:
runs-on: ubuntu-latest
needs:
- destroy
if: ${{ failure() }}
steps:
- name: Slack Notification
uses: rtCamp/action-slack-notify@v2
env:
SLACK_TITLE: ":boom: A destroy action has failed on ${{ github.repository }}."
MSG_MINIMAL: true
SLACK_WEBHOOK: ${{ secrets.INTEGRATIONS_SLACK_WEBHOOK }}
5 changes: 2 additions & 3 deletions .github/workflows/scan_security-hub-jira-integration.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,12 @@ jobs:
aws-region: ${{ secrets.AWS_DEFAULT_REGION }}
role-to-assume: ${{ secrets.PRODUCTION_SYNC_OIDC_ROLE }}
- name: Sync Security Hub and Jira
uses: Enterprise-CMCS/mac-fc-security-hub-visibility@v1.0.7
uses: Enterprise-CMCS/mac-fc-security-hub-visibility@v2.0.9
with:
jira-username: "mdct_github_service_account"
jira-token: ${{ secrets.JIRA_ENT_USER_TOKEN }}
jira-host: jiraent.cms.gov
jira-project-key: CMDCT
jira-ignore-statuses: Done, Closed, Canceled
jira-custom-fields: '{ "customfield_10100": "CMDCT-2280", "customfield_26700" : [{"id": "40105", "value": "MFP"}] }'
aws-severities: CRITICAL, HIGH, MEDIUM
assign-jira-ticket-to: "MWTW"
jira-assignee: "MWTW"
4 changes: 2 additions & 2 deletions playwright.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export default defineConfig({
testDir: "./tests/e2e",
testMatch: ["**/*.spec.js", "**/*.spec.ts"],
/* Run tests in files in parallel */
fullyParallel: true,
fullyParallel: false,
/* Fail the build on CI if you accidentally left test.only in the source code. */
forbidOnly: !!process.env.CI,
/* Retry on CI only */
Expand Down Expand Up @@ -47,7 +47,7 @@ export default defineConfig({
webServer: {
command: process.env.CI ? "" : "./run local",
url: process.env.BASE_URL || "http://localhost:3000",
reuseExistingServer: process.env.CI || false,
reuseExistingServer: !!process.env.CI,
stdout: "pipe",
},
});
2 changes: 1 addition & 1 deletion services/app-api/forms/wp.json
Original file line number Diff line number Diff line change
Expand Up @@ -562,7 +562,7 @@
"content": "Initiative close-out information, to be completed as appropriate during MFP Work Plan revisions",
"props": {
"style": {
"paddingTop": ".5rem"
"padding": ".5rem"
}
}
}
Expand Down
29 changes: 13 additions & 16 deletions services/app-api/handlers/banners/create.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import handler from "../handler-lib";
// utils
import dynamoDb from "../../utils/dynamo/dynamodb-lib";
import { hasPermissions } from "../../utils/auth/authorization";
import { error } from "../../utils/constants/constants";
// types
import { StatusCodes, UserRoles } from "../../utils/types";
import { number, object, string } from "yup";
import { validateData } from "../../utils/validation/validation";
import { putBanner } from "../../storage/banners";

export const createBanner = handler(async (event, _context) => {
if (!hasPermissions(event, [UserRoles.ADMIN])) {
Expand Down Expand Up @@ -34,22 +34,19 @@ export const createBanner = handler(async (event, _context) => {
);

if (validatedPayload) {
const params = {
TableName: process.env.BANNER_TABLE_NAME!,
Item: {
key: event.pathParameters.bannerId,
createdAt: Date.now(),
lastAltered: Date.now(),
lastAlteredBy: event?.headers["cognito-identity-id"],
title: validatedPayload.title,
description: validatedPayload.description,
link: validatedPayload.link,
startDate: validatedPayload.startDate,
endDate: validatedPayload.endDate,
},
const newBanner = {
key: event.pathParameters.bannerId,
createdAt: Date.now(),
lastAltered: Date.now(),
lastAlteredBy: event?.headers["cognito-identity-id"],
title: validatedPayload.title,
description: validatedPayload.description,
link: validatedPayload.link,
startDate: validatedPayload.startDate,
endDate: validatedPayload.endDate,
};
await dynamoDb.put(params);
return { status: StatusCodes.CREATED, body: params };
await putBanner(newBanner);
return { status: StatusCodes.CREATED, body: newBanner };
}
}
});
13 changes: 4 additions & 9 deletions services/app-api/handlers/banners/delete.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import handler from "../handler-lib";
// utils
import dynamoDb from "../../utils/dynamo/dynamodb-lib";
import { hasPermissions } from "../../utils/auth/authorization";
import { error } from "../../utils/constants/constants";
import { deleteBanner as deleteBannerById } from "../../storage/banners";
// types
import { StatusCodes, UserRoles } from "../../utils/types";

Expand All @@ -15,13 +15,8 @@ export const deleteBanner = handler(async (event, _context) => {
} else if (!event?.pathParameters?.bannerId!) {
throw new Error(error.NO_KEY);
} else {
const params = {
TableName: process.env.BANNER_TABLE_NAME!,
Key: {
key: event?.pathParameters?.bannerId!,
},
};
await dynamoDb.delete(params);
return { status: StatusCodes.SUCCESS, body: params };
const bannerId = event?.pathParameters?.bannerId!;
await deleteBannerById(bannerId);
return { status: StatusCodes.SUCCESS, body: { Key: bannerId } };
}
});
20 changes: 8 additions & 12 deletions services/app-api/handlers/banners/fetch.test.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
import { fetchBanner } from "./fetch";
import { DynamoDBDocumentClient, GetCommand } from "@aws-sdk/lib-dynamodb";
import { mockClient } from "aws-sdk-client-mock";
// utils
import { proxyEvent } from "../../utils/testing/proxyEvent";
import { error } from "../../utils/constants/constants";
import { mockBannerResponse } from "../../utils/testing/setupJest";
import { getBanner } from "../../storage/banners";
// types
import { APIGatewayProxyEvent, StatusCodes } from "../../utils/types";

const dynamoClientMock = mockClient(DynamoDBDocumentClient);

jest.mock("../../utils/auth/authorization", () => ({
isAuthorized: jest.fn().mockReturnValue(true),
}));

jest.mock("../../storage/banners", () => ({
getBanner: jest.fn(),
}));

const testEvent: APIGatewayProxyEvent = {
...proxyEvent,
headers: { "cognito-identity-id": "test" },
Expand All @@ -31,15 +32,12 @@ let consoleSpy: {
describe("Test fetchBanner API method", () => {
beforeEach(() => {
jest.restoreAllMocks();
dynamoClientMock.reset();
consoleSpy.debug = jest.spyOn(console, "debug").mockImplementation();
consoleSpy.error = jest.spyOn(console, "error").mockImplementation();
});

test("Test Successful Banner Fetch", async () => {
dynamoClientMock.on(GetCommand).resolves({
Item: mockBannerResponse,
});
(getBanner as jest.Mock).mockResolvedValueOnce(mockBannerResponse);
const res = await fetchBanner(testEvent, null);

expect(consoleSpy.debug).toHaveBeenCalled();
Expand All @@ -49,13 +47,11 @@ describe("Test fetchBanner API method", () => {
});

test("Test successful empty banner found fetch", async () => {
dynamoClientMock.on(GetCommand).resolves({
Item: undefined,
});
(getBanner as jest.Mock).mockResolvedValueOnce(undefined);
const res = await fetchBanner(testEvent, null);

expect(consoleSpy.debug).toHaveBeenCalled();
expect(res.body).not.toContain("testTitle");
expect(res.body).not.toBeDefined();
expect(res.statusCode).toBe(StatusCodes.SUCCESS);
});

Expand Down
15 changes: 4 additions & 11 deletions services/app-api/handlers/banners/fetch.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,15 @@
import handler from "../handler-lib";
import dynamoDb from "../../utils/dynamo/dynamodb-lib";
// types
import { StatusCodes } from "../../utils/types";
// utils
import { error } from "../../utils/constants/constants";
import { getBanner } from "../../storage/banners";

export const fetchBanner = handler(async (event, _context) => {
if (!event?.pathParameters?.bannerId!) {
throw new Error(error.NO_KEY);
}
const params = {
TableName: process.env.BANNER_TABLE_NAME!,
Key: {
key: event?.pathParameters?.bannerId!,
},
};
const response = await dynamoDb.get(params);

const status = StatusCodes.SUCCESS;
return { status: status, body: response };
const bannerId = event?.pathParameters?.bannerId!;
const banner = await getBanner(bannerId);
return { status: StatusCodes.SUCCESS, body: banner };
});
28 changes: 25 additions & 3 deletions services/app-api/handlers/reports/create.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,18 @@ import {
import { error } from "../../utils/constants/constants";
import * as authFunctions from "../../utils/auth/authorization";
import { getEligibleWorkPlan } from "../../utils/other/other";
import { putReportMetadata, putReportFieldData } from "../../storage/reports";
import {
queryReportMetadatasForState,
putReportMetadata,
putReportFieldData,
} from "../../storage/reports";
// types
import { APIGatewayProxyEvent, StatusCodes } from "../../utils/types";
import { copyFieldDataFromSource } from "../../utils/other/copy";
import { getOrCreateFormTemplate } from "../../utils/formTemplates/formTemplates";

jest.mock("../../storage/reports", () => ({
queryReportMetadatasForState: jest.fn(),
putReportFieldData: jest.fn(),
putReportMetadata: jest.fn(),
}));
Expand Down Expand Up @@ -66,7 +71,7 @@ const wpCreationEvent: APIGatewayProxyEvent = {
metadata: {
reportType: "WP",
reportYear: 2020,
reportPeriod: 2,
reportPeriod: 1,
submissionName: "submissionName",
status: "Not started",
lastAlteredBy: "Thelonious States",
Expand Down Expand Up @@ -198,6 +203,9 @@ describe("Test createReport API method", () => {
});

test("Test successful run of work plan report creation, not copied", async () => {
(queryReportMetadatasForState as jest.Mock).mockResolvedValue([
{ reportYear: 2020, reportPeriod: 1, archived: true },
]);
const res = await createReport(wpCreationEvent, null);
const body = JSON.parse(res.body);
expect(consoleSpy.debug).toHaveBeenCalled();
Expand All @@ -209,12 +217,23 @@ describe("Test createReport API method", () => {
mockWPReport.metadata.formTemplateId
);
expect(body.reportYear).toEqual(2020);
expect(body.reportPeriod).toEqual(2);
expect(body.reportPeriod).toEqual(1);
expect(putReportMetadata).toHaveBeenCalled();
expect(putReportFieldData).toHaveBeenCalled();
});

test("Test work plan report creation returns 400 if report in year and period exists", async () => {
(queryReportMetadatasForState as jest.Mock).mockResolvedValue([
{ reportYear: 2020, reportPeriod: 1, archived: undefined },
]);
const res = await createReport(wpCreationEvent, null);
expect(res.statusCode).toBe(400);
});

test("Test successful run of work plan report creation, copied", async () => {
(queryReportMetadatasForState as jest.Mock).mockResolvedValue([
{ reportYear: 2020, reportPeriod: 1, archived: undefined },
]);
(copyFieldDataFromSource as jest.Mock).mockResolvedValue(
mockReportFieldData
);
Expand Down Expand Up @@ -245,6 +264,9 @@ describe("Test createReport API method", () => {
workPlanMetadata: mockWPMetadata,
workPlanFieldData: mockWPFieldData,
});
(queryReportMetadatasForState as jest.Mock).mockResolvedValue([
{ reportYear: 2020, reportPeriod: 1, archived: true },
]);
const res = await createReport(sarCreationEvent, null);
const body = JSON.parse(res.body);
expect(consoleSpy.debug).toHaveBeenCalled();
Expand Down
Loading

0 comments on commit 11a3ac3

Please sign in to comment.