@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;
  }
  @Override
  public void onCompleted(AbstractBuild run, TaskListener listener) {
    Lamps plugin = Lamps.getInstance();
    Set<String> jobs = plugin.getJobs();
    String jobName = run.getParent().getFullName();
    XfEventMessage xfEventMessage = new XfEventMessage();

    if (jobs.contains(jobName)) {
      Result result = run.getResult();
      Set<Lamp> activeLamps = plugin.getLampsContainingJob(jobName);
      for (Lamp lamp : activeLamps) {
        Result lampResult = result;

        xfEventMessage.sendColorMessage(lamp, lampResult, States.Action.SOLID);

        // Create Notification for LCD
        StringBuilder infoMsg = new StringBuilder(64);
        infoMsg.append(jobName).append(' ').append(run.getDisplayName()).append('\n');
        if (Result.FAILURE.equals(result)) {
          ArrayList<String> blame = Lists.newArrayList();
          if (lamp.isBlame()) {
            Set<User> culprits = run.getCulprits();
            for (User user : culprits) {
              blame.add(user.getDisplayName());
            }
          }
          if (blame.isEmpty()) {
            blame.add("Somebody");
          }
          infoMsg.insert(0, Joiner.on(", ").join(blame) + " broke the build: ");
          infoMsg.append(result.toString());
          listener.getLogger().println("[XFD] Updating Lamp display: " + infoMsg.toString());
        } else if (Result.ABORTED.equals(result)) {
          String causeMsg = "BUILD ABORTED";
          infoMsg.append(causeMsg);
          listener.getLogger().println("[XFD] Updating Lamp display: " + infoMsg.toString());
        } else {
          infoMsg.append(result.toString());
        }
        xfEventMessage.sendLCDMessage(lamp, infoMsg.toString());

        if (lamp.isSfx()) {
          try {
            Thread.sleep(1000);
            xfEventMessage.sendSfxMessage(lamp, lampResult);
          } catch (InterruptedException e) {
            e.printStackTrace();
          }
        }

        if (States.resultColorMap.get(lampResult).equals(States.Color.RED) && lamp.isNoisy()) {
          try {
            Thread.sleep(1000);
            xfEventMessage.sendBuzzerMessage(lamp);
          } catch (InterruptedException e) {
            e.printStackTrace();
          }
        }
        if (lamp.isAggregate()) {
          try {
            Thread.sleep(1000);
          } catch (InterruptedException e) {
            e.printStackTrace();
          }
          plugin.updateAggregateStatus(lamp);
        }
      }
    }
  }