[READ-ONLY] Mirror of https://github.com/FoxxMD/komodo-import. Import existing compose stacks into Komodo foxxmd.github.io/komodo-import
compose docker import komodo toml
0

Configure Feed

Select the types of activity you want to include in your feed.

Merge pull request #4 from FoxxMD/apiSyncErrorLogging

feat: improve api sync error logging

authored by

Matt Foxx and committed by
GitHub
(Sep 3, 2025, 1:09 PM EDT) fa24af59 179fba33

+108 -36
+3
Dockerfile
··· 23 23 24 24 FROM base as app 25 25 26 + ARG APP_BUILD_VERSION 27 + ENV APP_VERSION=$APP_BUILD_VERSION 28 + 26 29 USER 1000 27 30 28 31 WORKDIR /usr/src/app
+3 -2
src/common/KomodoApi.ts
··· 44 44 if (isUndefinedOrEmptyString(process.env.KOMODO_URL)) { 45 45 throw new SimpleError(`Cannot use Komodo API because env KOMODO_URL is missing`); 46 46 } 47 - this.logger.verbose(`KOMODO_URL: ${process.env.KOMODO_URL} | Normalized: ${this.urlData.url.toString()}`); 47 + this.logger.verbose(`KOMODO_URL: ${process.env.KOMODO_URL} | Normalized: ${this.urlData.url.toString().replace(/\/$/, '')}`); 48 48 49 - this.api = KomodoClient(this.urlData.url.toString(), { 49 + // remove any trailing slash or calls may result in a 405 50 + this.api = KomodoClient(this.urlData.url.toString().replace(/\/$/, ''), { 50 51 type: "api-key", 51 52 params: { 52 53 key: process.env.API_KEY,
+51
src/common/errors.ts
··· 1 + import { __Serror } from "komodo_client/dist/types.js"; 2 + 1 3 export class SimpleError extends Error { 2 4 5 + } 6 + 7 + export class KomodoApiError extends Error { 8 + 9 + constructor(e: KomodoApiErrorResponse) { 10 + super(`(HTTP ${e.status}) ${e.result.error}`); 11 + this.name = 'KomodoApiError'; 12 + if(e.error !== undefined) { 13 + this.cause = e.error; 14 + } 15 + let kTrace: string; 16 + if(e.result.trace.length > 0) { 17 + kTrace = `${e.result.trace.join('\n')}` 18 + } else { 19 + kTrace = '(No trace returned from Komodo)' 20 + } 21 + this.stack = `${this.stack ?? ''}\nKomodo Trace: ${kTrace}`; 22 + } 23 + } 24 + 25 + export type KomodoResultError = __Serror; 26 + 27 + export const asKomodoResultError = (e: any): e is KomodoResultError => { 28 + return e !== null 29 + && typeof e === 'object' 30 + && 'error' in e 31 + && typeof e.error === 'string' 32 + && 'trace' in e 33 + && Array.isArray(e.trace); 34 + } 35 + 36 + export interface KomodoApiErrorResponse { 37 + result: KomodoResultError 38 + status: number 39 + error?: Error 40 + } 41 + 42 + export const asKomodoApiErrorResponse = (e: any): e is KomodoApiErrorResponse => { 43 + return e !== null 44 + && typeof e === 'object' 45 + && 'result' in e 46 + && asKomodoResultError(e.result) 47 + && 'status' in e 48 + && typeof e.status === 'number' 49 + && ( 50 + !('error' in e) 51 + || 52 + ('error' in e && e.error instanceof Error) 53 + ); 3 54 }
+51 -34
src/exporters/exportToApiSync.ts
··· 5 5 import { stripIndents } from "common-tags"; 6 6 import dayjs from "dayjs"; 7 7 import { getDefaultKomodoApi } from "../common/utils/komodo.js"; 8 + import { asKomodoApiErrorResponse, KomodoApiError } from "../common/errors.js"; 8 9 9 10 export const exportToSync = async (toml: string, parentLogger: Logger): Promise<void> => { 10 11 const logger = childLogger(parentLogger, 'Sync API'); ··· 12 13 if (parseBool(process.env.OUTPUT_API_SYNC)) { 13 14 try { 14 15 const komodo = getDefaultKomodoApi(); 15 - if(komodo === undefined) { 16 - throw new Error('Komodo API is unavaiable, cannot export to Sync'); 16 + if (komodo === undefined) { 17 + throw new Error('Komodo API is unavailable, cannot export to Sync'); 17 18 } 18 19 const syncName = isUndefinedOrEmptyString(process.env.SYNC_NAME) ? 'komodo-import' : process.env.SYNC_NAME; 19 20 logger.info(`Using '${syncName}' as Sync Name`); ··· 41 42 } 42 43 logger.verbose(`Resource Sync ${syncName} alerady exists (ID ${syncId}), will ${existingBehavior} contents`); 43 44 } catch (e) { 44 - // do nothing 45 + if (asKomodoApiErrorResponse(e)) { 46 + if (e.result.error.toLocaleLowerCase().includes('did not find any resourcesync matching')) { 47 + logger.verbose(`No Sync named ${syncName} exists, creating a new one.`); 48 + } else { 49 + const ke = new KomodoApiError(e); 50 + throw new Error('Unexpected error from Komodo API while checking for existing Sync', { cause: ke }); 51 + } 52 + } else { 53 + throw new Error('Unexpected error while trying to check for existing Sync', { cause: e }); 54 + } 45 55 } 46 56 47 57 48 - if (syncId === undefined) { 49 - const sync = await komodo.api.write('CreateResourceSync', { 50 - name: syncName, 51 - config: { 52 - include_resources: true, 53 - file_contents: toml 54 - } 55 - }); 56 - syncId = sync._id.$oid; 57 - logger.info('Resource Sync created.'); 58 - } else { 59 - if (existingBehavior === 'overwrite') { 60 - const sync = await komodo.api.write('UpdateResourceSync', { 61 - id: syncId, 58 + try { 59 + 60 + 61 + if (syncId === undefined) { 62 + logger.debug('Trying to create Sync via API...'); 63 + const sync = await komodo.api.write('CreateResourceSync', { 64 + name: syncName, 62 65 config: { 63 66 include_resources: true, 64 67 file_contents: toml 65 68 } 66 69 }); 67 - logger.info('Resource Sync overwrriten.'); 70 + syncId = sync._id.$oid; 71 + logger.info('Resource Sync created.'); 68 72 } else { 69 - const time = dayjs().format('YYYY-MM-DD--HH-mm-ss'); 70 - const sync = await komodo.api.write('UpdateResourceSync', { 71 - id: syncId, 72 - config: { 73 - include_resources: true, 74 - file_contents: stripIndents`${existingSync.config.file_contents} 73 + if (existingBehavior === 'overwrite') { 74 + logger.debug('Trying to overwrite existing Sync via API...'); 75 + const sync = await komodo.api.write('UpdateResourceSync', { 76 + id: syncId, 77 + config: { 78 + include_resources: true, 79 + file_contents: toml 80 + } 81 + }); 82 + logger.info('Resource Sync overwritten.'); 83 + } else { 84 + logger.debug('Trying to append to existing Sync via API...'); 85 + const time = dayjs().format('YYYY-MM-DD--HH-mm-ss'); 86 + const sync = await komodo.api.write('UpdateResourceSync', { 87 + id: syncId, 88 + config: { 89 + include_resources: true, 90 + file_contents: stripIndents`${existingSync.config.file_contents} 75 91 76 92 ## Added by Komodo Import ${time} 77 93 78 94 ${toml} 79 95 80 96 ##` 81 - } 82 - }); 83 - logger.info('Resource Sync appended.'); 97 + } 98 + }); 99 + logger.info('Resource Sync appended.'); 100 + } 101 + } 102 + } catch (e) { 103 + if (asKomodoApiErrorResponse(e)) { 104 + throw new Error('Unexpected error from Komodo API while writing Sync', { cause: new KomodoApiError(e) }); 105 + } else { 106 + throw new Error('Unexpected error while trying to write to Sync', { cause: e }); 84 107 } 85 108 } 86 109 ··· 89 112 logger.info(`Resource Sync URL: ${syncUrl}`); 90 113 } 91 114 catch (e) { 92 - let hint: string; 93 - switch (e.result?.error) { 94 - case 'Must provide unique name for resource.': 95 - hint = 'Sync already exists! Use a different name.' 96 - break; 97 - } 98 - throw new Error(`Komodo API error occurred while trying to export to Resource Sync${hint !== undefined ? `. Hint: ${hint}` : ''}`, { cause: e }); 115 + throw new Error(`Error occurred while trying to export to Resource Sync`, { cause: e }); 99 116 } 100 117 } else if (isDebugMode()) { 101 118 logger.debug('Not exporting to Resource Sync because env KOMODO_URL was not defined.');