public async createDeployment()

in source/packages/services/greengrass2-provisioning/src/deployments/deployments.service.ts [112:388]


    public async createDeployment(taskId: string, request: NewDeployment): Promise<Deployment> {
        logger.debug(
            `deployments.service createDeployment: in: taskId:${taskId}, request:${JSON.stringify(
                request
            )}`
        );

        // fail fast if invalid request
        ow(taskId, ow.string.nonEmpty);
        ow(request?.coreName, 'core name', ow.string.nonEmpty);

        // get the task to determine the template
        const task = await this.deploymentTasksDao.get(taskId);
        ow(task, 'deployment task', ow.object.nonEmpty);
        ow(task.template.name, 'template name', ow.string.nonEmpty);
        ow(task.template.version, 'template version', ow.number.greaterThan(0));

        // set deployment as in progress
        const deployment: Deployment = {
            ...request,
            taskStatus: 'InProgress',
            createdAt: new Date(),
            updatedAt: new Date(),
        };

        // and save the desired template info
        const futures: Promise<void>[] = [
            this.deploymentTasksDao.saveTaskDetail(taskId, deployment),
            this.coresService.associateTemplate(
                deployment.coreName,
                task.template.name,
                task.template.version as number,
                'desired'
            ),
        ];
        await Promise.all(futures);

        // determine if core is already registered with ggv2
        try {
            await this.ggv2.send(
                new GetCoreDeviceCommand({ coreDeviceThingName: deployment.coreName })
            );
        } catch (e) {
            logger.error(`deployments.service createDeployment: error: ${JSON.stringify(e)}`);
            if (e.name === 'ResourceNotFoundException') {
                this.markAsFailed(deployment, 'Core device not registered with Greengrass V2');
            } else {
                this.markAsFailed(
                    deployment,
                    `Unable to determine if core device is registered with Greengrass V2: ${e.name}`
                );
            }
        }

        // retrieve template/version
        let template: TemplateItem;
        if (this.isStillInProgress(deployment)) {
            template = await this.templatesService.get(task.template.name, task.template.version);
            if (template === undefined) {
                this.markAsFailed(deployment, 'Template not found');
            }
        }

        // determine if a deployment already exists for the template. if not, create it (and save to db)
        if (this.isStillInProgress(deployment)) {
            if (template.deployment?.id === undefined) {
                // deployment does not exist therefore create it
                // first create the thing group target
                const thingGroupName = this.getTemplateVersionThingGroupTarget(template);
                let thingGroupArn: string;
                try {
                    const r = await this.iot.send(
                        new CreateThingGroupCommand({
                            thingGroupName,
                            tags: [
                                {
                                    Key: DEPLOYMENT_TASK_ID_TAG_KEY,
                                    Value: taskId,
                                },
                            ],
                        })
                    );
                    logger.silly(
                        `deployments.service createDeployment: CreateThingGroupCommandOutput: ${JSON.stringify(
                            r
                        )}`
                    );
                    thingGroupArn = r.thingGroupArn;
                } catch (e) {
                    logger.error(
                        `deployments.service createDeployment: error: ${JSON.stringify(e)}`
                    );
                    if (e.name === 'ResourceAlreadyExistsException') {
                        logger.warn(
                            `deployments.service createDeployment: thingGroup: ${thingGroupName} already exists`
                        );
                        const r = await this.iot.send(
                            new DescribeThingGroupCommand({ thingGroupName })
                        );
                        logger.silly(
                            `deployments.service createDeployment: DescribeThingGroupCommandOutput: ${JSON.stringify(
                                r
                            )}`
                        );
                        thingGroupArn = r.thingGroupArn;
                    } else {
                        this.markAsFailed(deployment, `Unable to create thing group: ${e.name}`);
                    }
                }

                // now we can create the deployment
                if (this.isStillInProgress(deployment)) {
                    try {
                        const componentsMap: { [key: string]: ComponentDeploymentSpecification } =
                            {};
                        template.components?.forEach(
                            (c) =>
                                (componentsMap[c.key] = {
                                    componentVersion: c.version,
                                    configurationUpdate: c.configurationUpdate,
                                    runWith: c.runWith,
                                })
                        );
                        const r = await this.ggv2.send(
                            new CreateDeploymentCommand({
                                targetArn: thingGroupArn,
                                deploymentName: `CDF GreengrassV2 Provisoning - Template: ${template.name}, Version: ${template.version}`,
                                components: componentsMap,
                                iotJobConfiguration: template.jobConfig,
                                deploymentPolicies: template.deploymentPolicies,
                                tags: {
                                    DEPLOYMENT_TASK_ID_TAGGING_KEY: taskId,
                                },
                            })
                        );
                        logger.silly(
                            `deployments.service createDeployment: CreateDeploymentCommandOutput: ${JSON.stringify(
                                r
                            )}`
                        );

                        template.deployment = {
                            id: r.deploymentId,
                            thingGroupName,
                            jobId: r.iotJobId,
                        };

                        const tagResourceOutput = await this.iot.send(
                            new TagResourceCommand({
                                resourceArn: r.iotJobArn,
                                tags: [
                                    {
                                        Key: DEPLOYMENT_TASK_ID_TAG_KEY,
                                        Value: taskId,
                                    },
                                ],
                            })
                        );

                        logger.silly(
                            `deployments.service createDeployment: TagResourceCommandOutput: ${JSON.stringify(
                                tagResourceOutput
                            )}`
                        );
                    } catch (e) {
                        logger.error(
                            `deployments.service createDeployment: error: ${JSON.stringify(e)}`
                        );
                        this.markAsFailed(deployment, `Failed to create deployment: ${e.name}`);
                    }
                }

                // and save the state
                if (this.isStillInProgress(deployment)) {
                    try {
                        await this.templatesService.associateDeployment(template);
                    } catch (e) {
                        logger.error(
                            `deployments.service createDeployment: error: ${JSON.stringify(e)}`
                        );
                        this.markAsFailed(
                            deployment,
                            `Failed to associate deployment with template: ${e.name}`
                        );
                    }
                }
            }
        }

        if (this.isStillInProgress(deployment)) {
            // remove from all existing deployment thing groups
            try {
                const listThingGroupsForThingOutput = await this.iot.send(
                    new ListThingGroupsForThingCommand({
                        thingName: deployment.coreName,
                    })
                );
                logger.silly(
                    `deployments.service createDeployment: ListThingGroupsForThingOutput: ${JSON.stringify(
                        listThingGroupsForThingOutput
                    )}`
                );
                const existingDeploymentThingGroups =
                    listThingGroupsForThingOutput?.thingGroups?.filter((tg) =>
                        tg.groupName.startsWith(this.DEPLOYMENT_THING_GROUP_PREFIX)
                    );
                logger.silly(
                    `deployments.service createDeployment: deployment groups to remove from: ${JSON.stringify(
                        existingDeploymentThingGroups
                    )}`
                );
                if ((existingDeploymentThingGroups?.length ?? 0) > 0) {
                    for (const tg of existingDeploymentThingGroups) {
                        await this.iot.send(
                            new RemoveThingFromThingGroupCommand({
                                thingName: deployment.coreName,
                                thingGroupArn: tg.groupArn,
                            })
                        );
                    }
                }
            } catch (e) {
                logger.error(e);
                this.markAsFailed(
                    deployment,
                    `Failed removing from existing deployment thing groups: ${e.name}`
                );
            }

            // add thing to new deployment thing group
            try {
                await this.iot.send(
                    new AddThingToThingGroupCommand({
                        thingName: deployment.coreName,
                        thingGroupName: template.deployment.thingGroupName,
                    })
                );
            } catch (e) {
                logger.error(`deployments.service createDeployment: error: ${JSON.stringify(e)}`);
                this.markAsFailed(
                    deployment,
                    `Failed to add core device to deployment thing group target: ${e.name}`
                );
            }
        }

        // save
        if (deployment.taskStatus === 'InProgress') {
            deployment.taskStatus = 'Success';
            deployment.updatedAt = new Date();
            await this.cdfEventPublisher.emitEvent<DeploymentTaskCreatedPayload>({
                name: DeploymentTaskCreatedEvent,
                payload: {
                    taskId: taskId,
                    coreName: deployment.coreName,
                    status: 'success',
                },
            });
        }

        if (deployment.taskStatus === 'Failure') {
            await this.cdfEventPublisher.emitEvent<DeploymentTaskCreatedPayload>({
                name: DeploymentTaskCreatedEvent,
                payload: {
                    taskId: taskId,
                    coreName: deployment.coreName,
                    status: 'failed',
                    message: deployment.statusMessage,
                },
            });
        }

        await this.deploymentTasksDao.saveTaskDetail(taskId, deployment);

        logger.debug(`deployments.service createDeployment: exit: ${JSON.stringify(deployment)}`);
        return deployment;
    }