@corellium/corellium-cli 1.0.9 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- package/.gitlab-ci.yml +8 -0
- package/.nycrc +16 -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/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,
|