public Template buildTemplate()

in locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsLocation.java [1449:1605]


    public Template buildTemplate(ComputeService computeService, ConfigBag config, JcloudsLocationCustomizer customizersDelegate) {
        TemplateBuilder templateBuilder = config.get(TEMPLATE_BUILDER);
        if (templateBuilder==null) {
            templateBuilder = new PortableTemplateBuilder<PortableTemplateBuilder<?>>();
        } else {
            LOG.debug("jclouds using templateBuilder {} as custom base for provisioning in {} for {}", new Object[] {
                    templateBuilder, this, getCreationString(config)});
        }
        if (templateBuilder instanceof PortableTemplateBuilder<?>) {
            if (((PortableTemplateBuilder<?>)templateBuilder).imageChooser()==null) {
                Function<Iterable<? extends Image>, Image> chooser = getImageChooser(computeService, config);
                templateBuilder.imageChooser(chooser);
            } else {
                // an image chooser is already set, so do nothing
            }
        } else {
            // template builder supplied, and we cannot check image chooser status; warn, for now
            LOG.warn("Cannot check imageChooser status for {} due to manually supplied black-box TemplateBuilder; "
                + "it is recommended to use a PortableTemplateBuilder if you supply a TemplateBuilder", getCreationString(config));
        }

        if (!Strings.isEmpty(config.get(CLOUD_REGION_ID))) {
            templateBuilder.locationId(config.get(CLOUD_REGION_ID));
        }

        if (Strings.isNonBlank(config.get(HARDWARE_ID))) {
            String oldHardwareId = config.get(HARDWARE_ID);
            String newHardwareId = transformHardwareId(oldHardwareId, config);
            if (!Objects.equal(oldHardwareId, newHardwareId)) {
                LOG.info("Transforming hardwareId from " + oldHardwareId + " to " + newHardwareId + ", in " + toString());
                config.put(HARDWARE_ID, newHardwareId);
            }
        }

        // Apply the template builder and options properties
        for (Map.Entry<ConfigKey<?>, ? extends TemplateBuilderCustomizer> entry : SUPPORTED_TEMPLATE_BUILDER_PROPERTIES.entrySet()) {
            ConfigKey<?> key = entry.getKey();
            Object val = config.containsKey(key) ? config.get(key) : key.getDefaultValue();
            if (val != null) {
                TemplateBuilderCustomizer code = entry.getValue();
                code.apply(templateBuilder, config, val);
            }
        }

        if (templateBuilder instanceof PortableTemplateBuilder) {
            ((PortableTemplateBuilder<?>)templateBuilder).attachComputeService(computeService);
            // do the default last, and only if nothing else specified (guaranteed to be a PTB if nothing else specified)
            if (groovyTruth(config.get(DEFAULT_IMAGE_ID))) {
                if (((PortableTemplateBuilder<?>)templateBuilder).isBlank()) {
                    templateBuilder.imageId(config.get(DEFAULT_IMAGE_ID).toString());
                }
            }
        }

        customizersDelegate.customize(this, computeService, templateBuilder);

        LOG.debug("jclouds using templateBuilder {} for provisioning in {} for {}", new Object[] {
            templateBuilder, this, getCreationString(config)});

        // Finally try to build the template
        Template template = null;
        Image image;
        try {
            // in AWS, hardware types A1, T4g, M6g, C6g, and R6g use arm64 which is not compatible with the usual x86_64;
            // so don't pick those hardware types by default; would be nice to have a hardwareChooser on TemplateBuilder akin to
            // imageChooser, but we don't; and would be nice if AWSEC2 used org.jclouds.ec2.domain.Image, rather than org.jclouds.compute.domain.Image,
            // and Hardware stored org.jclouds.ec2.domain.Image.Architecture, so that TemplateBuilderImpl.resolveHardware / supportsImage did the
            // right thing with images selected (noting that OperatingSystem.arch is different (eg "HVM");
            // in the absence of that, in AWS we cheat and if min cores is not set, we set it to 2 to cause the good t3.micro to come back instead of a1.large;
            // to see more details, step in to templateBuilder.build and inspect the hardwareSorter.sortedCopy(hardwares) and filter(..., hardwarePredicate)
            if ("aws-ec2".equals(getProvider()) && Strings.isBlank(config.get(HARDWARE_ID)) && config.get(MIN_CORES)==null) {
                templateBuilder.minCores(2);
            }

            template = templateBuilder.build();
            if (template==null) throw new IllegalStateException("No matching template; check image and hardware constraints (e.g. OS, RAM); using "+templateBuilder);

            image = template.getImage();
            LOG.debug("jclouds found template "+template+" (image "+image+") for provisioning in "+this+" for "+getCreationString(config));
            if (image==null) throw new IllegalStateException("No matching image in template at "+toStringNice()+"; check image constraints (OS, providers, ID); using "+templateBuilder);
        } catch (AuthorizationException e) {
            LOG.warn("Error resolving template -- not authorized (rethrowing: "+e+"); template is: "+template);
            throw new IllegalStateException("Not authorized to access cloud "+toStringNice()+"; "+
                "check identity, credentials, and endpoint (identity='"+getIdentity()+"', credential length "+getCredential().length()+")", e);
        } catch (Exception e) {
            try {
                IOException ioe = Exceptions.getFirstThrowableOfType(e, IOException.class);
                if (ioe != null) {
                    LOG.warn("IOException found...", ioe);
                    throw ioe;
                }
                if (listedAvailableTemplatesOnNoSuchTemplate.compareAndSet(false, true)) {
                    // delay subsequent log.warns (put in synch block) so the "Loading..." message is obvious
                    LOG.warn("Unable to match required VM template constraints "+templateBuilder+" when trying to provision VM in "+this+" (rethrowing): "+e);
                    logAvailableTemplates(config);
                }
            } catch (Exception e2) {
                LOG.warn("Error loading available images to report (following original error matching template which will be rethrown): "+e2, e2);
                throw new IllegalStateException("Unable to access cloud "+this+" to resolve "+templateBuilder+": "+e, e);
            }
            throw new IllegalStateException("Unable to match required VM template constraints "+templateBuilder+" when trying to provision VM in "+this+"; "
                + "see list of images in log. Root cause: "+e, e);
        }
        TemplateOptions options = template.getOptions();

        // For windows, we need a startup-script to be executed that will enable winrm access.
        // If there is already conflicting userMetadata, then don't replace it (and just warn).
        // TODO this injection is hacky and (currently) cloud specific.
        boolean windows = isWindows(template, config);
        if (windows) {
            String initScript = WinRmMachineLocation.getDefaultUserMetadataString(config());
            String provider = getProvider();
            if ("google-compute-engine".equals(provider)) {
                // see https://cloud.google.com/compute/docs/startupscript:
                // Set "sysprep-specialize-script-cmd" in metadata.
                String startupScriptKey = "sysprep-specialize-script-cmd";
                Object metadataMapRaw = config.get(USER_METADATA_MAP);
                if (metadataMapRaw instanceof Map) {
                    Map<?,?> metadataMap = (Map<?, ?>) metadataMapRaw;
                    if (metadataMap.containsKey(startupScriptKey)) {
                        LOG.warn("Not adding startup-script for Windows VM on "+provider+", because already has key "+startupScriptKey+" in config "+USER_METADATA_MAP.getName());
                    } else {
                        Map<Object, Object> metadataMapReplacement = MutableMap.copyOf(metadataMap);
                        metadataMapReplacement.put(startupScriptKey, initScript);
                        config.put(USER_METADATA_MAP, metadataMapReplacement);
                        LOG.debug("Adding startup-script to enable WinRM for Windows VM on "+provider);
                    }
                } else if (metadataMapRaw == null) {
                    Map<String, String> metadataMapReplacement = MutableMap.of(startupScriptKey, initScript);
                    config.put(USER_METADATA_MAP, metadataMapReplacement);
                    LOG.debug("Adding startup-script to enable WinRM for Windows VM on "+provider);
                }
            } else {
                // For AWS and vCloudDirector, we just set user_metadata_string.
                // For Azure-classic, there is no capability to execute a startup script.
                boolean userMetadataString = config.containsKey(JcloudsLocationConfig.USER_METADATA_STRING);
                boolean userMetadataMap = config.containsKey(JcloudsLocationConfig.USER_METADATA_MAP);
                if (!(userMetadataString || userMetadataMap)) {
                    config.put(JcloudsLocationConfig.USER_METADATA_STRING, WinRmMachineLocation.getDefaultUserMetadataString(config()));
                    LOG.debug("Adding startup-script to enable WinRM for Windows VM on "+provider);
                } else {
                    LOG.warn("Not adding startup-script for Windows VM on "+provider+", because already has config "
                            +(userMetadataString ? USER_METADATA_STRING.getName() : USER_METADATA_MAP.getName()));
                }
            }
        }

        for (Map.Entry<ConfigKey<?>, ? extends TemplateOptionCustomizer> entry : SUPPORTED_TEMPLATE_OPTIONS_PROPERTIES.entrySet()) {
            ConfigKey<?> key = entry.getKey();
            TemplateOptionCustomizer code = entry.getValue();
            if (config.containsKey(key) && config.get(key) != null) {
                code.apply(options, config, config.get(key));
            }
        }

        return template;
    }