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

Closed
wants to merge 49 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 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
478512c
Merge branch 'main' into DOP-4599
anabellabuckvar Sep 13, 2024
089e06f
DOP-4599 push to preprd
anabellabuckvar Sep 13, 2024
7920ecb
DOP-4599 remove from preprd
anabellabuckvar Sep 13, 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
97 changes: 57 additions & 40 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 @@ -66,7 +81,7 @@ async function deployRepo(deployable: Array<any>, logger: ILogger, jobRepository
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!

}
}

Expand All @@ -86,25 +101,17 @@ 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}`;
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 {
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 @@ -115,11 +122,12 @@ export const getDeployableJobs = async (
const non_versioned = repoInfo.branches.length === 1;

const branchObject = await repoBranchesRepository.getRepoBranchAliases(repoName, branchName, repoInfo.project);
if (!branchObject?.aliasObject) continue;
if (branchObject.status == 'failure' || !branchObject?.aliasObject)
return prepResponse(401, 'text/plain', 'Branch not found in repos branches repository');

const publishOriginalBranchName: boolean = branchObject.aliasObject.publishOriginalBranchName;
const aliases: string[] | null = branchObject.aliasObject.urlAliases;
let urlSlug: string = branchObject.aliasObject.urlSlug; // string or null, string must match value in urlAliases or gitBranchName
let urlSlug: string = branchObject?.aliasObject.urlSlug; // string or null, string must match value in urlAliases or gitBranchName
const isStableBranch = !!branchObject.aliasObject.isStableBranch; // bool or Falsey, add strong typing

if (!urlSlug || !urlSlug.trim()) {
Expand All @@ -142,7 +150,7 @@ export const getDeployableJobs = async (
directory
);

if (!aliases || aliases.length === 0) {
if (!aliases || !aliases.length) {
if (non_versioned) {
newPayload.urlSlug = '';
}
Expand Down Expand Up @@ -214,21 +222,30 @@ export const DeployRepo = async (event: any = {}): Promise<any> => {

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);
} 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);
} catch (e) {
return prepResponse(401, 'text/plain', `${e} error within get deployable jobs`);
}
if (deployable.length > 0) {
await deployRepo(deployable, consoleLogger, jobRepository, c.get('jobsQueueUrl'));
try {
console.log('deploying repos');
deployRepo(deployable, consoleLogger, jobRepository, c.get('jobsQueueUrl'));
return {
statusCode: 200,
headers: { 'Content-Type': 'application/json' },
};
} catch (e) {
return prepResponse(401, 'text/plain', `${e} error deploying repos`);
}
}
return {
statusCode: 200,
headers: { 'Content-Type': 'application/json' },
};
};

function createPayload(
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
1 change: 1 addition & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const DOCS_METADATA = 'docs_metadata';
1 change: 1 addition & 0 deletions src/enhanced/utils/job/handle-job.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ export async function handleJob(jobId: string, db: mongodb.Db) {

try {
await jobManager.startSpecificJob(jobId);
console.log('in enhanced app handle job');
} catch (err) {
consoleLogger.info('enhancedApp', err);
}
Expand Down
8 changes: 8 additions & 0 deletions src/job/jobValidator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,18 @@ export class JobValidator implements IJobValidator {
}

async throwIfUserNotEntitled(job: Job): Promise<void> {
try {
const admin = await this._repoEntitlementRepository.getIsAdmin(job.user);
if (admin) return;
} catch (e) {
throw new InvalidJobError(`Invalid job user: ${job.user}`);
}
const entitlementsObject = await this._repoEntitlementRepository.getRepoEntitlementsByGithubUsername(job.user);
const entitlementToFind = `${job.payload.repoOwner}/${job.payload.repoName}${
job.payload.repoName === MONOREPO_NAME ? `/${job.payload.directory}` : ``
}`;
if (!entitlementsObject?.repos?.includes(entitlementToFind)) {
console.log(`Auth error for ${job}`);
throw new AuthorizationError(`${job.user} is not entitled for repo ${entitlementToFind}`);
}
}
Expand Down Expand Up @@ -69,6 +76,7 @@ export class JobValidator implements IJobValidator {
public async throwIfJobInvalid(job: Job): Promise<void> {
this._validateInput(job);
if (this.isProd(job.payload.jobType)) {
console.log(`checking if user is entitled for job ${job}`);
await this.throwIfUserNotEntitled(job);
}
}
Expand Down
1 change: 1 addition & 0 deletions src/onDemandApp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ async function init(): Promise<void> {
);
try {
await jobManager.startSpecificJob(c.get('jobId'));
console.log('in onDemandApp');
} catch (err) {
consoleLogger.info('onDemandApp', err);
}
Expand Down
2 changes: 2 additions & 0 deletions src/repositories/baseRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,13 @@ export abstract class BaseRepository {

protected async insertMany(docs: Array<any>, errorMsg: string): Promise<any> {
try {
console.log('inserting many', JSON.stringify(docs));
const insertManyResult = await this.promiseTimeoutS(
this._config.get('MONGO_TIMEOUT_S'),
this._collection.insertMany(docs),
errorMsg
);
console.log(JSON.stringify(insertManyResult));
if (insertManyResult?.insertedIds) {
return insertManyResult.insertedIds;
}
Expand Down
2 changes: 1 addition & 1 deletion src/repositories/jobRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ export class JobRepository extends BaseRepository {
// Insertion/re-enqueueing should be sent to jobs queue and updates for an existing job should be sent to jobUpdates Queue
this._logger.info(
'insertBulkJobs',
`Total Jobs Expected : ${jobs.length}, Jobs: ${JSON.stringify(jobIds)}, Total Jobs Sent: ${jobIds.length}`
`Total Jobs Expected : ${jobs.length}, Jobs: ${JSON.stringify(jobIds)}, Total Jobs Sent: ${jobIds.keys().length}`
);
await Promise.all(
Object.values(jobIds).map(async (jobId: string) => {
Expand Down
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
6 changes: 4 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,6 @@ export class RepoBranchesRepository extends BaseRepository {
{ $project: { branches: 1 } },
])
.toArray();

if (aliasArray.length === 1) {
returnObject['aliasObject'] = aliasArray[0].branches;
returnObject.status = 'success';
Expand Down
11 changes: 9 additions & 2 deletions src/repositories/repoEntitlementsRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,12 +47,19 @@ export class RepoEntitlementsRepository extends BaseRepository {
}
}

async getIsAdmin(slackUserId: string): Promise<boolean> {
const query = { slack_user_id: slackUserId };
async getIsAdmin(slackUserId?: string, githubUsername?: string): Promise<boolean> {
if (!arguments.length) {
throw new Error('getIsAdmin function must be given at least one argument');
}
let query;
if (slackUserId) {
query = { slack_user_id: slackUserId };
} else query = { github_username: githubUsername };
const entitlementsObject = await this.findOne(
query,
`Mongo Timeout Error: Timedout while retrieving entitlements for ${slackUserId}`
);
console.log(`getIsAdmin function was called, ${entitlementsObject?.admin}`);
return entitlementsObject?.admin;
}

Expand Down
17 changes: 9 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,12 @@ 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.filter((option) => !option.text.text.startsWith('(!inactive)')));
}
return values;
}

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