protected void appendTemplate(String template, String destination, SshMachineLocation machine) {
   String content = ((BindDnsServerSshDriver) getDriver()).processTemplate(template);
   String temp = "/tmp/template-" + Strings.makeRandomId(6);
   machine.copyTo(new ByteArrayInputStream(content.getBytes()), temp);
   machine.execScript(
       "updating file",
       ImmutableList.of(
           BashCommands.sudo(String.format("tee -a %s < %s", destination, temp)),
           String.format("rm -f %s", temp)));
 }
  private void uploadFileContents(String contents, String alternativeUri, String remotePath) {
    checkNotNull(remotePath, "remotePath");
    SshMachineLocation machine = getMachine();
    String tempRemotePath = String.format("%s/upload.tmp", getRunDir());

    if (contents == null && alternativeUri == null) {
      throw new IllegalStateException("No contents supplied for file " + remotePath);
    }
    InputStream stream =
        contents != null
            ? new ByteArrayInputStream(contents.getBytes())
            : resource.getResourceFromUrl(alternativeUri);
    Map<String, String> flags = MutableMap.of(SshTool.PROP_PERMISSIONS.getName(), "0600");
    machine.copyTo(flags, stream, tempRemotePath);
    newScript(CUSTOMIZING)
        .failOnNonZeroResultCode()
        .body
        .append(
            format("mkdir -p %s", remotePath.subSequence(0, remotePath.lastIndexOf("/"))),
            format("cp -p %s %s", tempRemotePath, remotePath),
            format("rm -f %s", tempRemotePath))
        .execute();
  }
  @Override
  public void customize() {
    newScript(CUSTOMIZING)
        .failOnNonZeroResultCode()
        .body
        .append(
            // workaround for AMP distribution placing everything in the root of this archive, but
            // brooklyn distribution placing everything in a subdirectory: check to see if
            // subdirectory
            // with expected name exists; symlink to same directory if it doesn't
            // FIXME remove when all downstream usages don't use this
            format("[ -d %1$s ] || ln -s . %1$s", getExpandedInstallDir(), getExpandedInstallDir()),

            // previously we only copied bin,conf and set BROOKLYN_HOME to the install dir;
            // but that does not play nicely if installing dists other than brooklyn
            // (such as what is built by our artifact)
            format("cp -R %s/* .", getExpandedInstallDir()),
            "mkdir -p ./lib/dropins/")
        .execute();

    SshMachineLocation machine = getMachine();
    BrooklynNode entity = getEntity();

    String brooklynGlobalPropertiesRemotePath =
        entity.getConfig(BrooklynNode.BROOKLYN_GLOBAL_PROPERTIES_REMOTE_PATH);
    String brooklynGlobalPropertiesContents =
        entity.getConfig(BrooklynNode.BROOKLYN_GLOBAL_PROPERTIES_CONTENTS);
    String brooklynGlobalPropertiesUri =
        entity.getConfig(BrooklynNode.BROOKLYN_GLOBAL_PROPERTIES_URI);

    String brooklynLocalPropertiesRemotePath =
        processTemplateContents(
            entity.getConfig(BrooklynNode.BROOKLYN_LOCAL_PROPERTIES_REMOTE_PATH));
    String brooklynLocalPropertiesContents =
        entity.getConfig(BrooklynNode.BROOKLYN_LOCAL_PROPERTIES_CONTENTS);
    String brooklynLocalPropertiesUri =
        entity.getConfig(BrooklynNode.BROOKLYN_LOCAL_PROPERTIES_URI);

    String brooklynCatalogRemotePath = entity.getConfig(BrooklynNode.BROOKLYN_CATALOG_REMOTE_PATH);
    String brooklynCatalogContents = entity.getConfig(BrooklynNode.BROOKLYN_CATALOG_CONTENTS);
    String brooklynCatalogUri = entity.getConfig(BrooklynNode.BROOKLYN_CATALOG_URI);

    // Override the ~/.brooklyn/brooklyn.properties if required
    if (brooklynGlobalPropertiesContents != null || brooklynGlobalPropertiesUri != null) {
      ExistingFileBehaviour onExisting = entity.getConfig(BrooklynNode.ON_EXISTING_PROPERTIES_FILE);
      Integer checkExists =
          DynamicTasks.queue(
                  SshEffectorTasks.ssh("ls \"" + brooklynGlobalPropertiesRemotePath + "\"")
                      .allowingNonZeroExitCode())
              .get();
      boolean doUpload = true;
      if (checkExists == 0) {
        switch (onExisting) {
          case USE_EXISTING:
            doUpload = false;
            break;
          case OVERWRITE:
            break;
          case DO_NOT_USE:
            throw new IllegalStateException(
                "Properties file "
                    + brooklynGlobalPropertiesContents
                    + " already exists and "
                    + "even though it is not being used, content for it was supplied");
          case FAIL:
            throw new IllegalStateException(
                "Properties file "
                    + brooklynGlobalPropertiesContents
                    + " already exists and "
                    + BrooklynNode.ON_EXISTING_PROPERTIES_FILE
                    + " response is to fail");
          default:
            throw new IllegalStateException(
                "Properties file "
                    + brooklynGlobalPropertiesContents
                    + " already exists and "
                    + BrooklynNode.ON_EXISTING_PROPERTIES_FILE
                    + " response "
                    + onExisting
                    + " is unknown");
        }
      }
      if (onExisting == ExistingFileBehaviour.DO_NOT_USE) {
        log.warn(
            "Global properties supplied when told not to use them; no global properties exists, so it will be installed, but it will not be used.");
      }
      if (doUpload)
        uploadFileContents(
            brooklynGlobalPropertiesContents,
            brooklynGlobalPropertiesUri,
            brooklynGlobalPropertiesRemotePath);
    }

    // Upload a local-brooklyn.properties if required
    if (brooklynLocalPropertiesContents != null || brooklynLocalPropertiesUri != null) {
      uploadFileContents(
          brooklynLocalPropertiesContents,
          brooklynLocalPropertiesUri,
          brooklynLocalPropertiesRemotePath);
    }

    // Override the ~/.brooklyn/catalog.xml if required
    if (brooklynCatalogContents != null || brooklynCatalogUri != null) {
      uploadFileContents(brooklynCatalogContents, brooklynCatalogUri, brooklynCatalogRemotePath);
    }

    // Copy additional resources to the server
    for (Map.Entry<String, String> entry :
        getEntity().getAttribute(BrooklynNode.COPY_TO_RUNDIR).entrySet()) {
      Map<String, String> substitutions = ImmutableMap.of("RUN", getRunDir());
      String localResource = entry.getKey();
      String remotePath = entry.getValue();
      String resolvedRemotePath = remotePath;
      for (Map.Entry<String, String> substitution : substitutions.entrySet()) {
        String key = substitution.getKey();
        String val = substitution.getValue();
        resolvedRemotePath =
            resolvedRemotePath.replace("${" + key + "}", val).replace("$" + key, val);
      }
      machine.copyTo(
          MutableMap.of("permissions", "0600"),
          resource.getResourceFromUrl(localResource),
          resolvedRemotePath);
    }

    for (Object entry : getEntity().getClasspath()) {
      String filename = null;
      String url = null;

      if (entry instanceof String) {
        url = (String) entry;
      } else {
        if (entry instanceof Map) {
          url = (String) ((Map) entry).get("url");
          filename = (String) ((Map) entry).get("filename");
        }
      }
      checkNotNull(url, "url");

      // If a local folder, then create archive from contents first
      if (Urls.isDirectory(url)) {
        File jarFile = ArchiveBuilder.jar().addDirContentsAt(new File(url), "").create();
        url = jarFile.getAbsolutePath();
      }

      if (filename == null) {
        // Determine filename
        filename = getFilename(url);
      }
      ArchiveUtils.deploy(
          MutableMap.<String, Object>of(),
          url,
          machine,
          getRunDir(),
          Os.mergePaths(getRunDir(), "lib", "dropins"),
          filename);
    }

    String cmd = entity.getConfig(BrooklynNode.EXTRA_CUSTOMIZATION_SCRIPT);
    if (Strings.isNonBlank(cmd)) {
      DynamicTasks.queueIfPossible(
              SshEffectorTasks.ssh(cmd)
                  .summary("Bespoke BrooklynNode customization script")
                  .requiringExitCodeZero())
          .orSubmitAndBlock(getEntity());
    }
  }