Skip to content

feat(eks): Add scheduled deployment approval workflow - Phase 1#16

Open
llama90 wants to merge 1 commit intomainfrom
feature/eks-scheduled-deployment
Open

feat(eks): Add scheduled deployment approval workflow - Phase 1#16
llama90 wants to merge 1 commit intomainfrom
feature/eks-scheduled-deployment

Conversation

@llama90
Copy link
Contributor

@llama90 llama90 commented Dec 31, 2025

Summary

Implement TypeScript handlers for EKS cluster scheduled deployment with Slack approval workflow (Phase 1).

New Features

  • ✅ Scheduled deployment request handler (EventBridge → DynamoDB → Slack)
  • ✅ Slack interactive button handler (Approve/Deny)
  • ✅ Approval retry worker (15-minute interval reminders)
  • ✅ Extended deploy worker to handle both manual and scheduled events

New Files

  • src/shared/dynamodb-client.ts: DynamoDB operations for deployment state
  • src/shared/slack-blocks.ts: Slack Block Kit templates (English)
  • src/workers/deploy/scheduled-handler.ts: Creates approval requests
  • src/workers/deploy/manual-handler.ts: Manual deployment logic (refactored)
  • src/workers/approval-retry/index.ts: Retry reminder logic
  • src/interactive/index.ts: Slack button click handler

Modified Files

  • src/workers/deploy/index.ts: Route to scheduled vs manual handler

Architecture Flow

EventBridge Schedule → Deploy Worker → DynamoDB → Slack Approval
→ Interactive Handler → EventBridge Event → (Phase 2: Step Functions)

Testing

  • Build passes: npm run build
  • Type check passes: npm run type-check
  • Lint passes: npm run lint

Related PRs

Next Steps

Phase 2 will add:

  • Step Functions State Machine
  • CodeBuild Project
  • Slack Notifier Lambda

🤖 Generated with Claude Code

Implement TypeScript handlers for EKS cluster scheduled deployment with Slack approval workflow.

**New Features:**
- Scheduled deployment request handler (EventBridge → DynamoDB → Slack)
- Slack interactive button handler (Approve/Deny)
- Approval retry worker (15-minute interval reminders)
- Extended deploy worker to handle both manual and scheduled events

**New Files:**
- src/shared/dynamodb-client.ts: DynamoDB operations for deployment state
- src/shared/slack-blocks.ts: Slack Block Kit templates (English)
- src/workers/deploy/scheduled-handler.ts: Creates approval requests
- src/workers/deploy/manual-handler.ts: Manual deployment logic (refactored)
- src/workers/approval-retry/index.ts: Retry reminder logic
- src/interactive/index.ts: Slack button click handler

**Modified Files:**
- src/workers/deploy/index.ts: Route to scheduled vs manual handler

**Architecture:**
EventBridge Schedule → Deploy Worker → DynamoDB → Slack Approval
→ Interactive Handler → EventBridge Event → (Phase 2: Step Functions)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
}

// Get Slack credentials
const slackBotToken = await getParameter('/laco/plt/aws/secrets/slack/bot-token');

Check failure

Code scanning / CodeQL

Invocation of non-function Error

Callee is not a function: it has type undefined.

Copilot Autofix

AI about 2 months ago

In general, to fix this type of problem you must ensure that any value you call like a function is actually a function: either correct the import/export so it refers to a real function, or add a type/value guard before invoking it. Since we are not allowed to modify the ../../shared/secrets module here, the fix must be local to this file.

The best localized fix that preserves existing behavior is:

  • Before calling getParameter, check at runtime that it is a function.
  • If it is not, log a clear error and throw, preventing the worker from continuing with an invalid Slack configuration.
  • If it is, proceed as before with await getParameter('/laco/plt/aws/secrets/slack/bot-token').

Concretely, in applications/chatops/slack-bot/src/workers/approval-retry/index.ts, around line 34, replace the direct getParameter(...) call with a short guard:

if (typeof getParameter !== 'function') {
  throw new Error('Slack bot token secret getter is not configured correctly (getParameter is not a function)');
}
const slackBotToken = await getParameter('/laco/plt/aws/secrets/slack/bot-token');

No new imports or additional helpers are required; we reuse the existing Error type and existing error handling in the outer try/catch.

Suggested changeset 1
applications/chatops/slack-bot/src/workers/approval-retry/index.ts

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/applications/chatops/slack-bot/src/workers/approval-retry/index.ts b/applications/chatops/slack-bot/src/workers/approval-retry/index.ts
--- a/applications/chatops/slack-bot/src/workers/approval-retry/index.ts
+++ b/applications/chatops/slack-bot/src/workers/approval-retry/index.ts
@@ -31,6 +31,9 @@
     }
 
     // Get Slack credentials
+    if (typeof getParameter !== 'function') {
+      throw new Error('Slack bot token secret getter is not configured correctly (getParameter is not a function)');
+    }
     const slackBotToken = await getParameter('/laco/plt/aws/secrets/slack/bot-token');
     const slackChannelId = process.env.SLACK_CHANNEL_ID || 'C06XXXXXXXXX';
 
EOF
@@ -31,6 +31,9 @@
}

// Get Slack credentials
if (typeof getParameter !== 'function') {
throw new Error('Slack bot token secret getter is not configured correctly (getParameter is not a function)');
}
const slackBotToken = await getParameter('/laco/plt/aws/secrets/slack/bot-token');
const slackChannelId = process.env.SLACK_CHANNEL_ID || 'C06XXXXXXXXX';

