public void execute()

in src/main/java/com/awslabs/aws/greengrass/provisioner/implementations/helpers/BasicDeploymentHelper.java [450:1097]


    public void execute(DeploymentArguments deploymentArguments) {
        // Make the directories for build, if necessary
        ioHelper.createDirectoryIfNecessary(ggConstants.getBuildDirectory());

        // Get the Greengrass group name
        GreengrassGroupName greengrassGroupName = ImmutableGreengrassGroupName.builder().groupName(deploymentArguments.groupName).build();

        // Get the core thing name
        ImmutableThingName coreThingName = ggVariables.getCoreThingName(greengrassGroupName);

        ///////////////////////////////////////
        // Load the deployment configuration //
        ///////////////////////////////////////

        DeploymentConf deploymentConf;

        if (isEmptyDeployment(deploymentArguments)) {
            deploymentConf = getEmptyDeploymentConf(deploymentArguments, greengrassGroupName);
        } else {
            deploymentConf = Try.of(() -> getDeploymentConf(coreThingName, deploymentArguments.deploymentConfigFilename, greengrassGroupName)).get();
        }

        ///////////////////////////////////////////////////
        // Create an AWS Greengrass Group and get its ID //
        ///////////////////////////////////////////////////

        if (v2GreengrassHelper.groupExists(greengrassGroupName) && (deploymentArguments.ec2LinuxVersion != null)) {
            throw new RuntimeException(String.join("", "Group [", deploymentArguments.groupName, "] already exists, cannot launch another EC2 instance for this group.  You can update the group configuration by not specifying the EC2 launch option."));
        }

        log.info("Creating a Greengrass group, if necessary");
        String groupId = greengrassHelper.createGroupIfNecessary(greengrassGroupName);
        GreengrassGroupId greengrassGroupId = ImmutableGreengrassGroupId.builder().groupId(groupId).build();

        ///////////////////////
        // Create core thing //
        ///////////////////////

        log.info("Creating core thing");
        ThingArn coreThingArn = v2IotHelper.createThing(coreThingName);

        ///////////////////////////////////////////////////
        // Determine the default function isolation mode //
        ///////////////////////////////////////////////////

        FunctionIsolationMode defaultFunctionIsolationMode;

        if (isEmptyDeployment(deploymentArguments)) {
            // If we're doing an empty deployment default to no container mode
            defaultFunctionIsolationMode = FunctionIsolationMode.NO_CONTAINER;
        } else if ((deploymentArguments.dockerLaunch) || (deploymentArguments.buildContainer)) {
            // If we're doing a Docker launch we always use no container
            log.warn("Setting default function isolation mode to no container because we're doing a Docker launch");
            defaultFunctionIsolationMode = FunctionIsolationMode.NO_CONTAINER;
        } else {
            // If we're not doing a Docker launch use the default values in the configuration file
            defaultFunctionIsolationMode = ggVariables.getDefaultFunctionIsolationMode();
        }

        ////////////////////////////////////////////////////////////
        // Build the default environment for all Lambda functions //
        ////////////////////////////////////////////////////////////

        Map<String, String> defaultEnvironment = environmentHelper.getDefaultEnvironment(greengrassGroupId, coreThingName, coreThingArn, greengrassGroupName);

        // Get a config object with the default environment values (eg. "${AWS_IOT_THING_NAME}" used in the function and connector configuration)
        Config defaultConfig = typeSafeConfigHelper.addDefaultValues(defaultEnvironment, Optional.empty());

        //////////////////////////////////////////////////////////////////////
        // Find enabled functions and create function conf objects for them //
        //////////////////////////////////////////////////////////////////////

        List<FunctionConf> functionConfs = getFunctionConfs(deploymentConf, defaultFunctionIsolationMode, defaultConfig);

        ///////////////////////////
        // Create the connectors //
        ///////////////////////////

        List<ConnectorConf> connectorConfs = connectorHelper.getConnectorConfObjects(defaultConfig, deploymentConf.getConnectors());

        log.info("Creating connector definition");
        Optional<String> optionalConnectionDefinitionVersionArn = greengrassHelper.createConnectorDefinitionVersion(connectorConfs);

        ////////////////////////////////////////////////////////////////////////////////////////////////////////////
        // Merge any additional permissions that the functions need into the core and service role configurations //
        ////////////////////////////////////////////////////////////////////////////////////////////////////////////

        List<Map> additionalCoreRoleIamPolicies = getRoleIamPolicies(functionConfs.stream()
                .map(FunctionConf::getCoreRoleIamPolicy));

        List<String> additionalCoreRoleIamManagedPolicies = getRoleIamManagedPolicies(functionConfs.stream()
                .map(FunctionConf::getCoreRoleIamManagedPolicies));

        RoleConf mergedCoreRoleConf = mergeRoleConf(deploymentConf.getCoreRoleConf(), additionalCoreRoleIamPolicies, additionalCoreRoleIamManagedPolicies);

        // Use the merged core role conf
        deploymentConf = ImmutableDeploymentConf.builder().from(deploymentConf)
                .coreRoleConf(mergedCoreRoleConf)
                .build();

        if (deploymentConf.getServiceRoleConf().isPresent()) {
            List<Map> additionalServiceRoleIamPolicies = getRoleIamPolicies(functionConfs.stream()
                    .map(FunctionConf::getServiceRoleIamPolicy));

            List<String> additionalServiceRoleIamManagedPolicies = getRoleIamManagedPolicies(functionConfs.stream()
                    .map(FunctionConf::getServiceRoleIamManagedPolicies));

            RoleConf mergedServiceRoleConf = mergeRoleConf(deploymentConf.getServiceRoleConf().get(), additionalServiceRoleIamPolicies, additionalServiceRoleIamManagedPolicies);

            // Use the merged service role conf
            deploymentConf = ImmutableDeploymentConf.builder().from(deploymentConf)
                    .serviceRoleConf(mergedServiceRoleConf)
                    .build();
        }

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////
        // Merge any additional permissions that the connectors need into the core and service role configurations //
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////

        additionalCoreRoleIamPolicies = getRoleIamPolicies(connectorConfs.stream()
                .map(ConnectorConf::getCoreRoleIamPolicy));

        additionalCoreRoleIamManagedPolicies = getRoleIamManagedPolicies(connectorConfs.stream()
                .map(ConnectorConf::getCoreRoleIamManagedPolicies));

        mergedCoreRoleConf = mergeRoleConf(deploymentConf.getCoreRoleConf(), additionalCoreRoleIamPolicies, additionalCoreRoleIamManagedPolicies);

        // Use the merged core role conf
        deploymentConf = ImmutableDeploymentConf.builder().from(deploymentConf)
                .coreRoleConf(mergedCoreRoleConf)
                .build();

        if (deploymentConf.getServiceRoleConf().isPresent()) {
            List<Map> additionalServiceRoleIamPolicies = getRoleIamPolicies(connectorConfs.stream()
                    .map(ConnectorConf::getServiceRoleIamPolicy));

            List<String> additionalServiceRoleIamManagedPolicies = getRoleIamManagedPolicies(connectorConfs.stream()
                    .map(ConnectorConf::getServiceRoleIamManagedPolicies));

            RoleConf mergedServiceRoleConf = mergeRoleConf(deploymentConf.getServiceRoleConf().get(), additionalServiceRoleIamPolicies, additionalServiceRoleIamManagedPolicies);

            // Use the merged service role conf
            deploymentConf = ImmutableDeploymentConf.builder().from(deploymentConf)
                    .serviceRoleConf(mergedServiceRoleConf)
                    .build();
        }

        //////////////////////////////////////////////////////////
        // Create the service role and role alias, if necessary //
        //////////////////////////////////////////////////////////

        Optional<Role> optionalGreengrassServiceRole;
        Optional<CreateRoleAliasResponse> optionalCreateRoleAliasResponse;

        // Create the role for the core, if necessary
        Role coreRole;

        if (deploymentArguments.coreRoleName != null) {
            RoleName coreRoleName = ImmutableRoleName.builder().name(deploymentArguments.coreRoleName).build();
            Optional<Role> optionalCoreRole = iamHelper.getRole(coreRoleName);

            if (!optionalCoreRole.isPresent()) {
                throw new RuntimeException(String.join("", "Greengrass core role is not present or GetRole failed due to insufficient permissions on [", deploymentArguments.coreRoleName, "]"));
            }

            coreRole = optionalCoreRole.get();
        } else {
            coreRole = createCoreRole(deploymentConf.getCoreRoleConf());
        }

        if (!deploymentArguments.serviceRoleExists) {
            // If the service role does not exist we should create it
            Role serviceRole = createServiceRole(deploymentConf.getServiceRoleConf().get());
            optionalGreengrassServiceRole = Optional.of(serviceRole);
        } else {
            // The service role exists already, do not try to create or modify it
            optionalGreengrassServiceRole = Optional.empty();
        }

        // Create the role alias so we can use IoT as a credentials provider with certificate based authentication
        if (!deploymentConf.getCoreRoleConf().getAlias().isPresent()) {
            throw new RuntimeException(String.join("", "No role alias specified for the Greengrass core role [", deploymentConf.getCoreRoleConf().getName(), "]"));
        }

        ImmutableRoleAlias coreRoleAlias = ImmutableRoleAlias.builder().name(deploymentConf.getCoreRoleConf().getAlias().get()).build();
        log.info(String.join("", "Creating core role alias [", coreRoleAlias.getName(), "]"));
        optionalCreateRoleAliasResponse = Optional.of(v2IotHelper.forceCreateRoleAlias(coreRole, coreRoleAlias));

        //////////////////////////////////
        // Create or reuse certificates //
        //////////////////////////////////

        Optional<GroupVersion> optionalGroupVersion = v2GreengrassHelper.getLatestGroupVersionByNameOrId(groupId);
        Optional<KeysAndCertificate> optionalCoreKeysAndCertificate = Optional.empty();

        Optional<CertificateArn> optionalCoreCertificateArn = Optional.empty();

        if (deploymentArguments.certificateArn != null) {
            // Use the certificate ARN supplied by the user, new or existing group
            log.info(String.join("", "Using user supplied certificate ARN for core certificate [", deploymentArguments.certificateArn, "]"));
            optionalCoreCertificateArn = Optional.of(ImmutableCertificateArn.builder().arn(deploymentArguments.certificateArn).build());
        } else if (deploymentArguments.csr != null) {
            // Sign the CSR supplied by the user, new or existing group
            log.info("Using user supplied CSR for core certificate");
            optionalCoreCertificateArn = Optional.of(v2IotHelper.signCsrAndReturnCertificateArn(ImmutableCertificateSigningRequest.builder().request(deploymentArguments.csr).build()));
        } else if (!optionalGroupVersion.isPresent()) {
            // New group, create new keys
            log.info("Group is new, no certificate ARN or CSR supplied, creating new keys");
            KeysAndCertificate coreKeysAndCertificate = iotHelper.createKeysAndCertificateForCore(greengrassGroupName);
            iotHelper.writePublicSignedCertificateFileForCore(coreKeysAndCertificate, greengrassGroupName);
            iotHelper.writePrivateKeyFileForCore(coreKeysAndCertificate, greengrassGroupName);
            iotHelper.writeRootCaFile(greengrassGroupName);
            iotHelper.writeIotCpPropertiesFile(greengrassGroupName, coreThingName, coreRoleAlias);
            optionalCoreKeysAndCertificate = Optional.of(coreKeysAndCertificate);
        } else {
            GroupVersion groupVersion = optionalGroupVersion.get();

            // Existing group, can we find the existing keys?
            optionalCoreKeysAndCertificate = iotHelper.loadKeysAndCertificateForCore(greengrassGroupName);

            if (optionalCoreKeysAndCertificate.isPresent()) {
                // Found keys, we'll reuse them
                log.info("Group is not new, loaded keys from credentials directory");
            } else if (deploymentArguments.forceCreateNewKeysOption) {
                // Didn't find keys but the user has requested that they be recreated
                log.info("Group is not new, user forcing new keys to be created");
                KeysAndCertificate coreKeysAndCertificate = iotHelper.createKeysAndCertificateForCore(greengrassGroupName);
                optionalCoreKeysAndCertificate = Optional.of(coreKeysAndCertificate);
            } else {
                log.info("Group is not new, keys could not be found, but user not forcing new keys to be created");
                log.info("Attempting to get the core certificate ARN from the latest group version information");
                optionalCoreCertificateArn = v2GreengrassHelper.getCoreCertificateArn(groupVersion);
            }
        }

        if (optionalCoreKeysAndCertificate.isPresent()) {
            // If we have keys and certificate then get the certificate ARN
            optionalCoreCertificateArn = optionalCoreKeysAndCertificate.map(KeysAndCertificate::getCertificateArn);
        }

        if (!optionalCoreCertificateArn.isPresent()) {
            // We need the certificate ARN at this point, fail if we don't have it
            StringBuilder message = new StringBuilder();
            message.append("Core certificate information/ARN could not be found. ");
            message.append(String.join("", "If you would like to recreate the keys you must specify the [", DeploymentArguments.LONG_FORCE_CREATE_NEW_KEYS_OPTION, "] option. "));
            message.append(String.join("", "If you'd like to reuse an existing certificate you must specify the [", DeploymentArguments.LONG_CERTIFICATE_ARN_OPTION, "] option."));

            throw new RuntimeException(message.toString());
        }

        CertificateArn coreCertificateArn = optionalCoreCertificateArn.get();

        ////////////////////////////////////////////////////
        // IoT policy creation for the core, if necessary //
        ////////////////////////////////////////////////////

        PolicyName corePolicyName;

        if (deploymentArguments.corePolicyName == null) {
            if (!deploymentConf.getCoreRoleConf().getIotPolicy().isPresent()) {
                throw new RuntimeException(String.join("", "No IoT policy specified for core role [", deploymentConf.getCoreRoleConf().getName(), "]"));
            }

            log.info("Creating policy for core");
            v2IotHelper.createPolicyIfNecessary(ggVariables.getCorePolicyName(greengrassGroupName),
                    ImmutablePolicyDocument.builder().document(deploymentConf.getCoreRoleConf().getIotPolicy().get()).build());
            corePolicyName = ggVariables.getCorePolicyName(greengrassGroupName);
        } else {
            corePolicyName = ImmutablePolicyName.builder().name(deploymentArguments.corePolicyName).build();
        }

        //////////////////////////////////
        // Attach policy to certificate //
        //////////////////////////////////

        v2IotHelper.attachPrincipalPolicy(corePolicyName, coreCertificateArn);

        /////////////////////////////////
        // Attach thing to certificate //
        /////////////////////////////////

        v2IotHelper.attachThingPrincipal(ggVariables.getCoreThingName(greengrassGroupName), coreCertificateArn);

        ////////////////////////////////////////////////
        // Associate the Greengrass role to the group //
        ////////////////////////////////////////////////

        associateRoleToGroup(coreRole, greengrassGroupId);

        ////////////////////////////////////////////
        // Create a core definition and a version //
        ////////////////////////////////////////////

        log.info("Creating core definition");
        String coreDefinitionVersionArn = greengrassHelper.createCoreDefinitionAndVersion(ggVariables.getCoreDefinitionName(greengrassGroupName), coreCertificateArn, coreThingArn, deploymentConf.isSyncShadow());

        //////////////////////////////////////////////
        // Create a logger definition and a version //
        //////////////////////////////////////////////

        log.info("Creating logger definition");
        String loggerDefinitionVersionArn;

        if (!deploymentConf.getLoggers().isPresent()) {
            log.warn("No loggers section defined in configuration files, using default logger configuration");
            loggerDefinitionVersionArn = greengrassHelper.createDefaultLoggerDefinitionAndVersion();
        } else {
            loggerDefinitionVersionArn = greengrassHelper.createLoggerDefinitionAndVersion(deploymentConf.getLoggers().get());
        }

        //////////////////////////////////////////////
        // Create the Lambda role for the functions //
        //////////////////////////////////////////////

        Optional<Role> optionalLambdaRole = Optional.empty();

        if (!deploymentConf.getFunctions().isEmpty()) {
            log.info("Creating Lambda role");

            RoleConf lambdaRoleConf = deploymentConf.getLambdaRoleConf().get();

            requireAssumeRolePolicy(lambdaRoleConf, "Lambda");

            optionalLambdaRole = Optional.of(createRoleFromRoleConf(lambdaRoleConf));
        }

        ////////////////////////////////////////////////////////
        // Start building the subscription and function lists //
        ////////////////////////////////////////////////////////

        List<Subscription> subscriptions = new ArrayList<>();

        ////////////////////////////////////////////////////
        // Determine if any functions need to run as root //
        ////////////////////////////////////////////////////

        boolean functionsRunningAsRoot = functionConfs.stream()
                .anyMatch(functionConf -> ((functionConf.getUid().isPresent() && (functionConf.getUid().get() == 0))
                        || (functionConf.getGid().isPresent() && (functionConf.getGid().get() == 0))));

        if (functionsRunningAsRoot) {
            log.warn("At least one function was detected that is configured to run as root");
        }

        ////////////////////////////////////////////////////////////////////////////
        // Determine if any functions are running inside the Greengrass container //
        ////////////////////////////////////////////////////////////////////////////

        List<FunctionName> functionsRunningInGreengrassContainer = functionConfs.stream()
                .filter(FunctionConf::isGreengrassContainer)
                .map(FunctionConf::getFunctionName)
                .collect(Collectors.toList());

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////
        // Check if launching or building a Docker container and functions are running in the Greengrass container //
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////

        if ((deploymentArguments.dockerLaunch || deploymentArguments.buildContainer) && !functionsRunningInGreengrassContainer.isEmpty()) {
            log.error("The following functions are marked to run in the Greengrass container:");

            functionsRunningInGreengrassContainer
                    .forEach(name -> log.error(String.join("", "  ", name.getName())));

            log.error("When running in Docker all functions must be running without the Greengrass container.");
            log.error("Set the greengrassContainer option to false in the functions.default.conf and/or the individual function configurations and try again.");
            System.exit(1);
        }

        /////////////////////////////////////////////////////
        // Launch any CloudFormation templates we've found //
        /////////////////////////////////////////////////////

        List<String> cloudFormationStacksLaunched = functionConfs.stream()
                .map(functionConf -> cloudFormationHelper.deployCloudFormationTemplate(defaultEnvironment, deploymentArguments.groupName, functionConf))
                .filter(Optional::isPresent)
                .map(Optional::get)
                .collect(Collectors.toList());

        /////////////////////////
        // Build the functions //
        /////////////////////////

        Map<Function, FunctionConf> functionToConfMap = new HashMap<>();

        // Only try to create functions if we have a Lambda role
        if (optionalLambdaRole.isPresent()) {
            Role lambdaRole = optionalLambdaRole.get();

            // Verify that all of the functions in the list are supported
            functionHelper.verifyFunctionsAreSupported(functionConfs);

            // Get the map of functions to function configuration (builds functions and publishes them to Lambda)
            functionToConfMap = functionHelper.buildFunctionsAndGenerateMap(deploymentArguments.s3Bucket, deploymentArguments.s3Directory, functionConfs, lambdaRole);
        }

        ////////////////////////////
        // Set up local resources //
        ////////////////////////////

        log.info("Creating resource definition");
        String resourceDefinitionVersionArn = greengrassHelper.createResourceDefinitionFromFunctionConfs(functionConfs);

        /////////////////////////////////////////////////////////////////////////
        // Build the function definition for the Lambda function and a version //
        /////////////////////////////////////////////////////////////////////////

        log.info("Creating function definition");
        String functionDefinitionVersionArn = greengrassHelper.createFunctionDefinitionVersion(ImmutableSet.copyOf(functionToConfMap.keySet()), defaultFunctionIsolationMode);

        ///////////////////////////////////////////////
        // Connection functions to cloud and shadows //
        ///////////////////////////////////////////////

        subscriptions.addAll(functionToConfMap.entrySet().stream()
                .flatMap(entry -> subscriptionHelper.createCloudSubscriptionsForArn(
                        entry.getValue().getFromCloudSubscriptions(),
                        entry.getValue().getToCloudSubscriptions(),
                        entry.getKey().functionArn()).stream())
                .collect(Collectors.toList()));

        subscriptions.addAll(subscriptionHelper.connectFunctionsToShadows(functionToConfMap));

        ////////////////////////////////////////
        // Connection functions to each other //
        ////////////////////////////////////////

        subscriptions.addAll(subscriptionHelper.connectFunctions(functionToConfMap));

        //////////////////////////////////////////////////////
        // Get a list of all of the connected thing shadows //
        //////////////////////////////////////////////////////

        Set<ThingName> connectedShadowThings = new HashSet<>();

        for (FunctionConf functionConf : functionToConfMap.values()) {
            connectedShadowThings.addAll(functionConf.getConnectedShadows().stream()
                    .map(connectedShadow -> ImmutableThingName.builder().name(connectedShadow).build())
                    .collect(Collectors.toList()));

            for (String connectedShadow : functionConf.getConnectedShadows()) {
                // Make sure all of the connected shadows exist
                v2IotHelper.createThing(ImmutableThingName.builder().name(connectedShadow).build());
            }
        }

        //////////////////////////////////////////////////////
        // Create the subscription definition from our list //
        //////////////////////////////////////////////////////

        log.info("Creating subscription definition");
        String subscriptionDefinitionVersionArn = greengrassHelper.createSubscriptionDefinitionAndVersion(subscriptions);

        ////////////////////////////////////
        // Create a minimal group version //
        ////////////////////////////////////

        log.info("Creating group version");

        GroupVersion.Builder groupVersionBuilder = GroupVersion.builder();

        // Connector definition can not be empty or the cloud service will reject it
        optionalConnectionDefinitionVersionArn.ifPresent(groupVersionBuilder::connectorDefinitionVersionArn);
        groupVersionBuilder.coreDefinitionVersionArn(coreDefinitionVersionArn);
        groupVersionBuilder.functionDefinitionVersionArn(functionDefinitionVersionArn);
        groupVersionBuilder.loggerDefinitionVersionArn(loggerDefinitionVersionArn);
        groupVersionBuilder.resourceDefinitionVersionArn(resourceDefinitionVersionArn);
        groupVersionBuilder.subscriptionDefinitionVersionArn(subscriptionDefinitionVersionArn);

        GroupVersion groupVersion = groupVersionBuilder.build();

        String groupVersionId = greengrassHelper.createGroupVersion(greengrassGroupId, groupVersion);

        /////////////////////////////////////////////
        // Do all of the output file related stuff //
        /////////////////////////////////////////////

        buildOutputFiles(deploymentArguments,
                greengrassGroupName,
                optionalCreateRoleAliasResponse,
                greengrassGroupId,
                coreThingName,
                coreThingArn,
                optionalCoreKeysAndCertificate,
                coreCertificateArn,
                functionsRunningAsRoot);

        //////////////////////////////////////////////////
        // Start building the EC2 instance if necessary //
        //////////////////////////////////////////////////

        Optional<String> optionalInstanceId = Optional.empty();

        if (deploymentArguments.ec2LinuxVersion != null) {
            log.info("Launching EC2 instance");

            Set<Integer> openPorts = functionConfs.stream()
                    // Get all of the environment variables from each function
                    .map(FunctionConf::getEnvironmentVariables)
                    // Extract the PORT variables
                    .map(environmentVariables -> Optional.ofNullable(environmentVariables.get("PORT")))
                    // Filter out missing values
                    .filter(Optional::isPresent)
                    .map(Optional::get)
                    // Parse the string into an integer
                    .map(Integer::parseInt)
                    .collect(Collectors.toSet());

            optionalInstanceId = launchEc2Instance(deploymentArguments.groupName, deploymentArguments.architecture, deploymentArguments.ec2LinuxVersion, deploymentArguments.mqttPort, openPorts);

            if (!optionalInstanceId.isPresent()) {
                // Something went wrong, bail out
                throw new RuntimeException("Couldn't obtain EC2 instance ID, bailing out");
            }
        }

        ///////////////////////////////////////////////////
        // Start the Docker container build if necessary //
        ///////////////////////////////////////////////////

        if (deploymentArguments.buildContainer) {
            log.info("Configuring container build");

            ecrDockerHelper.setEcrRepositoryName(Optional.ofNullable(deploymentArguments.ecrRepositoryNameString));
            ecrDockerHelper.setEcrImageName(Optional.ofNullable(deploymentArguments.ecrImageNameString));
            String imageName = ecrDockerHelper.getImageName();
            String currentDirectory = System.getProperty(USER_DIR);

            File dockerfile = officialGreengrassImageDockerHelper.getDockerfileForArchitecture(deploymentArguments.architecture);
            String dockerfileTemplate = ioHelper.readFileAsString(dockerfile);
            dockerfileTemplate = dockerfileTemplate.replaceAll("GROUP_NAME", deploymentArguments.groupName);

            // Add the group name and UUID so we don't accidentally overwrite an existing file
            File tempDockerfile = dockerfile.toPath().getParent().resolve(
                    String.join(".", "Dockerfile", deploymentArguments.groupName, ioHelper.getUuid())).toFile();
            ioHelper.writeFile(tempDockerfile.toString(), dockerfileTemplate.getBytes());
            tempDockerfile.deleteOnExit();

            try (DockerClient dockerClient = officialGreengrassImageDockerClientProvider.get()) {
                log.info("Building container");

                // Pull the official Greengrass Docker container first
                String officialGreengrassDockerImage = ggConstants.getOfficialGreengrassDockerImage();
                officialGreengrassImageDockerHelper.pullImage(officialGreengrassDockerImage);

                String imageId = dockerClient.build(new File(currentDirectory).toPath(),
                        basicProgressHandler,
                        DockerClient.BuildParam.dockerfile(tempDockerfile.toPath()));

                dockerClient.tag(imageId, imageName);
                pushContainerIfNecessary(deploymentArguments, imageId);
            } catch (DockerException | InterruptedException | IOException e) {
                log.error("Container build failed");
                throw new RuntimeException(e);
            }
        }

        // Create a deployment and wait for it to succeed.  Return if it fails.
        Try.run(() -> createAndWaitForDeployment(optionalGreengrassServiceRole, Optional.of(coreRole), greengrassGroupId, groupVersionId))
                .get();

        //////////////////////////////////////////////
        // Launch the Docker container if necessary //
        //////////////////////////////////////////////

        if (deploymentArguments.dockerLaunch) {
            log.info("Launching Docker container");
            String officialGreengrassDockerImage = ggConstants.getOfficialGreengrassDockerImage();
            officialGreengrassImageDockerHelper.pullImage(officialGreengrassDockerImage);
            officialGreengrassImageDockerHelper.createAndStartContainer(officialGreengrassDockerImage, greengrassGroupName);
        }

        ///////////////////////////////////////////////////////
        // Wait for the EC2 instance to launch, if necessary //
        ///////////////////////////////////////////////////////

        if (optionalInstanceId.isPresent()) {
            String instanceId = optionalInstanceId.get();

            DescribeInstancesRequest describeInstancesRequest = DescribeInstancesRequest.builder()
                    .instanceIds(instanceId)
                    .build();

            // Describe instances retry policy
            RetryPolicy<DescribeInstancesResponse> describeInstancesRetryPolicy = new RetryPolicy<DescribeInstancesResponse>()
                    .handleIf(throwable -> throwable.getMessage().contains(DOES_NOT_EXIST))
                    .withDelay(Duration.ofSeconds(5))
                    .withMaxRetries(3)
                    .onRetry(failure -> log.warn("Waiting for the instance to become visible..."))
                    .onRetriesExceeded(failure -> log.error("Instance never became visible. Cannot continue."));

            DescribeInstancesResponse describeInstancesResponse = Failsafe.with(describeInstancesRetryPolicy).get(() ->
                    ec2Client.describeInstances(describeInstancesRequest));

            Optional<Reservation> optionalReservation = describeInstancesResponse.reservations().stream().findFirst();

            if (!optionalReservation.isPresent()) {
                throw new RuntimeException("Error finding the EC2 reservation to wait for the instance to finish launching, this should never happen");
            }

            Reservation reservation = optionalReservation.get();

            Optional<Instance> optionalInstance = reservation.instances().stream().findFirst();

            if (!optionalInstance.isPresent()) {
                throw new RuntimeException("Error finding the EC2 instance to wait for it to finish launching, this should never happen");
            }

            Instance instance = optionalInstance.get();

            String publicIpAddress = instance.publicIpAddress();

            if (publicIpAddress == null) {
                throw new RuntimeException("Public IP address returned from EC2 was NULL, skipping EC2 setup");
            }

            Optional<String> username = Optional.empty();

            if (deploymentArguments.ec2LinuxVersion.equals(EC2LinuxVersion.Ubuntu1804)) {
                username = Optional.of("ubuntu");
            }

            if (deploymentArguments.ec2LinuxVersion.equals(EC2LinuxVersion.AmazonLinux2)) {
                username = Optional.of("ec2-user");
            }

            if (!username.isPresent()) {
                throw new RuntimeException(String.join("", "Unexpected EC2 Linux version requested [", deploymentArguments.ec2LinuxVersion.name(), "], this is a bug 2 [couldn't determine SSH username]"));
            }

            attemptBootstrap(deploymentArguments, publicIpAddress, username.get());
        }

        ///////////////////////////////////////////
        // Launch a non-EC2 system, if necessary //
        ///////////////////////////////////////////

        if (deploymentArguments.launch != null) {
            attemptBootstrap(deploymentArguments, deploymentArguments.launchHost, deploymentArguments.launchUser);
        }

        //////////////////////////////////////////////////////////////////////////
        // Wait for the CloudFormation stacks to finish launching, if necessary //
        //////////////////////////////////////////////////////////////////////////

        if (cloudFormationStacksLaunched.size() != 0) {
            waitForStacksToLaunch(cloudFormationStacksLaunched);
        }
    }