Skip to content

Commit

Permalink
Mv config task execution to GH Actions
Browse files Browse the repository at this point in the history
  • Loading branch information
alukach committed Oct 25, 2024
1 parent 2e88f6f commit 0ee970a
Show file tree
Hide file tree
Showing 5 changed files with 1,677 additions and 12 deletions.
14 changes: 12 additions & 2 deletions .github/workflows/deploy.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,10 @@ jobs:
role-duration-seconds: 900 # Adjust as necessary

- name: Setup Node.js
uses: actions/setup-node@v3
uses: actions/setup-node@v4
with:
node-version: 18 # Use the node version matching your project
cache: 'npm'

- name: Install dependencies
run: |
Expand All @@ -38,7 +39,7 @@ jobs:
- name: Deploy CDK to dev environment
run: |
cdk deploy --require-approval never
cdk deploy --require-approval never --outputs-file outputs.json
env:
AWS_ACCOUNT_ID: ${{ vars.AWS_ACCOUNT_ID }}
AWS_REGION: ${{ vars.AWS_REGION }}
Expand All @@ -47,3 +48,12 @@ jobs:
KEYCLOAK_VERSION: ${{ vars.KEYCLOAK_VERSION }}
SSL_CERTIFICATE_ARN: ${{ vars.SSL_CERTIFICATE_ARN }}
STAGE: ${{ vars.STAGE }}

- name: Get ConfigLambdaArn from CloudFormation
id: get-lambda-arn
run: |
ARN=$(jq -r .ConfigLambdaArn outputs.json)
echo "::set-output name=ConfigLambdaArn::$ARN"
- name: Run Apply Config
run: npm run apply-config "${{ steps.get-lambda-arn.outputs.ConfigLambdaArn }}"
146 changes: 146 additions & 0 deletions config/apply-config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
import { LambdaClient, InvokeCommand } from "@aws-sdk/client-lambda";
import {
ECSClient,
DescribeTasksCommand,
DescribeTaskDefinitionCommand,
} from "@aws-sdk/client-ecs";
import {
CloudWatchLogsClient,
GetLogEventsCommand,
} from "@aws-sdk/client-cloudwatch-logs";

async function main() {
const [_, __, lambdaArn] = process.argv;

// Set up AWS clients
const lambda = new LambdaClient({});
const ecs = new ECSClient({});
const logs = new CloudWatchLogsClient({});

// Step 1: Invoke the Lambda function
let taskArn, clusterArn;
try {
const response = await lambda.send(
new InvokeCommand({
FunctionName: lambdaArn,
InvocationType: "RequestResponse",
Payload: new TextEncoder().encode(JSON.stringify({})),
})
);

const payload = JSON.parse(new TextDecoder().decode(response.Payload));
taskArn = payload.taskArn;
clusterArn = payload.clusterArn;
console.log(
`Invoked Lambda function. Received taskArn=${taskArn}, clusterArn=${clusterArn}`
);
} catch (error) {
console.error(`Error invoking Lambda function: ${error}`);
process.exit(1);
}

// Step 2: Poll the ECS task until it reaches 'STOPPED' status
let task;
try {
while (true) {
const response = await ecs.send(
new DescribeTasksCommand({
cluster: clusterArn,
tasks: [taskArn],
})
);

if (!response.tasks || response.tasks.length === 0) {
console.error(`No tasks found with taskArn: ${taskArn}`);
process.exit(1);
}

task = response.tasks[0];
const lastStatus = task.lastStatus;
console.log(`Task status: ${lastStatus}`);

if (lastStatus === "STOPPED") break;
await new Promise((resolve) => setTimeout(resolve, 5000));
}
} catch (error) {
console.error(`Error polling ECS task: ${error}`);
process.exit(1);
}

// Retrieve the exit code from the task's containers
const exitCode = task.containers.find(
(container) => "exitCode" in container
)?.exitCode;

if (exitCode === undefined) {
console.error("Could not retrieve exit code from the ECS task.");
process.exit(1);
}

// Step 3: Retrieve log configuration from the task definition
let logGroup, logStreamName, region;
try {
const taskDefinitionArn = task.taskDefinitionArn;
const response = await ecs.send(
new DescribeTaskDefinitionCommand({
taskDefinition: taskDefinitionArn,
})
);

const containerDefinition = response.taskDefinition.containerDefinitions[0];
const logConfiguration = containerDefinition.logConfiguration || {};

if (logConfiguration.logDriver !== "awslogs") {
console.error("Log driver is not 'awslogs'.");
process.exit(1);
}

const options = logConfiguration.options || {};
logGroup = options["awslogs-group"];
const logStreamPrefix = options["awslogs-stream-prefix"];
region = options["awslogs-region"];

const containerName = containerDefinition.name;
const taskId = taskArn.split("/").pop();
logStreamName = `${logStreamPrefix}/${containerName}/${taskId}`;
} catch (error) {
console.error(`Error retrieving log configuration: ${error}`);
process.exit(1);
}

// Step 4: Retrieve and print the CloudWatch logs
try {
const logsClient = new CloudWatchLogsClient({ region });
let nextToken;
const events = [];

while (true) {
const params = {
logGroupName: logGroup,
logStreamName: logStreamName,
startFromHead: true,
...(nextToken && { nextToken }),
};

const response = await logsClient.send(new GetLogEventsCommand(params));
events.push(...response.events);

if (!response.nextForwardToken || response.nextForwardToken === nextToken)
break;
nextToken = response.nextForwardToken;
}

events.forEach((event) => console.log(event.message));
} catch (error) {
console.error(`Error retrieving CloudWatch logs: ${error}`);
process.exit(1);
}

// Exit with the exit code from the ECS task
process.exit(exitCode);
}

main().catch((error) => {
console.error(error);
process.exit(1);
});
16 changes: 7 additions & 9 deletions deploy/lib/KeycloakConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ export class KeycloakConfig extends Construct {
},
});

const runTaskLambda = new lambda.Function(this, "RunTaskLambda", {
const applyConfigLambda = new lambda.Function(this, "ApplyConfigLambda", {
code: lambda.Code.fromInline(`
const { ECSClient, RunTaskCommand } = require('@aws-sdk/client-ecs');
Expand All @@ -93,7 +93,8 @@ export class KeycloakConfig extends Construct {
try {
const result = await ecsClient.send(new RunTaskCommand(params));
console.log('ECS RunTask result:', result);
return { status: 'Task started' };
const { taskArn, clusterArn } = result.tasks[0];
return { taskArn, clusterArn };
} catch (error) {
console.error('Error running ECS task:', error);
throw new Error('Failed to start ECS task');
Expand All @@ -105,14 +106,11 @@ export class KeycloakConfig extends Construct {
timeout: cdk.Duration.minutes(5),
});

configTaskDef.grantRun(runTaskLambda);
configTaskDef.grantRun(applyConfigLambda);

const provider = new customResources.Provider(this, "Provider", {
onEventHandler: runTaskLambda,
});

new cdk.CustomResource(this, "TriggerConfigTask", {
serviceToken: provider.serviceToken,
new cdk.CfnOutput(this, "ConfigLambdaArn", {
key: "ConfigLambdaArn",
value: applyConfigLambda.functionArn,
});
}
}
Loading

0 comments on commit 0ee970a

Please sign in to comment.