@corellium/corellium-cli 1.0.8 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.gitlab-ci.yml +8 -0
- package/.nycrc +16 -0
- package/index.js +1 -0
- package/package.json +3 -2
- package/sonar-project.properties +2 -0
- package/src/commands/instances/Instance.js +8 -0
- package/src/commands/instances/index.js +2 -1
- package/src/commands/instances/restoreBackup.js +77 -0
- package/src/commands/instances/rotate.js +47 -0
- package/src/commands/webplayer/{WebPlayer.js → Webplayer.js} +7 -7
- package/src/commands/webplayer/create.js +5 -5
- package/src/commands/webplayer/destroy.js +6 -6
- package/src/commands/webplayer/index.js +1 -1
- package/src/commands/webplayer/list.js +4 -4
- package/src/commands/webplayer/login.js +6 -6
- package/src/upload.js +6 -4
- package/src/utils.js +18 -0
package/.gitlab-ci.yml
CHANGED
package/.nycrc
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
{
|
2
|
+
"check-coverage": false,
|
3
|
+
"report-dir": "./coverage",
|
4
|
+
"reporter": ["cobertura", "lcov", "text"],
|
5
|
+
"branches": 60,
|
6
|
+
"lines": 60,
|
7
|
+
"functions": 60,
|
8
|
+
"statements": 60,
|
9
|
+
"temp-dir": "/tmp",
|
10
|
+
"watermarks": {
|
11
|
+
"lines": [60, 80],
|
12
|
+
"functions": [60, 80],
|
13
|
+
"branches": [60, 80],
|
14
|
+
"statements": [60, 80]
|
15
|
+
}
|
16
|
+
}
|
package/index.js
CHANGED
@@ -8,6 +8,7 @@ require('dotenv').config()
|
|
8
8
|
async function run () {
|
9
9
|
return yargs(hideBin(process.argv))
|
10
10
|
.command(commands)
|
11
|
+
.scriptName('corellium')
|
11
12
|
.usage('Usage: corellium [OPTIONS]')
|
12
13
|
.demandCommand(1, '') // empty string overrides yargs nag message about specifying a command
|
13
14
|
.strict() // strict handling of commands and arguments
|
package/package.json
CHANGED
@@ -1,12 +1,13 @@
|
|
1
1
|
{
|
2
2
|
"name": "@corellium/corellium-cli",
|
3
|
-
"version": "1.0
|
3
|
+
"version": "1.1.0",
|
4
4
|
"description": "Corellium CLI Tool",
|
5
5
|
"scripts": {
|
6
6
|
"corellium": "node index.js",
|
7
7
|
"lint": "eslint .",
|
8
8
|
"lint-staged": "lint-staged",
|
9
|
-
"lint:fix": "eslint --fix ."
|
9
|
+
"lint:fix": "eslint --fix .",
|
10
|
+
"test:ci": "npx nyc report"
|
10
11
|
},
|
11
12
|
"bin": {
|
12
13
|
"corellium": "index.sh"
|
@@ -15,6 +15,14 @@ class InstanceCLI extends Client {
|
|
15
15
|
return await this._fetch('POST', `${this.instanceBasePath()}/upgrade`, { os, osbuild })
|
16
16
|
}
|
17
17
|
|
18
|
+
/**
|
19
|
+
* Restore backup to instance
|
20
|
+
* @returns {Promise<any>}
|
21
|
+
*/
|
22
|
+
restoreBackup = async () => {
|
23
|
+
return await this._fetch('POST', `${this.instanceBasePath()}/restoreBackup`)
|
24
|
+
}
|
25
|
+
|
18
26
|
/**
|
19
27
|
* Fetch instance information
|
20
28
|
* @returns {Promise<any>}
|
@@ -0,0 +1,77 @@
|
|
1
|
+
const log = require('../../logging')
|
2
|
+
const InstanceCLI = require('./Instance')
|
3
|
+
const { readProfile } = require('../../profile')
|
4
|
+
const { v4: uuidv4 } = require('uuid')
|
5
|
+
const { basename } = require('path')
|
6
|
+
const MultiProgress = require('multi-progress')
|
7
|
+
const { uploadWithProgress } = require('../../upload')
|
8
|
+
const { waitForTaskState } = require('../../utils')
|
9
|
+
|
10
|
+
async function builder (yargs) {
|
11
|
+
yargs
|
12
|
+
.positional('instanceId', {
|
13
|
+
type: 'string',
|
14
|
+
describe: 'Instance id',
|
15
|
+
demandOption: true
|
16
|
+
})
|
17
|
+
.positional('zipFile', {
|
18
|
+
type: 'string',
|
19
|
+
describe: 'Path to backup zip file',
|
20
|
+
demandOption: true
|
21
|
+
})
|
22
|
+
.option('verbose', {
|
23
|
+
alias: 'v',
|
24
|
+
type: 'boolean',
|
25
|
+
describe: 'Console will receive verbose error output',
|
26
|
+
demandOption: false
|
27
|
+
})
|
28
|
+
}
|
29
|
+
|
30
|
+
async function handler (argv) {
|
31
|
+
const profile = readProfile()
|
32
|
+
const { instanceId, zipFile } = argv
|
33
|
+
const name = basename(zipFile)
|
34
|
+
const image = uuidv4()
|
35
|
+
const url = profile.endpoint +
|
36
|
+
'/api/v1/instances' +
|
37
|
+
'/' +
|
38
|
+
encodeURIComponent(instanceId) +
|
39
|
+
'/' +
|
40
|
+
'image-upload/backup' +
|
41
|
+
'/' +
|
42
|
+
encodeURIComponent(image) +
|
43
|
+
'/' +
|
44
|
+
encodeURIComponent(name)
|
45
|
+
|
46
|
+
const multi = new MultiProgress(process.stderr)
|
47
|
+
|
48
|
+
try {
|
49
|
+
await uploadWithProgress(url, profile.token, zipFile, multi, {
|
50
|
+
'x-corellium-image-encapsulated': 'false'
|
51
|
+
})
|
52
|
+
console.log(`Backup uploaded ${image}`)
|
53
|
+
|
54
|
+
const instance = new InstanceCLI(argv)
|
55
|
+
await instance.restoreBackup()
|
56
|
+
|
57
|
+
await waitForTaskState(instanceId, instance.info.bind(instance), 'restoring-backup')
|
58
|
+
console.log('Restoring in progress')
|
59
|
+
await waitForTaskState(instanceId, instance.info.bind(instance), 'none')
|
60
|
+
|
61
|
+
log.info(`Restored backup ${zipFile} to ${argv.instanceId}`)
|
62
|
+
} catch (err) {
|
63
|
+
const errorMessage = err.message
|
64
|
+
log.error(`Restore backup failed: ${errorMessage}`)
|
65
|
+
if (err.response && err.response.data && err.response.data.error) {
|
66
|
+
log.error(err.response.data.error)
|
67
|
+
}
|
68
|
+
if (argv.verbose) { log.error(err) }
|
69
|
+
}
|
70
|
+
}
|
71
|
+
|
72
|
+
module.exports = {
|
73
|
+
builder,
|
74
|
+
handler,
|
75
|
+
command: 'restore-backup <instanceId> <zipFile> [--verbose]',
|
76
|
+
describe: 'Restore iOS backup to instance'
|
77
|
+
}
|
@@ -0,0 +1,47 @@
|
|
1
|
+
const { getErrorMessage } = require('../../error')
|
2
|
+
const asciiArt = require('ascii-art')
|
3
|
+
|
4
|
+
const Client = require('../Client')
|
5
|
+
async function builder (yargs) {
|
6
|
+
yargs.positional('instanceId', {
|
7
|
+
type: 'string',
|
8
|
+
describe: 'Instance id',
|
9
|
+
demandOption: true
|
10
|
+
})
|
11
|
+
.option('verbose', {
|
12
|
+
alias: 'v',
|
13
|
+
type: 'boolean',
|
14
|
+
describe: 'Console will receive verbose error output'
|
15
|
+
})
|
16
|
+
.option('orientation', {
|
17
|
+
alias: 'o',
|
18
|
+
type: 'number',
|
19
|
+
describe: 'Orientation type'
|
20
|
+
})
|
21
|
+
}
|
22
|
+
|
23
|
+
async function handler (argv) {
|
24
|
+
const client = new Client(argv)
|
25
|
+
const instanceId = argv.instanceId
|
26
|
+
const orientation = argv.orientation || 4
|
27
|
+
try {
|
28
|
+
await client._fetch('POST', `v1/instances/${instanceId}/rotate`, { orientation })
|
29
|
+
console.log(
|
30
|
+
asciiArt.style(`Successfully rotated instance ${instanceId}`, 'green', true)
|
31
|
+
)
|
32
|
+
} catch (error) {
|
33
|
+
const errorMessage = getErrorMessage(error)
|
34
|
+
console.error(`Failed to rotate ${instanceId}: ${errorMessage}`)
|
35
|
+
|
36
|
+
if (argv.detailedErrors) {
|
37
|
+
console.log(error)
|
38
|
+
}
|
39
|
+
}
|
40
|
+
}
|
41
|
+
|
42
|
+
module.exports = {
|
43
|
+
builder,
|
44
|
+
handler,
|
45
|
+
command: 'rotate <instanceId> [orientation]',
|
46
|
+
describe: 'Rotate instance'
|
47
|
+
}
|
@@ -4,9 +4,9 @@ const {
|
|
4
4
|
} = require('../../profile')
|
5
5
|
|
6
6
|
/**
|
7
|
-
*
|
7
|
+
* Webplayer class
|
8
8
|
*/
|
9
|
-
class
|
9
|
+
class Webplayer extends Client {
|
10
10
|
constructor (argv) {
|
11
11
|
super(argv)
|
12
12
|
const { features, permissions, expiresIn } = this.argv
|
@@ -16,7 +16,7 @@ class WebPlayer extends Client {
|
|
16
16
|
}
|
17
17
|
|
18
18
|
/**
|
19
|
-
* Create a
|
19
|
+
* Create a Webplayer session
|
20
20
|
* @returns {Promise<Array<object>>}
|
21
21
|
*/
|
22
22
|
createSession = async () => this._fetch('POST', 'v1/webplayer', {
|
@@ -28,14 +28,14 @@ class WebPlayer extends Client {
|
|
28
28
|
})
|
29
29
|
|
30
30
|
/**
|
31
|
-
* Destroy a
|
31
|
+
* Destroy a Webplayer session
|
32
32
|
* @param {string} sessionId
|
33
33
|
* @returns {Promise<{object}>}
|
34
34
|
*/
|
35
35
|
destroySession = async (sessionId) => this._fetch('DELETE', `v1/webplayer/${sessionId}`, {})
|
36
36
|
|
37
37
|
/**
|
38
|
-
* List
|
38
|
+
* List Webplayer sessions
|
39
39
|
* @returns {Promise<Array<object>>}
|
40
40
|
*/
|
41
41
|
listSessions = async (sessionId) => {
|
@@ -44,7 +44,7 @@ class WebPlayer extends Client {
|
|
44
44
|
}
|
45
45
|
|
46
46
|
/**
|
47
|
-
* Log into
|
47
|
+
* Log into Webplayer session by activating Webplayer credentials
|
48
48
|
* @param {string} sessionId
|
49
49
|
* @returns {Promise<void>}
|
50
50
|
*/
|
@@ -54,4 +54,4 @@ class WebPlayer extends Client {
|
|
54
54
|
}
|
55
55
|
}
|
56
56
|
|
57
|
-
module.exports =
|
57
|
+
module.exports = Webplayer
|
@@ -1,4 +1,4 @@
|
|
1
|
-
const
|
1
|
+
const Webplayer = require('./Webplayer')
|
2
2
|
const log = require('../../logging')
|
3
3
|
const { displayTable } = require('../../table')
|
4
4
|
const { validateNonEmpty } = require('../../utils')
|
@@ -49,13 +49,13 @@ async function builder (yargs) {
|
|
49
49
|
|
50
50
|
async function handler (argv) {
|
51
51
|
try {
|
52
|
-
const webplayer = new
|
52
|
+
const webplayer = new Webplayer(argv)
|
53
53
|
const session = await webplayer.createSession()
|
54
|
-
// log.info('Created
|
54
|
+
// log.info('Created Webplayer session')
|
55
55
|
await displayTable(argv.format || 'json', [session], ['identifier', 'token', 'expiration', 'features'])
|
56
56
|
} catch (err) {
|
57
57
|
const errorMessage = getErrorMessage(err)
|
58
|
-
log.error(`Create
|
58
|
+
log.error(`Create Webplayer session failed: ${errorMessage}`)
|
59
59
|
if (argv.detailedErrors) { log.error(err) }
|
60
60
|
}
|
61
61
|
}
|
@@ -64,5 +64,5 @@ module.exports = {
|
|
64
64
|
builder,
|
65
65
|
handler,
|
66
66
|
command: 'create',
|
67
|
-
describe: 'Create a
|
67
|
+
describe: 'Create a Webplayer session returning the URL and token'
|
68
68
|
}
|
@@ -1,4 +1,4 @@
|
|
1
|
-
const
|
1
|
+
const Webplayer = require('./Webplayer')
|
2
2
|
const log = require('../../logging')
|
3
3
|
const { validateNonEmpty } = require('../../utils')
|
4
4
|
const { getErrorMessage } = require('../../error')
|
@@ -16,7 +16,7 @@ async function builder (yargs) {
|
|
16
16
|
validateNonEmpty
|
17
17
|
}).option('session', {
|
18
18
|
type: 'input',
|
19
|
-
describe: '
|
19
|
+
describe: 'Webplayer Session ID',
|
20
20
|
demandOption: true,
|
21
21
|
string: true,
|
22
22
|
validateNonEmpty
|
@@ -26,12 +26,12 @@ async function builder (yargs) {
|
|
26
26
|
async function handler (argv) {
|
27
27
|
const { session: sessionId } = argv
|
28
28
|
try {
|
29
|
-
const webplayer = new
|
29
|
+
const webplayer = new Webplayer(argv)
|
30
30
|
await webplayer.destroySession(sessionId)
|
31
|
-
log.info(`Destroyed
|
31
|
+
log.info(`Destroyed Webplayer session ${sessionId}`)
|
32
32
|
} catch (err) {
|
33
33
|
const errorMessage = getErrorMessage(err)
|
34
|
-
log.error(`Destroy
|
34
|
+
log.error(`Destroy Webplayer session ${sessionId} failed: ${errorMessage}`)
|
35
35
|
if (argv.detailedErrors) { log.error(err) }
|
36
36
|
}
|
37
37
|
}
|
@@ -40,5 +40,5 @@ module.exports = {
|
|
40
40
|
builder,
|
41
41
|
handler,
|
42
42
|
command: 'destroy',
|
43
|
-
describe: 'Destroy a
|
43
|
+
describe: 'Destroy a Webplayer session'
|
44
44
|
}
|
@@ -11,5 +11,5 @@ module.exports = {
|
|
11
11
|
builder: yargs => (_yargs = yargs) && yargs.command(subcommands).scriptName(''),
|
12
12
|
handler: argv => !argv[1] && _yargs.showHelp(),
|
13
13
|
command: 'webplayer',
|
14
|
-
describe: '
|
14
|
+
describe: 'Webplayer related commands'
|
15
15
|
}
|
@@ -1,4 +1,4 @@
|
|
1
|
-
const
|
1
|
+
const Webplayer = require('./Webplayer')
|
2
2
|
const log = require('../../logging')
|
3
3
|
const { displayTable } = require('../../table')
|
4
4
|
const { validateNonEmpty } = require('../../utils')
|
@@ -17,7 +17,7 @@ async function builder (yargs) {
|
|
17
17
|
validateNonEmpty
|
18
18
|
}).option('session', {
|
19
19
|
type: 'input',
|
20
|
-
describe: '
|
20
|
+
describe: 'Webplayer Session ID',
|
21
21
|
demandOption: false,
|
22
22
|
string: true
|
23
23
|
}).option('format', {
|
@@ -30,7 +30,7 @@ async function builder (yargs) {
|
|
30
30
|
|
31
31
|
async function handler (argv) {
|
32
32
|
try {
|
33
|
-
const webplayer = new
|
33
|
+
const webplayer = new Webplayer(argv)
|
34
34
|
const result = await webplayer.listSessions(argv.session)
|
35
35
|
if (!result.length && argv.format !== 'json') {
|
36
36
|
console.error('Found no matching Webplayer sessions')
|
@@ -63,5 +63,5 @@ module.exports = {
|
|
63
63
|
builder,
|
64
64
|
handler,
|
65
65
|
command: 'list',
|
66
|
-
describe: 'List
|
66
|
+
describe: 'List Webplayer sessions for a project'
|
67
67
|
}
|
@@ -1,4 +1,4 @@
|
|
1
|
-
const
|
1
|
+
const Webplayer = require('./Webplayer')
|
2
2
|
const log = require('../../logging')
|
3
3
|
const { validateNonEmpty } = require('../../utils')
|
4
4
|
const { getErrorMessage } = require('../../error')
|
@@ -16,7 +16,7 @@ async function builder (yargs) {
|
|
16
16
|
validateNonEmpty
|
17
17
|
}).option('session', {
|
18
18
|
type: 'input',
|
19
|
-
describe: '
|
19
|
+
describe: 'Webplayer Session ID',
|
20
20
|
demandOption: true,
|
21
21
|
string: true,
|
22
22
|
validateNonEmpty
|
@@ -26,12 +26,12 @@ async function builder (yargs) {
|
|
26
26
|
async function handler (argv) {
|
27
27
|
const { session: sessionId } = argv
|
28
28
|
try {
|
29
|
-
const webplayer = new
|
29
|
+
const webplayer = new Webplayer(argv)
|
30
30
|
await webplayer.login(sessionId)
|
31
|
-
log.info(`Login to
|
31
|
+
log.info(`Login to Webplayer session ${sessionId} successful`)
|
32
32
|
} catch (err) {
|
33
33
|
const errorMessage = getErrorMessage(err)
|
34
|
-
log.error(`Login to
|
34
|
+
log.error(`Login to Webplayer session ${sessionId} failed: ${errorMessage}`)
|
35
35
|
if (argv.detailedErrors) {
|
36
36
|
log.error(err)
|
37
37
|
}
|
@@ -42,5 +42,5 @@ module.exports = {
|
|
42
42
|
builder,
|
43
43
|
handler,
|
44
44
|
command: 'login',
|
45
|
-
describe: 'Log CLI into a
|
45
|
+
describe: 'Log CLI into a Webplayer session'
|
46
46
|
}
|
package/src/upload.js
CHANGED
@@ -19,13 +19,14 @@ class File {
|
|
19
19
|
}
|
20
20
|
}
|
21
21
|
|
22
|
-
async function uploadFile (token, url, filePath, progress) {
|
22
|
+
async function uploadFile (token, url, filePath, progress, headers) {
|
23
23
|
return new Promise((resolve, reject) => {
|
24
24
|
const r = new Resumable({
|
25
25
|
target: url,
|
26
26
|
headers: {
|
27
27
|
authorization: token,
|
28
|
-
'x-corellium-image-encoding': 'plain'
|
28
|
+
'x-corellium-image-encoding': 'plain',
|
29
|
+
...headers
|
29
30
|
},
|
30
31
|
uploadMethod: 'PUT',
|
31
32
|
chunkSize: 5 * 1024 * 1024,
|
@@ -64,7 +65,7 @@ async function uploadFile (token, url, filePath, progress) {
|
|
64
65
|
})
|
65
66
|
}
|
66
67
|
|
67
|
-
async function uploadWithProgress (url, token, file, multi) {
|
68
|
+
async function uploadWithProgress (url, token, file, multi, headers) {
|
68
69
|
const fileName = basename(file)
|
69
70
|
|
70
71
|
const bar = multi.newBar(` loading ${fileName} [:bar] :percent`, {
|
@@ -82,7 +83,8 @@ async function uploadWithProgress (url, token, file, multi) {
|
|
82
83
|
(progress) => {
|
83
84
|
const ticks = Math.ceil(100 * progress)
|
84
85
|
bar.tick(ticks)
|
85
|
-
}
|
86
|
+
},
|
87
|
+
headers
|
86
88
|
)
|
87
89
|
return res
|
88
90
|
} catch (error) {
|
package/src/utils.js
CHANGED
@@ -63,6 +63,23 @@ const waitForState = async (instanceId, fn, state, attempts = 20, reporterFn = n
|
|
63
63
|
}
|
64
64
|
}
|
65
65
|
|
66
|
+
const waitForTaskState = async (instanceId, fn, taskState, attempts = 20) => {
|
67
|
+
let response = await fn(instanceId)
|
68
|
+
|
69
|
+
while (response.taskState !== taskState) {
|
70
|
+
if (response.state === 'error') {
|
71
|
+
throw new Error(response.error)
|
72
|
+
}
|
73
|
+
|
74
|
+
if (attempts <= 0) {
|
75
|
+
throw new Error(`Timed out waiting for taskState ${taskState}`)
|
76
|
+
}
|
77
|
+
--attempts
|
78
|
+
await sleep(waitForIntervalMilliSec)
|
79
|
+
response = await fn(instanceId)
|
80
|
+
}
|
81
|
+
}
|
82
|
+
|
66
83
|
async function getApi () {
|
67
84
|
const profile = readProfile()
|
68
85
|
const api = new CorelliumClient.CorelliumApi()
|
@@ -145,6 +162,7 @@ module.exports = {
|
|
145
162
|
planOptions,
|
146
163
|
makePassword,
|
147
164
|
waitForState,
|
165
|
+
waitForTaskState,
|
148
166
|
getApi,
|
149
167
|
getAxios,
|
150
168
|
validateNonEmpty,
|