vmx-aps 2.2.0__tar.gz

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.
vmx_aps-2.2.0/LICENSE ADDED
@@ -0,0 +1,17 @@
1
+ MIT License
2
+ Copyright (c) 2025 Verimatrix
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+ The above copyright notice and this permission notice shall be included in all
10
+ copies or substantial portions of the Software.
11
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
12
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
13
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
14
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
15
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
16
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
17
+ SOFTWARE.
@@ -0,0 +1 @@
1
+ include LICENSE
vmx_aps-2.2.0/PKG-INFO ADDED
@@ -0,0 +1,22 @@
1
+ Metadata-Version: 2.2
2
+ Name: vmx-aps
3
+ Version: 2.2.0
4
+ Summary: APS command line wrapper
5
+ Author: Verimatrix Inc.
6
+ Author-email: blackhole@verimatrix.com
7
+ Classifier: Programming Language :: Python :: 3
8
+ Classifier: Operating System :: OS Independent
9
+ License-File: LICENSE
10
+ Requires-Dist: python-dateutil
11
+ Requires-Dist: requests
12
+ Requires-Dist: pyaxmlparser
13
+ Requires-Dist: backoff
14
+ Requires-Dist: coloredlogs
15
+ Dynamic: author
16
+ Dynamic: author-email
17
+ Dynamic: classifier
18
+ Dynamic: description
19
+ Dynamic: requires-dist
20
+ Dynamic: summary
21
+
22
+ APS command line wrapper
@@ -0,0 +1,49 @@
1
+ # APS API/CLI library
2
+
3
+ Set up virtual environment:
4
+
5
+ ```
6
+ $ pipenv install -d
7
+ ```
8
+
9
+ Enter the venv
10
+
11
+ ```
12
+ $ pipenv shell
13
+ ```
14
+
15
+ Run all unit tests
16
+
17
+ ```
18
+ $ pytest
19
+ ```
20
+
21
+ Run all unit tests with extra logging
22
+
23
+ ```
24
+ $ pytest -v
25
+ ```
26
+
27
+ Run all unit tests and capture covarage
28
+
29
+ ```
30
+ $ coverage run -m pytest
31
+ ```
32
+
33
+ Display coverage report
34
+
35
+ ```
36
+ $ coverage report
37
+ ```
38
+
39
+ Install CLI in you virtual environment
40
+
41
+ ```
42
+ $ pip install -e .
43
+ ```
44
+
45
+ Run commands eg:
46
+
47
+ ```
48
+ $ vmx-aps --api-key-file ~/Downloads/api-key.json get_version
49
+ ```
@@ -0,0 +1,2 @@
1
+ # Copyright (c) 2025. Verimatrix. All Rights Reserved.
2
+ # All information in this file is Verimatrix Confidential and Proprietary.
@@ -0,0 +1,524 @@
1
+ #!/usr/bin/python
2
+ # Copyright (c) 2025. Verimatrix. All Rights Reserved.
3
+ # All information in this file is Verimatrix Confidential and Proprietary.
4
+ '''Entrypoint for APS CLI'''
5
+ import argparse
6
+ import json
7
+ import logging
8
+ import os
9
+ import sys
10
+ import traceback
11
+
12
+ from .aps_api import ApsApi
13
+ import coloredlogs
14
+
15
+ LOGGER = logging.getLogger(__name__)
16
+
17
+ SUBSCRIPTION_TYPES=['APPSHIELD_PLATFORM',
18
+ 'COUNTERSPY_PLATFORM',
19
+ 'XTD_PLATFORM']
20
+
21
+ # set environment variables that control coloredlog module output
22
+ os.environ['COLOREDLOGS_LOG_FORMAT'] = '%(levelname)s: %(message)s'
23
+ os.environ['COLOREDLOGS_FIELD_STYLES'] = ''
24
+ os.environ['COLOREDLOGS_LEVEL_STYLES'] = 'debug=blue;info=green;warning=yellow;' +\
25
+ 'error=red;critical=red,bold'
26
+
27
+ def supported_commands():
28
+ '''Returns the list of supported commands'''
29
+ return ['protect',
30
+ 'list-applications',
31
+ 'add-application',
32
+ 'update-application',
33
+ 'delete-application',
34
+ 'set-signing-certificate',
35
+ 'set-mapping-file',
36
+ 'list-builds',
37
+ 'add-build',
38
+ 'delete-build',
39
+ 'protect-start',
40
+ 'protect-get-status',
41
+ 'protect-cancel',
42
+ 'protect-download',
43
+ 'get-account-info',
44
+ 'display-application-package-id',
45
+ 'get-sail-config',
46
+ 'get-version' ]
47
+
48
+ class Aps:
49
+ '''Class encapsulating all supported command line options'''
50
+ def __init__(self):
51
+
52
+ self.commands = None
53
+
54
+ parser = argparse.ArgumentParser(
55
+ description='APS command line tool',
56
+ usage='''vmx-aps [global-options] <command> [<command-options>]
57
+ The following commands are available
58
+
59
+ * protect
60
+
61
+ * list-applications
62
+ * add-application
63
+ * update-application
64
+ * delete-application
65
+ * set-signing-certificate
66
+ * set-mapping-file
67
+
68
+ * list-builds
69
+ * add-build
70
+ * delete-build
71
+
72
+ * protect-start
73
+ * protect-get-status
74
+ * protect-cancel
75
+ * protect-download
76
+
77
+ * get-account-info
78
+ * display-application-package-id
79
+ * get-sail-config
80
+ * get-version
81
+
82
+
83
+ Use vmx-aps <command> -h for information on a specific command.
84
+ Use vmx-aps -h for information on the global options.
85
+ ''')
86
+
87
+ parser.add_argument('command', help='Command to run')
88
+
89
+ group = parser.add_mutually_exclusive_group(required=True)
90
+
91
+ group.add_argument('-a', '--api-key', type=str, help='The value of "encodedKey" from api-key.json file.')
92
+
93
+ group.add_argument('--api-key-file', type=str, help='Path to api-key.json file')
94
+
95
+ parser.add_argument('-l', '--logging', type=str,
96
+ help='Logging Level',
97
+ choices=['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'])
98
+
99
+ parser.add_argument('--api-gateway-url', type=str, required=False, help='Optional API gateway URL')
100
+ parser.add_argument('--access-token-url', type=str, required=False, help='Optional Access token URL')
101
+
102
+ # find the index of the command argument
103
+ self.command_pos = len(sys.argv)
104
+ i = 0
105
+ for arg in sys.argv[1:]:
106
+ i += 1
107
+ if arg in supported_commands():
108
+ self.command_pos = i + 1
109
+ break
110
+
111
+ # parse_args defaults to [1:] for args, but we exclude the command arguments
112
+ # Defer parsing and validation of the global args (initialize_from_global_args method)
113
+ # until we have parsed the command arguments. Otherwise this would prevent
114
+ # running aps.py COMMAND -h to get help on the COMMAND arguments unless
115
+ # all mandatory global args were to be supplied.
116
+ args = parser.parse_args(sys.argv[1:self.command_pos])
117
+
118
+ # python doesn't allow for hyphens in method names
119
+ mapped_command = args.command.replace('-', '_')
120
+
121
+ if not hasattr(self, mapped_command):
122
+ print('Unrecognized command')
123
+ parser.print_help()
124
+ sys.exit(1)
125
+
126
+ # invoke command
127
+ try:
128
+ response = getattr(self, mapped_command)(args)
129
+ if response is not None:
130
+ if isinstance(response, str):
131
+ print(response)
132
+ else:
133
+ print(json.dumps(response, indent=2, sort_keys=True))
134
+ except Exception:
135
+ traceback.print_exc()
136
+ sys.exit(1)
137
+
138
+ def initialize_from_global_args(self, args):
139
+ '''Parse global command line arguments'''
140
+ if args.logging:
141
+ coloredlogs.install(level=args.logging)
142
+
143
+ api_key = None
144
+ if args.api_key:
145
+ try:
146
+ # Attempt to parse the string as JSON
147
+ data = json.loads(args.api_key)
148
+ api_key = data.get("encodedKey")
149
+ except json.JSONDecodeError:
150
+ api_key = args.api_key
151
+ elif args.api_key_file:
152
+ try:
153
+ with open(args.api_key_file, 'r') as f:
154
+ data = json.load(f)
155
+ api_key = data.get("encodedKey")
156
+ except FileNotFoundError:
157
+ LOGGER.error(f"API key file not found: {args.api_key_file}")
158
+ sys.exit(1)
159
+ except json.JSONDecodeError:
160
+ LOGGER.error(f"Failed to parse JSON in API key file: {args.api_key_file}")
161
+ sys.exit(1)
162
+ else:
163
+ LOGGER.error('Missing API key. Provide API key with either -a/--api-key argument, or use --api_key_file argument to point to api-key.json file.')
164
+ sys.exit(1)
165
+
166
+ self.commands = ApsApi(api_key=api_key, api_gateway_url=args.api_gateway_url, access_token_url=args.access_token_url)
167
+
168
+ self.commands.ensure_authenticated()
169
+
170
+ def protect(self, global_args):
171
+ '''Perform APS protection from an input file.
172
+
173
+ This is a high level command that takes an input
174
+ binary to be protected, performs protection and outputs the protected
175
+ binary. This command may take many minutes to complete.'''
176
+
177
+ parser = argparse.ArgumentParser(
178
+ usage='vmx-aps protect [<args>]',
179
+ description='Perform APS protection on the input file.')
180
+
181
+ parser.add_argument('--file', type=str, required=True,
182
+ help='Build file (aab, apk or zipped xcarchive folder)')
183
+ parser.add_argument('--subscription-type', type=str, required=False,
184
+ help='Subscription Type',
185
+ choices=SUBSCRIPTION_TYPES)
186
+ parser.add_argument('--signing-certificate', type=str, required=False,
187
+ help='PEM encoded certificate file.')
188
+ parser.add_argument('--mapping-file', type=str, required=False,
189
+ help='R8/Proguard mapping file for android')
190
+
191
+
192
+ # inside subcommands ignore the first command_pos argv's
193
+ args = parser.parse_args(sys.argv[self.command_pos:])
194
+
195
+ self.initialize_from_global_args(global_args)
196
+
197
+ return self.commands.protect(args.file,
198
+ signing_certificate=args.signing_certificate,
199
+ subscription_type=args.subscription_type,
200
+ mapping_file=args.mapping_file)
201
+
202
+ def get_account_info(self, global_args):
203
+ '''Get info about the user and organization'''
204
+ parser = argparse.ArgumentParser(
205
+ usage='vmx-aps get-account-info [<args>]',
206
+ description='Returns information about the user and organization (customer)')
207
+
208
+ parser.parse_args(sys.argv[self.command_pos:])
209
+
210
+ self.initialize_from_global_args(global_args)
211
+ return self.commands.get_account_info()
212
+
213
+ def add_application(self, global_args):
214
+ '''Add a new application'''
215
+ parser = argparse.ArgumentParser(
216
+ usage='vmx-aps add-application [<args>]',
217
+ description='''Add a new application. By default the application is
218
+ accessible to other users within your organization. The --private, --no-upload,
219
+ --no-delete options can be used to restrict access to the application.
220
+ ''')
221
+ parser.add_argument('--os', type=str, required=True,
222
+ choices=['ios', 'android'], help='Operating System.')
223
+ parser.add_argument('--name', type=str, required=True,
224
+ help='Friendly name for application.')
225
+ parser.add_argument('--package-id', type=str, required=True, help='Application package ID.')
226
+ parser.add_argument('--group', type=str, required=False, help='Optional group identifier.')
227
+ parser.add_argument('--subscription-type', type=str, required=False,
228
+ help='Subscription Type',
229
+ choices=SUBSCRIPTION_TYPES)
230
+ parser.add_argument('--private',
231
+ help='''Prevent the application from being visible to other users.
232
+ This option will automatically set each of --no-upload
233
+ and --no-delete options.''',
234
+ action='store_true', default=False)
235
+ parser.add_argument('--no-upload',
236
+ help='Prevent other users from uploading new builds for this app.',
237
+ action='store_true', default=False)
238
+ parser.add_argument('--no-delete',
239
+ help='Prevent other users from deleting builds for this app.',
240
+ action='store_true', default=False)
241
+
242
+ # inside subcommands ignore the first command_pos argv's
243
+ args = parser.parse_args(sys.argv[self.command_pos:])
244
+ permissions = {}
245
+ permissions['private'] = args.private
246
+ permissions['no_upload'] = args.no_upload
247
+ permissions['no_delete'] = args.no_delete
248
+
249
+ self.initialize_from_global_args(global_args)
250
+ return self.commands.add_application(args.name,
251
+ args.package_id,
252
+ args.os,
253
+ permissions,
254
+ args.group,
255
+ args.subscription_type)
256
+
257
+ def update_application(self, global_args):
258
+ '''Update application properties'''
259
+ parser = argparse.ArgumentParser(
260
+ usage='vmx-aps update-application [<args>]',
261
+ description='''Update application properties. The application name and
262
+ permission related properties can be modified''')
263
+ parser.add_argument('--application-id', type=str, required=True,
264
+ help='''Application ID. This identifies the application whose
265
+ properties should be updated, this property cannot itself be
266
+ changed. The remaining arguments correspond to application
267
+ properties that can be updated by this call.''')
268
+ parser.add_argument('--name', type=str, required=True, help='Friendly name for application')
269
+ parser.add_argument('--private',
270
+ help='''Prevent the app from being visible to other users. This option
271
+ will automatically set each of the --no-upload
272
+ and --no-delete options.''',
273
+ action='store_true', default=False)
274
+ parser.add_argument('--no-upload',
275
+ help='Prevent other users from uploading new builds for this app.',
276
+ action='store_true', default=False)
277
+ parser.add_argument('--no-delete',
278
+ help='Prevent other users from deleting builds for this app.',
279
+ action='store_true', default=False)
280
+
281
+ # inside subcommands ignore the first command_pos argv's
282
+ args = parser.parse_args(sys.argv[self.command_pos:])
283
+ permissions = {}
284
+ permissions['private'] = args.private
285
+ permissions['no_upload'] = args.no_upload
286
+ permissions['no_delete'] = args.no_delete
287
+
288
+ self.initialize_from_global_args(global_args)
289
+ return self.commands.update_application(args.application_id, args.name, permissions)
290
+
291
+ def list_applications(self, global_args):
292
+ '''List applications'''
293
+ parser = argparse.ArgumentParser(
294
+ usage='vmx-aps list-applications [<args>]',
295
+ description='''List applications.
296
+ Optional "application-id" or "group" parameters can be specified to restrict
297
+ the list of applications that are reported by this call.
298
+
299
+ When the "application-id" parameter is provided this operation returns the
300
+ specific application identified by "application-id".
301
+
302
+ When the "group" parameter is provided this operation returns all
303
+ applications belonging to the specified group.
304
+
305
+ When neither "application-id" or "group" are provided this operation returns the
306
+ list of all applications.''')
307
+ parser.add_argument('--application-id', type=str, required=False, help='Application ID')
308
+ parser.add_argument('--group', type=str, required=False,
309
+ help='Application group identifier')
310
+ parser.add_argument('--subscription-type', type=str, required=False,
311
+ help='Subscription Type',
312
+ choices=SUBSCRIPTION_TYPES)
313
+
314
+ # inside subcommands ignore the first command_pos argv's
315
+ args = parser.parse_args(sys.argv[self.command_pos:])
316
+
317
+ self.initialize_from_global_args(global_args)
318
+ return self.commands.list_applications(args.application_id,
319
+ args.group,
320
+ args.subscription_type)
321
+ def delete_application(self, global_args):
322
+ '''Delete an application'''
323
+ parser = argparse.ArgumentParser(
324
+ usage='vmx-aps delete-application [<args>]',
325
+ description='''Delete application. This operation will also delete all builds
326
+ belonging to this application.''')
327
+ parser.add_argument('--application-id', type=str, required=True, help='Application ID')
328
+
329
+ # inside subcommands ignore the first command_pos argv's
330
+ args = parser.parse_args(sys.argv[self.command_pos:])
331
+
332
+ self.initialize_from_global_args(global_args)
333
+ return self.commands.delete_application(args.application_id)
334
+
335
+ def list_builds(self, global_args):
336
+ '''List builds'''
337
+ parser = argparse.ArgumentParser(
338
+ usage='vmx-aps list-builds [<args>]',
339
+ description='''List builds.
340
+
341
+ Optional "application-id" or "build-id" parameters can be specified to restrict
342
+ the list of builds that are reported by this call.
343
+
344
+ When the "application-id" parameter is provided this operation returns the list
345
+ of builds for that particular application. When the "build-id" parameter is
346
+ provided this operation returns the specific build identified by "build-id".
347
+
348
+ When neither "application-id" or "build-id" are provided this operation returns
349
+ all builds.''')
350
+
351
+ parser.add_argument('--application-id', type=str, required=False, help='Application ID')
352
+ parser.add_argument('--build-id', type=str, required=False, help='Build ID')
353
+ parser.add_argument('--subscription-type', type=str, required=False,
354
+ help='Subscription Type',
355
+ choices=SUBSCRIPTION_TYPES)
356
+
357
+ # inside subcommands ignore the first command_pos argv's
358
+ args = parser.parse_args(sys.argv[self.command_pos:])
359
+
360
+ self.initialize_from_global_args(global_args)
361
+ return self.commands.list_builds(args.application_id, args.build_id, args.subscription_type)
362
+
363
+ def add_build(self, global_args):
364
+ '''Add a new build'''
365
+ parser = argparse.ArgumentParser(
366
+ usage='vmx-add-build [<args>]',
367
+ description='Add a new build')
368
+
369
+ parser.add_argument('--application-id', type=str, required=True, help='Application ID')
370
+ parser.add_argument('--file', type=str, required=False,
371
+ help='Build file (apk or xcarchive folder)')
372
+ parser.add_argument('--subscription-type', type=str, required=False,
373
+ help='Subscription Type',
374
+ choices=SUBSCRIPTION_TYPES)
375
+ # inside subcommands ignore the first command_pos argv's
376
+ args = parser.parse_args(sys.argv[self.command_pos:])
377
+
378
+ self.initialize_from_global_args(global_args)
379
+ return self.commands.add_build(args.file, application_id=args.application_id, subscription_type=args.subscription_type)
380
+
381
+ def delete_build(self, global_args):
382
+ '''Delete a build'''
383
+ parser = argparse.ArgumentParser(
384
+ usage='vmx-aps delete-build [<args>]',
385
+ description='Delete build')
386
+ parser.add_argument('--build-id', type=str, required=True, help='Build ID')
387
+
388
+ # inside subcommands ignore the first command_pos argv's
389
+ args = parser.parse_args(sys.argv[self.command_pos:])
390
+
391
+ self.initialize_from_global_args(global_args)
392
+ return self.commands.delete_build(args.build_id)
393
+
394
+ def protect_start(self, global_args):
395
+ '''Start build protection'''
396
+ parser = argparse.ArgumentParser(
397
+ usage='vmx-aps protect-start [<args>]',
398
+ description='Initiate protection of a previously added build')
399
+
400
+ parser.add_argument('--build-id', type=str, required=True, help='Build ID')
401
+ # inside subcommands ignore the first command_pos argv's
402
+ args = parser.parse_args(sys.argv[self.command_pos:])
403
+
404
+ self.initialize_from_global_args(global_args)
405
+ return self.commands.protect_start(args.build_id)
406
+
407
+ def protect_cancel(self, global_args):
408
+ '''Cancel protection of a build'''
409
+ parser = argparse.ArgumentParser(
410
+ usage='vmx-aps protect-cancel [<args>]',
411
+ description='Cancel protection of a build.')
412
+
413
+ parser.add_argument('--build-id', type=str, required=True, help='Build ID')
414
+ # inside subcommands ignore the first command_pos argv's
415
+ args = parser.parse_args(sys.argv[self.command_pos:])
416
+
417
+ self.initialize_from_global_args(global_args)
418
+ return self.commands.protect_cancel(args.build_id)
419
+
420
+ def protect_get_status(self, global_args):
421
+ '''Get the status of a build'''
422
+ parser = argparse.ArgumentParser(
423
+ usage='vmx-aps protect-get-status [<args>]',
424
+ description='''Get the status of a build. This includes progress
425
+ information when a protection build is ongoing.''')
426
+
427
+ parser.add_argument('--build-id', type=str, required=True, help='Build ID')
428
+ # inside subcommands ignore the first command_pos argv's
429
+ args = parser.parse_args(sys.argv[self.command_pos:])
430
+
431
+ self.initialize_from_global_args(global_args)
432
+ return self.commands.protect_get_status(args.build_id)
433
+
434
+ def protect_download(self, global_args):
435
+ '''Download a protected build'''
436
+ parser = argparse.ArgumentParser(
437
+ usage='vmx-aps protect-download [<args>]',
438
+ description='Download a previously protected build.')
439
+
440
+ parser.add_argument('--build-id', type=str, required=True, help='Build ID')
441
+ # inside subcommands ignore the first command_pos argv's
442
+ args = parser.parse_args(sys.argv[self.command_pos:])
443
+
444
+ self.initialize_from_global_args(global_args)
445
+ return self.commands.protect_download(args.build_id)
446
+
447
+ def display_application_package_id(self, global_args):
448
+ '''Utility to extract and display the application package id from a file.'''
449
+ parser = argparse.ArgumentParser(
450
+ usage='vmx-aps display_application_package_id [<args>]',
451
+ description='''Display the application package id for a input file.
452
+ This can be used as input when calling add-application.
453
+ ''')
454
+
455
+ parser.add_argument('--file', type=str, required=True,
456
+ help='Input file (apk or xcarchive folder)')
457
+ # inside subcommands ignore the first command_pos argv's
458
+ args = parser.parse_args(sys.argv[self.command_pos:])
459
+
460
+ self.initialize_from_global_args(global_args)
461
+ return self.commands.display_application_package_id(args.file)
462
+
463
+ def set_signing_certificate(self, global_args):
464
+ '''Set signing certificate'''
465
+ parser = argparse.ArgumentParser(
466
+ usage='vmx-aps set-signing-certificate [<args>]',
467
+ description='''Set signing certificate for an application.''')
468
+ parser.add_argument('--application-id', type=str, required=True, help='Application ID')
469
+ parser.add_argument('--file', type=str, required=False,
470
+ help='PEM encoded certificate file. If omitted, this unsets the current certificate')
471
+
472
+ # inside subcommands ignore the first command_pos argv's
473
+ args = parser.parse_args(sys.argv[self.command_pos:])
474
+
475
+ self.initialize_from_global_args(global_args)
476
+ return self.commands.set_signing_certificate(args.application_id, args.file)
477
+
478
+ def set_mapping_file(self, global_args):
479
+ '''Set mapping file'''
480
+ parser = argparse.ArgumentParser(
481
+ usage='vmx-aps set-mapping-file [<args>]',
482
+ description='''Set r8/proguard mapping file for an Android build.''')
483
+ parser.add_argument('--build-id', type=str, required=True, help='Build ID')
484
+ parser.add_argument('--file', type=str, required=True,
485
+ help='R8/Proguard mapping file for android')
486
+
487
+ # inside subcommands ignore the first command_pos argv's
488
+ args = parser.parse_args(sys.argv[self.command_pos:])
489
+
490
+ self.initialize_from_global_args(global_args)
491
+ return self.commands.set_mapping_file(args.build_id, args.file)
492
+
493
+ def get_sail_config(self, global_args):
494
+ '''Get SAIL configuration'''
495
+ parser = argparse.ArgumentParser(
496
+ usage='vmx-aps get-sail-config [<args>]',
497
+ description='Get SAIL configuration.')
498
+
499
+ parser.add_argument('--os', type=str, required=True, help='OS',
500
+ choices=['ios', 'android'])
501
+ parser.add_argument('--version', type=str, required=False, help='Version')
502
+ # inside subcommands ignore the first command_pos argv's
503
+ args = parser.parse_args(sys.argv[self.command_pos:])
504
+
505
+ self.initialize_from_global_args(global_args)
506
+ return self.commands.get_sail_config(args.os, args.version)
507
+
508
+ def get_version(self, global_args):
509
+ '''Get Version'''
510
+ parser = argparse.ArgumentParser(
511
+ usage='vmx-aps get-version [<args>]',
512
+ description='Get version.')
513
+
514
+ # inside subcommands ignore the first command_pos argv's
515
+ parser.parse_args(sys.argv[self.command_pos:])
516
+
517
+ self.initialize_from_global_args(global_args)
518
+ return self.commands.get_version()
519
+
520
+ def main():
521
+ Aps()
522
+
523
+ if __name__ == '__main__':
524
+ main()