From b884b657fd7d0d5a914198f496ed8fcc5f61f6f8 Mon Sep 17 00:00:00 2001 From: Luis Toledo Date: Wed, 31 May 2023 19:30:39 -0400 Subject: [PATCH 1/6] use http-step plugin as dependency add circle ci update dependencies --- .circleci/config.yml | 37 ++++ build.gradle | 48 +++-- gradle/wrapper/gradle-wrapper.properties | 2 +- .../com/rundeck/plugin/HttpLogger.groovy | 28 +++ .../plugin/HttpNotificationPlugin.groovy | 189 +++++------------ .../rundeck/plugin/oauth/OAuthClient.groovy | 196 ------------------ 6 files changed, 147 insertions(+), 353 deletions(-) create mode 100644 .circleci/config.yml create mode 100644 src/main/groovy/com/rundeck/plugin/HttpLogger.groovy delete mode 100644 src/main/groovy/com/rundeck/plugin/oauth/OAuthClient.groovy diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 0000000..aac0d17 --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,37 @@ +# Java Gradle CircleCI 2.0 configuration file +# See: https://circleci.com/docs/2.0/language-java/ +version: 2.1 + +executors: + job-executor: + docker: + - image: circleci/openjdk:11-jdk + working_directory: ~/repo + + environment: + # Customize the JVM maximum heap limit + JVM_OPTS: -Xmx3200m + TERM: dumb + +jobs: + build: + executor: job-executor + steps: + - checkout + - restore_cache: + keys: + - v1-dependencies-{{ checksum "build.gradle" }} + # fallback to using the latest cache if no exact match is found + - v1-dependencies- + + - run: ./gradlew build + - save_cache: + paths: + - ~/.gradle + key: v1-dependencies-{{ checksum "build.gradle" }} + +workflows: + version: 2 + build_and_test: + jobs: + - build diff --git a/build.gradle b/build.gradle index 1a7cc0a..3d06289 100644 --- a/build.gradle +++ b/build.gradle @@ -1,3 +1,5 @@ +import org.gradle.api.tasks.testing.Test + buildscript { repositories { mavenCentral() @@ -6,7 +8,7 @@ buildscript { } plugins { - id 'pl.allegro.tech.build.axion-release' version '1.10.0' + id 'pl.allegro.tech.build.axion-release' version '1.13.4' } group 'com.rundeck.plugin' @@ -45,35 +47,36 @@ configurations{ pluginLibs //declare compile to extend from pluginLibs so it inherits the dependencies - compile{ + implementation{ extendsFrom pluginLibs } } repositories { mavenCentral() + maven { url 'https://jitpack.io' } + } dependencies { - compile group: 'org.rundeck', name: 'rundeck-core', version: '2.10.1' - compile 'org.slf4j:slf4j-api:1.7.30' - pluginLibs (group: 'org.codehaus.groovy.modules.http-builder', name: 'http-builder', version: '0.7.1') { - exclude (group: "commons-collections", module: "commons-collections") - exclude (group: "commons-beanutils", module: "commons-beanutils") - } - // bump xerces version brought by http-builder affected by CVE-2012-0881 - pluginLibs("xerces:xercesImpl:2.12.0") - pluginLibs group: 'com.google.code.gson', name: 'gson', version: '2.8.2' - pluginLibs group: 'com.esotericsoftware.yamlbeans', name: 'yamlbeans', version: '1.13' - compile 'org.codehaus.groovy:groovy-all:2.3.11' - testCompile group: 'junit', name: 'junit', version: '4.12' + implementation group: 'org.rundeck', name: 'rundeck-core', version: '4.3.+' + + pluginLibs 'com.github.rundeck-plugins:http-step:RUN-1770-SNAPSHOT' + pluginLibs group: 'org.apache.httpcomponents', name: 'httpclient', version: '4.5.14' + pluginLibs group: 'com.google.code.gson', name: 'gson', version:'2.10.1' + pluginLibs group: 'com.esotericsoftware.yamlbeans', name: 'yamlbeans', version:'1.13' + implementation 'org.slf4j:slf4j-api:2.0.7' - testCompile "org.codehaus.groovy:groovy-all:2.3.7" - testCompile "org.spockframework:spock-core:0.7-groovy-2.0" - testCompile "cglib:cglib-nodep:2.2.2" - testCompile 'org.objenesis:objenesis:1.4' + implementation 'org.codehaus.groovy:groovy-all:3.0.17' + + testImplementation group: 'junit', name: 'junit', version: '4.12' + + testImplementation "org.codehaus.groovy:groovy-all:3.0.17" + testImplementation "org.spockframework:spock-core:2.0-groovy-3.0" + testImplementation "cglib:cglib-nodep:2.2.2" + testImplementation 'org.objenesis:objenesis:1.4' } @@ -110,7 +113,10 @@ jar { //set jar task to depend on copyToLib jar.dependsOn(copyToLib) -task wrapper(type: Wrapper) { - gradleVersion = '3.3' - distributionUrl = "https://services.gradle.org/distributions/gradle-$gradleVersion-all.zip" +wrapper { + gradleVersion = '4.4.1' } + +tasks.withType(Test) { + useJUnitPlatform() +} \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index ffb6ed0..5770daa 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-all.zip diff --git a/src/main/groovy/com/rundeck/plugin/HttpLogger.groovy b/src/main/groovy/com/rundeck/plugin/HttpLogger.groovy new file mode 100644 index 0000000..8ab75d5 --- /dev/null +++ b/src/main/groovy/com/rundeck/plugin/HttpLogger.groovy @@ -0,0 +1,28 @@ +package com.rundeck.plugin + +import com.dtolabs.rundeck.plugins.PluginLogger +import org.slf4j.Logger + +class HttpLogger implements PluginLogger{ + + Logger log + + HttpLogger(Logger log) { + this.log = log + } + + @Override + void log(int level, String message) { + log.info(message) + } + + @Override + void log(int level, String message, Map eventMeta) { + log.info(message) + } + + @Override + void event(String eventType, String message, Map eventMeta) { + + } +} diff --git a/src/main/groovy/com/rundeck/plugin/HttpNotificationPlugin.groovy b/src/main/groovy/com/rundeck/plugin/HttpNotificationPlugin.groovy index d52bf39..9dac27b 100644 --- a/src/main/groovy/com/rundeck/plugin/HttpNotificationPlugin.groovy +++ b/src/main/groovy/com/rundeck/plugin/HttpNotificationPlugin.groovy @@ -1,5 +1,7 @@ package com.rundeck.plugin +import com.dtolabs.rundeck.core.execution.workflow.steps.StepException +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 @@ -9,14 +11,14 @@ import com.dtolabs.rundeck.plugins.descriptions.PluginDescription import com.dtolabs.rundeck.plugins.notification.NotificationPlugin import com.dtolabs.rundeck.plugins.util.DescriptionBuilder import com.dtolabs.rundeck.plugins.util.PropertyBuilder -import com.esotericsoftware.yamlbeans.YamlReader -import com.google.gson.Gson -import com.rundeck.plugin.oauth.OAuthClient -import groovyx.net.http.ContentType -import groovyx.net.http.HTTPBuilder -import groovyx.net.http.Method +import org.apache.http.HttpEntity +import org.apache.http.client.config.RequestConfig +import org.apache.http.client.methods.RequestBuilder +import org.apache.http.entity.ByteArrayEntity import org.slf4j.Logger import org.slf4j.LoggerFactory +import edu.ohio.ais.rundeck.HttpBuilder +import edu.ohio.ais.rundeck.util.OAuthClient /** * Created by rundeck on 12/27/17. @@ -220,164 +222,81 @@ class HttpNotificationPlugin implements NotificationPlugin, Describable { Boolean print = Boolean.valueOf(config.get(HTTP_PRINT)) String printFile = config.containsKey(HTTP_PRINT_FILE) ? config.get(HTTP_PRINT_FILE).toString() : null - if(remoteUrl == null || method == null) { throw new Exception("Remote URL and Method are required."); } - def requestHeaders = [:] - def requestBody = parseBody(bodyStr) - - def http = new HTTPBuilder() - if(ignoreSSL){ - http.ignoreSSLIssues() - } + HttpLogger logger = new HttpLogger(log) - ContentType contentType = getContentType(contentTypeStr) - String authentication = getAuthentication(config) + HttpBuilder builder = new HttpBuilder() + builder.setLog(logger) + builder.setOauthClients(oauthClients) - if(authentication!=null){ - requestHeaders."Authorization" = authentication - } + // Setup the request and process it. + RequestBuilder request = RequestBuilder.create(method) + .setUri(remoteUrl) + .setConfig(RequestConfig.custom() + .setConnectionRequestTimeout(timeout) + .setConnectTimeout(timeout) + .setSocketTimeout(timeout) + .build()); - requestHeaders."trigger" = trigger - requestHeaders.putAll(parseHeaders(headersStr)) + String authHeader = getAuthentication(config) - if(timeout>0){ - http.getClient().getParams().setParameter("http.connection.timeout", new Integer(timeout)) - http.getClient().getParams().setParameter("http.socket.timeout", new Integer(timeout)) + if(authHeader != null) { + request.setHeader("Authorization", authHeader); } - if(proxy){ - String proxyIp = config.containsKey(HTTP_PROXY_IP) ? config.get(HTTP_PROXY_IP).toString() : null - Integer proxyPort = config.containsKey(HTTP_PROXY_PORT) ? Integer.parseInt(config.get(HTTP_PROXY_PORT).toString()) : null - http.setProxy(proxyIp, proxyPort, 'http') + //add custom headers, it could be json or yml + if(headersStr !=null){ + builder.setHeaders(headersStr, request); } - def result = false - - try{ - result = http.request( remoteUrl, Method.valueOf(method),contentType) { req -> - - requestHeaders.each { key, value -> - headers."${key}" = "${value}" - } - - if(requestBody!=null){ - body = requestBody - } - - response.success = { resp, reader -> - println "--------------------------------------------" - println "Got response: ${resp.statusLine}" - println "Content-Type: ${resp.headers.'Content-Type'}" - - //print the response content - if( print) { - println "Response: ${reader.toString()}" - File file = new File(printFile); - file.write reader.toString() - } - - return true - } - - response.failure = { resp, reader -> - println "--------------------------------------------" - println "Unexpected failure: ${resp.statusLine}" - - //print the response content - if( print) { - println "Response: ${reader.toString()}" - File file = new File(printFile); - file.write reader.toString() - } - return false - } - - response.'404' = { - println "--------------------------------------------" - println 'Error 404, Not found' - - return false - } - } - }catch(Exception e){ - println "--------------------------------------------" - println "Error calling the endpoint: ${e.getMessage()}" - result=false + if(contentTypeStr){ + request.setHeader("Content-Type", contentTypeStr) } + request.setHeader("trigger", trigger) - return result - } - - Map parseHeaders(String headers){ - Map requestHeaders = new HashMap<>(); - - //checking json - Gson gson = new Gson(); - - - try { - requestHeaders = (Map) gson.fromJson(headers, requestHeaders.getClass()); - } catch (Exception e) { - requestHeaders = null; - } - - //checking yml - if(requestHeaders == null) { + //send body + if(bodyStr !=null){ + HttpEntity entity = null; try { - YamlReader reader = new YamlReader(headers); - requestHeaders = (Map) reader.read(); - } catch (Exception e) { - requestHeaders = null; + entity = new ByteArrayEntity(bodyStr.getBytes("UTF-8")); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); } + request.setEntity(entity); } - if(requestHeaders == null){ - requestHeaders = new HashMap<>(); + Map configuration = [ + "sslVerify":ignoreSSL, + "proxySettings": proxy, + "printResponseToFile": print, + ] + if(proxy){ + String proxyIp = config.containsKey(HTTP_PROXY_IP) ? config.get(HTTP_PROXY_IP).toString() : null + Integer proxyPort = config.containsKey(HTTP_PROXY_PORT) ? Integer.parseInt(config.get(HTTP_PROXY_PORT).toString()) : null + configuration.put("proxyIP", proxyIp) + configuration.put("proxyPort", proxyPort) } - return requestHeaders - - } - - def parseBody(String body){ - - Map bodyResponse = new HashMap<>(); - - //checking json - Gson gson = new Gson(); - try { - bodyResponse = (Map) gson.fromJson(body, bodyResponse.getClass()); - return bodyResponse - } catch (Exception e) { - bodyResponse = null; + if(print){ + configuration.put("file", printFile) } - if(bodyResponse == null){ - return body + try { + builder.doRequest(configuration, request.build(), 1) + } catch (StepException e) { + log.error(e.getMessage()) + return false } - } + return true - def getContentType(String type){ - ContentType contentType = null - switch (type) { - case "application/json": contentType=ContentType.JSON; break; - case "application/xml": contentType=ContentType.XML; break; - case "text/xml": contentType=ContentType.XML; break; - case "text/html": contentType=ContentType.HTML; break; - case "application/x-www-form-urlencoded": contentType=ContentType.URLENC; break; - default: contentType=ContentType.TEXT - } - - return contentType } - def getAuthentication(Map config){ String authentication = config.containsKey(HTTP_AUTHENTICATION) ? config.get(HTTP_AUTHENTICATION).toString() : AUTH_NONE diff --git a/src/main/groovy/com/rundeck/plugin/oauth/OAuthClient.groovy b/src/main/groovy/com/rundeck/plugin/oauth/OAuthClient.groovy deleted file mode 100644 index a17cdcb..0000000 --- a/src/main/groovy/com/rundeck/plugin/oauth/OAuthClient.groovy +++ /dev/null @@ -1,196 +0,0 @@ -package com.rundeck.plugin.oauth - -import com.fasterxml.jackson.databind.JsonNode -import com.fasterxml.jackson.databind.ObjectMapper -import groovyx.net.http.HTTPBuilder -import groovyx.net.http.HttpResponseException -import groovyx.net.http.Method -import org.apache.http.util.EntityUtils -import org.slf4j.Logger -import org.slf4j.LoggerFactory - -/** - * Created by rundeck on 12/27/17. - */ -class OAuthClient { - private static final Logger log = LoggerFactory.getLogger(OAuthClient.class); - - - public static final String JSON_CONTENT_TYPE = "application/json"; - public static final String FORM_CONTENT_TYPE = "application/x-www-form-urlencoded"; - - public static final String FIELD_GRANT_TYPE = "grant_type"; - public static final String FIELD_ACCESS_TOKEN = "access_token"; - - - public static final Integer STATUS_SUCCESS = 200; - public static final Integer STATUS_AUTHORIZATION_REQUIRED = 401; - - protected ObjectMapper jsonParser = new ObjectMapper(); - - enum GrantType { - CLIENT_CREDENTIALS - } - - static class OAuthException extends Exception { - OAuthException(String message) { - super(message); - } - } - - - String clientId; - String clientSecret; - GrantType grantType; - String tokenEndpoint; - String validateEndpoint; - String accessToken; - - - void doTokenRequest() throws OAuthException, IOException { - this.accessToken = null; - - def requestHeaders = [:] - requestHeaders."Authorization" = "Basic " + com.dtolabs.rundeck.core.utils.Base64.encode(this.clientId + ":" + this.clientSecret) - requestHeaders."Accept" = JSON_CONTENT_TYPE - requestHeaders."Content-Type" = FORM_CONTENT_TYPE - - def http = new HTTPBuilder(this.tokenEndpoint) - - this.accessToken = http.request(Method.POST, FORM_CONTENT_TYPE) { req -> - - // add possible headers - requestHeaders.each { key, value -> - headers."${key}" = "${value}" - } - - body = ["${this.FIELD_GRANT_TYPE}": this.grantType.name().toLowerCase()] - - response.success = { resp, json -> - JsonNode data = jsonParser.readTree(EntityUtils.toString(json)); - String token = data.get(FIELD_ACCESS_TOKEN).asText(); - return token - } - - response.failure = { resp -> - "Unexpected failure: ${resp.statusLine}" - return null - } - - response.'404' = { - println 'Not found' - return null - } - } - - if(this.accessToken == null) { - throw new Exception("Error getting the token"); - } - - this.doTokenValidate(true); - } - - - void doTokenValidate() throws HttpResponseException, IOException, OAuthException { - this.doTokenValidate(false); - } - - - void doTokenValidate(Boolean newToken) throws HttpResponseException, IOException, OAuthException { - if(this.accessToken == null) { - this.doTokenRequest(); - } - - if(this.validateEndpoint != null) { - def http = new HTTPBuilder(this.tokenEndpoint) - - def requestHeaders = [:] - requestHeaders."Authorization" = "Bearer " + this.accessToken - requestHeaders."Accept" = JSON_CONTENT_TYPE - - String clientId = http.request(Method.GET,JSON_CONTENT_TYPE) { req -> - - // add possible headers - requestHeaders.each { key, value -> - println "${key} - ${value}" - headers."${key}" = "${value}" - } - - body = ["${this.FIELD_GRANT_TYPE}": this.grantType.name().toLowerCase()] - - response.success = { resp, json -> - - if(resp.statusLine==STATUS_SUCCESS){ - String clientId = json.client - return clientId - }else if (resp.statusLine == STATUS_AUTHORIZATION_REQUIRED) { - return "newAuth" - }else { - return null - } - } - - response.failure = { resp -> - println "Unexpected failure: ${resp.statusLine}" - return null - } - - response.'404' = { - println 'Not found' - return null - } - } - - if(clientId==null){ - throw new OAuthException("It couldn't get the node"); - } - - if(clientId.equals("newAuth")){ - this.accessToken = null; - if(newToken) { - throw new OAuthException("Newly acquired token is still not valid."); - } else { - doTokenRequest(); - } - }else{ - if (!this.clientId.equals(clientId)) { - throw new OAuthException("Token received for a client other than us."); - } - } - - } else { - log.debug("No validate endpoint exists, skipping validation."); - } - } - - OAuthClient(GrantType grantType) { - this.grantType = grantType; - } - - - void setCredentials(String clientId, String clientSecret) { - log.trace("Setting credentials to " + this.clientId + ":" + this.clientSecret); - - this.clientId = clientId; - this.clientSecret = clientSecret; - } - - void setTokenEndpoint(String tokenEndpoint) { - this.tokenEndpoint = tokenEndpoint; - } - - void setValidateEndpoint(String validateEndpoint) { - this.validateEndpoint = validateEndpoint; - } - - - String getAccessToken() throws OAuthException, Exception { - if(this.accessToken == null) { - this.doTokenValidate(); - } - - return this.accessToken; - } - -} - From 15c770b2b6c2f51c0edf2760d053be0e6868fde4 Mon Sep 17 00:00:00 2001 From: Luis Toledo Date: Wed, 31 May 2023 19:36:09 -0400 Subject: [PATCH 2/6] cleaning --- build.gradle | 4 ---- 1 file changed, 4 deletions(-) diff --git a/build.gradle b/build.gradle index 3d06289..5eb9d49 100644 --- a/build.gradle +++ b/build.gradle @@ -113,10 +113,6 @@ jar { //set jar task to depend on copyToLib jar.dependsOn(copyToLib) -wrapper { - gradleVersion = '4.4.1' -} - tasks.withType(Test) { useJUnitPlatform() } \ No newline at end of file From 2cc28205b5f022b41a50ff365a3a61320ab89198 Mon Sep 17 00:00:00 2001 From: Luis Toledo Date: Wed, 31 May 2023 19:46:29 -0400 Subject: [PATCH 3/6] storage test result --- .circleci/config.yml | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index aac0d17..34675fa 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -13,6 +13,22 @@ executors: JVM_OPTS: -Xmx3200m TERM: dumb +commands: + collect-gradle-tests: + description: Collect JUNIT test reports from entire project + steps: + - run: + name: Save test results + command: | + mkdir -p ~/test-results/junit/ + find . -type f -regex ".*/build/test-results/.*xml" -exec cp {} ~/test-results/junit/ \; + find . -type f -regex ".*/build/test-results/.*xml" | zip ~/test-results/junit/junit.zip -@; + when: always + - store_test_results: + path: ~/test-results + - store_artifacts: + path: ~/test-results/junit + jobs: build: executor: job-executor @@ -25,6 +41,7 @@ jobs: - v1-dependencies- - run: ./gradlew build + - collect-gradle-tests - save_cache: paths: - ~/.gradle From 3e1ef2260c74d7d9605f90735219aee9b600cfcc Mon Sep 17 00:00:00 2001 From: Luis Toledo Date: Thu, 1 Jun 2023 10:14:57 -0400 Subject: [PATCH 4/6] fix dependencies issues add CompileStatic --- build.gradle | 19 ++-- .../com/rundeck/plugin/HttpLogger.groovy | 31 +++++- .../plugin/HttpNotificationPlugin.groovy | 104 +++++++++--------- 3 files changed, 89 insertions(+), 65 deletions(-) diff --git a/build.gradle b/build.gradle index 5eb9d49..c4b8342 100644 --- a/build.gradle +++ b/build.gradle @@ -1,4 +1,3 @@ -import org.gradle.api.tasks.testing.Test buildscript { repositories { @@ -54,24 +53,22 @@ configurations{ repositories { mavenCentral() + mavenLocal() maven { url 'https://jitpack.io' } } dependencies { + implementation 'org.rundeck:rundeck-core:4.13.0-20230515' - implementation group: 'org.rundeck', name: 'rundeck-core', version: '4.3.+' - - pluginLibs 'com.github.rundeck-plugins:http-step:RUN-1770-SNAPSHOT' - pluginLibs group: 'org.apache.httpcomponents', name: 'httpclient', version: '4.5.14' - pluginLibs group: 'com.google.code.gson', name: 'gson', version:'2.10.1' - pluginLibs group: 'com.esotericsoftware.yamlbeans', name: 'yamlbeans', version:'1.13' - - implementation 'org.slf4j:slf4j-api:2.0.7' + pluginLibs ('com.github.rundeck-plugins:http-step:RUN-1770-SNAPSHOT'){ + exclude group: 'org.rundeck', module: 'rundeck-core' + } - implementation 'org.codehaus.groovy:groovy-all:3.0.17' + implementation group: 'org.apache.httpcomponents', name: 'httpclient', version: '4.5.14' + implementation 'org.codehaus.groovy:groovy-all:3.0.9' - testImplementation group: 'junit', name: 'junit', version: '4.12' + testImplementation group: 'junit', name: 'junit', version: '4.13.1' testImplementation "org.codehaus.groovy:groovy-all:3.0.17" testImplementation "org.spockframework:spock-core:2.0-groovy-3.0" diff --git a/src/main/groovy/com/rundeck/plugin/HttpLogger.groovy b/src/main/groovy/com/rundeck/plugin/HttpLogger.groovy index 8ab75d5..57afe1c 100644 --- a/src/main/groovy/com/rundeck/plugin/HttpLogger.groovy +++ b/src/main/groovy/com/rundeck/plugin/HttpLogger.groovy @@ -1,8 +1,10 @@ package com.rundeck.plugin import com.dtolabs.rundeck.plugins.PluginLogger +import groovy.transform.CompileStatic import org.slf4j.Logger +@CompileStatic class HttpLogger implements PluginLogger{ Logger log @@ -13,16 +15,41 @@ class HttpLogger implements PluginLogger{ @Override void log(int level, String message) { - log.info(message) + printMessage(level, message) + } @Override void log(int level, String message, Map eventMeta) { - log.info(message) + printMessage(level, message) } @Override void event(String eventType, String message, Map eventMeta) { } + + void printMessage(int level, String message){ + switch (level){ + case 0: + log.error(message) + break + case 1: + log.warn(message) + break + case 2: + log.info(message) + break + case 3: + log.info(message) + break + case 4: + log.debug(message) + break + default: + log.info(message) + break + } + } + } diff --git a/src/main/groovy/com/rundeck/plugin/HttpNotificationPlugin.groovy b/src/main/groovy/com/rundeck/plugin/HttpNotificationPlugin.groovy index 9dac27b..3e3aa9e 100644 --- a/src/main/groovy/com/rundeck/plugin/HttpNotificationPlugin.groovy +++ b/src/main/groovy/com/rundeck/plugin/HttpNotificationPlugin.groovy @@ -1,7 +1,6 @@ package com.rundeck.plugin import com.dtolabs.rundeck.core.execution.workflow.steps.StepException -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 @@ -11,6 +10,7 @@ import com.dtolabs.rundeck.plugins.descriptions.PluginDescription import com.dtolabs.rundeck.plugins.notification.NotificationPlugin import com.dtolabs.rundeck.plugins.util.DescriptionBuilder import com.dtolabs.rundeck.plugins.util.PropertyBuilder +import groovy.transform.CompileStatic import org.apache.http.HttpEntity import org.apache.http.client.config.RequestConfig import org.apache.http.client.methods.RequestBuilder @@ -23,8 +23,9 @@ import edu.ohio.ais.rundeck.util.OAuthClient /** * Created by rundeck on 12/27/17. */ -@Plugin(service= ServiceNameConstants.Notification, name=HttpNotificationPlugin.SERVICE_PROVIDER_NAME) -@PluginDescription(title=HttpNotificationPlugin.SERVICE_TITLE, description=HttpNotificationPlugin.SERVICE_PROVIDER_DESCRIPTION) +@Plugin(service= ServiceNameConstants.Notification, name= SERVICE_PROVIDER_NAME) +@PluginDescription(title= SERVICE_TITLE, description= SERVICE_PROVIDER_DESCRIPTION) +@CompileStatic class HttpNotificationPlugin implements NotificationPlugin, Describable { private static final Logger log = LoggerFactory.getLogger(HttpNotificationPlugin.class); @@ -217,9 +218,9 @@ class HttpNotificationPlugin implements NotificationPlugin, Describable { Integer timeout = config.containsKey(HTTP_TIMEOUT) ? Integer.parseInt(config.get(HTTP_TIMEOUT).toString()) : DEFAULT_TIMEOUT String headersStr = config.containsKey(HTTP_HEADERS) ? config.get(HTTP_HEADERS).toString() : null String bodyStr = config.containsKey(HTTP_BODY) ? config.get(HTTP_BODY).toString() : null - Boolean ignoreSSL = Boolean.valueOf(config.get(HTTP_NO_SSL_VERIFICATION)) - Boolean proxy = Boolean.valueOf(config.get(HTTP_PROXY_ENABLE)) - Boolean print = Boolean.valueOf(config.get(HTTP_PRINT)) + Boolean ignoreSSL = Boolean.valueOf((String)config.get(HTTP_NO_SSL_VERIFICATION)) + Boolean proxy = Boolean.valueOf((String)config.get(HTTP_PROXY_ENABLE)) + Boolean print = Boolean.valueOf((String)config.get(HTTP_PRINT)) String printFile = config.containsKey(HTTP_PRINT_FILE) ? config.get(HTTP_PRINT_FILE).toString() : null if(remoteUrl == null || method == null) { @@ -239,17 +240,17 @@ class HttpNotificationPlugin implements NotificationPlugin, Describable { .setConnectionRequestTimeout(timeout) .setConnectTimeout(timeout) .setSocketTimeout(timeout) - .build()); + .build()) - String authHeader = getAuthentication(config) + String authHeader = getAuthentication(config, logger) if(authHeader != null) { - request.setHeader("Authorization", authHeader); + request.setHeader("Authorization", authHeader) } //add custom headers, it could be json or yml if(headersStr !=null){ - builder.setHeaders(headersStr, request); + builder.setHeaders(headersStr, request) } if(contentTypeStr){ @@ -260,20 +261,20 @@ class HttpNotificationPlugin implements NotificationPlugin, Describable { //send body if(bodyStr !=null){ - HttpEntity entity = null; + HttpEntity entity = null try { - entity = new ByteArrayEntity(bodyStr.getBytes("UTF-8")); + entity = new ByteArrayEntity(bodyStr.getBytes("UTF-8")) } catch (UnsupportedEncodingException e) { - e.printStackTrace(); + e.printStackTrace() } - request.setEntity(entity); + request.setEntity(entity) } - Map configuration = [ - "sslVerify":ignoreSSL, - "proxySettings": proxy, - "printResponseToFile": print, - ] + Map configuration = new HashMap<>() + + configuration.put("sslVerify", !ignoreSSL) + configuration.put("proxySettings", proxy) + configuration.put("printResponseToFile", print) if(proxy){ String proxyIp = config.containsKey(HTTP_PROXY_IP) ? config.get(HTTP_PROXY_IP).toString() : null @@ -286,18 +287,18 @@ class HttpNotificationPlugin implements NotificationPlugin, Describable { configuration.put("file", printFile) } - try { - builder.doRequest(configuration, request.build(), 1) - } catch (StepException e) { - log.error(e.getMessage()) - return false - } + try { + builder.doRequest(configuration, request.build(), 1) + } catch (StepException e) { + log.error(e.getMessage()) + return false + } - return true + return true } - def getAuthentication(Map config){ + def getAuthentication(Map config, HttpLogger logger){ String authentication = config.containsKey(HTTP_AUTHENTICATION) ? config.get(HTTP_AUTHENTICATION).toString() : AUTH_NONE String password = config.containsKey(HTTP_PASSWORD) ? config.get(HTTP_PASSWORD).toString() : AUTH_NONE @@ -311,62 +312,61 @@ class HttpNotificationPlugin implements NotificationPlugin, Describable { throw new Exception("Username and password not provided for BASIC Authentication"); } - authHeader = username + ":" + password; + authHeader = username + ":" + password //As per RFC2617 the Basic Authentication standard has to send the credentials Base64 encoded. - authHeader = "Basic " + com.dtolabs.rundeck.core.utils.Base64.encode(authHeader); + 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 = config.containsKey(HTTP_AUTHTOKEN_ENDPOINT) ? config.get(HTTP_AUTHTOKEN_ENDPOINT).toString() : null; - String validateEndpoint = config.containsKey(HTTP_AUTHTVALIDATE_ENDPOINT) ? config.get(HTTP_AUTHTVALIDATE_ENDPOINT).toString() : null; - String clientId = config.containsKey(HTTP_USERNAME) ? config.get(HTTP_USERNAME).toString() : null; - String clientSecret = password; + String tokenEndpoint = config.containsKey(HTTP_AUTHTOKEN_ENDPOINT) ? config.get(HTTP_AUTHTOKEN_ENDPOINT).toString() : null + String validateEndpoint = config.containsKey(HTTP_AUTHTVALIDATE_ENDPOINT) ? config.get(HTTP_AUTHTVALIDATE_ENDPOINT).toString() : null + String clientId = config.containsKey(HTTP_USERNAME) ? config.get(HTTP_USERNAME).toString() : null + String clientSecret = password if(tokenEndpoint == null) { - throw new Exception("Token endpoint not provided for OAuth 2.0 Authentication."); + throw new Exception("Token endpoint not provided for OAuth 2.0 Authentication.") } - String clientKey = clientId + "@" + tokenEndpoint; - String accessToken; + String clientKey = clientId + "@" + tokenEndpoint + String accessToken // Another thread may be trying to do the same thing. synchronized(this.oauthClients) { - OAuthClient client; + OAuthClient client if(this.oauthClients.containsKey(clientKey)) { // Update the existing client with our options if it exists. // We do this so that changes to configuration will always // update clients on next run. - log.trace("Found existing OAuth client with key " + clientKey); - client = this.oauthClients.get(clientKey); - client.setCredentials(clientId, clientSecret); - client.setValidateEndpoint(validateEndpoint); + log.trace("Found existing OAuth client with key " + clientKey) + client = this.oauthClients.get(clientKey) + client.setCredentials(clientId, clientSecret) + client.setValidateEndpoint(validateEndpoint) } else { // Create a brand new client - log.trace("Creating new OAuth client with key " + clientKey); - client = new OAuthClient(OAuthClient.GrantType.CLIENT_CREDENTIALS); - client.setCredentials(clientId, clientSecret); - client.setTokenEndpoint(tokenEndpoint); - client.setValidateEndpoint(validateEndpoint); + log.trace("Creating new OAuth client with key " + clientKey) + client = new OAuthClient(OAuthClient.GrantType.CLIENT_CREDENTIALS, logger ) + client.setCredentials(clientId, clientSecret) + client.setTokenEndpoint(tokenEndpoint) + client.setValidateEndpoint(validateEndpoint) } // Grab the access token try { - log.trace("Attempting to fetch access token..."); - accessToken = client.getAccessToken(); + log.trace("Attempting to fetch access token...") + accessToken = client.getAccessToken() } catch(Exception ex) { - Exception se = new Exception("Error obtaining OAuth Access Token: " + ex.getMessage()); + Exception se = new Exception("Error obtaining OAuth Access Token: " + ex.getMessage()) se.initCause(ex); throw se; } - this.oauthClients.put(clientKey, client); + this.oauthClients.put(clientKey, client) } - authHeader = "Bearer " + accessToken; + authHeader = "Bearer " + accessToken } return authHeader } - } From 7e9e4fdb0ad01c77a2e0cacd23406e6568e049d7 Mon Sep 17 00:00:00 2001 From: Luis Toledo Date: Thu, 1 Jun 2023 11:38:29 -0400 Subject: [PATCH 5/6] use github actions to build and release --- .circleci/config.yml | 54 ----------------------------------- .github/workflows/gradle.yml | 34 ++++++++++++++++++++++ .github/workflows/release.yml | 46 +++++++++++++++++++++++++++++ build.gradle | 2 +- 4 files changed, 81 insertions(+), 55 deletions(-) delete mode 100644 .circleci/config.yml create mode 100644 .github/workflows/gradle.yml create mode 100644 .github/workflows/release.yml diff --git a/.circleci/config.yml b/.circleci/config.yml deleted file mode 100644 index 34675fa..0000000 --- a/.circleci/config.yml +++ /dev/null @@ -1,54 +0,0 @@ -# Java Gradle CircleCI 2.0 configuration file -# See: https://circleci.com/docs/2.0/language-java/ -version: 2.1 - -executors: - job-executor: - docker: - - image: circleci/openjdk:11-jdk - working_directory: ~/repo - - environment: - # Customize the JVM maximum heap limit - JVM_OPTS: -Xmx3200m - TERM: dumb - -commands: - collect-gradle-tests: - description: Collect JUNIT test reports from entire project - steps: - - run: - name: Save test results - command: | - mkdir -p ~/test-results/junit/ - find . -type f -regex ".*/build/test-results/.*xml" -exec cp {} ~/test-results/junit/ \; - find . -type f -regex ".*/build/test-results/.*xml" | zip ~/test-results/junit/junit.zip -@; - when: always - - store_test_results: - path: ~/test-results - - store_artifacts: - path: ~/test-results/junit - -jobs: - build: - executor: job-executor - steps: - - checkout - - restore_cache: - keys: - - v1-dependencies-{{ checksum "build.gradle" }} - # fallback to using the latest cache if no exact match is found - - v1-dependencies- - - - run: ./gradlew build - - collect-gradle-tests - - save_cache: - paths: - - ~/.gradle - key: v1-dependencies-{{ checksum "build.gradle" }} - -workflows: - version: 2 - build_and_test: - jobs: - - build diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml new file mode 100644 index 0000000..6ab0ded --- /dev/null +++ b/.github/workflows/gradle.yml @@ -0,0 +1,34 @@ +name: Java CI + +on: [push] + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + with: + fetch-depth: 0 + - name: Get Fetch Tags + run: git -c protocol.version=2 fetch --tags --progress --no-recurse-submodules origin + if: "!contains(github.ref, 'refs/tags')" + - name: Set up JDK 1.8 + uses: actions/setup-java@v1 + with: + java-version: 1.8 + - name: Grant execute permission for gradlew + run: chmod +x gradlew + - name: Build with Gradle + run: ./gradlew build + - name: Get Release Version + id: get_version + run: VERSION=$(./gradlew currentVersion -q -Prelease.quiet) && echo ::set-output name=VERSION::$VERSION + - name: Upload plugin jar + uses: actions/upload-artifact@v1.0.0 + with: + # Artifact name + name: Grails-Plugin-${{ steps.get_version.outputs.VERSION }} + # Directory containing files to upload + path: build/libs/http-notification-plugin-${{ steps.get_version.outputs.VERSION }}.jar \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..b3bdd8b --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,46 @@ +on: + push: + # Sequence of patterns matched against refs/tags + tags: + - '*' # Push events to matching v*, i.e. v1.0, v20.15.10 + +name: Upload Release Asset + +jobs: + build: + name: Upload Release Asset + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v2 + with: + fetch-depth: 0 + - name: set up JDK 1.8 + uses: actions/setup-java@v1 + with: + java-version: 1.8 + - name: Build with Gradle + run: ./gradlew build + - name: Get Release Version + id: get_version + run: VERSION=$(./gradlew currentVersion -q -Prelease.quiet) && echo ::set-output name=VERSION::$VERSION + - name: Create Release + id: create_release + uses: actions/create-release@v1.0.0 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tag_name: ${{ github.ref }} + release_name: Release ${{ steps.get_version.outputs.VERSION }} + draft: false + prerelease: false + - name: Upload Release Asset (jar) + id: upload-release-asset + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.create_release.outputs.upload_url }} + asset_path: build/libs/http-notification-plugin-${{ steps.get_version.outputs.VERSION }}.jar + asset_name: http-notification-plugin-${{ steps.get_version.outputs.VERSION }}.jar + asset_content_type: application/octet-stream \ No newline at end of file diff --git a/build.gradle b/build.gradle index c4b8342..531fd4a 100644 --- a/build.gradle +++ b/build.gradle @@ -24,7 +24,7 @@ ext.pluginDescription = 'A notification plugin that makes HTTP requests' scmVersion { - ignoreUncommittedChanges = false + ignoreUncommittedChanges = true tag { prefix = '' versionSeparator = '' From 65160b452e932f4e5dbdc9323458dc237e2ba401 Mon Sep 17 00:00:00 2001 From: Luis Toledo Date: Thu, 1 Jun 2023 11:52:10 -0400 Subject: [PATCH 6/6] use last release http-step --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 531fd4a..4490fbf 100644 --- a/build.gradle +++ b/build.gradle @@ -61,7 +61,7 @@ repositories { dependencies { implementation 'org.rundeck:rundeck-core:4.13.0-20230515' - pluginLibs ('com.github.rundeck-plugins:http-step:RUN-1770-SNAPSHOT'){ + pluginLibs ('com.github.rundeck-plugins:http-step:1.1.2'){ exclude group: 'org.rundeck', module: 'rundeck-core' }