Skip to content

Commit

Permalink
Merge pull request #6 from twan-westerhof/main
Browse files Browse the repository at this point in the history
fix: add terminateOperation flag to /sync call
  • Loading branch information
Lucas-Desouza authored Aug 15, 2024
2 parents 53ad92f + e816d1e commit 054b9d6
Show file tree
Hide file tree
Showing 6 changed files with 211 additions and 2 deletions.
5 changes: 5 additions & 0 deletions .changeset/bright-queens-raise.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@roadiehq/backstage-plugin-argo-cd': patch
---

Adding the terminateOperation flag to the /sync endpoint. It is an optional boolean flag that can be set to true and it will terminate the previous Sync in progress before starting a new sync. This is a non-breaking change because if the flag is not provided and set to true the existing logic will not be impacted.
Original file line number Diff line number Diff line change
Expand Up @@ -496,6 +496,7 @@ export class ArgoService implements ArgoServiceApi {

async resyncAppOnAllArgos({
appSelector,
terminateOperation,
}: ResyncProps): Promise<SyncResponse[][]> {
const argoAppResp: findArgoAppResp[] = await this.findArgoApp({
selector: appSelector,
Expand All @@ -506,6 +507,19 @@ export class ArgoService implements ArgoServiceApi {
try {
const token = await this.getArgoToken(argoInstance);
try {
if (terminateOperation) {
const terminateResp = argoInstance.appName.map(
(argoApp: any): Promise<any> => {
return this.terminateArgoAppOperation({
baseUrl: argoInstance.url,
argoAppName: argoApp,
argoToken: token,
});
},
);
await Promise.all(terminateResp);
}

const resp = argoInstance.appName.map(
(argoApp: any): Promise<SyncResponse> => {
return this.syncArgoApp({
Expand All @@ -524,7 +538,6 @@ export class ArgoService implements ArgoServiceApi {
}
},
);

return await Promise.all(parallelSyncCalls);
}
return [];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -805,6 +805,123 @@ describe('ArgoCD service', () => {
],
]);
});

it('should sync all apps with terminateOperation flag on', async () => {
// findArgoApp
fetchMock.mockResponseOnce(
JSON.stringify({
items: [
{
metadata: {
name: 'testAppName',
namespace: 'testNamespace',
},
},
],
}),
);
// token
fetchMock.mockResponseOnce(
JSON.stringify({
token: 'testToken',
}),
);
// terminateOperation
fetchMock.mockResponseOnce(JSON.stringify({}), { status: 200 });
// sync
fetchMock.mockResponseOnce('{}');
const resp = await argoService.resyncAppOnAllArgos({
appSelector: 'testApp',
terminateOperation: true,
});

expect(resp).toStrictEqual([
[
{
message: 'Re-synced testAppName on argoInstance1',
status: 'Success',
},
],
]);
});

it('should fail to sync all apps when terminateOperation fails', async () => {
// findArgoApp
fetchMock.mockResponseOnce(
JSON.stringify({
items: [
{
metadata: {
name: 'testAppName',
namespace: 'testNamespace',
},
},
],
}),
);
// token
fetchMock.mockResponseOnce(
JSON.stringify({
token: 'testToken',
}),
);
// terminateOperation
fetchMock.mockResponseOnce(JSON.stringify({ status: 400 }));
// sync
fetchMock.mockResponseOnce(JSON.stringify({}), { status: 400 });

const resp = await argoService.resyncAppOnAllArgos({
appSelector: 'testApp',
terminateOperation: true,
});

expect(resp).toStrictEqual([
[
{
message: 'Failed to resync testAppName on argoInstance1',
status: 'Failure',
},
],
]);
});

it('should not sync all apps when terminateOperation errors', async () => {
// findArgoApp
fetchMock.mockResponseOnce(
JSON.stringify({
items: [
{
metadata: {
name: 'testAppName',
namespace: 'testNamespace',
},
},
],
}),
);
// token
fetchMock.mockResponseOnce(
JSON.stringify({
token: 'testToken',
}),
);
// terminateOperation
fetchMock.mockRejectOnce(new Error('Failed on terminateOperation step'));

const resp = await argoService.resyncAppOnAllArgos({
appSelector: 'testApp',
terminateOperation: true,
});

expect(resp).toStrictEqual([
[
{
message: 'Failed on terminateOperation step',
status: 'Failure',
},
],
]);
});
});

describe('deleteAppandProject', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ const mockGetArgoInstanceArray = jest.fn();
const mockUpdateArgoProjectAndApp = jest.fn();
const mockGetArgoApplicationInfo = jest.fn();
const mockTerminateArgoAppOperation = jest.fn();
const mockResyncAppOnAllArgos = jest.fn();
jest.mock('./argocd.service', () => {
return {
ArgoService: jest.fn().mockImplementation(() => {
Expand All @@ -33,6 +34,7 @@ jest.mock('./argocd.service', () => {
updateArgoProjectAndApp: mockUpdateArgoProjectAndApp,
getArgoApplicationInfo: mockGetArgoApplicationInfo,
terminateArgoAppOperation: mockTerminateArgoAppOperation,
resyncAppOnAllArgos: mockResyncAppOnAllArgos,
};
}),
};
Expand Down Expand Up @@ -347,6 +349,72 @@ describe('router', () => {
});
});

describe('POST /sync', () => {
it('succeeds in syncing argo application', async () => {
const mockedResponseObj = {
status: 'Success',
message: 'Re-synced application on argoInstance',
};
mockResyncAppOnAllArgos.mockResolvedValueOnce(mockedResponseObj);

const resp = await request(app).post('/sync').send({
appSelector: 'backstage-name:application',
terminateOperation: true,
});

expect(resp.body).toEqual(
expect.objectContaining({
status: 'Success',
message: 'Re-synced application on argoInstance',
}),
);
expect(resp.status).toBe(200);
});

it('fails to sync because of some error', async () => {
const mockedResponseObj = {
message: 'Failed to sync your app, backstage-name:application.',
status: 500,
};
mockResyncAppOnAllArgos.mockRejectedValueOnce(new Error());

const resp = await request(app).post('/sync').send({
appSelector: 'backstage-name:application',
});

expect(resp.body).toEqual(mockedResponseObj);
});

it('succeeds in syncing multiple argo applications', async () => {
const mockedResponseObj = {
status: 'Success',
message: 'Re-synced application on argoInstance',
};
mockResyncAppOnAllArgos.mockResolvedValueOnce([
mockedResponseObj,
mockedResponseObj,
]);

const resp = await request(app).post('/sync').send({
appSelector: 'backstage-name:application',
});

expect(resp.body).toEqual(
expect.objectContaining([
{
status: 'Success',
message: 'Re-synced application on argoInstance',
},
{
status: 'Success',
message: 'Re-synced application on argoInstance',
},
]),
);
expect(resp.status).toBe(200);
});
});

describe('DELETE /argoInstance/:argoInstanceName/applications/:argoAppName/operation', () => {
it('succeeds in terminating current operation', async () => {
const mockedResponseObj = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -353,8 +353,13 @@ export function createRouter({
});
router.post('/sync', async (request, response) => {
const appSelector = request.body.appSelector;
const terminateOperation: boolean =
Boolean(request.body.terminateOperation) ?? false;
try {
const argoSyncResp = await argoSvc.resyncAppOnAllArgos({ appSelector });
const argoSyncResp = await argoSvc.resyncAppOnAllArgos({
appSelector,
terminateOperation,
});
return response.send(argoSyncResp);
} catch (e: any) {
return response.status(e.status || 500).send({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ export interface SyncArgoApplicationProps {

export interface ResyncProps {
appSelector: string;
terminateOperation?: boolean;
}

export interface ArgoServiceApi {
Expand Down

0 comments on commit 054b9d6

Please sign in to comment.