Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

FlexDeploy change management systems integration (has out of box or custom) can be configured globally for use by various Projects. Configurations can be overridden at Application, Folder or Global level as necessary.

To configure the integration at the Global level, select the Administration->Change Management Systems menu item.

Image Removed

...

Image Removed

You can update name, description and implementation.

...

Field Name

...

Required

...

Description

...

Id

...

Yes

...

System generated identifier (read-only).

...

Name

...

Yes

...

Name of the change management system. (read-only for out of box integrations)

...

Description

...

No

...

Description of the change management system.

...

Default Configuration

You can provide default configuration for change management system on Default Configuration tab. You can still override these settings at the project, folder or environment level.

Image Removed

The Default tab provides configuration for the Global scope, meaning it applies to all deployment system-wide.  One or more of these options may be selected.

  • Require Change Ticket for Deployment - When submitting deployment requests, a user is required to specify a valid ServiceNow Change Ticket number.
  • Create Change Ticket on Deployment - When a deployment request is submitted, a Change Ticket is automatically created within ServiceNow.
  • Create Incident on Deployment Failure - Whenever a deployment request fails, an Incident will automatically be created within ServiceNow.

When choosing Create Change Ticket on Deployment you have the option of providing a Groovy expressions which calculate specific fields to set on the generated Change Ticket.

...

When choosing Create Incident on Deployment Failure you have the option of providing a Groovy expressions which calculate specific fields to set on the generated Change Ticket.

...

All Groovy script fields have access to the following variables, and can be selected using the Image Removed drop-down on the right side of the field:

...

Example Groovy expressions

  • "Deployment request to " + EnvironmentName + " for project " + ProjectName + " by " + WorkflowRequestor
  • "Deployment for " + ProjectName + " failed in " + EnvironmentName
  • "Simple String"

Environment Configuration

The Environment Configuration tab allows configuration by environment.  Click the Add button to add a new configuration, and select an environment.  This overrides any configuration on the Default tab for the selected environment.

Image Removed

The remaining configuration options are exactly as defined for the Default configuration.  Optionally, click the Add button again to add configuration for other environments.  

Properties

The Properties tab lists the metadata which is required for this Change Management System.  Values for these properties will be provided when you create a Change Management Instance in the topology.  See Creating/Editing a Change Management Instance, Properties are read-only for out of box integrations.

Image Removedintegration with ServiceNow, but you can easily integrate with other change management systems. Such third party Change Management System integration can be enabled using Java or Groovy implementation.

Go to Change Management Systems page using Administration - Integrations - Change Management Systems menu.

Image Added

Let's look at more details on how to create a custom change management system integration with Flex Deploy. Click Create to implement integration with custom change management system.

  • Provide a unique name and description
  • Define properties for the new change management system. Properties are configuration values used by FlexDeploy to connect to the new system.
    • If you define properties, you can indicate display and validation details. You can also indicate if property is required and/or encrypted.
    • Enter a unique Name before adding any properties
  • Provide either Java Implementation or Groovy API.
  • Click Save.

Let's define an example change management system in FlexDeploy. You can provide implementation as Java class or just Groovy script. Groovy script would allow for dynamic update whereas use of Java code will require restart of server.

Here we are creating custom change management system with the properties, you can add more as necessary.

Image Added

API Implementation

Implementation must implement the following API operations to integrate with FlexDeploy.


Method Name

Parameter(s)
Return Type

Description

createRequest

String pDescription, String pCommentvoid

Creates a Change Request ticket using the pDescription and pComment

createIncident

String pDescription, String pCommentString

Creates an Incident ticket using the pDescription and pComment

findRequestByType

String pRequestNumber, RequestType pRequestTypevoid

Find a Change Request using the request number and type. Custome CMS services should cast other types like QUESTION, PROBLEM to pRequestType when returning the matching Request object

Note : FlexDeploy supports the following RequestType enum

REQUEST - Use if the ticket is a change request

INCIDENT - Use if the ticket is an incident due to any failure in an env

OTHER - If the ticket doesn't fall in the above two categories

isRequestApproved

String pRequestNumber, String pEnvironmentCodevoid

Checks if the given change request number (pRequestNumber) is approved in the environment (pEnvironmentCode)

isRequestRejected

String pRequestNumber, String pEnvironmentCodeStringChecks if the given change request number (pRequestNumber) is rejected in the environment (pEnvironmentCode)

checkConnection


void

This should invoke any status or heath check URL of the change management system to ensure the system is up and running

isDoPolling


void

Return flag true or false.

Disable automatic polling (every minute) of change management tickets for status changes.


