private boolean updateExistingJob(AbstractProject<?, ?> project, String config) {
    boolean created;

    // Leverage XMLUnit to perform diffs
    Diff diff;
    try {
      String oldJob = project.getConfigFile().asString();
      diff = XMLUnit.compareXML(oldJob, config);
      if (diff.similar()) {
        LOGGER.log(Level.FINE, String.format("Project %s is identical", project.getName()));
        return false;
      }
    } catch (Exception e) {
      // It's not a big deal if we can't diff, we'll just move on
      LOGGER.warning(e.getMessage());
    }

    // TODO Perform comparison between old and new, and print to console
    // TODO Print out, for posterity, what the user might have changed, in the format of the DSL

    LOGGER.log(Level.FINE, String.format("Updating project %s as %s", project.getName(), config));
    StreamSource streamSource =
        new StreamSource(new StringReader(config)); // TODO use real xmlReader
    try {
      project.updateByXml(streamSource);
      created = true;
    } catch (IOException ioex) {
      LOGGER.log(Level.WARNING, String.format("Error writing updated project to file."), ioex);
      created = false;
    }
    return created;
  }
 protected static String resolveParametersInString(
     AbstractBuild<?, ?> build, BuildListener listener, String input) {
   try {
     return build.getEnvironment(listener).expand(input);
   } catch (Exception e) {
     listener
         .getLogger()
         .println(
             "Failed to resolve parameters in string \""
                 + input
                 + "\" due to following error:\n"
                 + e.getMessage());
   }
   return input;
 }
  @Override
  public boolean perform(AbstractBuild<?, ?> build, Launcher launcher, BuildListener listener)
      throws IOException, InterruptedException {

    listener.getLogger().println("[CucumberReportPublisher] Compiling Cucumber Html Reports ...");

    File workspaceJsonReportDirectory = new File(build.getWorkspace().toURI().getPath());
    if (!jsonReportDirectory.isEmpty()) {
      workspaceJsonReportDirectory =
          new File(build.getWorkspace().toURI().getPath(), jsonReportDirectory);
    }
    File targetBuildDirectory = new File(build.getRootDir(), "cucumber-html-reports");

    String buildNumber = Integer.toString(build.getNumber());
    String buildProject = build.getProject().getName();

    if (!targetBuildDirectory.exists()) {
      targetBuildDirectory.mkdirs();
    }

    boolean buildResult = true;

    // if we are on a slave
    if (Computer.currentComputer() instanceof SlaveComputer) {
      listener
          .getLogger()
          .println("[CucumberReportPublisher] detected this build is running on a slave ");
      FilePath projectWorkspaceOnSlave = build.getProject().getSomeWorkspace();
      FilePath masterJsonReportDirectory = new FilePath(targetBuildDirectory);
      listener
          .getLogger()
          .println(
              "[CucumberReportPublisher] copying json from: "
                  + projectWorkspaceOnSlave.toURI()
                  + "to reports directory: "
                  + masterJsonReportDirectory.toURI());
      projectWorkspaceOnSlave.copyRecursiveTo("**/*.json", "", masterJsonReportDirectory);
    } else {
      // if we are on the master
      listener
          .getLogger()
          .println("[CucumberReportPublisher] detected this build is running on the master ");
      String[] files = findJsonFiles(workspaceJsonReportDirectory);

      if (files.length != 0) {
        listener
            .getLogger()
            .println(
                "[CucumberReportPublisher] copying json to reports directory: "
                    + targetBuildDirectory);
        for (String file : files) {
          FileUtils.copyFile(
              new File(workspaceJsonReportDirectory.getPath() + "/" + file),
              new File(targetBuildDirectory, file));
        }
      } else {
        listener
            .getLogger()
            .println(
                "[CucumberReportPublisher] there were no json results found in: "
                    + workspaceJsonReportDirectory);
      }
    }

    // generate the reports from the targetBuildDirectory
    String[] jsonReportFiles = findJsonFiles(targetBuildDirectory);
    if (jsonReportFiles.length != 0) {

      listener
          .getLogger()
          .println(
              "[CucumberReportPublisher] Found the following number of json files: "
                  + jsonReportFiles.length);
      int jsonIndex = 0;
      for (String jsonReportFile : jsonReportFiles) {
        listener
            .getLogger()
            .println(
                "[CucumberReportPublisher] "
                    + jsonIndex
                    + ". Found a json file: "
                    + jsonReportFile);
        jsonIndex++;
      }
      listener.getLogger().println("[CucumberReportPublisher] Generating HTML reports");

      try {
        ReportBuilder reportBuilder =
            new ReportBuilder(
                fullPathToJsonFiles(jsonReportFiles, targetBuildDirectory),
                targetBuildDirectory,
                pluginUrlPath,
                buildNumber,
                buildProject,
                skippedFails,
                undefinedFails,
                !noFlashCharts,
                true,
                false,
                "",
                false);
        reportBuilder.generateReports();
        buildResult = reportBuilder.getBuildStatus();
      } catch (Exception e) {
        e.printStackTrace();
        listener
            .getLogger()
            .println("[CucumberReportPublisher] there was an error generating the reports: " + e);
        for (StackTraceElement error : e.getStackTrace()) {
          listener.getLogger().println(error);
        }
      }
    } else {
      listener
          .getLogger()
          .println(
              "[CucumberReportPublisher] there were no json results found in: "
                  + targetBuildDirectory);
    }

    build.addAction(new CucumberReportBuildAction(build));
    return buildResult;
  }
  @Override
  public boolean perform(AbstractBuild build, Launcher launcher, BuildListener listener) {
    // This method deserves a refactor and cleanup.
    boolean success = true;
    Log log = new Log(listener);
    if (Result.FAILURE.equals(build.getResult())) {
      log.info("Not deploying due to job being in FAILED state.");
      return success;
    }

    logStartHeader(log);
    // todo: getting from descriptor is ugly. refactor?
    getDescriptorImpl().setGlobalConfiguration();
    OctopusApi api = getDescriptorImpl().api;

    VariableResolver resolver = build.getBuildVariableResolver();
    EnvVars envVars;
    try {
      envVars = build.getEnvironment(listener);
    } catch (Exception ex) {
      log.fatal(
          String.format(
              "Failed to retrieve environment variables for this build - '%s'", ex.getMessage()));
      return false;
    }
    EnvironmentVariableValueInjector envInjector =
        new EnvironmentVariableValueInjector(resolver, envVars);
    // NOTE: hiding the member variables of the same name with their env-injected equivalents
    String project = envInjector.injectEnvironmentVariableValues(this.project);
    String releaseVersion = envInjector.injectEnvironmentVariableValues(this.releaseVersion);
    String environment = envInjector.injectEnvironmentVariableValues(this.environment);
    String variables = envInjector.injectEnvironmentVariableValues(this.variables);

    com.octopusdeploy.api.Project p = null;
    try {
      p = api.getProjectByName(project);
    } catch (Exception ex) {
      log.fatal(
          String.format(
              "Retrieving project name '%s' failed with message '%s'", project, ex.getMessage()));
      success = false;
    }
    com.octopusdeploy.api.Environment env = null;
    try {
      env = api.getEnvironmentByName(environment);
    } catch (Exception ex) {
      log.fatal(
          String.format(
              "Retrieving environment name '%s' failed with message '%s'",
              environment, ex.getMessage()));
      success = false;
    }
    if (p == null) {
      log.fatal("Project was not found.");
      success = false;
    }
    if (env == null) {
      log.fatal("Environment was not found.");
      success = false;
    }
    if (!success) // Early exit
    {
      return success;
    }
    Set<com.octopusdeploy.api.Release> releases = null;
    try {
      releases = api.getReleasesForProject(p.getId());
    } catch (Exception ex) {
      log.fatal(
          String.format(
              "Retrieving releases for project '%s' failed with message '%s'",
              project, ex.getMessage()));
      success = false;
    }
    if (releases == null) {
      log.fatal("Releases was not found.");
      return false;
    }
    Release releaseToDeploy = null;
    for (Release r : releases) {
      if (releaseVersion.equals(r.getVersion())) {
        releaseToDeploy = r;
        break;
      }
    }
    if (releaseToDeploy == null) // early exit
    {
      log.fatal(
          String.format(
              "Unable to find release version %s for project %s", releaseVersion, project));
      return false;
    }
    Properties properties = new Properties();
    try {
      properties.load(new StringReader(variables));
    } catch (Exception ex) {
      log.fatal(
          String.format(
              "Unable to load entry variables failed with message '%s'", ex.getMessage()));
      success = false;
    }

    // TODO: Can we tell if we need to call? For now I will always try and get variable and use if I
    // find them
    Set<com.octopusdeploy.api.Variable> variablesForDeploy = null;

    try {
      String releaseId = releaseToDeploy.getId();
      String environmentId = env.getId();
      variablesForDeploy =
          api.getVariablesByReleaseAndEnvironment(releaseId, environmentId, properties);
    } catch (Exception ex) {
      log.fatal(
          String.format(
              "Retrieving variables for release '%s' to environment '%s' failed with message '%s'",
              releaseToDeploy.getId(), env.getName(), ex.getMessage()));
      success = false;
    }
    try {
      String results =
          api.executeDeployment(releaseToDeploy.getId(), env.getId(), variablesForDeploy);
      if (isTaskJson(results)) {
        JSON resultJson = JSONSerializer.toJSON(results);
        String urlSuffix = ((JSONObject) resultJson).getJSONObject("Links").getString("Web");
        String url = getDescriptorImpl().octopusHost;
        if (url.endsWith("/")) {
          url = url.substring(0, url.length() - 2);
        }
        log.info("Deployment executed: \n\t" + url + urlSuffix);
        build.addAction(
            new BuildInfoSummary(
                BuildInfoSummary.OctopusDeployEventType.Deployment, url + urlSuffix));
        if (waitForDeployment) {

          log.info("Waiting for deployment to complete.");
          String resultState = waitForDeploymentCompletion(resultJson, api, log);
          if (resultState == null) {
            log.info("Marking build failed due to failure in waiting for deployment to complete.");
            success = false;
          }

          if ("Failed".equals(resultState)) {
            log.info("Marking build failed due to deployment task status.");
            success = false;
          }
        }
      }
    } catch (IOException ex) {
      log.fatal("Failed to deploy: " + ex.getMessage());
      success = false;
    }

    return success;
  }
  protected void buildOpenMergeRequests(
      GitLabPushTrigger trigger, Integer projectId, String projectRef) {
    try {
      GitLab api = new GitLab();
      // TODO Replace this with a call to GitlabAPI.getOpenMergeRequests, once timols has deployed
      // version 1.1.7
      String tailUrl =
          GitlabProject.URL
              + "/"
              + projectId
              + GitlabMergeRequest.URL
              + "?state=opened&per_page=100";
      List<GitlabMergeRequest> mergeRequests =
          api.instance().retrieve().getAll(tailUrl, GitlabMergeRequest[].class);

      for (org.gitlab.api.models.GitlabMergeRequest mr : mergeRequests) {
        if (projectRef.endsWith(mr.getSourceBranch())
            || (trigger.getTriggerOpenMergeRequestOnPush().equals("both")
                && projectRef.endsWith(mr.getTargetBranch()))) {

          if (trigger.getCiSkip() && mr.getDescription().contains("[ci-skip]")) {
            LOGGER.log(Level.INFO, "Skipping MR " + mr.getTitle() + " due to ci-skip.");
            continue;
          }
          GitlabBranch branch =
              api.instance().getBranch(api.instance().getProject(projectId), mr.getSourceBranch());
          LastCommit lastCommit = new LastCommit();
          lastCommit.setId(branch.getCommit().getId());
          lastCommit.setMessage(branch.getCommit().getMessage());
          lastCommit.setUrl(
              GitlabProject.URL
                  + "/"
                  + projectId
                  + "/repository"
                  + GitlabCommit.URL
                  + "/"
                  + branch.getCommit().getId());

          LOGGER.log(
              Level.FINE,
              "Generating new merge trigger from "
                  + mr.toString()
                  + "\n source: "
                  + mr.getSourceBranch()
                  + "\n target: "
                  + mr.getTargetBranch()
                  + "\n state: "
                  + mr.getState()
                  + "\n assign: "
                  + mr.getAssignee()
                  + "\n author: "
                  + mr.getAuthor()
                  + "\n id: "
                  + mr.getId()
                  + "\n iid: "
                  + mr.getIid()
                  + "\n last commit: "
                  + lastCommit.getId()
                  + "\n\n");
          GitLabMergeRequest newReq = new GitLabMergeRequest();
          newReq.setObject_kind("merge_request");
          newReq.setObjectAttribute(new GitLabMergeRequest.ObjectAttributes());
          if (mr.getAssignee() != null)
            newReq.getObjectAttribute().setAssigneeId(mr.getAssignee().getId());
          if (mr.getAuthor() != null)
            newReq.getObjectAttribute().setAuthorId(mr.getAuthor().getId());
          newReq.getObjectAttribute().setDescription(mr.getDescription());
          newReq.getObjectAttribute().setId(mr.getId());
          newReq.getObjectAttribute().setIid(mr.getIid());
          newReq.getObjectAttribute().setMergeStatus(mr.getState());
          newReq.getObjectAttribute().setSourceBranch(mr.getSourceBranch());
          newReq.getObjectAttribute().setSourceProjectId(mr.getSourceProjectId());
          newReq.getObjectAttribute().setTargetBranch(mr.getTargetBranch());
          newReq.getObjectAttribute().setTargetProjectId(projectId);
          newReq.getObjectAttribute().setTitle(mr.getTitle());
          newReq.getObjectAttribute().setLastCommit(lastCommit);

          Authentication old = SecurityContextHolder.getContext().getAuthentication();
          SecurityContextHolder.getContext().setAuthentication(ACL.SYSTEM);
          try {
            trigger.onPost(newReq);
          } finally {
            SecurityContextHolder.getContext().setAuthentication(old);
          }
        }
      }
    } catch (Exception e) {
      LOGGER.warning(
          "failed to communicate with gitlab server to determine is this is an update for a merge request: "
              + e.getMessage());
      e.printStackTrace();
    }
  }