@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 CHANGED
@@ -19,3 +19,11 @@ test:
19
19
  # - mysqladmin -u root -ppassword -P 3306 -h mariadb status
20
20
 
21
21
  - npm ci
22
+ - npm run test:ci
23
+ artifacts:
24
+ paths:
25
+ - coverage/
26
+ reports:
27
+ coverage_report:
28
+ coverage_format: cobertura
29
+ path: coverage/cobertura-coverage.xml
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.8",
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"
@@ -0,0 +1,2 @@
1
+ sonar.projectKey=middleware_corellium-cli_AYbC7e_Y2VKo8EJiY-py
2
+ sonar.qualitygate.wait=true
@@ -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>}
@@ -4,7 +4,8 @@ const subcommands = [
4
4
  require('./delete'),
5
5
  require('./stop'),
6
6
  require('./start'),
7
- require('./upgrade')
7
+ require('./upgrade'),
8
+ require('./restoreBackup')
8
9
  ]
9
10
 
10
11
  let _yargs
@@ -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
- * WebPlayer class
7
+ * Webplayer class
8
8
  */
9
- class WebPlayer extends Client {
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 Web Player session
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 Web Player session
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 Web Player sessions
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 WebPlayer session by activating WebPlayer credentials
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 = WebPlayer
57
+ module.exports = Webplayer
@@ -1,4 +1,4 @@
1
- const WebPlayer = require('./WebPlayer')
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 WebPlayer(argv)
52
+ const webplayer = new Webplayer(argv)
53
53
  const session = await webplayer.createSession()
54
- // log.info('Created web player session')
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 web player session failed: ${errorMessage}`)
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 web player session returning the URL and token'
67
+ describe: 'Create a Webplayer session returning the URL and token'
68
68
  }
@@ -1,4 +1,4 @@
1
- const WebPlayer = require('./WebPlayer')
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: 'WebPlayer Session ID',
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 WebPlayer(argv)
29
+ const webplayer = new Webplayer(argv)
30
30
  await webplayer.destroySession(sessionId)
31
- log.info(`Destroyed web player session ${sessionId}`)
31
+ log.info(`Destroyed Webplayer session ${sessionId}`)
32
32
  } catch (err) {
33
33
  const errorMessage = getErrorMessage(err)
34
- log.error(`Destroy web player session ${sessionId} failed: ${errorMessage}`)
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 web player session'
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: 'Web Player related commands'
14
+ describe: 'Webplayer related commands'
15
15
  }
@@ -1,4 +1,4 @@
1
- const WebPlayer = require('./WebPlayer')
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: 'Web Player Session ID',
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 WebPlayer(argv)
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 web player sessions for a project'
66
+ describe: 'List Webplayer sessions for a project'
67
67
  }
@@ -1,4 +1,4 @@
1
- const WebPlayer = require('./WebPlayer')
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: 'WebPlayer Session ID',
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 WebPlayer(argv)
29
+ const webplayer = new Webplayer(argv)
30
30
  await webplayer.login(sessionId)
31
- log.info(`Login to Web Player session ${sessionId} successful`)
31
+ log.info(`Login to Webplayer session ${sessionId} successful`)
32
32
  } catch (err) {
33
33
  const errorMessage = getErrorMessage(err)
34
- log.error(`Login to Web Player session ${sessionId} failed: ${errorMessage}`)
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 web player session'
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,