Add temp patch for CRUD ops race conditions

This commit is contained in:
Tuan Dang
2023-01-13 18:15:17 +07:00
parent 6992c51e17
commit cbd8302afe
2 changed files with 193 additions and 189 deletions

View File

@@ -67,8 +67,17 @@ export const createSecrets = async (req: Request, res: Response) => {
})) }))
); );
setTimeout(async () => {
// trigger event - push secrets
await EventService.handleEvent({
event: eventPushSecrets({
workspaceId
})
});
}, 5000);
// (EE) add secret versions for new secrets // (EE) add secret versions for new secrets
EESecretService.addSecretVersions({ await EESecretService.addSecretVersions({
secretVersions: newSecrets.map(({ secretVersions: newSecrets.map(({
_id, _id,
version, version,
@@ -104,13 +113,6 @@ export const createSecrets = async (req: Request, res: Response) => {
})) }))
}); });
// trigger event - push secrets
await EventService.handleEvent({
event: eventPushSecrets({
workspaceId
})
});
const addAction = await EELogService.createActionSecret({ const addAction = await EELogService.createActionSecret({
name: ACTION_ADD_SECRETS, name: ACTION_ADD_SECRETS,
userId: req.user._id.toString(), userId: req.user._id.toString(),
@@ -342,11 +344,13 @@ export const updateSecrets = async (req: Request, res: Response) => {
Object.keys(workspaceSecretObj).forEach(async (key) => { Object.keys(workspaceSecretObj).forEach(async (key) => {
// trigger event - push secrets // trigger event - push secrets
await EventService.handleEvent({ setTimeout(async () => {
event: eventPushSecrets({ await EventService.handleEvent({
workspaceId: key event: eventPushSecrets({
}) workspaceId: key
}); })
});
}, 10000);
const updateAction = await EELogService.createActionSecret({ const updateAction = await EELogService.createActionSecret({
name: ACTION_UPDATE_SECRETS, name: ACTION_UPDATE_SECRETS,

View File

@@ -306,188 +306,188 @@ const syncSecretsNetlify = async ({
}) => { }) => {
try { try {
interface NetlifyValue { interface NetlifyValue {
id?: string; id?: string;
context: string; // 'dev' | 'branch-deploy' | 'deploy-preview' | 'production', context: string; // 'dev' | 'branch-deploy' | 'deploy-preview' | 'production',
value: string; value: string;
} }
interface NetlifySecret { interface NetlifySecret {
key: string; key: string;
values: NetlifyValue[]; values: NetlifyValue[];
} }
interface NetlifySecretsRes { interface NetlifySecretsRes {
[index: string]: NetlifySecret; [index: string]: NetlifySecret;
} }
const getParams = new URLSearchParams({ const getParams = new URLSearchParams({
context_name: 'all', // integration.context or all context_name: 'all', // integration.context or all
site_id: integration.siteId site_id: integration.siteId
}); });
const res = (await axios.get( const res = (await axios.get(
`${INTEGRATION_NETLIFY_API_URL}/api/v1/accounts/${integrationAuth.accountId}/env`, `${INTEGRATION_NETLIFY_API_URL}/api/v1/accounts/${integrationAuth.accountId}/env`,
{ {
params: getParams, params: getParams,
headers: { headers: {
Authorization: `Bearer ${accessToken}` Authorization: `Bearer ${accessToken}`
} }
} }
)) ))
.data .data
.reduce((obj: any, secret: any) => ({ .reduce((obj: any, secret: any) => ({
...obj, ...obj,
[secret.key]: secret [secret.key]: secret
}), {}); }), {});
const newSecrets: NetlifySecret[] = []; // createEnvVars const newSecrets: NetlifySecret[] = []; // createEnvVars
const deleteSecrets: string[] = []; // deleteEnvVar const deleteSecrets: string[] = []; // deleteEnvVar
const deleteSecretValues: NetlifySecret[] = []; // deleteEnvVarValue const deleteSecretValues: NetlifySecret[] = []; // deleteEnvVarValue
const updateSecrets: NetlifySecret[] = []; // setEnvVarValue const updateSecrets: NetlifySecret[] = []; // setEnvVarValue
// identify secrets to create and update
Object.keys(secrets).map((key) => {
if (!(key in res)) {
// case: Infisical secret does not exist in Netlify -> create secret
newSecrets.push({
key,
values: [{
value: secrets[key],
context: integration.context
}]
});
} else {
// case: Infisical secret exists in Netlify
const contexts = res[key].values
.reduce((obj: any, value: NetlifyValue) => ({
...obj,
[value.context]: value
}), {});
if (integration.context in contexts) {
// case: Netlify secret value exists in integration context
if (secrets[key] !== contexts[integration.context].value) {
// case: Infisical and Netlify secret values are different
// -> update Netlify secret context and value
updateSecrets.push({
key,
values: [{
context: integration.context,
value: secrets[key]
}]
});
}
} else {
// case: Netlify secret value does not exist in integration context
// -> add the new Netlify secret context and value
updateSecrets.push({
key,
values: [{
context: integration.context,
value: secrets[key]
}]
});
}
}
})
// identify secrets to delete
// TODO: revise (patch case where 1 context was deleted but others still there
Object.keys(res).map((key) => {
// loop through each key's context
if (!(key in secrets)) {
// case: Netlify secret does not exist in Infisical
const numberOfValues = res[key].values.length;
res[key].values.forEach((value: NetlifyValue) => {
if (value.context === integration.context) {
if (numberOfValues <= 1) {
// case: Netlify secret value has less than 1 context -> delete secret
deleteSecrets.push(key);
} else {
// case: Netlify secret value has more than 1 context -> delete secret value context
deleteSecretValues.push({
key,
values: [{
id: value.id,
context: integration.context,
value: value.value
}]
});
}
}
});
}
});
// identify secrets to create and update const syncParams = new URLSearchParams({
Object.keys(secrets).map((key) => { site_id: integration.siteId
if (!(key in res)) { });
// case: Infisical secret does not exist in Netlify -> create secret
newSecrets.push({
key,
values: [{
value: secrets[key],
context: integration.context
}]
});
} else {
// case: Infisical secret exists in Netlify
const contexts = res[key].values
.reduce((obj: any, value: NetlifyValue) => ({
...obj,
[value.context]: value
}), {});
if (integration.context in contexts) {
// case: Netlify secret value exists in integration context
if (secrets[key] !== contexts[integration.context].value) {
// case: Infisical and Netlify secret values are different
// -> update Netlify secret context and value
updateSecrets.push({
key,
values: [{
context: integration.context,
value: secrets[key]
}]
});
}
} else {
// case: Netlify secret value does not exist in integration context
// -> add the new Netlify secret context and value
updateSecrets.push({
key,
values: [{
context: integration.context,
value: secrets[key]
}]
});
}
}
})
// identify secrets to delete
// TODO: revise (patch case where 1 context was deleted but others still there
Object.keys(res).map((key) => {
// loop through each key's context
if (!(key in secrets)) {
// case: Netlify secret does not exist in Infisical
const numberOfValues = res[key].values.length;
res[key].values.forEach((value: NetlifyValue) => {
if (value.context === integration.context) {
if (numberOfValues <= 1) {
// case: Netlify secret value has less than 1 context -> delete secret
deleteSecrets.push(key);
} else {
// case: Netlify secret value has more than 1 context -> delete secret value context
deleteSecretValues.push({
key,
values: [{
id: value.id,
context: integration.context,
value: value.value
}]
});
}
}
});
}
});
const syncParams = new URLSearchParams({ if (newSecrets.length > 0) {
site_id: integration.siteId await axios.post(
}); `${INTEGRATION_NETLIFY_API_URL}/api/v1/accounts/${integrationAuth.accountId}/env`,
newSecrets,
{
params: syncParams,
headers: {
Authorization: `Bearer ${accessToken}`
}
}
);
}
if (newSecrets.length > 0) { if (updateSecrets.length > 0) {
await axios.post( updateSecrets.forEach(async (secret: NetlifySecret) => {
`${INTEGRATION_NETLIFY_API_URL}/api/v1/accounts/${integrationAuth.accountId}/env`, await axios.patch(
newSecrets, `${INTEGRATION_NETLIFY_API_URL}/api/v1/accounts/${integrationAuth.accountId}/env/${secret.key}`,
{ {
params: syncParams, context: secret.values[0].context,
headers: { value: secret.values[0].value
Authorization: `Bearer ${accessToken}` },
} {
} params: syncParams,
); headers: {
} Authorization: `Bearer ${accessToken}`
}
}
);
});
}
if (updateSecrets.length > 0) { if (deleteSecrets.length > 0) {
updateSecrets.forEach(async (secret: NetlifySecret) => { deleteSecrets.forEach(async (key: string) => {
await axios.patch( await axios.delete(
`${INTEGRATION_NETLIFY_API_URL}/api/v1/accounts/${integrationAuth.accountId}/env/${secret.key}`, `${INTEGRATION_NETLIFY_API_URL}/api/v1/accounts/${integrationAuth.accountId}/env/${key}`,
{ {
context: secret.values[0].context, params: syncParams,
value: secret.values[0].value headers: {
}, Authorization: `Bearer ${accessToken}`
{ }
params: syncParams, }
headers: { );
Authorization: `Bearer ${accessToken}` });
} }
}
);
});
}
if (deleteSecrets.length > 0) { if (deleteSecretValues.length > 0) {
deleteSecrets.forEach(async (key: string) => { deleteSecretValues.forEach(async (secret: NetlifySecret) => {
await axios.delete( await axios.delete(
`${INTEGRATION_NETLIFY_API_URL}/api/v1/accounts/${integrationAuth.accountId}/env/${key}`, `${INTEGRATION_NETLIFY_API_URL}/api/v1/accounts/${integrationAuth.accountId}/env/${secret.key}/value/${secret.values[0].id}`,
{ {
params: syncParams, params: syncParams,
headers: { headers: {
Authorization: `Bearer ${accessToken}` Authorization: `Bearer ${accessToken}`
} }
} }
); );
}); });
} }
if (deleteSecretValues.length > 0) {
deleteSecretValues.forEach(async (secret: NetlifySecret) => {
await axios.delete(
`${INTEGRATION_NETLIFY_API_URL}/api/v1/accounts/${integrationAuth.accountId}/env/${secret.key}/value/${secret.values[0].id}`,
{
params: syncParams,
headers: {
Authorization: `Bearer ${accessToken}`
}
}
);
});
}
} catch (err) { } catch (err) {
Sentry.setUser(null); Sentry.setUser(null);
Sentry.captureException(err); Sentry.captureException(err);
throw new Error('Failed to sync secrets to Heroku'); throw new Error('Failed to sync secrets to Heroku');
} }
} }