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

DOP-4599: Deploy-all slack button deploys all active versions of repos #1042

Open
wants to merge 46 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 35 commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
544843d
DOP-4599 logging
anabellabuckvar May 7, 2024
cb4e403
DOP-4599 add a comment for future reference
anabellabuckvar May 8, 2024
dcd0287
DOP-4599 reconfigure deploy all
anabellabuckvar May 9, 2024
0f135d3
DOP-4599 fix naming
anabellabuckvar May 9, 2024
de56312
DOP-4599 push to preprd
anabellabuckvar May 9, 2024
299f50b
DOP-4599 fixing errors
anabellabuckvar May 9, 2024
a9178df
DOP-4599 fixing admin deploy all
anabellabuckvar May 10, 2024
9d07f14
DOP-4599 fixing project name
anabellabuckvar May 10, 2024
b34ce94
DOP-4599 logging
anabellabuckvar May 10, 2024
7eaad4d
DOP-4599 change snooty frontend version
anabellabuckvar May 10, 2024
c5b0ec2
Merge branch 'main' into DOP-4599
anabellabuckvar May 10, 2024
1c2b892
DOP-4599 change snooty frontend version
anabellabuckvar May 10, 2024
ea820e1
DOP-4599 fix entitledbranches building
anabellabuckvar May 10, 2024
9af98cb
DOP-4599 logging
anabellabuckvar May 10, 2024
551c9c8
DOP-4599 fixing env var issues
anabellabuckvar May 10, 2024
c0e5341
DOP-4599 logging
anabellabuckvar May 10, 2024
7086c63
DOP-4599 fix db name
anabellabuckvar May 10, 2024
e22f65f
DOP-4599 remove logging
anabellabuckvar May 10, 2024
a89d735
DOP-4599 remove logging
anabellabuckvar May 10, 2024
5eec23f
DOP-4599 push to preprd and remove extraneous param
anabellabuckvar May 13, 2024
59c7d6b
DOP-4599 fix snooty frontend version
anabellabuckvar May 13, 2024
de648ad
DOP-4599 push to preprd, check env vars functionality
anabellabuckvar May 20, 2024
929f904
DOP-4599 undo env var change
anabellabuckvar May 20, 2024
8e2018d
DOP-4599 initialize repo option
anabellabuckvar May 20, 2024
a9a5af6
DOP-4599 logging
anabellabuckvar May 21, 2024
476c5e8
DOP-4599 change append method
anabellabuckvar May 21, 2024
b3c67e7
DOP-4599 change optional
anabellabuckvar May 21, 2024
8633599
DOP-4599 remove optional
anabellabuckvar May 21, 2024
9c96bfb
Merge branch 'main' into DOP-4599
anabellabuckvar May 22, 2024
12ac0ac
DOP-4599 add debug option
anabellabuckvar May 22, 2024
a2bae05
DOP-4599 add debug option
anabellabuckvar May 22, 2024
03aaeb3
DOP-4599 cleaning up
anabellabuckvar May 23, 2024
0b608ac
DOP-4599 logging and try catches
anabellabuckvar May 23, 2024
685fff4
DOP-4599 logging
anabellabuckvar May 23, 2024
6f2da5f
DOP-4599 logging
anabellabuckvar May 23, 2024
45cdaa4
DOP-4599 cleaning up logging and error handling
anabellabuckvar May 28, 2024
8a7935b
DOP-4599 nits
anabellabuckvar May 30, 2024
5598d46
DOP-4599 return for deploy all
anabellabuckvar May 31, 2024
88a39ff
DOP-4599 remove console error logging
anabellabuckvar May 31, 2024
d9be310
DOP-4599 fix unecessary entitlements errors
anabellabuckvar Jun 5, 2024
25b5e1e
DOP-4599 logging
anabellabuckvar Jun 5, 2024
dbf9c6e
DOP-4599 logging
anabellabuckvar Jun 6, 2024
e063139
DOP-4599 logging
anabellabuckvar Jun 6, 2024
8c37f85
DOP-4599 re-push to preprd
anabellabuckvar Jun 14, 2024
fc2f934
DOP-4599 logging
anabellabuckvar Jun 14, 2024
59277a5
DOP-4599 remove from preprd for testing
anabellabuckvar Jun 14, 2024
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
3 changes: 2 additions & 1 deletion .github/workflows/deploy-stg-ecs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ on:
branches:
- "main"
- "integration"
- "DOP-4599"
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will be deleted upon PR approval

