diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..07e6e472 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/node_modules diff --git a/CommandsManual.md b/CommandsManual.md index da3f57f7..130168bc 100644 --- a/CommandsManual.md +++ b/CommandsManual.md @@ -205,8 +205,10 @@ Attributes accepted as `` (in order of search): - *development* — for the impCentral API’s "development_devicegroup" type - *pre-factory* — for the impCentral API’s "pre_factoryfixture_devicegroup" type +- *pre-dut* — for the impCentral API’s "pre_dut_devicegroup" type - *pre-production* — for the impCentral API’s "pre_production_devicegroup" type - *factory* — for the impCentral API’s "factoryfixture_devicegroup" type +- *dut* — for the impCentral API’s "dut_devicegroup" type - *production* — for the impCentral API’s "production_devicegroup" type ## Auth Files ## @@ -741,7 +743,8 @@ The user is asked to confirm the operation if any Deployment is going to be dele ``` impt dg create --name [--dg-type ] [--product ] [--descr ] - [--target ] [--region ] [--output ] [--help] + [--dut ] [--target ] + [--region ] [--output ] [--help] ``` Creates a new Device Group for the specified Product. Fails if a Device Group with the specified name already exists under the specified Product. @@ -752,6 +755,7 @@ Creates a new Device Group for the specified Product. Fails if a Device Group wi | --dg-type | -y | No | Yes | The new Device Group’s [type](#device-group-type). Default: *development*. If the type value is invalid, the command fails | | --product | -p | Yes/[Project](#project-files) | Yes | The [Product identifier](#product-identifier) of the Product to which the Device Group belongs. If not specified, the Product referenced by the [Project file](#project-files) in the current directory is used (if there is no Project file, the command fails) | | --descr | -s | No | Yes | An optional description of the Device Group | +| --dut | -u | No | Yes | The [Device Group identifier](#device-group-identifier) of the new Device Group’s device-under-test target Device Group. Should only be specified if the new Device Group is of the *factory* or *pre-factory* [type](#device-group-type). The device-under-test target Device Group must be of the [type](#device-group-type) *dut* or *pre-dut* correspondingly, and belong to the specified Product. Otherwise the command fails | | --target | -t | No | Yes | The [Device Group identifier](#device-group-identifier) of the new Device Group’s production target Device Group. Should only be specified if the new Device Group is of the *factory* or *pre-factory* [type](#device-group-type). The target Device Group must be of the [type](#device-group-type) *production* or *pre-production* correspondingly, and belong to the specified Product. Otherwise the command fails | | --region | -r | No | Yes | A region. May be specified if the new Device Group is of the *production* or *pre-production* [type](#device-group-type) only | | --output | -z | No | Yes | Adjusts the [command's output](#command-output) | @@ -765,7 +769,7 @@ impt dg delete [--dg ] [--builds] [--force] [--confirme Deletes the specified Device Group and, optionally, all of the related builds (Deployments). -The command fails if the Device Group is the production target of another Device Group. Use either [`impt dg update`](#device-group-update) to update the production target of the other Device Group, or `impt dg delete` to delete the other Device Group before the specified one. +The command fails if the Device Group is the device-under-test target or the production target of another Device Group. Use either [`impt dg update`](#device-group-update) to update the device-under-test / production target of the other Device Group, or `impt dg delete` to delete the other Device Group before the specified one. The command also fails when the `--force` option is not specified and: - There are devices assigned to the specified Device Group. Use either the `--force` option, [`impt dg unassign`](#device-group-unassign) or [`impt dg reassign`](#device-group-reassign) to unassign the devices from this Device Group. @@ -872,7 +876,8 @@ Unassigns all of the devices from the specified Device Group. Does nothing if th ``` impt dg update [--dg ] [--name ] - [--descr ] [--target ] + [--descr ] + [--dut ] [--target ] [--load-code-after-blessing [true|false]] [--min-supported-deployment ] [--output ] [--help] ``` @@ -884,6 +889,7 @@ Updates the specified Device Group. Fails if the specified Device Group does not | --dg | -g | Yes/[Project](#project-files) | Yes | A [Device Group identifier](#device-group-identifier). If not specified, the Device Group referenced by the [Project file](#project-files) in the current directory is used (if there is no Project file, the command fails) | | --name | -n | No | Yes | The Device Group’s new name. Must be unique among all of the Device Groups belonging to the Product | | --descr | -s | No | Yes | An optional description of the Device Group | +| --dut | -u | No | Yes | The [Device Group identifier](#device-group-identifier) of the specified Device Group’s device-under-test target Device Group. May only be specified for *factory* and *pre-factory* Device Groups. The device-under-test target Device Group must be of the [type](#device-group-type) *dut* or *pre-dut* correspondingly, and belong to the same Product as the specified Device Group. Otherwise the command fails | | --target | -t | No | Yes | The [Device Group identifier](#device-group-identifier) of the specified Device Group’s production target Device Group. May only be specified for *factory* and *pre-factory* Device Groups. The target Device Group must be of the [type](#device-group-type) *production* or *pre-production* correspondingly, and belong to the same Product as the specified Device Group. Otherwise the command fails | | --load-code-after-blessing | -l | No | No | Only applicable to *production* and *pre-production* Device Groups. If `true` or no value is supplied, production application code is immediately loaded by the device after blessing. If `false`, production code will be loaded when the device first connects as part of BlinkUp. Newly created Production Device Groups default this setting to `true` | | --min-supported-deployment | -m | No | Yes | The [Build identifier](#build-identifier) of the new *min_supported_deployment* (see the impCentral API specification). The Deployment should belong to this Device Group and should be newer than the current *min_supported_deployment* | @@ -1114,7 +1120,8 @@ Updates the specified Product with a new name and/or description. Fails if the s ``` impt project create --product [--create-product] --name [--descr ] [--device-file ] [--agent-file ] - [--pre-factory] [--target ] [--create-target] [--confirmed] + [--pre-factory] [--dut ] [--create-dut] + [--target ] [--create-target] [--confirmed] [--output ] [--help] ``` @@ -1123,6 +1130,7 @@ Creates a new Device Group for the specified Product and creates a new [Project The command fails if: - The specified Product does not exist and the `--create-product` option was not specified. Use either the `--create-product` option or the [`impt product create`](#product-create) command to create the Product first. - The Device Group with the specified name already exist in the specified Product. Use [`impt project link`](#project-link) to create the Project linked to that Device Group. +- The optionally specified device-under-test target Device Group does not exist and the `--create-dut` option was not specified. Use either the `--create-dut` option or the [`impt dg create`](#device-group-create) command to create the required Device Group of the [type](#device-group-type) *pre-dut*. - The optionally specified production target Device Group does not exist and the `--create-target` option was not specified. Use either the `--create-target` option or the [`impt dg create`](#device-group-create) command to create the required Device Group of the [type](#device-group-type) *pre-production*. The user is asked to confirm the operation if the current directory already contains a [Project file](#project-files), unless confirmed automatically with the `--confirmed` option. If confirmed, the existing [Project file](#project-files) is overwritten. @@ -1140,6 +1148,8 @@ At the end of the command execution, information about the Project is displayed | --device-file | -x | No | Yes | The device source code file name. Default: `device.nut`. If the file does not exist, an empty file is created | | --agent-file | -y | No | Yes | The agent source code file name. Default: `agent.nut`. If the file does not exist, an empty file is created | | --pre-factory | -f | No | No | If not specified, the new Device Group is of the [type](#device-group-type) *development*. If specified, the new Device Group is of the [type](#device-group-type) *pre-factory* | +| --dut | -u | No | Yes | The [Device Group identifier](#device-group-identifier) of the new Project Device Group’s device-under-test target Device Group. May be specified only if `--pre-factory` is also specified. The specified Device Group must be of the [type](#device-group-type) *pre-dut* and belong to the specified Product. Otherwise the command fails | +| --create-dut | -w | No | No | If the Device Group specified by `--dut` option does not exist, it is created. In this case, the value of `--dut` is used as the name of the new Device Group. If `--dut` is not specified or the Device Group specified by `--dut` exists, `--create-dut` is ignored | | --target | -t | No | Yes | The [Device Group identifier](#device-group-identifier) of the new Project Device Group’s production target Device Group. May be specified only if `--pre-factory` is also specified. The specified Device Group must be of the [type](#device-group-type) *pre-production* and belong to the specified Product. Otherwise the command fails | | --create-target | -r | No | No | If the Device Group specified by `--target` option does not exist, it is created. In this case, the value of `--target` is used as the name of the new Device Group. If `--target` is not specified or the Device Group specified by `--target` exists, `--create-target` is ignored | | --confirmed | -q | No | No | Executes the operation without asking additional confirmation from user | @@ -1158,11 +1168,15 @@ If the `--entities` option is specified, the command additionally: - Unassigns all devices from the Project Device Group. - Deletes the Project Device Group. - Deletes all of the Project Device Group’s builds (Deployments), including Deployments with their *flagged* attribute set to `true`. +- If the Project Device Group has a device-under-test target Device Group, and the latter is the device-under-test target for the Project Device Group only: + - Unassigns all devices from the device-under-test target Device Group. + - Deletes the device-under-test target Device Group. + - Deletes all the device-under-test target Device Group’s builds (Deployments), including Deployments with their *flagged* attribute set to `true`. - If the Project Device Group has a production target Device Group, and the latter is the production target for the Project Device Group only: - Unassigns all devices from the production target Device Group. - Deletes the production target Device Group. - Deletes all the production target Device Group’s builds (Deployments), including Deployments with their *flagged* attribute set to `true`. -- The corresponding Product if the corresponding Product (the Product which contains the Project Device Group) includes only the Project Device Group and, if applicable, the production target Device Group mentioned above. +- The corresponding Product if the corresponding Product (the Product which contains the Project Device Group) includes only the Project Device Group and, if applicable, the device-under-test target and production target Device Group mentioned above. The user is informed about all entities and files which are going to be deleted or updated, and is asked to confirm the operation, unless confirmed automatically with the `--confirmed` option. @@ -1223,11 +1237,12 @@ At the end of the command execution, information about the Project is displayed ``` impt project update [--name ] [--descr ] - [--device-file ] [--agent-file ] [--target ] + [--device-file ] [--agent-file ] + [--dut ] [--target ] [--output ] [--help] ``` -Updates the Project settings and/or the name, description, or production target of the Device Group referenced by the [Project file](#project-files). Fails if there is no [Project file](#project-files) in the current directory. +Updates the Project settings and/or the name, description, device-under-test target or production target of the Device Group referenced by the [Project file](#project-files). Fails if there is no [Project file](#project-files) in the current directory. Informs the user if the Device Group referenced by the [Project file](#project-files) does not exist. The [Project file](#project-files) is not updated or deleted in this case. To delete it, call [`impt project delete`](#project-delete). @@ -1239,6 +1254,7 @@ At the end of the command execution, information about the Project is displayed | --descr | -s | No | Yes | The Project Device Group’s new description | | --device-file | -x | No | Yes | A new device source code file name. If the file does not exist, an empty file is created | | --agent-file | -y | No | Yes | A new agent source code file name. If the file does not exist, an empty file is created | +| --dut | -u | No | Yes | The [Device Group identifier](#device-group-identifier) of the Project Device Group’s device-under-test target Device Group. May only be specified if the Project Device Group is of the *pre-factory* [type](#device-group-type). The specified device-under-test target Device Group must be of the [type](#device-group-type) *pre-dut* and belong to the same Product as the Project Device Group. Otherwise the command fails | | --target | -t | No | Yes | The [Device Group identifier](#device-group-identifier) of the Project Device Group’s production target Device Group. May only be specified if the Project Device Group is of the *pre-factory* [type](#device-group-type). The specified target Device Group must be of the [type](#device-group-type) *pre-production* and belong to the same Product as the Project Device Group. Otherwise the command fails | | --output | -z | No | Yes | Adjusts the [command's output](#command-output) | | --help | -h | No | No | Displays a description of the command. Ignores any other options | @@ -1508,9 +1524,9 @@ Updates the specified webhook with a new target URL and/or MIME content-type. Fa | -r | --create-target, --remove-tag, --remove, --region | | -s | --descr, --sha, --page-size, --stop-on-fail | | -t | --tag, --timeout, --temp, --target, --to, --tests | -| -u | --user, --full, --unflagged, --unflag, --unassigned, --unbond, --url | +| -u | --user, --full, --unflagged, --unflag, --unassigned, --unbond, --url, --dut | | -v | --version | -| -w | --wh, --pwd | +| -w | --wh, --pwd, --create-dut | | -x | --device-file | | -y | --agent-file, --dg-type | | -z | --output | diff --git a/DevelopmentGuide.md b/DevelopmentGuide.md index 6e9b0bea..dbb2b447 100644 --- a/DevelopmentGuide.md +++ b/DevelopmentGuide.md @@ -20,7 +20,8 @@ The full *impt* commands specification is described in the [*impt* Commands Manu - [Deleting a Project](#deleting-a-project) - [Typical Use-cases](#typical-use-cases) - [Develop Application Firmware](#develop-application-firmware) - - [Develop Factory Firmware](#develop-factory-firmware) + - [Develop Factory Test Firmware](#develop-factory-test-firmware) + - [Develop Factory Fixture Firmware](#develop-factory-fixture-firmware) - [Clean-up](#clean-up) ## Projects ## @@ -95,12 +96,19 @@ Use the [`impt project create`](./CommandsManual.md#project-create) command. Thi - If you already have the Product, specify its ID or name as a value of the `--product ` option. - If you want to create a new Product, specify its name as a value of the `--product ` option and add the `--create-product` option. -By default, it is assumed that the new Project is going to be used for application firmware development, so the new Device Group will be a Development Device Group. If you create the Project for [factory firmware](https://developer.electricimp.com/examples/factoryfirmware) development, specify the `--pre-factory` option to create a Pre-factory Device Group. In this case, you also need to specify a production target Device Group which should be of the *pre-production* [type](./CommandsManual.md#device-group-type) and belong to the same Product: +By default, it is assumed that the new Project is going to be used for application firmware development, so the new Device Group will be a Development Device Group. -- If you already have the production target Device Group, specify its ID or name as a value of the `--target ` option. -- If you need to create a new production target Device Group, specify its name as a value of the `--target ` option and add the `--create-target` option. +If you create the Project for [factory firmware](https://developer.electricimp.com/examples/factoryfirmware) development, specify the `--pre-factory` option to create a Pre-factory Device Group. In this case, you also need to specify two target Device Groups which should belong to the same Product: -Alternatively, you can create the required impCentral API entities in advance using other *impt* commands. For example, use [`impt product create`](./CommandsManual.md#product-create) to create the Product, and [`impt dg create`](./CommandsManual.md#device-group-create) to create the production target Device Group and/or the Project's Device Group itself. +1. Device-under-test (DUT) target Device Group which should be of the *pre-dut* [type](./CommandsManual.md#device-group-type). + - If you already have the device-under-test target Device Group, specify its ID or name as a value of the `--dut ` option. + - If you need to create a new device-under-test target Device Group, specify its name as a value of the `--dut ` option and add the `--create-dut` option. + +2. Production target Device Group which should be of the *pre-production* [type](./CommandsManual.md#device-group-type). + - If you already have the production target Device Group, specify its ID or name as a value of the `--target ` option. + - If you need to create a new production target Device Group, specify its name as a value of the `--target ` option and add the `--create-target` option. + +Alternatively, you can create the required impCentral API entities in advance using other *impt* commands. For example, use [`impt product create`](./CommandsManual.md#product-create) to create the Product, and [`impt dg create`](./CommandsManual.md#device-group-create) to create the production and/or DUT target Device Group and/or the Project's Device Group itself. The source code files can be specified directly using the `--device-file ` and `--agent-file ` options. Or the default names can be used. If a specified file does not exist, the command creates it as an empty file. @@ -133,7 +141,7 @@ IMPT COMMAND SUCCEEDS You can update your Project at any time with the [`impt project update`](./CommandsManual.md#project-update) command. The following can be updated: -- The project’s Device Group name and description, and the production target. The same can be done with [`impt dg update`](./CommandsManual.md#device-group-update). +- The project’s Device Group name and description, the production and device-under-test targets. The same can be done with [`impt dg update`](./CommandsManual.md#device-group-update). - Change the source files which are linked to the Project. **Note** You can update other impCentral API entities related to your Project by using other *impt* commands. For example, use [`impt product update`](./CommandsManual.md#product-update) to change the name and/or description of the related Product. @@ -555,23 +563,32 @@ Deployment "e0059ee6-2483-4ab1-50eb-e693e62155b7" is updated successfully. IMPT COMMAND SUCCEEDS ``` -### Develop Factory Firmware ### +### Develop Factory Test Firmware ### + +You may develop the tests which will be run on the devices-under-test during the factory process the same way as your application firmware. See [Develop Application Firmware](#develop-application-firmware) section. + +### Develop Factory Fixture Firmware ### **Note:** you need to have appropriate permission to make use of the impCentral API entities related to pre-production processes. -The following assumes you are making use of the Product created in the use-case above. +The following assumes: +- you are making use of the Product created in the use-case above, +- you already have the [Factory Test Firmware](#develop-factory-test-firmware) for devices tested during the factory process, +- you already have the [Application Firmware](#develop-application-firmware) for devices blessed during the factory process. 1. Create a new directory called, for example, `factory`. 2. Go to the new directory. -3. Create a Project for [factory firmware](https://developer.electricimp.com/examples/factoryfirmware) which is linked to the existing Product "MyProduct"; create new Device Groups "MyPreFactoryDG" and "MyPreProductionDG" in that Product, and empty files `factory.device.nut` and `factory.agent.nut`, and a [Project file](./CommandsManual.md#project-files). +3. Create a Project for [factory firmware](https://developer.electricimp.com/examples/factoryfirmware) which is linked to the existing Product "MyProduct"; create new Device Groups "MyPreFactoryDG", "MyPreDUTDG" and "MyPreProductionDG" in that Product, and empty files `factory.device.nut` and `factory.agent.nut`, and a [Project file](./CommandsManual.md#project-files). ``` > impt project create --pre-factory --product MyProduct --name MyPreFactoryDG - --descr "Factory Firmware" --target MyPreProductionDG --create-target + --descr "Factory Firmware" --dut MyPreDUTDG --create-dut + --target MyPreProductionDG --create-target --device-file factory.device.nut --agent-file factory.agent.nut Device Group "MyPreProductionDG" is created successfully. +Device Group "MyPreDUTDG" is created successfully. Device Group "MyPreFactoryDG" is created successfully. Device source file "factory.device.nut" is created successfully. Agent source file "factory.agent.nut" is created successfully. @@ -594,10 +611,23 @@ Project: id: 53ca29f4-937d-e684-729b-7e6b6a192c19 type: pre-production name: MyPreProductionDG + DUT Target: + id: 10c6179c-8608-1af2-7aee-5d1dc5539cda + type: pre-dut + name: MyPreDUTDG IMPT COMMAND SUCCEEDS ``` -4. Copy your application’s build tagged as “MyRC1” to the “MyPreProductionDG” Device Group. “MyRC1” contains the code for devices blessed by your factory firmware. Build attributes are not copied. +4. Copy your factory test firmware's build tagged as “MyTestsRC1” to the “MyPreDUTDG” Device Group. “MyTestsRC1” contains the test code for devices tested during the factory process. Build attributes are not copied. + +``` +> impt build copy --build MyTestsRC1 --dg MyPreDUTDG +Deployment "26036a34-9751-705b-4f91-3f50c2a9ac47" is created successfully. +Deployment "MyTestsRC1" is copied successfully to Deployment "26036a34-9751-705b-4f91-3f50c2a9ac47". +IMPT COMMAND SUCCEEDS +``` + +5. Copy your application firmware’s build tagged as “MyRC1” to the “MyPreProductionDG” Device Group. “MyRC1” contains the code for devices blessed during the factory process. Build attributes are not copied. ``` > impt build copy --build MyRC1 --dg MyPreProductionDG @@ -606,11 +636,11 @@ Deployment "MyRC1" is copied successfully to Deployment "a0e8e599-c6c5-62c0-2a88 IMPT COMMAND SUCCEEDS ``` -5. Write your [factory firmware](https://developer.electricimp.com/examples/factoryfirmware) using the Project’s source code files. +6. Write your [factory fixture firmware](https://developer.electricimp.com/examples/factoryfirmware) using the Project’s source code files. -6. Add your test factory BlinkUp fixture to your Electric Imp account as if it were a development device. +7. Add your test factory BlinkUp fixture to your Electric Imp account as if it were a development device. -7. List all of your unassigned devices and find the test fixture. +8. List all of your unassigned devices and find the test fixture. ``` > impt device list --unassigned @@ -628,7 +658,7 @@ Device: IMPT COMMAND SUCCEEDS ``` -8. Add the test fixture to your project. You can specify the device by its ID, Name, MAC or agent ID. +9. Add the test fixture to your project. You can specify the device by its ID, Name, MAC or agent ID. ``` > impt device assign --device 5000d8c46a56cfca @@ -636,7 +666,7 @@ Device "5000d8c46a56cfca" is assigned successfully to Device Group "71c3be05-a7d IMPT COMMAND SUCCEEDS ``` -9. Create a new factory firmware build, run it and start logging. +10. Create a new factory firmware build, run it and start logging. ``` > impt build run --log @@ -655,13 +685,13 @@ IMPT COMMAND SUCCEEDS Press to exit. ``` -10. Use the fixture to configure test DUTs. They will connect, and download and run the factory firmware which will test and bless them. +11. Use the fixture to configure test DUTs. They will connect, and download and run the test application which will test and bless them. -11. Stop the logging by pressing *Ctrl-C*. +12. Stop the logging by pressing *Ctrl-C*. -12. If needed, update your factory firmware code, and create and run a new build by using the command listed in Step 9. +13. If needed, update your factory fixture firmware code, and create and run a new build by using the command listed in Step 10. -13. When you are satisfied with your factory firmware, mark the latest build by a tag, eg. “MyFactoryRC1”, and set its *flagged* attribute to `true` (this protects it from accidental deletion). +14. When you are satisfied with your factory fixture firmware, mark the latest build by a tag, eg. “MyFactoryRC1”, and set its *flagged* attribute to `true` (this protects it from accidental deletion). ``` > impt build update --descr "My Factory Firmware Release Candidate 1" --tag MyFactoryRC1 --flagged @@ -673,11 +703,11 @@ IMPT COMMAND SUCCEEDS #### Go To Production #### -If you are developing production firmware, whether application, factory or both, you may want to keep the impCentral API entities your created, especially the final builds, but still do some minimal clean-up after your development activities are complete. +If you are developing production firmware, whether application, factory fixture, factory test or all, you may want to keep the impCentral API entities your created, especially the final builds, but still do some minimal clean-up after your development activities are complete. 1. Go to the `factory` directory. -2. Delete all unnecessary builds of your factory firmware code (*flagged* builds will not be deleted). +2. Delete all unnecessary builds of your factory fixture firmware code (*flagged* builds will not be deleted). ``` > impt dg builds --remove @@ -740,7 +770,7 @@ Device: IMPT COMMAND SUCCEEDS ``` -5. If you want, delete your factory firmware Project and the source files in this directory. The impCentral API entities will not be deleted; you may keep the source files in your version control or software configuration management tool. +5. If you want, delete your factory fixture firmware Project and the source files in this directory. The impCentral API entities will not be deleted; you may keep the source files in your version control or software configuration management tool. ``` > impt project delete --files @@ -840,6 +870,21 @@ Product: Min supported Deployment: id: 429c3a4c-947b-3d84-77f8-fa15cf3038b5 sha: f4756c7578aa69910a8857d8ec08ff15bc2d7e1a8fc8007caf98e3ea9fca07a3 + Device Group: + id: 10c6179c-8608-1af2-7aee-5d1dc5539cda + type: pre-dut + name: MyPreDUTDG + Current Deployment: + id: 26036a34-9751-705b-4f91-3f50c2a9ac47 + sha: 5c832c6b984ea536dfa5a8f56707809a8cfac089df49196bd742fda158501b27 + Min supported Deployment: + id: 26036a34-9751-705b-4f91-3f50c2a9ac47 + sha: 5c832c6b984ea536dfa5a8f56707809a8cfac089df49196bd742fda158501b27 + DUT Target for: + Device Group: + id: 71c3be05-a7d2-a326-8906-8af3b205bd13 + type: pre-factory + name: MyPreFactoryDG Device Group: id: 71c3be05-a7d2-a326-8906-8af3b205bd13 type: pre-factory @@ -856,6 +901,10 @@ Product: id: 53ca29f4-937d-e684-729b-7e6b6a192c19 type: pre-production name: MyPreProductionDG + DUT Target: + id: 10c6179c-8608-1af2-7aee-5d1dc5539cda + type: pre-dut + name: MyPreDUTDG Device Group: id: 53ca29f4-937d-e684-729b-7e6b6a192c19 type: pre-production @@ -900,6 +949,8 @@ Global logout is successful. IMPT COMMAND SUCCEEDS ``` +**Note.** If you developed the factory test code as a project, you may clean-up it the same way as your application project. + The *impt* usage for factory and production processes is described fully in the [*impt* Production Guide](./ProductionGuide.md). #### Full Clean-up #### @@ -917,6 +968,10 @@ Device Group: id: 53ca29f4-937d-e684-729b-7e6b6a192c19 type: pre-production name: MyPreProductionDG +Device Group: + id: 10c6179c-8608-1af2-7aee-5d1dc5539cda + type: pre-dut + name: MyPreDUTDG Device Group: id: 71c3be05-a7d2-a326-8906-8af3b205bd13 type: pre-factory @@ -924,6 +979,9 @@ Device Group: Deployment: id: a0e8e599-c6c5-62c0-2a88-a8d4ac3e07d8 sha: f4756c7578aa69910a8857d8ec08ff15bc2d7e1a8fc8007caf98e3ea9fca07a3 +Deployment: + id: 26036a34-9751-705b-4f91-3f50c2a9ac47 + sha: 5c832c6b984ea536dfa5a8f56707809a8cfac089df49196bd742fda158501b27 Deployment: id: 4a7339e4-1f7c-3caa-2ce5-15c367df9a3f sha: f6f710ce359d1e1bee10b17f62184a8f2a9884d17dd44292b0e9dc640c52daf6 @@ -960,8 +1018,10 @@ Device "5000d8c46a56cfca" is unassigned successfully. Deployment "4a7339e4-1f7c-3caa-2ce5-15c367df9a3f" is updated successfully. Device Group "71c3be05-a7d2-a326-8906-8af3b205bd13" is deleted successfully. Device Group "53ca29f4-937d-e684-729b-7e6b6a192c19" is deleted successfully. +Device Group "10c6179c-8608-1af2-7aee-5d1dc5539cda" is deleted successfully. Deployment "5dbfc968-5c94-fc4c-dd37-424f779b7e40" is deleted successfully. Deployment "4a7339e4-1f7c-3caa-2ce5-15c367df9a3f" is deleted successfully. +Deployment "26036a34-9751-705b-4f91-3f50c2a9ac47" is deleted successfully. Deployment "a0e8e599-c6c5-62c0-2a88-a8d4ac3e07d8" is deleted successfully. Device/agent source files "factory.device.nut", "factory.agent.nut" are deleted successfully. Project is deleted successfully. @@ -1034,3 +1094,5 @@ IMPT COMMAND SUCCEEDS Global logout is successful. IMPT COMMAND SUCCEEDS ``` + +**Note.** If you developed the factory test code as a project, you may clean-up it the same way as your application project. diff --git a/LICENSE b/LICENSE index 4667df8d..84f77b3b 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright 2018 Electric Imp +Copyright 2018-2019 Electric Imp SPDX-License-Identifier: MIT diff --git a/ProductionGuide.md b/ProductionGuide.md index 94391081..bf6fee92 100644 --- a/ProductionGuide.md +++ b/ProductionGuide.md @@ -1,5 +1,7 @@ # impt Production Guide # +**NOTE:** The contents of this guide have **NOT** been updated with the release of v2.5.0 (support for DUT device groups), and so some of the contents may be out of date. + This additional guide is intended for those customers who use *impt* with [production processes](https://developer.electricimp.com/manufacturing). You may use scripts on top of *impt* commands to automate some of the operations. Please read the main [Read Me file](./README.md) first as it covers all the basic *impt* usage and its common components. @@ -44,14 +46,14 @@ Device Groups may be of different [types](./CommandsManual.md#device-group-type) ### Production Device Groups ### -Your production devices, which are utilized by end-users, are organized into one or more Device Groups of the *production* type. Different Production Device Groups within the same Product may be used to encapsulate and manage different versions or flavors of your application. Production devices are units that have been blessed; up until that point they are referred to as devices under test (DUTs). +Your production devices, which are utilized by end-users, are organized into one or more Device Groups of the *production* type. Different Production Device Groups within the same Product may be used to encapsulate and manage different versions or flavors of your application. Production devices are units that have been blessed; up until that point they are referred to as devices under test (DUTs). You can create a Production Device Group with [`impt dg create --dg-type production`](./CommandsManual.md#device-group-create). Production Device Groups have an attribute called *load-code-after-blessing*. This defines when your application is loaded onto production devices: in your factory after blessing, or when an end-user activates a device using BlinkUp™. When a new Production Device Group is created, this attribute always has the value `true`. To change the attribute you can use the `--load-code-after-blessing` option of the [`impt dg update`](./CommandsManual.md#device-group-update) command: ``` -impt dg create --name MyProductionDG --descr "Production Device Group for application" +impt dg create --name MyProductionDG --descr "Production Device Group for application" --dg-type production --product MyProduct impt dg update --dg MyProductionDG --load-code-after-blessing false ``` @@ -63,11 +65,11 @@ For your [factory setup](https://developer.electricimp.com/manufacturing/factory You can create Factory Device Group with the [`impt dg create --dg-type factory`](./CommandsManual.md#device-group-create) command. Each Factory Device Group references a single Production Device Group within the same Product. This is the Factory Device Group’s *target* and this the Production Device Group to which DUTs will automatically be assigned once they have been blessed and become production devices. You specify a Factory Device Group’s target by using the `--target` option during Factory Device Group creation: - + ``` -impt dg create --name MyFactoryDG --descr "Factory Device Group for factory firmware" +impt dg create --name MyFactoryDG --descr "Factory Device Group for factory firmware" --dg-type factory --product MyProduct --target MyProductionDG -``` +``` ## Deployments ## @@ -84,7 +86,7 @@ This guide assumes you have already developed and tested your application and fa impt build copy --build MyRC1 --dg MyProductionDG ``` -``` +``` impt build copy --build MyFactoryRC1 --dg MyFactoryDG ``` @@ -98,7 +100,7 @@ In order to connect your Factory BlinkUp Fixtures to the factory’s WiFi networ ``` impt device assign --device --dg MyFactoryDG -``` +``` ### Devices Under Test (DUTs) ### diff --git a/bin/cmds/dg/create.js b/bin/cmds/dg/create.js index acb165a5..9cbda054 100644 --- a/bin/cmds/dg/create.js +++ b/bin/cmds/dg/create.js @@ -1,6 +1,6 @@ // MIT License // -// Copyright 2018 Electric Imp +// Copyright 2018-2019 Electric Imp // // SPDX-License-Identifier: MIT // @@ -59,6 +59,13 @@ exports.builder = function (yargs) { describe : 'An optional description of the Device Group.', _usage : '' }, + [Options.DUT] : { + demandOption : false, + describe : Util.format("The Device Group identifier of the new Device Group's device-under-test target Device Group." + + " Should only be specified if the new Device Group is of the %s or %s type." + + " The device-under-test target Device Group must be of the type %s or %s correspondingly, and belong to the specified Product.", + Options.DG_TYPE_FACTORY, Options.DG_TYPE_PRE_FACTORY, Options.DG_TYPE_DUT, Options.DG_TYPE_PRE_DUT) + }, [Options.TARGET] : { demandOption : false, describe : Util.format("The Device Group identifier of the new Device Group's production target Device Group." + @@ -74,7 +81,11 @@ exports.builder = function (yargs) { .options(options) .check(function (argv) { const opts = new Options(argv); - if (!opts.target && Options.isProductionTargetRequired(opts.deviceGroupType)) { + if (!opts.dut && Options.isTargetRequired(opts.deviceGroupType)) { + return new Errors.ImptError(UserInteractor.ERRORS.CMD_TARGET_REQUIRED, + Options.DUT, Options.getDeviceGroupTypeName(opts.deviceGroupType)); + } + if (!opts.target && Options.isTargetRequired(opts.deviceGroupType)) { return new Errors.ImptError(UserInteractor.ERRORS.CMD_TARGET_REQUIRED, Options.TARGET, Options.getDeviceGroupTypeName(opts.deviceGroupType)); } diff --git a/bin/cmds/dg/update.js b/bin/cmds/dg/update.js index d51fc49d..17c7288b 100644 --- a/bin/cmds/dg/update.js +++ b/bin/cmds/dg/update.js @@ -1,6 +1,6 @@ // MIT License // -// Copyright 2018 Electric Imp +// Copyright 2018-2019 Electric Imp // // SPDX-License-Identifier: MIT // @@ -50,6 +50,14 @@ exports.builder = function (yargs) { describe : 'An optional description of the Device Group.', _usage : '' }, + [Options.DUT] : { + demandOption : false, + describe : Util.format("The Device Group identifier of the specified Device Group's device-under-test target Device Group." + + " May only be specified for %s and %s Device Groups." + + " The device-under-test target Device Group must be of the type %s or %s correspondingly," + + " and belong to the same Product as the specified Device Group.", + Options.DG_TYPE_FACTORY, Options.DG_TYPE_PRE_FACTORY, Options.DG_TYPE_DUT, Options.DG_TYPE_PRE_DUT) + }, [Options.TARGET] : { demandOption : false, describe : Util.format("The Device Group identifier of the specified Device Group's production target Device Group." + diff --git a/bin/cmds/project/create.js b/bin/cmds/project/create.js index a332043f..d053319d 100644 --- a/bin/cmds/project/create.js +++ b/bin/cmds/project/create.js @@ -1,6 +1,6 @@ // MIT License // -// Copyright 2018 Electric Imp +// Copyright 2018-2019 Electric Imp // // SPDX-License-Identifier: MIT // @@ -65,6 +65,14 @@ exports.builder = function (yargs) { default: 'agent.nut' }, [Options.PRE_FACTORY] : false, + [Options.DUT] : { + demandOption : false, + describe : Util.format("The Device Group identifier of the new Project Device Group's device-under-test target Device Group." + + " May be specified only if --%s is also specified." + + " The specified Device Group must be of the type %s and belong to the specified Product.", + Options.PRE_FACTORY, Options.DG_TYPE_PRE_DUT) + }, + [Options.CREATE_DUT] : false, [Options.TARGET] : { demandOption : false, describe : Util.format("The Device Group identifier of the new Project Device Group's production target Device Group." + @@ -84,6 +92,9 @@ exports.builder = function (yargs) { if (opts.preFactory && !opts.target || !opts.preFactory && opts.target) { return new Errors.CommandSyntaxError(UserInteractor.ERRORS.CMD_COOPERATIVE_OPTIONS, Options.PRE_FACTORY, Options.TARGET); } + if (opts.preFactory && !opts.dut || !opts.preFactory && opts.dut) { + return new Errors.CommandSyntaxError(UserInteractor.ERRORS.CMD_COOPERATIVE_OPTIONS, Options.PRE_FACTORY, Options.DUT); + } return Options.checkOptions(argv, options); }) .strict(); diff --git a/bin/cmds/project/update.js b/bin/cmds/project/update.js index c1834cef..d7d78121 100644 --- a/bin/cmds/project/update.js +++ b/bin/cmds/project/update.js @@ -1,6 +1,6 @@ // MIT License // -// Copyright 2018 Electric Imp +// Copyright 2018-2019 Electric Imp // // SPDX-License-Identifier: MIT // @@ -60,6 +60,13 @@ exports.builder = function (yargs) { demandOption : false, describe: 'A new agent source code file name. If the file does not exist, an empty file is created.' }, + [Options.DUT] : { + demandOption : false, + describe : Util.format("The Device Group identifier of the Project Device Group's device-under-test target Device Group." + + " May only be specified if the Project Device Group is of the %s type." + + " The specified device-under-test target Device Group must be of the type %s and belong to the same Product as the Project Device Group.", + Options.DG_TYPE_PRE_FACTORY, Options.DG_TYPE_PRE_DUT) + }, [Options.TARGET] : { demandOption : false, describe : Util.format("The Device Group identifier of the Project Device Group's production target Device Group." + diff --git a/lib/DeviceGroup.js b/lib/DeviceGroup.js index 3f319ef9..ee2cf6d9 100644 --- a/lib/DeviceGroup.js +++ b/lib/DeviceGroup.js @@ -1,6 +1,6 @@ // MIT License // -// Copyright 2018 Electric Imp +// Copyright 2018-2019 Electric Imp // // SPDX-License-Identifier: MIT // @@ -32,6 +32,8 @@ const ListHelper = require('./util/ListHelper'); const DeleteHelper = require('./util/DeleteHelper'); const UserInteractor = require('./util/UserInteractor'); const Identifier = require('./util/Identifier'); +const ProductionTarget = require('./util/Target').ProductionTarget; +const DutTarget = require('./util/Target').DutTarget; const Entity = require('./util/Entity'); const Options = require('./util/Options'); const Errors = require('./util/Errors'); @@ -47,6 +49,7 @@ const ATTR_REGION = 'region'; const REL_CURRENT_DEPLOYMENT = 'current_deployment'; const REL_MIN_SUPPORTED_DEPLOYMENT = 'min_supported_deployment'; const REL_PRODUCTION_TARGET = 'production_target'; +const REL_DUT_TARGET = 'dut_target'; // This class represents Device Group impCentral API entity. // Provides methods used by impt Device Group Manipulation Commands. @@ -80,6 +83,14 @@ class DeviceGroup extends Entity { return undefined; } + // Returns the DUT target Device Group, if exists + get dutTargetId() { + if (REL_DUT_TARGET in this.apiEntity.relationships) { + return this.apiEntity.relationships[REL_DUT_TARGET].id; + } + return undefined; + } + // Returns the Device Group related Product id get relatedProductId() { return this.apiEntity.relationships[Entity.REL_PRODUCT].id; @@ -106,7 +117,10 @@ class DeviceGroup extends Entity { _createByProductId(productId, options, deviceGroupType) { const dgType = deviceGroupType ? deviceGroupType : options.deviceGroupType; - return this._processProductionTarget(productId, dgType, options). + this._productionTarget = new ProductionTarget(options, dgType); + this._dutTarget = new DutTarget(options, dgType); + return this._processTarget(this._productionTarget, productId). + then(() => this._processTarget(this._dutTarget, productId)). then(() => { const attrs = { [Entity.ATTR_NAME] : options.name, @@ -116,64 +130,25 @@ class DeviceGroup extends Entity { attrs[ATTR_REGION] = options.region; } this.initByName(options.name); - return super._createEntity(productId, dgType, attrs, this._getProductionTargetOptions()); + return super._createEntity( + productId, + dgType, + attrs, + this._getTargetOptions(this._productionTarget), + this._getTargetOptions(this._dutTarget)); }); } - _getProductionTargetType(deviceGroupType) { - switch (deviceGroupType) { - case DeviceGroups.TYPE_PRE_FACTORY_FIXTURE: - return DeviceGroups.TYPE_PRE_PRODUCTION; - case DeviceGroups.TYPE_FACTORY_FIXTURE: - return DeviceGroups.TYPE_PRODUCTION; - } - return null; - } - - _processProductionTarget(productId, deviceGroupType, options) { - if (options.target) { - if (!Options.isProductionTargetRequired(deviceGroupType)) { - return Promise.reject(new Errors.ImptError( - UserInteractor.ERRORS.WRONG_DG_TYPE_FOR_OPTION, - Options.TARGET, - Options.getDeviceGroupTypeName(deviceGroupType), - Options.getDeviceGroupTypeName(DeviceGroups.TYPE_PRE_FACTORY_FIXTURE), - Options.getDeviceGroupTypeName(DeviceGroups.TYPE_FACTORY_FIXTURE))); - } - this._productionTarget = new DeviceGroup(); - return this._productionTarget.initByIdentifier(options.target).findEntity().then(entity => { - const targetRequiredType = this._getProductionTargetType(deviceGroupType); - if (entity) { - if (this._productionTarget.type !== targetRequiredType) { - return Promise.reject( - new Errors.ImptError( - UserInteractor.ERRORS.TARGET_DG_WRONG_TYPE, - this._productionTarget.identifierInfo, - Options.getDeviceGroupTypeName(this._productionTarget.type), - Options.getDeviceGroupTypeName(targetRequiredType))); - } - else if (this._productionTarget.relatedProductId !== productId) { - return Promise.reject( - new Errors.ImptError(UserInteractor.ERRORS.TARGET_DG_WRONG_PRODUCT)); - } - return Promise.resolve(); - } - else if (options.createTarget) { - return this._productionTarget._createByProductId( - productId, new Options({ [Options.NAME] : options.target }), targetRequiredType); - } - else { - return Promise.reject( - new Errors.ImptError(UserInteractor.ERRORS.TARGET_DG_NOT_FOUND, this._productionTarget.identifierInfo)); - } - }); + _processTarget(target, productId) { + if (target.isSpecified()) { + return target.findOrCreate(productId); } return Promise.resolve(); } - _getProductionTargetOptions() { - return this._productionTarget ? - { [Entity.ATTR_ID] : this._productionTarget.id, [ATTR_TYPE] : this._productionTarget.type } : + _getTargetOptions(target) { + return target.deviceGroupId ? + { [Entity.ATTR_ID] : target.deviceGroupId, [ATTR_TYPE] : target.deviceGroupType } : null; } @@ -187,7 +162,14 @@ class DeviceGroup extends Entity { // or rejects with an error _update(options) { return this.getEntity(). - then(() => this._processProductionTarget(this.relatedProductId, this.type, options)). + then(() => { + this._productionTarget = new ProductionTarget(options, this.type); + return this._processTarget(this._productionTarget, this.relatedProductId); + }). + then(() => { + this._dutTarget = new DutTarget(options, this.type); + return this._processTarget(this._dutTarget, this.relatedProductId); + }). then(() => { if (options.loadCodeAfterBlessing !== undefined && !Options.isProductionDeviceGroupType(this.type)) { return Promise.reject(new Errors.ImptError( @@ -233,7 +215,11 @@ class DeviceGroup extends Entity { if (options.loadCodeAfterBlessing !== undefined) { attrs[ATTR_LOAD_CODE_AFTER_BLESSING] = options.loadCodeAfterBlessing; } - return super._updateEntity(this.type, attrs, this._getProductionTargetOptions()); + return super._updateEntity( + this.type, + attrs, + this._getTargetOptions(this._productionTarget), + this._getTargetOptions(this._dutTarget)); }); } @@ -275,14 +261,14 @@ class DeviceGroup extends Entity { }); } - _collectFullTargetInfo() { - if (Options.isProductionDeviceGroupType(this.type)) { + _collectFullTargetInfo(target) { + if (Options.isProductionDeviceGroupType(this.type) || Options.isDutDeviceGroupType(this.type)) { const productId = this.relatedProductId; return new DeviceGroup().listByProduct(productId). then(devGroups => this._addRelatedEntities( - devGroups.filter((devGroup) => devGroup.productionTargetId === this.id), + devGroups.filter((devGroup) => this.id === target.getTargetId(devGroup)), false, - UserInteractor.MESSAGES.DG_PRODUCTION_TARGET_FOR)); + target.backRelationName)); } return Promise.resolve(); } @@ -301,7 +287,8 @@ class DeviceGroup extends Entity { } _collectFullInfo() { - return this._collectFullTargetInfo(). + return this._collectFullTargetInfo(new ProductionTarget(null, this.type)). + then(() => this._collectFullTargetInfo(new DutTarget(null, this.type))). then(() => this._collectFullWebhooksInfo()). then(() => this._collectFullDevicesInfo()); } @@ -538,6 +525,11 @@ class DeviceGroup extends Entity { name : REL_PRODUCTION_TARGET, Entity : DeviceGroup, displayName : UserInteractor.MESSAGES.DG_PRODUCTION_TARGET, + }, + { + name : REL_DUT_TARGET, + Entity : DeviceGroup, + displayName : UserInteractor.MESSAGES.DG_DUT_TARGET, } ].concat(super._getInfoRelationships()); } @@ -559,6 +551,11 @@ class DeviceGroup extends Entity { name : REL_PRODUCTION_TARGET, Entity : DeviceGroup, displayName : UserInteractor.MESSAGES.DG_PRODUCTION_TARGET, + }, + { + name : REL_DUT_TARGET, + Entity : DeviceGroup, + displayName : UserInteractor.MESSAGES.DG_DUT_TARGET, } ]; } @@ -616,6 +613,7 @@ class DeviceGroup extends Entity { this._Entity = DeviceGroup; this._identifier.init(Identifier.ENTITY_TYPE.TYPE_DEVICE_GROUP, [Entity.ATTR_NAME]); this._productionTarget = null; + this._dutTarget = null; } // Sets the Device Group specific options based on impt command options: diff --git a/lib/Project.js b/lib/Project.js index 1748282e..838e44fa 100644 --- a/lib/Project.js +++ b/lib/Project.js @@ -1,6 +1,6 @@ // MIT License // -// Copyright 2018 Electric Imp +// Copyright 2018-2019 Electric Imp // // SPDX-License-Identifier: MIT // @@ -35,6 +35,8 @@ const DeleteHelper = require('./util/DeleteHelper'); const Options = require('./util/Options'); const Product = require('./Product'); const DeviceGroup = require('./DeviceGroup'); +const ProductionTarget = require('./util/Target').ProductionTarget; +const DutTarget = require('./util/Target').DutTarget; const Build = require('./Build'); const Device = require('./Device'); const Auth = require('./Auth'); @@ -162,7 +164,7 @@ class Project { // Returns: Promise that resolves when the Project updated successfully // or rejects with an error _updateDeviceGroup(options) { - if (options.name || options.description || options.target) { + if (options.name || options.description || options.target || options.dut) { return this._deviceGroup._update(options); } return Promise.resolve(); @@ -262,25 +264,18 @@ class Project { return this._initDeviceGroup(). then(() => { const productId = this._deviceGroup.relatedProductId; + const relatedDeviceGroupCount = this._deviceGroup.productionTargetId && this._deviceGroup.dutTargetId ? 3 : 1; return new DeviceGroup().listByProduct(productId). then(devGroups => { productDeviceGroups = devGroups; - if (devGroups.length === 1 || this._deviceGroup.productionTargetId && devGroups.length === 2) { + if (devGroups.length === relatedDeviceGroupCount) { return new Product().initById(productId)._collectDeleteSummary(summary, new Options()); } return Promise.resolve(); }); }). - then(() => { - const productionTargetId = this._deviceGroup.productionTargetId; - if (productionTargetId) { - if (productDeviceGroups.filter(devGroup => devGroup.productionTargetId === productionTargetId).length === 1) { - return new DeviceGroup().initById(productionTargetId). - _collectDeleteSummary(summary, new Options({ [Options.FORCE] : true, [Options.BUILDS] : true })); - } - } - return Promise.resolve(); - }). + then(() => this._collectTargetDeleteSummary(summary, productDeviceGroups, new ProductionTarget(null, this._deviceGroup.type))). + then(() => this._collectTargetDeleteSummary(summary, productDeviceGroups, new DutTarget(null, this._deviceGroup.type))). then(() => this._deviceGroup._collectDeleteSummary(summary, new Options({ [Options.FORCE] : true, [Options.BUILDS] : true }))). catch(error => { if (error instanceof Errors.OutdatedConfigEntityError) { @@ -296,6 +291,16 @@ class Project { } } + _collectTargetDeleteSummary(summary, productDeviceGroups, target) { + const targetDeviceGroupId = target.getTargetId(this._deviceGroup); + if (targetDeviceGroupId && productDeviceGroups.filter(devGroup => + targetDeviceGroupId === target.getTargetId(devGroup)).length === 1) { + return new DeviceGroup().initById(targetDeviceGroupId). + _collectDeleteSummary(summary, new Options({ [Options.FORCE] : true, [Options.BUILDS] : true })); + } + return Promise.resolve(); + } + // 'project delete' command implementation. // Deletes the current Project: // - deletes Project File located in the current directory diff --git a/lib/util/DeleteHelper.js b/lib/util/DeleteHelper.js index 0e8f3c7c..3b4725ef 100644 --- a/lib/util/DeleteHelper.js +++ b/lib/util/DeleteHelper.js @@ -1,6 +1,6 @@ // MIT License // -// Copyright 2018 Electric Imp +// Copyright 2018-2019 Electric Imp // // SPDX-License-Identifier: MIT // @@ -29,6 +29,7 @@ const UserInteractor = require('./UserInteractor'); const ImpCentralApiHelper = require('./ImpCentralApiHelper'); const Identifier = require('./Identifier'); const Utils = require('./Utils'); +const Errors = require('./Errors'); // Helper class for impt delete commands. class DeleteHelper { @@ -186,17 +187,25 @@ class DeleteHelper { } _unassignDevices() { - return this._helper.makeConcurrentOperations(this._summary.assignedDevices, (device) => device._unassign()); + return this._helper.makeConcurrentOperations( + this._summary.assignedDevices, + (device) => device._unassign(), + this._onDeviceAlreadyUnassignedError.bind(this)); } _unflagBuilds() { - return this._helper.makeConcurrentOperations(this._summary.flaggedBuilds, (build) => build._unflag()); + return this._helper.makeConcurrentOperations( + this._summary.flaggedBuilds, + (build) => build._unflag(), + this._onEntityNotFoundError.bind(this)); } _deleteEntities(entities) { - return this._helper.makeConcurrentOperations(entities, - (entity) => entity._deleteEntity().then(() => - UserInteractor.printInfo(UserInteractor.MESSAGES.ENTITY_DELETED, entity.identifierInfo))); + return this._helper.makeConcurrentOperations( + entities, + (entity) => entity._deleteEntity(). + then(() => UserInteractor.printInfo(UserInteractor.MESSAGES.ENTITY_DELETED, entity.identifierInfo)), + this._onEntityNotFoundError.bind(this)); } _deleteAllEntities() { @@ -218,7 +227,7 @@ class DeleteHelper { const devGroupsWithTarget = []; const devGroupsWithoutTarget = []; for (let devGroup of entities) { - if (devGroup.productionTargetId) { + if (devGroup.productionTargetId || devGroup.dutTargetId) { devGroupsWithTarget.push(devGroup); } else { @@ -235,9 +244,29 @@ class DeleteHelper { } _removeDevices() { - return this._helper.makeConcurrentOperations(this._summary.devices, (device) => - device._deleteEntity().then(() => - UserInteractor.printInfo(UserInteractor.MESSAGES.ENTITY_REMOVED, device.identifierInfo))); + return this._helper.makeConcurrentOperations( + this._summary.devices, + (device) => device._deleteEntity(). + then(() => UserInteractor.printInfo(UserInteractor.MESSAGES.ENTITY_REMOVED, device.identifierInfo)), + this._onEntityNotFoundError.bind(this)); + } + + _onEntityNotFoundError(error, entity) { + if (error instanceof Errors.EntityNotFoundError) { + UserInteractor.printInfo(UserInteractor.MESSAGES.ENTITY_ALREADY_DELETED, entity.identifierInfo); + } + else { + throw error; + } + } + + _onDeviceAlreadyUnassignedError(error, device) { + if (error instanceof Errors.EntityNotFoundError || this._helper.isMultipleEntitiesNotFoundError(error)) { + UserInteractor.printInfo(UserInteractor.MESSAGES.DEVICE_ALREADY_UNASSIGNED, device.identifierInfo); + } + else { + throw error; + } } _deleteSourceFiles(summary) { diff --git a/lib/util/ImpCentralApiHelper.js b/lib/util/ImpCentralApiHelper.js index f06e4aa3..c8fe09b6 100644 --- a/lib/util/ImpCentralApiHelper.js +++ b/lib/util/ImpCentralApiHelper.js @@ -1,6 +1,6 @@ // MIT License // -// Copyright 2018 Electric Imp +// Copyright 2018-2019 Electric Imp // // SPDX-License-Identifier: MIT // @@ -127,7 +127,7 @@ class ImpCentralApiHelper { return Promise.reject(new Errors.InternalLibraryError(UserInteractor.ERRORS.WRONG_IDENTIFIER_TYPE)); } - makeConcurrentOperations(elements, operation) { + makeConcurrentOperations(elements, operation, onOperationError = null) { const chunks = []; for (var i = 0; i < elements.length; i += CONCURRENT_REQUESTS_LIMIT) { chunks.push(elements.slice(i, i + CONCURRENT_REQUESTS_LIMIT)); @@ -136,7 +136,17 @@ class ImpCentralApiHelper { let result = []; return chunks.reduce( (acc, chunk) => acc. - then(() => Promise.all(chunk.map(elem => operation(elem)))). + then(() => Promise.all(chunk.map(elem => { + return operation(elem). + catch(error => { + if (onOperationError) { + onOperationError(error, elem); + } + else { + throw error; + } + }); + }))). then(res => { result = result.concat(res); return Promise.resolve(); @@ -308,8 +318,18 @@ class ImpCentralApiHelper { this._entityStorage.setList(entityType, listRequest); } return listRequest.then(result => { - this._entityStorage.setListEntities(result); - return result; + // It seems impCentral sometimes returns duplicated entities during paginated list. + // Need to check the final list of entities and remove duplicates. + const uniqueEntities = []; + const ids = new Set(); + for (let entity of result) { + if (!ids.has(entity.id)) { + ids.add(entity.id); + uniqueEntities.push(entity); + } + } + this._entityStorage.setListEntities(uniqueEntities); + return uniqueEntities; }).catch(error => { if (noFilters) { this._entityStorage.clearList(entityType); @@ -538,6 +558,14 @@ class ImpCentralApiHelper { return error instanceof ImpCentralApi.Errors.ImpCentralApiError && error._statusCode === 403; } + + isMultipleEntitiesNotFoundError(error) { + return error instanceof ImpCentralApi.Errors.ImpCentralApiError && + error.statusCode === 400 && + error.body && error.body.hasOwnProperty('errors') && + Array.isArray(error.body.errors) && error.body.errors.length > 0 && + error.body.errors.every(err => err.hasOwnProperty('status') && Number(err.status) === 404); + } } module.exports = ImpCentralApiHelper; diff --git a/lib/util/Options.js b/lib/util/Options.js index a87074c5..b57a9196 100644 --- a/lib/util/Options.js +++ b/lib/util/Options.js @@ -1,6 +1,6 @@ // MIT License // -// Copyright 2018 Electric Imp +// Copyright 2018-2019 Electric Imp // // SPDX-License-Identifier: MIT // @@ -54,6 +54,10 @@ class Options { return 'pre-production'; } + static get DG_TYPE_PRE_DUT() { + return 'pre-dut'; + } + static get DG_TYPE_DEVELOPMENT() { return 'development'; } @@ -66,6 +70,10 @@ class Options { return 'production'; } + static get DG_TYPE_DUT() { + return 'dut'; + } + static get WEBHOOK_MIME_JSON() { return 'json'; } @@ -348,6 +356,18 @@ class Options { default: undefined, _usage: '' }, + [Options.CREATE_DUT] : { + describe: Util.format('If the Device Group specified by --%s option does not exist, it is created.' + + ' In this case, the value of --%s is used as the name of the new Device Group.' + + ' If --%s is not specified or the Device Group specified by --%s exists, --%s is ignored.', + Options.DUT, Options.DUT, Options.DUT, Options.DUT, Options.CREATE_DUT), + alias: 'w', + nargs: 0, + type : 'boolean', + noValue: true, + default: undefined, + _usage: '' + }, [Options.CREATE_TARGET] : { describe: Util.format('If the Device Group specified by --%s option does not exist, it is created.' + ' In this case, the value of --%s is used as the name of the new Device Group.' + @@ -378,8 +398,10 @@ class Options { choices: [ Options.DG_TYPE_DEVELOPMENT, Options.DG_TYPE_PRE_FACTORY, + Options.DG_TYPE_PRE_DUT, Options.DG_TYPE_PRE_PRODUCTION, Options.DG_TYPE_FACTORY, + Options.DG_TYPE_DUT, Options.DG_TYPE_PRODUCTION ], requiresArg : true, @@ -415,6 +437,13 @@ class Options { type : 'string', _usage: '' }, + [Options.DUT] : { + alias: 'u', + nargs: 1, + type : 'string', + requiresArg : true, + _usage: '' + }, [Options.ENDPOINT] : { describe: 'The base impCentral API URL.', alias: 'e', @@ -867,10 +896,14 @@ class Options { switch (typeName) { case Options.DG_TYPE_PRE_FACTORY: return DeviceGroups.TYPE_PRE_FACTORY_FIXTURE; + case Options.DG_TYPE_PRE_DUT: + return DeviceGroups.TYPE_PRE_DUT; case Options.DG_TYPE_PRE_PRODUCTION: return DeviceGroups.TYPE_PRE_PRODUCTION; case Options.DG_TYPE_FACTORY: return DeviceGroups.TYPE_FACTORY_FIXTURE; + case Options.DG_TYPE_DUT: + return DeviceGroups.TYPE_DUT; case Options.DG_TYPE_PRODUCTION: return DeviceGroups.TYPE_PRODUCTION; case Options.DG_TYPE_DEVELOPMENT: @@ -883,10 +916,14 @@ class Options { switch (deviceGroupType) { case DeviceGroups.TYPE_PRE_FACTORY_FIXTURE: return Options.DG_TYPE_PRE_FACTORY; + case DeviceGroups.TYPE_PRE_DUT: + return Options.DG_TYPE_PRE_DUT; case DeviceGroups.TYPE_PRE_PRODUCTION: return Options.DG_TYPE_PRE_PRODUCTION; case DeviceGroups.TYPE_FACTORY_FIXTURE: return Options.DG_TYPE_FACTORY; + case DeviceGroups.TYPE_DUT: + return Options.DG_TYPE_DUT; case DeviceGroups.TYPE_PRODUCTION: return Options.DG_TYPE_PRODUCTION; case DeviceGroups.TYPE_DEVELOPMENT: @@ -917,7 +954,7 @@ class Options { } } - static isProductionTargetRequired(deviceGroupType) { + static isTargetRequired(deviceGroupType) { return deviceGroupType === DeviceGroups.TYPE_PRE_FACTORY_FIXTURE || deviceGroupType === DeviceGroups.TYPE_FACTORY_FIXTURE; } @@ -927,6 +964,11 @@ class Options { deviceGroupType === DeviceGroups.TYPE_PRE_PRODUCTION; } + static isDutDeviceGroupType(deviceGroupType) { + return deviceGroupType === DeviceGroups.TYPE_DUT || + deviceGroupType === DeviceGroups.TYPE_PRE_DUT; + } + setOption(option, value) { this._options[option] = value; } @@ -1043,6 +1085,14 @@ class Options { return this._options[Options.CREATE_TARGET]; } + static get CREATE_DUT() { + return 'create-dut'; + } + + get createDut() { + return this._options[Options.CREATE_DUT]; + } + static get DESCRIPTION() { return 'descr'; } @@ -1105,6 +1155,14 @@ class Options { return this._options[Options.DEVICE_ONLY]; } + static get DUT() { + return 'dut'; + } + + get dut() { + return this._options[Options.DUT]; + } + static get ENDPOINT() { return 'endpoint'; } diff --git a/lib/util/Target.js b/lib/util/Target.js new file mode 100644 index 00000000..8d30102b --- /dev/null +++ b/lib/util/Target.js @@ -0,0 +1,146 @@ +// MIT License +// +// Copyright 2019 Electric Imp +// +// SPDX-License-Identifier: MIT +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO +// EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES +// OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; + +const ImpCentralApi = require('imp-central-api'); +const DeviceGroups = ImpCentralApi.DeviceGroups; +const Options = require('./Options'); +const Errors = require('./Errors'); +const UserInteractor = require('./UserInteractor'); + +// Auxiliary classes to work with Device Group targets: production target and dut target. +class Target { + constructor(parentDeviceGroupType, deviceGroupType, option, relationName, backRelationName) { + this._parentDeviceGroupType = parentDeviceGroupType; + this._deviceGroupType = deviceGroupType; + this._option = option; + this._name = relationName; + this._backRelationName = backRelationName; + this._identifier = undefined; + this._createIfAbsent = false; + this._deviceGroup = null; + } + + get deviceGroupType() { + return this._deviceGroupType; + } + + get backRelationName() { + return this._backRelationName; + } + + get deviceGroupId() { + return this._deviceGroup ? this._deviceGroup.id : null; + } + + isSpecified() { + return this._identifier !== undefined; + } + + findOrCreate(parentProductId) { + if (!Options.isTargetRequired(this._parentDeviceGroupType)) { + throw new Errors.ImptError( + UserInteractor.ERRORS.WRONG_DG_TYPE_FOR_OPTION, + this._option, + Options.getDeviceGroupTypeName(this._parentDeviceGroupType), + Options.getDeviceGroupTypeName(DeviceGroups.TYPE_PRE_FACTORY_FIXTURE), + Options.getDeviceGroupTypeName(DeviceGroups.TYPE_FACTORY_FIXTURE)); + } + const DeviceGroup = require('../DeviceGroup'); + this._deviceGroup = new DeviceGroup(); + return this._deviceGroup.initByIdentifier(this._identifier).findEntity().then(entity => { + if (entity) { + if (this._deviceGroup.type !== this._deviceGroupType) { + throw new Errors.ImptError( + UserInteractor.ERRORS.TARGET_DG_WRONG_TYPE, + this._name, + this._deviceGroup.identifierInfo, + Options.getDeviceGroupTypeName(this._deviceGroup.type), + Options.getDeviceGroupTypeName(this._deviceGroupType)); + } + else if (this._deviceGroup.relatedProductId !== parentProductId) { + throw new Errors.ImptError(UserInteractor.ERRORS.TARGET_DG_WRONG_PRODUCT, this._name); + } + return Promise.resolve(); + } + else if (this._createIfAbsent) { + return this._deviceGroup._createByProductId( + parentProductId, new Options({ [Options.NAME] : this._identifier }), this._deviceGroupType); + } + else { + throw new Errors.ImptError( + UserInteractor.ERRORS.TARGET_DG_NOT_FOUND, + this._name, + this._deviceGroup.identifierInfo); + } + }); + } +} + +class ProductionTarget extends Target { + constructor(options, parentDeviceGroupType) { + super( + parentDeviceGroupType, + parentDeviceGroupType === DeviceGroups.TYPE_PRE_FACTORY_FIXTURE ? + DeviceGroups.TYPE_PRE_PRODUCTION : + DeviceGroups.TYPE_PRODUCTION, + Options.TARGET, + UserInteractor.MESSAGES.DG_PRODUCTION_TARGET, + UserInteractor.MESSAGES.DG_PRODUCTION_TARGET_FOR); + if (options) { + this._identifier = options.target; + this._createIfAbsent = options.createTarget; + } + } + + getTargetId(deviceGroup) { + return deviceGroup.productionTargetId; + } +} + +class DutTarget extends Target { + constructor(options, parentDeviceGroupType) { + super( + parentDeviceGroupType, + parentDeviceGroupType === DeviceGroups.TYPE_PRE_FACTORY_FIXTURE ? + DeviceGroups.TYPE_PRE_DUT : + DeviceGroups.TYPE_DUT, + Options.DUT, + UserInteractor.MESSAGES.DG_DUT_TARGET, + UserInteractor.MESSAGES.DG_DUT_TARGET_FOR); + if (options) { + this._identifier = options.dut; + this._createIfAbsent = options.createDut; + } + } + + getTargetId(deviceGroup) { + return deviceGroup.dutTargetId; + } +} + +module.exports = Target; +module.exports.ProductionTarget = ProductionTarget; +module.exports.DutTarget = DutTarget; diff --git a/lib/util/UserInteractor.js b/lib/util/UserInteractor.js index 22b7aa99..d5fe5170 100644 --- a/lib/util/UserInteractor.js +++ b/lib/util/UserInteractor.js @@ -1,6 +1,6 @@ // MIT License // -// Copyright 2018 Electric Imp +// Copyright 2018-2019 Electric Imp // // SPDX-License-Identifier: MIT // @@ -61,6 +61,7 @@ const MESSAGES = { ENTITY_UPDATED : '%s is updated successfully.', ENTITY_DELETED : '%s is deleted successfully.', ENTITY_REMOVED : '%s is removed successfully.', + ENTITY_ALREADY_DELETED : '%s is already deleted.', ENTITY_LIST : '%s list (%d items):', ENTITY_EMPTY_LIST : 'No %ss are found.', FILES_DELETED : 'Device/agent source files %s are deleted successfully.', @@ -139,6 +140,8 @@ const MESSAGES = { DG_MIN_SUPPORTED_DEPLOYMENT : _MIN_SUPPORTED_DEPLOYMENT, DG_PRODUCTION_TARGET : 'Production Target', DG_PRODUCTION_TARGET_FOR : 'Production Target for', + DG_DUT_TARGET : 'DUT Target', + DG_DUT_TARGET_FOR : 'DUT Target for', BUILD_SOURCE_DEVICE : 'device', BUILD_SOURCE_AGENT : 'agent', TEST_GITHUB_USER : 'GitHub username', @@ -228,10 +231,10 @@ const ERRORS = { CONFIG_OUTDATED : '%s, saved in %s File, does not exist anymore.', ACCESS_FAILED : 'Access to impCentral failed or timed out. Please check your network connection.', PROJECT_LINK_WRONG_DG_TYPE : `Project can not be linked to ${_DEVICE_GROUP} of the type "%s". Only "%s" or "%s" ${_DEVICE_GROUPS} are allowed.`, - TARGET_DG_NOT_FOUND : 'Production target %s is not found.', + TARGET_DG_NOT_FOUND : '%s %s is not found.', WRONG_DG_TYPE_FOR_OPTION : `Invalid option "--%s" for ${_DEVICE_GROUP} of the type "%s". It may be specified for "%s" or "%s" ${_DEVICE_GROUPS} only.`, - TARGET_DG_WRONG_TYPE : 'Production target %s is of the type "%s" but must be of the type "%s".', - TARGET_DG_WRONG_PRODUCT : `Production target ${_DEVICE_GROUP} must be from the same ${_PRODUCT}.`, + TARGET_DG_WRONG_TYPE : '%s %s is of the type "%s" but must be of the type "%s".', + TARGET_DG_WRONG_PRODUCT : `%s ${_DEVICE_GROUP} must be from the same ${_PRODUCT}.`, DG_WRONG_MIN_SUPPORTED_DEPLOYMENT : `%s does not belong to %s. Impossible to set it as ${_MIN_SUPPORTED_DEPLOYMENT}.`, DG_OLD_MIN_SUPPORTED_DEPLOYMENT : `${_MIN_SUPPORTED_DEPLOYMENT} cannot be set to an earlier ${_DEPLOYMENT} than the current ${_MIN_SUPPORTED_DEPLOYMENT}.`, BUILD_DELETE_ERR : `Impossible to delete %s. It is the ${_MIN_SUPPORTED_DEPLOYMENT} or newer for %s.`, diff --git a/package-lock.json b/package-lock.json index 1496f439..1fd247ee 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "imp-central-impt", - "version": "2.4.2", + "version": "2.5.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -51,9 +51,9 @@ } }, "add-matchers": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/add-matchers/-/add-matchers-0.5.0.tgz", - "integrity": "sha1-UCGQ5HUM1XIWGDkyaLYaFXNm52U=", + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/add-matchers/-/add-matchers-0.6.2.tgz", + "integrity": "sha512-hVO2wodMei9RF00qe+506MoeJ/NEOdCMEkSJ12+fC3hx/5Z4zmhNiP92nJEF6XhmXokeB0hOtuQrjHCx2vmXrQ==", "dev": true }, "agent-base": { @@ -466,9 +466,9 @@ "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" }, "interpret": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.1.0.tgz", - "integrity": "sha1-ftGxQQxqDg94z5XTuEQMY/eLhhQ=", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.2.0.tgz", + "integrity": "sha512-mT34yGKMNceBQUoVn7iCDKDntA7SC6gycMAWzGx1z/CMCTV7b2AAtXlo3nRyHZ1FelRkQbQjprHSYGwzLtkVbw==", "dev": true }, "invert-kv": { @@ -518,12 +518,12 @@ "dev": true }, "jasmine-expect": { - "version": "3.8.4", - "resolved": "https://registry.npmjs.org/jasmine-expect/-/jasmine-expect-3.8.4.tgz", - "integrity": "sha512-XliRILF8hX+yREQAtAxr4isXeMTL1H01etWJcG5BlBGKckfEOhCyM0ZlrrcV/4+MqknWlVa8nLxSZ/EVztAlug==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jasmine-expect/-/jasmine-expect-4.0.1.tgz", + "integrity": "sha512-tn+sdVx04MRHpW6ltIkEKANRO29C7AUdmkeupFrwbAawd8ICA/IZT9YsdIljGuxU+wLYoOJsaics6swnw2lO2g==", "dev": true, "requires": { - "add-matchers": "0.5.0" + "add-matchers": "0.6.2" } }, "jsbn": { @@ -864,12 +864,12 @@ "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=" }, "resolve": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.8.1.tgz", - "integrity": "sha512-AicPrAC7Qu1JxPCZ9ZgCZlY35QgFnNqc+0LtbRNxnVw4TXvjQ72wnuL9JQcEBgXkI9JM8MsT9kaQoHcpCRJOYA==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.10.0.tgz", + "integrity": "sha512-3sUr9aq5OfSg2S9pNtPA9hL1FVEAjvfOC4leW0SNf/mpnaakz2a9femSd6LqAww2RaFctwyf1lCqnTHuF1rxDg==", "dev": true, "requires": { - "path-parse": "^1.0.5" + "path-parse": "^1.0.6" } }, "safe-buffer": { diff --git a/package.json b/package.json index 801abb74..cce42546 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "imp-central-impt", - "version": "2.4.2", + "version": "2.5.0", "description": "impt - command line tool for the Electric Imp impCentral API", "main": "bin/impt", "engines": { @@ -40,7 +40,7 @@ "dateformat": "^3.0.3", "glob": "^7.1.3", "hoek": "^6.0.2", - "imp-central-api": "^1.4.0", + "imp-central-api": "^1.5.0", "osenv": "^0.1.3", "prompt-sync": "^4.1.6", "random-words": "^1.1.0",