Skip to content

Commit 02be96a

Browse files
authored
Merge pull request #703 from adobe/issue/tail-logs-refresh-token
fix(auth): tail logs refresh token #702
2 parents 96fed0e + 23a7474 commit 02be96a

File tree

5 files changed

+114
-4
lines changed

5 files changed

+114
-4
lines changed

src/ValidationErrors.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,3 +61,4 @@ E('INVALID_TAG_SYNTAX', 'tag flag should not be specified with "refs/tags/" pref
6161
E('JSON_PARSE_NUMBER', 'parsed flag value as a number')
6262
E('BLANK_VARIABLE_VALUE', 'Blank variable values are not allowed. Use the proper flag if you intend to delete a variable.')
6363
E('MALFORMED_NAME_VALUE_PAIR', 'Please provide correct values for flags')
64+
E('MAX_RETRY_REACHED', 'Max retries reached')

src/cloudmanager-helpers.js

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ const Config = require('@adobe/aio-lib-core-config')
1414
const { init } = require('@adobe/aio-lib-cloudmanager')
1515
const { cli } = require('cli-ux')
1616
const { context, getToken, Ims } = require('@adobe/aio-lib-ims')
17+
const logger = require('@adobe/aio-lib-core-logging')('@adobe/aio-lib-cloudmanager', { provider: 'debug' })
1718
const moment = require('moment')
1819
const _ = require('lodash')
1920
const { CLI } = require('@adobe/aio-lib-ims/src/context')
@@ -306,6 +307,37 @@ function handleError (_error, errorFn) {
306307
})
307308
}
308309

310+
async function executeWithRetries (fn, maxRetries = 5) {
311+
let retries = 0
312+
let startTime = Date.now()
313+
while (retries < maxRetries) {
314+
try {
315+
return await fn()
316+
} catch (error) {
317+
if (error.sdkDetails && error.sdkDetails.response && (error.sdkDetails.response.status === 401 || error.sdkDetails.response.status === 403)) {
318+
logger.debug('Received 401, 403 error, retrying.')
319+
} else {
320+
throw error
321+
}
322+
if (shouldResetRetires(startTime)) {
323+
retries = 0
324+
startTime = Date.now()
325+
}
326+
retries++
327+
}
328+
}
329+
throw new validationCodes.MAX_RETRY_REACHED()
330+
}
331+
332+
function shouldResetRetires (startTime, resetInterval = 3600000) {
333+
const elapsedTime = Date.now() - startTime
334+
if (elapsedTime >= resetInterval) {
335+
logger.debug(`Resetting retries after ${resetInterval / 1000} seconds.`)
336+
return true
337+
}
338+
return false
339+
}
340+
309341
module.exports = {
310342
getProgramId,
311343
getOutputFormat,
@@ -328,4 +360,5 @@ module.exports = {
328360
getActiveOrganizationId,
329361
getFullOrgIdentity,
330362
handleError,
363+
executeWithRetries,
331364
}

src/commands/cloudmanager/environment/tail-log.js

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ governing permissions and limitations under the License.
1111
*/
1212

1313
const BaseCommand = require('../../../base-command')
14-
const { initSdk, getProgramId, sanitizeEnvironmentId } = require('../../../cloudmanager-helpers')
14+
const { initSdk, getProgramId, sanitizeEnvironmentId, executeWithRetries } = require('../../../cloudmanager-helpers')
1515
const commonFlags = require('../../../common-flags')
1616
const commonArgs = require('../../../common-args')
1717

@@ -31,8 +31,10 @@ class TailLog extends BaseCommand {
3131
}
3232

3333
async tailLog (programId, environmentId, service, logName, imsContextName = null) {
34-
const sdk = await initSdk(imsContextName)
35-
return sdk.tailLog(programId, environmentId, service, logName, process.stdout)
34+
return executeWithRetries(async () => {
35+
const sdk = await initSdk(imsContextName)
36+
return sdk.tailLog(programId, environmentId, service, logName, process.stdout)
37+
})
3638
}
3739
}
3840

test/cloudmanager-helpers.test.js

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ governing permissions and limitations under the License.
1212

