@corellium/corellium-cli 1.0.8 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
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,