Java Implementation

Here are high level steps for Java implementation. You can use any IDE to prepare this implementation. 

  • Create java class that extends flexagon.fd.model.integration.cms.api.ChangeManagementSystem. Example shown below has the methods implemented which uses properties map to retrieve the configuration values to connect to the change management system.
  • All properties defined are available in Map returned by getProperties method.
Code Block
languagegroovy
themeEmacs
titleExample class ZendeskServiceIntegration
linenumberstrue
package mycompany.extension.flexdeploy.zendesk;

import com.sun.jersey.api.client.Client;
import com.sun.jersey.api.client.ClientResponse;
import com.sun.jersey.api.client.WebResource;
import com.sun.jersey.api.client.filter.LoggingFilter;

import flexagon.fd.model.integration.cms.api.ChangeManagementSystem;
import flexagon.fd.model.integration.cms.api.Request;
import flexagon.fd.model.integration.util.ApiException;

import java.io.Serializable;
import java.io.StringReader;

import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.json.Json;
import javax.json.JsonObject;
import javax.json.JsonObjectBuilder;
import javax.json.JsonReader;

import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;

public class ZendeskServiceIntegration
  extends ChangeManagementSystem
{
  private static final String CLZ_NAM = ZendeskServiceIntegration.class.getName();
  private Client mRestClient;
  private String mAccessToken;
  private Logger mLogger;

  public ZendeskServiceIntegration()
  {
    super();
    this.mLogger = Logger.getLogger(CLZ_NAM);
  }

  private void getOAuthAccessToken(String pScope)
    throws ApiException
  {
    JsonObjectBuilder zenOAuthRequestJson = Json.createObjectBuilder();
    zenOAuthRequestJson.add("grant_type", "password");
    zenOAuthRequestJson.add("client_id", getClientId());
    zenOAuthRequestJson.add("client_secret", getClientSecret());
    zenOAuthRequestJson.add("username", getUserName());
    zenOAuthRequestJson.add("password", getPassword());
    zenOAuthRequestJson.add("scope", pScope);
    String payLoad = zenOAuthRequestJson.build().toString();
    ClientResponse clientResponse =
      getRestClient().resource(getZendeskDomain()).path(getOAuthURI()).accept(MediaType.APPLICATION_JSON_TYPE).type(MediaType.APPLICATION_JSON_TYPE).post(ClientResponse.class, payLoad);
    checkResponse(clientResponse);
    JsonObject jsonResponseObject = readJsonObject(clientResponse.getEntity(String.class));
    print("OAuth Response " + jsonResponseObject);
    mAccessToken = jsonResponseObject.getString("access_token");
  }

  private String getAccessToken(String pScope)
    throws ApiException
  {
    getOAuthAccessToken(pScope);
    return "Bearer " + mAccessToken;
  }

  private String getOAuthURI()
  {
    return getPropertyAsString("ZD_OAUTH_URI");
  }

  private String getClientId()
  {
    return getPropertyAsString("ZD_CLIENT_ID");
  }

  private String getClientSecret()
  {
    return getPropertyAsString("ZD_CLIENT_SECRET");
  }

  @Override
  public Request createRequest(String pDescription, String pComment)
    throws ApiException
  {
    String methodName = "createRequest";
    print(methodName + String.format(" Creating new Request using the description [%s] and comment[%s]", pDescription, pComment));
    return postTicket("request", "ZD_REQUEST_CREATE_PATTERN", "question", pDescription, pComment, "requests:write read");
  }

  private Request postTicket(String pObjectType, String pURLKey, String pTicketType, String pDescription, String pComment, String pScope)
    throws ApiException
  {
    String methodName = "postTicket";
    print(methodName + String.format(" Creating new Request using the description [%s] and comment[%s]", pDescription, pComment));
    JsonObjectBuilder zenInputJson = Json.createObjectBuilder();
    JsonObjectBuilder inputParamJsonBuider = Json.createObjectBuilder();
    inputParamJsonBuider.add("subject", pDescription);
    JsonObjectBuilder descriptionJsonBuider = Json.createObjectBuilder();
    descriptionJsonBuider.add("body", pComment);
    inputParamJsonBuider.add("comment", descriptionJsonBuider.build());
    inputParamJsonBuider.add("type", pTicketType);
    zenInputJson.add(pObjectType, inputParamJsonBuider.build());

    WebResource postRequest = getWebResource(getPropertyAsString(pURLKey));
    String payLoad = zenInputJson.build().toString();
    print(methodName + String.format(" Creating new %s %s", pObjectType, payLoad));
    ClientResponse clientResponse =
      postRequest.accept(MediaType.APPLICATION_JSON_TYPE).type(MediaType.APPLICATION_JSON_TYPE).header("Authorization", getAccessToken(pScope)).post(ClientResponse.class, payLoad);
    checkResponse(clientResponse);
    JsonObject jsonResponseObject = readJsonObject(clientResponse.getEntity(String.class));
    JsonObject requestResponseJson = jsonResponseObject.getJsonObject(pObjectType);
    RequestImpl request =
      new RequestImpl(String.valueOf(requestResponseJson.getInt("id")), requestResponseJson.getString("description"), requestResponseJson.getString("status"), requestResponseJson.getString("type"),
                      true, requestResponseJson);
    print(methodName + String.format(" Successfully created ticket %s", request.getNumber()));
    return request;
  }

  private JsonObject readJsonObject(String source)
  {
    String methodName = "readJsonObject";
    JsonReader jsonReader = Json.createReader(new StringReader(source));
    JsonObject object = null;
    try
    {
      object = jsonReader.readObject();
      print(methodName + String.format(" Json object created for response = %s", source));
    }
    catch (Exception e)
    {
      mLogger.logp(Level.INFO, mLogger.getName(), methodName, String.format(" Exception while creating response json object, error %s, response = %s", e.getMessage(), source), e);
    }
    finally
    {
      if (jsonReader != null)
      {
        jsonReader.close();
      }
    }

    return object;
  }

  @Override
  public Request createIncident(String pDescription, String pComment)
  {
    String methodName = "createIncident";
    Request request = null;
    try
    {
      request = postTicket("ticket", "ZD_TICKET_CREATE_PATTERN", "incident", pDescription, pComment, "tickets:write read");
    }
    catch (ApiException e)
    {
      mLogger.logp(Level.INFO, mLogger.getName(), methodName, String.format(" Exception while creating incident, error %s", e.getMessage()), e);
    }
    return request;
  }

  @Override
  public Request findRequestByType(String pRequestNumber, ChangeManagementSystem.RequestType pRequestType)
    throws ApiException
  {
    Request changeRequest = getChangeRequest(pRequestNumber);
    if (changeRequest != null && changeRequest.getTypeName().equalsIgnoreCase("QUESTION") && pRequestType.equals(ChangeManagementSystem.RequestType.REQUEST))
    {
      print(pRequestNumber + " is of type QUESTION, setting it as REQUEST for FD");
      ((RequestImpl) changeRequest).setType(ChangeManagementSystem.RequestType.REQUEST.name());
    }
    return getChangeRequest(pRequestNumber);
  }

  private Request getChangeRequest(String pRequestNumber)
    throws ApiException
  {
    return getTicket("request", "ZD_REQUEST_GET_PATTERN", pRequestNumber, "{ZENDESK_REQUEST}");
  }


  private Request getTicket(String pObjectType, String pURLKey, String pRequestNumber, String pSearchPattern)
    throws ApiException
  {
    String methodName = "getTicket";
    print(methodName + String.format(" Find Ticket [%s] ", pRequestNumber));

    //Zendesk Get Request URL Pattern (/api/v2/requests/{ZENDESK_REQUEST}.json)
    String urlString = getPropertyAsString(pURLKey);
    urlString = urlString.replace(pSearchPattern, pRequestNumber);
    ClientResponse clientResponse = getWebResource(urlString).header("Authorization", getAccessToken("tickets:write read")).get(ClientResponse.class);
    checkResponse(clientResponse);
    JsonObject jsonResponseObject = readJsonObject(clientResponse.getEntity(String.class));
    JsonObject ticketResponseJson = jsonResponseObject.getJsonObject(pObjectType);
    RequestImpl request =
      new RequestImpl(String.valueOf(ticketResponseJson.getInt("id")), ticketResponseJson.getString("description"), ticketResponseJson.getString("status"), ticketResponseJson.getString("type"), true,
                      ticketResponseJson);
    print(methodName + String.format(" Successfully found ticket %s", request.getNumber()));
    return request;
  }

  @Override
  public Boolean isRequestApproved(String requestNumber, String environmentCode)
  {
    Request findRequest = null;
    Boolean approved = false;
    try
    {
      findRequest = getChangeRequest(requestNumber);
      if (findRequest != null)
      {
        String status = findRequest.getStatus();
        print(String.format("#[%s] status [%s]", requestNumber, status));
        if ("solved".equalsIgnoreCase(status))
        {
          approved = true;
        }
      }
    }
    catch (Exception e)
    {
      mLogger.logp(Level.INFO, mLogger.getName(), "isRequestApproved", "Unable to check the status, failed - " + e.getMessage(), e);
    }
    return approved;
  }


  @Override
  public Boolean isRequestRejected(String requestNumber, String environmentCode)
  {
    Request findRequest = null;
    Boolean rejected = false;
    try
    {
      findRequest = getChangeRequest(requestNumber);
      if (findRequest != null)
      {
        String status = findRequest.getStatus();
        print(String.format("#[%s] status [%s]", requestNumber, status));
        if ((!"New".equalsIgnoreCase(status)) && ("on-hold".equalsIgnoreCase(status) || "Closed".equalsIgnoreCase(status)))
        {
          rejected = true;
        }
      }
    }
    catch (Exception e)
    {
      mLogger.logp(Level.INFO, mLogger.getName(), "isRequestRejected", "Unable to check the status, failed - " + e.getMessage(), e);
    }
    return rejected;
  }

  @Override
  public Boolean isDoPolling()
  {
    return false;
  }

  @Override
  public void checkConnection()
    throws ApiException
  {
    String methodName = "checkConnection";
    print(methodName + " getting user details to check connection");
    ClientResponse clientResponse = getWebResource("/api/v2/users/me.json").header("Authorization", getAccessToken("organizations:write read")).get(ClientResponse.class);
    checkResponse(clientResponse);
    print(methodName + " Test connection response code looks valid, check content of response");
    String responseString = clientResponse.getEntity(String.class);
    print(methodName + "responseString=" + responseString + ", Validated that JSON data was received from test connction URL invocation.");
  }

  private StringBuilder getZendeskURLBuilder()
  {
    StringBuilder urlBuilder = new StringBuilder(getPropertyAsString("ZD_DOMAIN_NAME"));
    return urlBuilder;
  }

  private void checkResponse(ClientResponse clientResponse)
    throws ApiException
  {
    String methodName = "checkResponse";
    print(methodName + String.format(" Invoked connection URL %s", clientResponse));
    int statusCode = clientResponse.getStatusInfo().getStatusCode();
    if (statusCode == 401)
    {
      throw new ApiException("Invalid credentials.", "");
    }
    if (!(clientResponse.getStatusInfo().getFamily() == Response.Status.Family.SUCCESSFUL))
    {
      throw new ApiException(clientResponse.getEntity(String.class), clientResponse.getStatusInfo().getReasonPhrase());
    }
  }

  private String getZendeskDomain()
  {
    return getZendeskURLBuilder().toString();
  }

  private String getUserName()
  {
    String userName = getPropertyAsString("ZD_USER_NAME");
    print("userName=" + userName);
    return userName;
  }

  private void print(String pMessage)
  {
    mLogger.logp(Level.INFO, mLogger.getName(), "print", pMessage);
  }

  private String getPassword()
  {
    return getPropertyAsString("ZD_PASSWORD");
  }

  private String getPropertyAsString(String pKey)
  {
    return (String) getProperties().get(pKey);
  }

  private Client getRestClient()
    throws ApiException
  {
    if (mRestClient == null)
    {
      mRestClient = Client.create();
      mRestClient.addFilter(new LoggingFilter(System.out));
      mRestClient.setReadTimeout(20000);
      mRestClient.setConnectTimeout(10000);
    }
    return mRestClient;
  }

  public WebResource getWebResource(String resource)
    throws ApiException
  {
    if (resource != null && !resource.isEmpty() && !resource.startsWith("/"))
    {
      resource += "/" + resource;
    }
    WebResource webResource = null;
    try
    {
      webResource = getRestClient().resource(getZendeskDomain()).path(resource);
    }
    catch (Exception e)
    {
      throw new ApiException(e.getMessage(), e.getMessage());
    }

    return webResource;
  } 
}
  • In order to compile your java class, you will need FlexDeployAPI.jar on classpath.
  • Implement all the methods described in the table in the API Implementation section.
  • For any failure connecting to the system or if any issues with the data, then you can throw exception. For example throw new ApiException("Invalid credentials.", "");
  • Once you are ready with unit testing, you can prepare Jar file for your credential store java class and other utility classes. This jar file can be placed on server classpath now.
    • For Tomcat, put this jar file in apache-tomcat-flexdeploy/lib folder.
    • For WebLogic, put this jar file in Domain lib folder.
    • If you are using any third party libraries from your Java implementation, then those jar files will also need to be added to same lib folder. Keep in mind that this can cause issues with server functioning, so be prepared to remove your additional library files.