1313
const { setCurrentOrgId, context } = require('@adobe/aio-lib-ims')
1414
const { setStore } = require('@adobe/aio-lib-core-config')
15-
const { initSdk, getOutputFormat, columnWithArray, disableCliAuth, enableCliAuth, formatDuration } = require('../src/cloudmanager-helpers')
15+
const { initSdk, getOutputFormat, columnWithArray, disableCliAuth, enableCliAuth, formatDuration, executeWithRetries } = require('../src/cloudmanager-helpers')
1616
const { init } = require('@adobe/aio-lib-cloudmanager')
1717
const { setDecodedToken, resetDecodedToken } = require('jsonwebtoken')
1818

@@ -133,3 +133,66 @@ test('formatDuration -- only finished', async () => {
133133
finishedAt: '2021-05-01',
134134
})).toEqual('')
135135
})
136+
137+
describe('executeWithRetries', () => {
138+
afterEach(() => {
139+
jest.restoreAllMocks()
140+
})
141+
142+
test('should not retry when no 401, 403 thrown', async () => {
143+
const mockFn = jest.fn().mockResolvedValue('success')
144+
145+
executeWithRetries(mockFn)
146+
147+
expect(mockFn.mock.calls.length).toEqual(1)
148+
})
149+
150+
test('should retry when 401 thrown', async () => {
151+
const mockFn = jest.fn()
152+
.mockRejectedValueOnce({ sdkDetails: { response: { status: 401 } } })
153+
.mockResolvedValue('success')
154+
155+
await executeWithRetries(mockFn)
156+
157+
expect(mockFn.mock.calls.length).toEqual(2)
158+
})
159+
160+
test('should retry when 403 thrown', async () => {
161+
const mockFn = jest.fn()
162+
.mockRejectedValueOnce({ sdkDetails: { response: { status: 401 } } })
163+
.mockResolvedValue('success')
164+
165+
await executeWithRetries(mockFn)
166+
167+
expect(mockFn.mock.calls.length).toEqual(2)
168+
})
169+
170+
test('should throw error when no 401, 403 thrown', async () => {
171+
const mockFn = jest.fn()
172+
.mockRejectedValue(new Error())
173+
174+
await expect(executeWithRetries(mockFn)).rejects.toThrow()
175+
176+
expect(mockFn.mock.calls.length).toEqual(1)
177+
})
178+
179+
test('should retry 5 times and throw exception', async () => {
180+
const mockFn = jest.fn().mockRejectedValue({ sdkDetails: { response: { status: 401 } } })
181+
182+
await expect(executeWithRetries(mockFn)).rejects.toThrow('[CloudManagerCLI:MAX_RETRY_REACHED] Max retries')
183+
184+
expect(mockFn.mock.calls.length).toEqual(5)
185+
})
186+
187+
test('should reset retry counter after 1 hour', async () => {
188+
jest.spyOn(Date, 'now')
189+
.mockReturnValueOnce(new Date(Date.UTC(2024, 11, 1, 0, 0, 0)))
190+
.mockReturnValueOnce(new Date(Date.UTC(2024, 11, 1, 0, 0, 0)))
191+
.mockReturnValue(new Date(Date.UTC(2024, 11, 1, 1, 0, 0)))
192+
const mockFn = jest.fn().mockRejectedValue({ sdkDetails: { response: { status: 401 } } })
193+
194+
await expect(executeWithRetries(mockFn)).rejects.toThrow('[CloudManagerCLI:MAX_RETRY_REACHED] Max retries')
195+
196+
expect(mockFn.mock.calls.length).toEqual(6)
197+
})
198+
})

test/commands/environment/tail-log.test.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,3 +47,14 @@ test('tail-log - config', async () => {
4747
await expect(mockSdk.tailLog.mock.calls.length).toEqual(1)
4848
await expect(mockSdk.tailLog).toHaveBeenCalledWith('5', '17', 'author', 'aemerror', process.stdout)
4949
})
50+
51+
test('tail-log - should retry 5 times and throw error', async () => {
52+
setCurrentOrgId('good')
53+
mockSdk.tailLog.mockRejectedValue({ sdkDetails: { response: { status: 401 } } })
54+
55+
expect.assertions(2)
56+
57+
const runResult = TailLog.run(['17', 'author', 'aemerror', '--programId', '5'])
58+
await expect(runResult).rejects.toThrow('[CloudManagerCLI:MAX_RETRY_REACHED] Max retries reached')
59+
await expect(mockSdk.tailLog.mock.calls.length).toEqual(5)
60+
})

0 commit comments

Comments
 (0)