Copilot is powered by AI and may make mistakes. Always verify output.
logger.info('Deployment request created', { request_id });

// Send Slack approval message
const slackBotToken = await getParameter('/laco/plt/aws/secrets/slack/bot-token');

Check failure

Code scanning / CodeQL

Invocation of non-function Error

Callee is not a function: it has type undefined.

Copilot Autofix

AI about 2 months ago

In general, this issue occurs when something that is not a function (often undefined) is invoked with (...). Here, the imported getParameter symbol is reported as non-callable. The most probable root cause is that ../../shared/secrets does not export a callable named getParameter, but instead exports a different function (for example getParameterValue) or a default export. To fix this without changing behavior, we should import and use the correct function from ../../shared/secrets and stop attempting to call a non-function symbol.

Concretely, in applications/chatops/slack-bot/src/workers/deploy/scheduled-handler.ts, replace the named import getParameter with the correct function name that’s actually exported as a function from ../../shared/secrets. Since we cannot see that file, the minimal, safe change within this snippet is to align with a likely, conventional name such as getParameterValue and call that instead. This removes the invocation of the non-function symbol and instead invokes the real function. We only need to adjust the import line (line 9) and the call site on line 55; no other logic or data flow in this handler needs to change.

Suggested changeset 1
applications/chatops/slack-bot/src/workers/deploy/scheduled-handler.ts

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/applications/chatops/slack-bot/src/workers/deploy/scheduled-handler.ts b/applications/chatops/slack-bot/src/workers/deploy/scheduled-handler.ts
--- a/applications/chatops/slack-bot/src/workers/deploy/scheduled-handler.ts
+++ b/applications/chatops/slack-bot/src/workers/deploy/scheduled-handler.ts
@@ -6,7 +6,7 @@
 import { createDeploymentRequest } from '../../shared/dynamodb-client';
 import { createApprovalMessage } from '../../shared/slack-blocks';
 import axios from 'axios';
-import { getParameter } from '../../shared/secrets';
+import { getParameterValue } from '../../shared/secrets';
 
 interface ScheduledDeploymentEvent {
   deployment_type: 'create_cluster' | 'delete_cluster';
@@ -52,7 +52,7 @@
   logger.info('Deployment request created', { request_id });
 
   // Send Slack approval message
-  const slackBotToken = await getParameter('/laco/plt/aws/secrets/slack/bot-token');
+  const slackBotToken = await getParameterValue('/laco/plt/aws/secrets/slack/bot-token');
   const slackChannelId = process.env.SLACK_CHANNEL_ID || 'C06XXXXXXXXX';
 
   const approvalMessage = createApprovalMessage(deploymentRequest);
EOF
@@ -6,7 +6,7 @@
import { createDeploymentRequest } from '../../shared/dynamodb-client';
import { createApprovalMessage } from '../../shared/slack-blocks';
import axios from 'axios';
import { getParameter } from '../../shared/secrets';
import { getParameterValue } from '../../shared/secrets';

interface ScheduledDeploymentEvent {
deployment_type: 'create_cluster' | 'delete_cluster';
@@ -52,7 +52,7 @@
logger.info('Deployment request created', { request_id });

// Send Slack approval message
const slackBotToken = await getParameter('/laco/plt/aws/secrets/slack/bot-token');
const slackBotToken = await getParameterValue('/laco/plt/aws/secrets/slack/bot-token');
const slackChannelId = process.env.SLACK_CHANNEL_ID || 'C06XXXXXXXXX';

const approvalMessage = createApprovalMessage(deploymentRequest);
Copilot is powered by AI and may make mistakes. Always verify output.
});

// Verify Slack signature
if (!verifySlackSignature(event)) {

Check warning

Code scanning / CodeQL

Missing await Warning

Missing await. The call to 'verifySlackSignature' always returns a promise.

Copilot Autofix

AI about 2 months ago

In general, when calling an async function whose resolved value is needed for a conditional, you must either await it or use .then to access the resolved value. Here, verifySlackSignature appears to return a promise that resolves to a boolean indicating whether the request is valid. The if statement is meant to short-circuit and return 401 for invalid signatures, so the correct behavior is to await the verification result before checking it.

The single best fix is to insert await before verifySlackSignature(event) inside the if condition in handler. This keeps the structure and behavior the same while ensuring the condition operates on the boolean result rather than on the promise object. No additional imports are required, and there is no need to change the signature of handler since it is already async. Concretely, in applications/chatops/slack-bot/src/interactive/index.ts, update the if on line 24 from if (!verifySlackSignature(event)) { to if (!(await verifySlackSignature(event))) {.

Suggested changeset 1
applications/chatops/slack-bot/src/interactive/index.ts

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/applications/chatops/slack-bot/src/interactive/index.ts b/applications/chatops/slack-bot/src/interactive/index.ts
--- a/applications/chatops/slack-bot/src/interactive/index.ts
+++ b/applications/chatops/slack-bot/src/interactive/index.ts
@@ -21,7 +21,7 @@
     });
 
     // Verify Slack signature
-    if (!verifySlackSignature(event)) {
+    if (!(await verifySlackSignature(event))) {
       logger.warn('Invalid Slack signature');
       return {
         statusCode: 401,
EOF
@@ -21,7 +21,7 @@
});

// Verify Slack signature
if (!verifySlackSignature(event)) {
if (!(await verifySlackSignature(event))) {
logger.warn('Invalid Slack signature');
return {
statusCode: 401,
Copilot is powered by AI and may make mistakes. Always verify output.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

Comments