Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Plugin property mapping w fixed tests #33

Merged
merged 14 commits into from
Aug 19, 2024
123 changes: 109 additions & 14 deletions src/main/java/edu/ohio/ais/rundeck/HttpBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import com.dtolabs.rundeck.core.execution.workflow.steps.StepException;
import com.dtolabs.rundeck.core.execution.workflow.steps.StepFailureReason;
import com.dtolabs.rundeck.core.storage.ResourceMeta;
import com.dtolabs.rundeck.core.utils.IPropertyLookup;
import com.dtolabs.rundeck.plugins.PluginLogger;
import com.dtolabs.rundeck.plugins.step.PluginStepContext;
import com.google.gson.Gson;
Expand Down Expand Up @@ -88,13 +89,14 @@ public enum Reason implements FailureReason {
}


public CloseableHttpClient getHttpClient(Map<String, Object> options) throws GeneralSecurityException {
public CloseableHttpClient getHttpClient(Map<String, Object> options) throws GeneralSecurityException, StepException {
HttpClientBuilder httpClientBuilder = HttpClientBuilder.create();

httpClientBuilder.disableAuthCaching();
httpClientBuilder.disableAutomaticRetries();

if(options.containsKey("sslVerify") && !Boolean.parseBoolean(options.get("sslVerify").toString())) {

if(!getBooleanOption(options, "sslVerify", true)) {
log.log(5,"Disabling all SSL certificate verification.");
SSLContextBuilder sslContextBuilder = new SSLContextBuilder();
sslContextBuilder.loadTrustMaterial(null, new TrustStrategy() {
Expand All @@ -107,8 +109,27 @@ public boolean isTrusted(X509Certificate[] x509Certificates, String s) throws Ce
httpClientBuilder.setSSLHostnameVerifier(new NoopHostnameVerifier());
httpClientBuilder.setSSLContext(sslContextBuilder.build());
}
if(options.containsKey("proxySettings") && Boolean.parseBoolean(options.get("proxySettings").toString())){
HttpHost proxy = new HttpHost(options.get("proxyIP").toString(), Integer.valueOf((String)options.get("proxyPort")), "http");
if(getBooleanOption(options, "useSystemProxySettings", false) && !getBooleanOption(options, "proxySettings", false)) {
log.log(5, "Using proxy settings set on system");
String proxyHost = System.getProperty("http.proxyHost");
String proxyPort = System.getProperty("http.proxyPort");
if (proxyPort.isEmpty() || proxyHost.isEmpty()) {
throw new StepException("proxyHost and proxyPort are required to use System Proxy Settings", StepFailureReason.ConfigurationFailure);
}
HttpHost proxy = new HttpHost(proxyHost, Integer.parseInt(proxyPort), "http");
httpClientBuilder.setProxy(proxy);
}
if (getBooleanOption(options, "proxySettings", false)) {
String proxyIP = getStringOption(options, "proxyIP", "");
String proxyPort = getStringOption(options, "proxyPort", "");

if (proxyIP.isEmpty() || proxyPort.isEmpty()) {
throw new StepException("Proxy IP and Proxy Port are required to use Proxy Settings.", StepFailureReason.ConfigurationFailure);
}

log.log(5, "proxy IP set in job: " + proxyIP);
log.log(5, "proxy Port set in job: " + proxyPort);
HttpHost proxy = new HttpHost(proxyIP, Integer.parseInt(proxyPort), "http");
httpClientBuilder.setProxy(proxy);
}

Expand All @@ -132,19 +153,19 @@ public void doRequest(Map<String, Object> options, HttpUriRequest request, Integ
try {
response = this.getHttpClient(options).execute(request);

if(options.containsKey("printResponseCode") && Boolean.parseBoolean(options.get("printResponseCode").toString())) {
if(getBooleanOption(options,"printResponseCode",false)) {
String responseCode = response.getStatusLine().toString();
log.log(2, "Response Code: " + responseCode);
}

//print the response content
if(options.containsKey("printResponse") && Boolean.parseBoolean(options.get("printResponse").toString())) {
if(getBooleanOption(options,"printResponse",false)) {
output = getOutputForResponse(this.prettyPrint(response));
//print response
log.log(2, output);
}

if(options.containsKey("printResponseToFile") && Boolean.parseBoolean(options.get("printResponseToFile").toString())){
if(getBooleanOption(options,"printResponseToFile",false)){
File file = new File(options.get("file").toString());
BufferedWriter writer = new BufferedWriter(new FileWriter(file));
if( output.isEmpty() ){
Expand All @@ -158,7 +179,7 @@ public void doRequest(Map<String, Object> options, HttpUriRequest request, Integ
}

//check response status
if(options.containsKey("checkResponseCode") && Boolean.parseBoolean(options.get("checkResponseCode").toString())) {
if(getBooleanOption(options,"checkResponseCode",false)) {

if(options.containsKey("responseCode")){
int responseCode = Integer.valueOf( (String) options.get("responseCode"));
Expand Down Expand Up @@ -335,14 +356,14 @@ public String prettyPrint(HttpResponse response){


String getAuthHeader(PluginStepContext pluginStepContext, Map<String, Object> options) throws StepException {
String authentication = options.containsKey("authentication") ? options.get("authentication").toString() : AUTH_NONE;
String authentication = getStringOption(options, "authentication",AUTH_NONE);
//moving the password to the key storage
String password=null;
String authHeader = null;


if(options.containsKey("password") ){
String passwordRaw = options.containsKey("password") ? options.get("password").toString() : null;
String passwordRaw = getStringOption(options, "password");
//to avid the test error add a try-catch
//if it didn't find the key path, it will use the password directly
byte[] content = SecretBundleUtil.getStoragePassword(pluginStepContext.getExecutionContext(),passwordRaw );
Expand All @@ -356,7 +377,7 @@ String getAuthHeader(PluginStepContext pluginStepContext, Map<String, Object> o

if(authentication.equals(AUTH_BASIC)) {
// Setup the authentication header for BASIC
String username = options.containsKey("username") ? options.get("username").toString() : null;
String username = getStringOption(options, "username");

if(username == null || password == null) {
throw new StepException("Username and password not provided for BASIC Authentication",
Expand All @@ -369,9 +390,12 @@ String getAuthHeader(PluginStepContext pluginStepContext, Map<String, Object> o
authHeader = "Basic " + com.dtolabs.rundeck.core.utils.Base64.encode(authHeader);
} else if (authentication.equals(AUTH_OAUTH2)) {
// Get an OAuth token and setup the auth header for OAuth
String tokenEndpoint = options.containsKey("oauthTokenEndpoint") ? options.get("oauthTokenEndpoint").toString() : null;
String validateEndpoint = options.containsKey("oauthValidateEndpoint") ? options.get("oauthValidateEndpoint").toString() : null;
String clientId = options.containsKey("username") ? options.get("username").toString() : null;
String tokenEndpoint = getStringOption(options, "oauthTokenEndpoint");
String validateEndpoint = getStringOption(options, "oauthValidateEndpoint");
String clientId = getStringOption(options, "username");



String clientSecret = password;


Expand Down Expand Up @@ -460,5 +484,76 @@ public void setHeaders(String headers, RequestBuilder request){
}
}

static void propertyResolver(String pluginType, String property, Map<String,Object> Configuration, PluginStepContext context, String SERVICE_PROVIDER_NAME) {

String projectPrefix = "project.plugin." + pluginType + "." + SERVICE_PROVIDER_NAME + ".";
String frameworkPrefix = "framework.plugin." + pluginType + "." + SERVICE_PROVIDER_NAME + ".";

Map<String,String> projectProperties = context.getFramework().getFrameworkProjectMgr().getFrameworkProject(context.getFrameworkProject()).getProperties();
IPropertyLookup frameworkProperties = context.getFramework().getPropertyLookup();

if(!Configuration.containsKey(property) && projectProperties.containsKey(projectPrefix + property)) {

Configuration.put(property, projectProperties.get(projectPrefix + property));

} else if (!Configuration.containsKey(property) && frameworkProperties.hasProperty(frameworkPrefix + property)) {

Configuration.put(property, frameworkProperties.getProperty(frameworkPrefix + property));

}
}

/**
* Retrieves a string value from the options map.
* If the key does not exist or the value is null, it returns null.
*
* @param options the map containing option keys and values
* @param key the key whose associated value is to be returned
* @return the string value associated with the specified key, or null if the key does not exist or the value is null
*/
static String getStringOption(Map<String, Object> options, String key) {
return getStringOption(options, key, null);
}

/**
* Retrieves a string value from the options map.
* If the key does not exist or the value is null, it returns the specified default value.
*
* @param options the map containing option keys and values
* @param key the key whose associated value is to be returned
* @param defValue the default value to return if the key does not exist or the value is null
* @return the string value associated with the specified key, or the default value if the key does not exist or the value is null
*/
static String getStringOption(Map<String, Object> options, String key, String defValue) {
return options.containsKey(key) && options.get(key) != null ? options.get(key).toString() : defValue;
}

/**
* Retrieves an integer value from the options map.
* If the key does not exist or the value is null, it returns the specified default value.
*
* @param options the map containing option keys and values
* @param key the key whose associated value is to be returned
* @param defValue the default value to return if the key does not exist or the value is null
* @return the integer value associated with the specified key, or the default value if the key does not exist or the value is null
* @throws NumberFormatException if the value cannot be parsed as an integer
*/
public static Integer getIntOption(Map<String, Object> options, String key, Integer defValue) {
return options.containsKey(key) && options.get(key) != null ? Integer.parseInt(options.get(key).toString()) : defValue;
}

/**
* Retrieves a boolean value from the options map.
* If the key does not exist or the value is null, it returns the specified default value.
*
* @param options the map containing option keys and values
* @param key the key whose associated value is to be returned
* @param defValue the default value to return if the key does not exist or the value is null
* @return the boolean value associated with the specified key, or the default value if the key does not exist or the value is null
*/
public static Boolean getBooleanOption(Map<String, Object> options, String key, Boolean defValue) {
return options.containsKey(key) && options.get(key) != null ? Boolean.parseBoolean(options.get(key).toString()) : defValue;
}


}
8 changes: 8 additions & 0 deletions src/main/java/edu/ohio/ais/rundeck/HttpDescription.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.dtolabs.rundeck.core.plugins.configuration.Describable;
import com.dtolabs.rundeck.core.plugins.configuration.Description;
import com.dtolabs.rundeck.core.plugins.configuration.PropertyScope;
import com.dtolabs.rundeck.core.plugins.configuration.StringRenderingConstants;
import com.dtolabs.rundeck.plugins.util.DescriptionBuilder;
import com.dtolabs.rundeck.plugins.util.PropertyBuilder;
Expand Down Expand Up @@ -165,6 +166,13 @@ public Description getDescription() {
.defaultValue("false")
.renderingOption(StringRenderingConstants.GROUP_NAME,"Print")
.build())
.property(PropertyBuilder.builder()
.title("System Proxy Settings")
.booleanType("useSystemProxySettings")
.description("Choose whether to use proxy settings set on the JVM.")
.defaultValue("false")
.scope(PropertyScope.Project)
.build())
.build();
}
}
26 changes: 19 additions & 7 deletions src/main/java/edu/ohio/ais/rundeck/HttpWorkflowNodeStepPlugin.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,20 @@
import com.dtolabs.rundeck.core.execution.ExecutionContext;
import com.dtolabs.rundeck.core.execution.proxy.ProxySecretBundleCreator;
import com.dtolabs.rundeck.core.execution.proxy.SecretBundle;
import com.dtolabs.rundeck.core.execution.utils.ResolverUtil;
import com.dtolabs.rundeck.core.execution.workflow.steps.StepException;
import com.dtolabs.rundeck.core.execution.workflow.steps.StepFailureReason;
import com.dtolabs.rundeck.core.execution.workflow.steps.node.NodeStepException;
import com.dtolabs.rundeck.core.plugins.Plugin;
import com.dtolabs.rundeck.core.plugins.configuration.Describable;
import com.dtolabs.rundeck.core.plugins.configuration.Description;
import com.dtolabs.rundeck.core.utils.IPropertyLookup;
import com.dtolabs.rundeck.plugins.PluginLogger;
import com.dtolabs.rundeck.plugins.ServiceNameConstants;
import com.dtolabs.rundeck.plugins.descriptions.PluginProperty;
import com.dtolabs.rundeck.plugins.step.NodeStepPlugin;
import com.dtolabs.rundeck.plugins.step.PluginStepContext;
import com.dtolabs.rundeck.plugins.util.DescriptionBuilder;
import edu.ohio.ais.rundeck.util.OAuthClient;
import edu.ohio.ais.rundeck.util.SecretBundleUtil;
import org.apache.http.HttpEntity;
Expand All @@ -25,6 +29,10 @@
import java.io.UnsupportedEncodingException;
import java.util.*;

import static edu.ohio.ais.rundeck.HttpBuilder.propertyResolver;
import static edu.ohio.ais.rundeck.HttpBuilder.getIntOption;
import static edu.ohio.ais.rundeck.HttpBuilder.getStringOption;

@Plugin(name = HttpWorkflowNodeStepPlugin.SERVICE_PROVIDER_NAME, service = ServiceNameConstants.WorkflowNodeStep)
public class HttpWorkflowNodeStepPlugin implements NodeStepPlugin, Describable, ProxySecretBundleCreator {
public static final String SERVICE_PROVIDER_NAME = "edu.ohio.ais.rundeck.HttpWorkflowNodeStepPlugin";
Expand All @@ -40,7 +48,6 @@ public class HttpWorkflowNodeStepPlugin implements NodeStepPlugin, Describable,
*/
public static final Integer DEFAULT_TIMEOUT = 30*1000;


/**
* Synchronized map of all existing OAuth clients. This is indexed by
* the Client ID and the token URL so that we can store and re-use access tokens.
Expand All @@ -58,13 +65,17 @@ public Description getDescription() {
public void executeNodeStep(PluginStepContext context, Map<String, Object> configuration, INodeEntry entry) throws NodeStepException {
PluginLogger log = context.getLogger();

// Parse out the options
String remoteUrl = configuration.containsKey("remoteUrl") ? configuration.get("remoteUrl").toString() : null;
String method = configuration.containsKey("method") ? configuration.get("method").toString() : null;
Description description = new HttpDescription(SERVICE_PROVIDER_NAME, "HTTP Request Node Step", "Performs an HTTP request with or without authentication (per node)").getDescription();
description.getProperties().forEach(prop->
propertyResolver("WorkflowNodeStep", prop.getName(), configuration, context, SERVICE_PROVIDER_NAME)
);

Integer timeout = configuration.containsKey("timeout") ? Integer.parseInt(configuration.get("timeout").toString()) : DEFAULT_TIMEOUT;
String headers = configuration.containsKey("headers") ? configuration.get("headers").toString() : null;
String body = configuration.containsKey("body") ? configuration.get("body").toString() : null;
// Parse out the options
String remoteUrl = getStringOption(configuration, "remoteUrl");
String method = getStringOption(configuration, "method");
Integer timeout =getIntOption(configuration,"timeout", DEFAULT_TIMEOUT);
String headers = getStringOption(configuration, "headers");
String body = getStringOption(configuration, "body");

log.log(5, "remoteUrl: " + remoteUrl);
log.log(5, "method: " + method);
Expand Down Expand Up @@ -146,4 +157,5 @@ public SecretBundle prepareSecretBundleWorkflowNodeStep(ExecutionContext context
public List<String> listSecretsPathWorkflowNodeStep(ExecutionContext context, INodeEntry node, Map<String, Object> configuration) {
return SecretBundleUtil.getListSecrets(configuration);
}

}
19 changes: 14 additions & 5 deletions src/main/java/edu/ohio/ais/rundeck/HttpWorkflowStepPlugin.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@
import java.io.*;
import java.util.*;

import static edu.ohio.ais.rundeck.HttpBuilder.propertyResolver;
import static edu.ohio.ais.rundeck.HttpBuilder.getIntOption;
import static edu.ohio.ais.rundeck.HttpBuilder.getStringOption;


/**
* Main implementation of the plugin. This will handle fetching
Expand Down Expand Up @@ -69,12 +73,17 @@ public Description getDescription() {
public void executeStep(PluginStepContext pluginStepContext, Map<String, Object> options) throws StepException {
PluginLogger log = pluginStepContext.getLogger();

Description description = new HttpDescription(SERVICE_PROVIDER_NAME, "HTTP Request Node Step", "Performs an HTTP request with or without authentication (per node)").getDescription();
description.getProperties().forEach(prop->
propertyResolver("WorkflowStep",prop.getName(), options, pluginStepContext, SERVICE_PROVIDER_NAME)
);

// Parse out the options
String remoteUrl = options.containsKey("remoteUrl") ? options.get("remoteUrl").toString() : null;
String method = options.containsKey("method") ? options.get("method").toString() : null;
Integer timeout = options.containsKey("timeout") ? Integer.parseInt(options.get("timeout").toString()) : DEFAULT_TIMEOUT;
String headers = options.containsKey("headers") ? options.get("headers").toString() : null;
String body = options.containsKey("body") ? options.get("body").toString() : null;
String remoteUrl = getStringOption(options, "remoteUrl");
String method = getStringOption(options, "method");
Integer timeout =getIntOption(options,"timeout", DEFAULT_TIMEOUT);
String headers = getStringOption(options, "headers");
String body = getStringOption(options, "body");

if(remoteUrl == null || method == null) {
throw new StepException("Remote URL and Method are required.", StepFailureReason.ConfigurationFailure);
Expand Down
Loading
Loading