concurrency:
group: environment-stg-${{ github.ref }}
cancel-in-progress: true
Expand All @@ -16,7 +17,7 @@ jobs:
with:
node-version: '18.x'
- name: Install Serverless Framework
run: npm install -g serverless
run: npm install -g serverless@3
- name: Serverless AWS authentication
run: sls config credentials --provider aws --key ${{ secrets.AWS_ACCESS_KEY_ID }} --secret ${{ secrets.AWS_SECRET_ACCESS_KEY }}
- name: Configure AWS credentials
Expand Down
92 changes: 59 additions & 33 deletions api/controllers/v1/slack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import {
} from '../../handlers/slack';
import { DocsetsRepository } from '../../../src/repositories/docsetsRepository';
import { Payload } from '../../../src/entities/job';
import { ProjectsRepository } from '../../../src/repositories/projectsRepository';
import DOCS_METADATA from '../../../src/constants';

export const DisplayRepoOptions = async (event: APIGatewayEvent): Promise<APIGatewayProxyResult> => {
const consoleLogger = new ConsoleLogger();
Expand All @@ -34,21 +36,34 @@ export const DisplayRepoOptions = async (event: APIGatewayEvent): Promise<APIGat
const client = new mongodb.MongoClient(c.get('dbUrl'));
await client.connect();
const db = client.db(process.env.DB_NAME);
const projectsRepository = new ProjectsRepository(client.db(DOCS_METADATA), c, consoleLogger);
const repoEntitlementRepository = new RepoEntitlementsRepository(db, c, consoleLogger);
const repoBranchesRepository = new RepoBranchesRepository(db, c, consoleLogger);
const key_val = getQSString(event.body);
const entitlement = await repoEntitlementRepository.getRepoEntitlementsBySlackUserId(key_val['user_id']);
if (!isUserEntitled(entitlement) || isRestrictedToDeploy(key_val['user_id'])) {
const { restrictedProdDeploy } = c.get<any>('prodDeploy');
const response = restrictedProdDeploy
? 'Production freeze in place - please notify DOP if seeing this past 3/26'
: 'User is not entitled!';
return prepResponse(401, 'text/plain', response);
}

const isAdmin = await repoEntitlementRepository.getIsAdmin(key_val['user_id']);
let entitledRepos: any[] = [];
//if user has admin permissions, they can deploy all repo branches
if (isAdmin) {
const repos = await repoBranchesRepository.getProdDeployableRepoBranches();
for (const repo of repos) {
const projectEntry = await projectsRepository.getProjectEntry(repo.project);
const repoOwner = projectEntry?.github?.organization;
if (repoOwner) entitledRepos.push(`${repoOwner}/${repo.repoName}`);
}
} else {
const entitlements = await repoEntitlementRepository.getRepoEntitlementsBySlackUserId(key_val['user_id']);
if (!isUserEntitled(entitlements) || isRestrictedToDeploy(key_val['user_id'])) {
const { restrictedProdDeploy } = c.get<any>('prodDeploy');
const response = restrictedProdDeploy
? 'Production freeze in place - please notify DOP if seeing this past 3/26'
: 'User is not entitled!';
return prepResponse(401, 'text/plain', response);
}
entitledRepos = entitlements.repos;
}

const entitledBranches = await buildEntitledGroupsList(entitlement, repoBranchesRepository);
const entitledBranches = await buildEntitledGroupsList(entitledRepos, repoBranchesRepository);
const resp = await slackConnector.displayRepoOptions(entitledBranches, key_val['trigger_id'], isAdmin);
if (resp?.status == 200 && resp?.data?.ok) {
return {
Expand All @@ -63,15 +78,17 @@ export const DisplayRepoOptions = async (event: APIGatewayEvent): Promise<APIGat
};

async function deployRepo(deployable: Array<any>, logger: ILogger, jobRepository: JobRepository, jobQueueUrl) {
console.log('in little deploy repo');
try {
await jobRepository.insertBulkJobs(deployable, jobQueueUrl);
} catch (err) {
logger.error('deployRepo', err);
console.error('Deploy repo error');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we still log the error? It can sometimes be such a blessing.

Copy link
Contributor Author

@anabellabuckvar anabellabuckvar May 30, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Happy to, but it won't make much of a difference in this case! The only place we can view the output for the Slack Deploy Repo lambda is in its corresponding CloudWatch group. The output for console.error looks like the following, and logging the error would just change the format slightly:
Screenshot 2024-05-30 at 2 52 48 PM

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fascinating! I wonder if that's always the case... I lean towards keeping the error logged just in case it can sometimes be helpful. But good catch!

}
}

// Used solely for adding parallel deploy jobs to another array
const deployHelper = (deployable, payload, jobTitle, jobUserName, jobUserEmail) => {
console.log('inside deploy helper!');
deployable.push(createJob({ ...payload }, jobTitle, jobUserName, jobUserEmail));
};

Expand All @@ -86,25 +103,19 @@ export const getDeployableJobs = async (
const deployable = [];

for (let i = 0; i < values?.repo_option?.length; i++) {
let jobTitle: string, repoOwner: string, repoName: string, branchName: string, directory: string | undefined;
if (values.deploy_option == 'deploy_all') {
repoOwner = 'mongodb';
branchName = 'master';
repoName = values.repo_option[i].repoName;
jobTitle = `Slack deploy: ${repoOwner}/${repoName}/${branchName}, by ${entitlement.github_username}`;
let repoOwner: string, repoName: string, branchName: string, directory: string | undefined;
const splitValues = values.repo_option[i].value.split('/');
const jobTitle = `Slack deploy: ${values.repo_option[i].value}, by ${entitlement.github_username}`;
console.log(jobTitle);
if (splitValues.length === 3) {
// e.g. mongodb/docs-realm/master => (owner/repo/branch)
[repoOwner, repoName, branchName] = splitValues;
console.log('repoOwner: ', repoOwner, ' repoName:', repoName, 'branchName: ', branchName);
} else if (splitValues.length === 4 && process.env.FEATURE_FLAG_MONOREPO_PATH === 'true') {
// e.g. 10gen/docs-monorepo/cloud-docs/master => (owner/monorepo/repoDirectory/branch)
[repoOwner, repoName, directory, branchName] = splitValues;
} else {
const splitValues = values.repo_option[i].value.split('/');
jobTitle = `Slack deploy: ${values.repo_option[i].value}, by ${entitlement.github_username}`;

if (splitValues.length === 3) {
// e.g. mongodb/docs-realm/master => (owner/repo/branch)
[repoOwner, repoName, branchName] = splitValues;
} else if (splitValues.length === 4 && process.env.FEATURE_FLAG_MONOREPO_PATH === 'true') {
// e.g. 10gen/docs-monorepo/cloud-docs/master => (owner/monorepo/repoDirectory/branch)
[repoOwner, repoName, directory, branchName] = splitValues;
} else {
throw Error('Selected entitlement value is configured incorrectly. Check user entitlements!');
}
throw Error('Selected entitlement value is configured incorrectly. Check user entitlements!');
}

const hashOption = values?.hash_option ?? null;
Expand All @@ -114,7 +125,9 @@ export const getDeployableJobs = async (
const repoInfo = await docsetsRepository.getRepo(repoName, directory);
const non_versioned = repoInfo.branches.length === 1;

console.log(repoName, branchName, repoInfo.project);
const branchObject = await repoBranchesRepository.getRepoBranchAliases(repoName, branchName, repoInfo.project);
console.log(JSON.stringify(branchObject));
if (!branchObject?.aliasObject) continue;

const publishOriginalBranchName: boolean = branchObject.aliasObject.publishOriginalBranchName;
Expand Down Expand Up @@ -142,7 +155,7 @@ export const getDeployableJobs = async (
directory
);

if (!aliases || aliases.length === 0) {
if (!aliases || aliases.length) {
rayangler marked this conversation as resolved.
Show resolved Hide resolved
if (non_versioned) {
newPayload.urlSlug = '';
}
Expand Down Expand Up @@ -207,23 +220,36 @@ export const DeployRepo = async (event: any = {}): Promise<any> => {
return prepResponse(200, 'text/plain', 'Form not submitted, will not process request');
}

console.log('parsed type:', parsed.type);
const entitlement = await repoEntitlementRepository.getRepoEntitlementsBySlackUserId(parsed.user.id);
if (!isUserEntitled(entitlement)) {
console.log('User is not entitled');
return prepResponse(401, 'text/plain', 'User is not entitled!');
}

let values = [];
const isAdmin = await repoEntitlementRepository.getIsAdmin(parsed.user.id);
const optionGroups = parsed.view.blocks[0]?.element?.option_groups;
try {
values = await slackConnector.parseSelection(stateValues, isAdmin, repoBranchesRepository);
values = await slackConnector.parseSelection(stateValues, isAdmin, optionGroups);
console.log(JSON.stringify(values));
} catch (e) {
console.log(`Error parsing selection: ${e}`);
return prepResponse(401, 'text/plain', e);
}
const deployable = await getDeployableJobs(values, entitlement, repoBranchesRepository, docsetsRepository);

let deployable;
try {
deployable = await getDeployableJobs(values, entitlement, repoBranchesRepository, docsetsRepository);
console.log(JSON.stringify(deployable));
} catch (e) {
console.log(JSON.stringify(deployable), e, 'error within get deployable jobs');
}
if (deployable.length > 0) {
await deployRepo(deployable, consoleLogger, jobRepository, c.get('jobsQueueUrl'));
try {
await deployRepo(deployable, consoleLogger, jobRepository, c.get('jobsQueueUrl'));
} catch (e) {
console.log(e, 'error deploying repo');
}
}
return {
statusCode: 200,
Expand Down
10 changes: 5 additions & 5 deletions api/handlers/slack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ export function prepResponse(statusCode, contentType, body) {
}

//if person is admin, get all prod deployable repos
export async function buildEntitledGroupsList(entitlement: any, repoBranchesRepository: RepoBranchesRepository) {
const repoOptions: any[] = [];
for (const repo of entitlement.repos) {
export async function buildEntitledGroupsList(entitledRepos: any, repoBranchesRepository: RepoBranchesRepository) {
const entitledBranches: any[] = [];
for (const repo of entitledRepos) {
const [repoOwner, repoName, directoryPath] = repo.split('/');
const branches = await repoBranchesRepository.getRepoBranches(repoName, directoryPath);

Expand Down Expand Up @@ -62,10 +62,10 @@ export async function buildEntitledGroupsList(entitlement: any, repoBranchesRepo
.localeCompare(branchOne.text.text.toString().replace(/\d+/g, (n) => +n + 100000))
),
};
repoOptions.push(repoOption);
entitledBranches.push(repoOption);
}
}
return repoOptions.sort((repoOne, repoTwo) => repoOne.label.text.localeCompare(repoTwo.label.text));
return entitledBranches.sort((repoOne, repoTwo) => repoOne.label.text.localeCompare(repoTwo.label.text));
}

export function getQSString(qs: string) {
Expand Down
3 changes: 3 additions & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
const DOCS_METADATA = 'docs_metadata';

export default DOCS_METADATA;
rayangler marked this conversation as resolved.
Show resolved Hide resolved
2 changes: 1 addition & 1 deletion src/repositories/projectsRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { ILogger } from '../services/logger';

export class ProjectsRepository extends BaseRepository {
constructor(db: mongodb.Db, config: IConfig, logger: ILogger) {
super(config, logger, 'ProjectsRepository', db.collection(process.env.PROJECTS_COL_NAME || ''));
super(config, logger, 'ProjectsRepository', db.collection(process.env.PROJECTS_COL_NAME || 'projects'));
}

async getProjectEntry(name: string): Promise<mongodb.WithId<mongodb.BSON.Document> | null> {
Expand Down
7 changes: 5 additions & 2 deletions src/repositories/repoBranchesRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,10 @@ export class RepoBranchesRepository extends BaseRepository {

async getProdDeployableRepoBranches(): Promise<Document[]> {
const reposArray = await this._collection
.aggregate([{ $match: { prodDeployable: true, internalOnly: false } }, { $project: { _id: 0, repoName: 1 } }])
.aggregate([
{ $match: { prodDeployable: true, internalOnly: false } },
{ $project: { _id: 0, repoName: 1, project: 1 } },
])
.toArray();
return reposArray ?? [];
}
Expand All @@ -49,7 +52,7 @@ export class RepoBranchesRepository extends BaseRepository {
{ $project: { branches: 1 } },
])
.toArray();

console.log(JSON.stringify(aliasArray));
if (aliasArray.length === 1) {
returnObject['aliasObject'] = aliasArray[0].branches;
returnObject.status = 'success';
Expand Down
22 changes: 14 additions & 8 deletions src/services/slack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import axios from 'axios';
import { ILogger } from './logger';
import { IConfig } from 'config';
import * as crypto from 'crypto';
import { RepoBranchesRepository } from '../repositories/repoBranchesRepository';
export const axiosApi = axios.create();

function bufferEqual(a: Buffer, b: Buffer) {
Expand All @@ -24,7 +23,7 @@ function timeSafeCompare(a: string, b: string) {
export interface ISlackConnector {
validateSlackRequest(payload: any): boolean;
displayRepoOptions(repos: Array<string>, triggerId: string, isAdmin: boolean): Promise<any>;
parseSelection(payload: any, isAdmin: boolean, repoBranchesRepository: RepoBranchesRepository): any;
parseSelection(payload: any, isAdmin: boolean, optionGroups: any[]): any;
sendMessage(message: any, user: string): Promise<any>;
}

Expand Down Expand Up @@ -54,11 +53,7 @@ export class SlackConnector implements ISlackConnector {
return {};
}

async parseSelection(
stateValues: any,
isAdmin: boolean,
repoBranchesRepository: RepoBranchesRepository
): Promise<any> {
async parseSelection(stateValues: any, isAdmin: boolean, optionGroups: any[]): Promise<any> {
const values = {};
const inputMapping = {
block_repo_option: 'repo_option',
Expand All @@ -72,7 +67,17 @@ export class SlackConnector implements ISlackConnector {
}

values['deploy_option'] = 'deploy_all';
values['repo_option'] = await repoBranchesRepository.getProdDeployableRepoBranches();
//go through all options in dropdown by option group
//append version to repo_option if active
values['repo_option'] = [];
for (const group of optionGroups) {
values['repo_option'].push(
...group.options.map((option) => {
if (!option.text.text.startsWith('(!inactive)')) return option;
})
);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should use the filter method for this rather than map so that we don't end up with undefined values in the array

}
console.log(JSON.stringify(values['repo_option']));
return values;
}

Expand Down Expand Up @@ -207,6 +212,7 @@ export class SlackConnector implements ISlackConnector {
},
option_groups: repos,
},
optional: true,
},
{
type: 'input',
Expand Down
Loading