2021-08-29 11:58:11 -07:00
/* eslint-disable @typescript-eslint/restrict-plus-operands */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable no-console */
import { Command , flags } from '@oclif/command' ;
2021-01-20 14:51:49 -08:00
2021-08-29 11:58:11 -07:00
import { Credentials , UserSettings } from 'n8n-core' ;
2021-02-18 02:32:46 -08:00
2021-08-29 11:58:11 -07:00
import { IDataObject , LoggerProxy } from 'n8n-workflow' ;
2021-05-01 20:43:01 -07:00
2021-01-20 14:51:49 -08:00
import * as fs from 'fs' ;
import * as path from 'path' ;
2021-08-29 11:58:11 -07:00
import { getLogger } from '../../src/Logger' ;
import { Db , ICredentialsDecryptedDb } from '../../src' ;
2021-01-20 14:51:49 -08:00
export class ExportCredentialsCommand extends Command {
static description = 'Export credentials' ;
static examples = [
` $ n8n export:credentials --all ` ,
` $ n8n export:credentials --id=5 --output=file.json ` ,
2021-02-18 02:32:46 -08:00
` $ n8n export:credentials --all --output=backups/latest.json ` ,
2021-01-21 01:52:33 -08:00
` $ n8n export:credentials --backup --output=backups/latest/ ` ,
2021-02-18 02:32:46 -08:00
` $ n8n export:credentials --all --decrypted --output=backups/decrypted.json ` ,
2021-01-20 14:51:49 -08:00
] ;
static flags = {
help : flags.help ( { char : 'h' } ) ,
all : flags.boolean ( {
description : 'Export all credentials' ,
} ) ,
2021-01-21 01:52:33 -08:00
backup : flags.boolean ( {
2021-08-29 11:58:11 -07:00
description :
'Sets --all --pretty --separate for simple backups. Only --output has to be set additionally.' ,
2021-01-21 01:52:33 -08:00
} ) ,
2021-01-20 14:51:49 -08:00
id : flags.string ( {
description : 'The ID of the credential to export' ,
} ) ,
output : flags.string ( {
2021-01-21 01:52:33 -08:00
char : 'o' ,
2021-01-20 14:51:49 -08:00
description : 'Output file name or directory if using separate files' ,
} ) ,
pretty : flags.boolean ( {
description : 'Format the output in an easier to read fashion' ,
} ) ,
separate : flags.boolean ( {
2021-08-29 11:58:11 -07:00
description :
'Exports one file per credential (useful for versioning). Must inform a directory via --output.' ,
2021-01-20 14:51:49 -08:00
} ) ,
2021-02-18 02:32:46 -08:00
decrypted : flags.boolean ( {
2021-08-29 11:58:11 -07:00
description :
'Exports data decrypted / in plain text. ALL SENSITIVE INFORMATION WILL BE VISIBLE IN THE FILES. Use to migrate from a installation to another that have a different secret key (in the config file).' ,
2021-02-18 02:32:46 -08:00
} ) ,
2021-01-20 14:51:49 -08:00
} ;
2021-08-29 11:58:11 -07:00
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
2021-01-20 14:51:49 -08:00
async run() {
2021-05-01 20:43:01 -07:00
const logger = getLogger ( ) ;
LoggerProxy . init ( logger ) ;
2021-01-20 14:51:49 -08:00
2021-08-29 11:58:11 -07:00
// eslint-disable-next-line @typescript-eslint/no-shadow
2021-05-01 20:43:01 -07:00
const { flags } = this . parse ( ExportCredentialsCommand ) ;
2021-08-29 11:58:11 -07:00
2021-01-21 01:52:33 -08:00
if ( flags . backup ) {
flags . all = true ;
flags . pretty = true ;
flags . separate = true ;
}
2021-01-20 14:51:49 -08:00
if ( ! flags . all && ! flags . id ) {
2021-05-01 20:43:01 -07:00
console . info ( ` Either option "--all" or "--id" have to be set! ` ) ;
2021-01-20 14:51:49 -08:00
return ;
}
if ( flags . all && flags . id ) {
2021-05-01 20:43:01 -07:00
console . info ( ` You should either use "--all" or "--id" but never both! ` ) ;
2021-01-20 14:51:49 -08:00
return ;
}
if ( flags . separate ) {
try {
if ( ! flags . output ) {
2021-05-01 20:43:01 -07:00
console . info ( ` You must inform an output directory via --output when using --separate ` ) ;
2021-01-20 14:51:49 -08:00
return ;
}
if ( fs . existsSync ( flags . output ) ) {
if ( ! fs . lstatSync ( flags . output ) . isDirectory ( ) ) {
2021-05-01 20:43:01 -07:00
console . info ( ` The paramenter --output must be a directory ` ) ;
2021-01-20 14:51:49 -08:00
return ;
}
} else {
fs . mkdirSync ( flags . output , { recursive : true } ) ;
}
} catch ( e ) {
2021-08-29 11:58:11 -07:00
console . error (
'Aborting execution as a filesystem error has been encountered while creating the output directory. See log messages for details.' ,
) ;
2021-05-01 20:43:01 -07:00
logger . error ( '\nFILESYSTEM ERROR' ) ;
logger . info ( '====================================' ) ;
logger . error ( e . message ) ;
logger . error ( e . stack ) ;
2021-01-20 14:51:49 -08:00
this . exit ( 1 ) ;
}
} else if ( flags . output ) {
if ( fs . existsSync ( flags . output ) ) {
if ( fs . lstatSync ( flags . output ) . isDirectory ( ) ) {
2021-05-01 20:43:01 -07:00
console . info ( ` The paramenter --output must be a writeble file ` ) ;
2021-01-20 14:51:49 -08:00
return ;
}
}
}
try {
await Db . init ( ) ;
const findQuery : IDataObject = { } ;
if ( flags . id ) {
findQuery . id = flags . id ;
}
2021-08-29 11:58:11 -07:00
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
2021-01-20 14:51:49 -08:00
const credentials = await Db . collections . Credentials ! . find ( findQuery ) ;
2021-02-18 02:32:46 -08:00
if ( flags . decrypted ) {
const encryptionKey = await UserSettings . getEncryptionKey ( ) ;
if ( encryptionKey === undefined ) {
throw new Error ( 'No encryption key got found to decrypt the credentials!' ) ;
}
for ( let i = 0 ; i < credentials . length ; i ++ ) {
2021-02-21 13:37:39 -08:00
const { name , type , nodesAccess , data } = credentials [ i ] ;
2021-02-18 02:32:46 -08:00
const credential = new Credentials ( name , type , nodesAccess , data ) ;
const plainData = credential . getData ( encryptionKey ) ;
( credentials [ i ] as ICredentialsDecryptedDb ) . data = plainData ;
}
}
2021-01-20 14:51:49 -08:00
if ( credentials . length === 0 ) {
throw new Error ( 'No credentials found with specified filters.' ) ;
}
if ( flags . separate ) {
2021-08-29 11:58:11 -07:00
let fileContents : string ;
let i : number ;
2021-01-20 14:51:49 -08:00
for ( i = 0 ; i < credentials . length ; i ++ ) {
fileContents = JSON . stringify ( credentials [ i ] , null , flags . pretty ? 2 : undefined ) ;
2021-08-29 11:58:11 -07:00
const filename = ` ${
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
( flags . output ! . endsWith ( path . sep ) ? flags . output ! : flags . output + path . sep ) +
credentials [ i ] . id
} . json ` ;
2021-01-20 14:51:49 -08:00
fs . writeFileSync ( filename , fileContents ) ;
}
2021-05-01 20:43:01 -07:00
console . info ( ` Successfully exported ${ i } credentials. ` ) ;
2021-01-20 14:51:49 -08:00
} else {
const fileContents = JSON . stringify ( credentials , null , flags . pretty ? 2 : undefined ) ;
if ( flags . output ) {
2021-08-29 11:58:11 -07:00
fs . writeFileSync ( flags . output , fileContents ) ;
2021-05-01 20:43:01 -07:00
console . info ( ` Successfully exported ${ credentials . length } credentials. ` ) ;
2021-01-20 14:51:49 -08:00
} else {
2021-05-01 20:43:01 -07:00
console . info ( fileContents ) ;
2021-01-20 14:51:49 -08:00
}
}
2021-05-01 20:43:01 -07:00
// Force exit as process won't exit using MySQL or Postgres.
process . exit ( 0 ) ;
2021-01-20 14:51:49 -08:00
} catch ( error ) {
2021-05-01 20:43:01 -07:00
console . error ( 'Error exporting credentials. See log messages for details.' ) ;
logger . error ( error . message ) ;
2021-01-20 14:51:49 -08:00
this . exit ( 1 ) ;
}
}
}