├── .gitignore ├── Dockerfile ├── Gemfile ├── LICENSE ├── README.md ├── build.gradle ├── jenkins-xml-to-jobdsl.rb ├── server.rb ├── tests └── pipeline-example │ └── config.xml └── views └── upload.haml /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | build/ 3 | 4 | # Ignore Gradle GUI config 5 | gradle-app.setting 6 | 7 | # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) 8 | !gradle-wrapper.jar 9 | 10 | # Cache of project 11 | .gradletasknamecache 12 | 13 | # # Work around https://youtrack.jetbrains.com/issue/IDEA-116898 14 | # gradle/wrapper/gradle-wrapper.properties 15 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:latest 2 | 3 | MAINTAINER Raymond Barbiero 4 | 5 | ENV DEBIAN_FRONTEND noninteractive 6 | ENV NOKOGIRI_USE_SYSTEM_LIBRARIES 1 7 | 8 | # Nokogiri dependencies 9 | RUN true \ 10 | && apt-get update \ 11 | && apt-get install -qy --force-yes git ruby ruby-dev build-essential \ 12 | && apt-get clean \ 13 | && rm -rf /var/lib/apt/lists/* 14 | 15 | RUN true \ 16 | && apt-get update \ 17 | && apt-get install -qy libxslt1-dev libxml2-dev libssl-dev libyaml-dev \ 18 | && apt-get clean \ 19 | && rm -rf /var/lib/apt/lists/* 20 | 21 | RUN true \ 22 | && apt-get update \ 23 | && apt-get install -qy --no-install-recommends openjdk-8-jdk openjdk-8-jre \ 24 | && apt-get clean \ 25 | && rm -rf /var/lib/apt/lists/* 26 | 27 | RUN true \ 28 | && apt-get update \ 29 | && apt-get install -qy --no-install-recommends curl\ 30 | && apt-get clean \ 31 | && rm -rf /var/lib/apt/lists/* 32 | 33 | RUN curl -sL https://services.gradle.org/distributions/gradle-5.6.4-bin.zip -o gradle-5.6.4.zip \ 34 | && unzip -d /usr/share gradle-5.6.4.zip \ 35 | && ln -s /usr/share/gradle-5.6.4/bin/gradle /usr/bin/gradle \ 36 | && gradle --version \ 37 | && rm gradle-5.6.4.zip 38 | 39 | RUN true \ 40 | && apt-get update \ 41 | && apt-get install -qy vim curl lsof libxml2 libxml2-dev pkg-config \ 42 | && apt-get clean \ 43 | && gem install bundler nokogiri \ 44 | && rm -rf /var/lib/apt/lists/* 45 | 46 | RUN true \ 47 | && git clone https://github.com/jenkinsci/job-dsl-plugin.git /jdsl \ 48 | && cd /jdsl \ 49 | && gradle oneJar 50 | 51 | ENV DSL_JAR "/jdsl/job-dsl-core/build/libs/job-dsl-core-*-standalone.jar" 52 | 53 | ENV APP_HOME /app 54 | ENV HOME /root 55 | RUN mkdir $APP_HOME 56 | WORKDIR $APP_HOME 57 | COPY Gemfile* $APP_HOME/ 58 | RUN bundle config --global silence_root_warning 1 \ 59 | && bundle install 60 | 61 | # Upload source 62 | COPY . $APP_HOME 63 | 64 | # Start server 65 | ENV PORT 3000 66 | EXPOSE 3000 67 | 68 | VOLUME ["/var/www/uploads"] 69 | CMD ["ruby", "server.rb"] 70 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gem 'rack' 4 | gem 'rack-test' 5 | gem 'sinatra' 6 | gem 'haml' 7 | gem 'json' 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2016 Raymond Barbiero 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # jenkins-xml-to-jobdsl 2 | Translates jenkins xml jobs into jobdsl groovy 3 | 4 | ## Building 5 | 6 | docker build -t jdsl . 7 | 8 | ## Manual invocation 9 | 10 | Enter the container: 11 | 12 | docker run -it --rm -v $PWD:$PWD -w $PWD --name jdsl jdsl bash 13 | 14 | Inside the container, run: 15 | 16 | ruby jenkins-xml-to-jobdsl.rb tests/pipeline-example/config.xml 17 | 18 | ## Translation as a service 19 | 20 | Start the container: 21 | 22 | docker run -d --rm --name jdsl -p 3000:3000 jdsl 23 | 24 | Upload a job for translation: 25 | 26 | curl \ 27 | -F file=@"$PWD/tests/pipeline-example/config.xml" \ 28 | -F name='pipeline-example' \ 29 | http://localhost:3000 30 | 31 | Or as a function: 32 | 33 | function jenkins-translate () { 34 | job=$(basename $(dirname $1)) 35 | dir=$(cd $(dirname $1) && pwd) 36 | file=$(basename $1) 37 | curl \ 38 | -F file=@"${dir}/${file}" \ 39 | -F name="${job}" \ 40 | http://localhost:3000 41 | } 42 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | 2 | buildscript { 3 | repositories { 4 | maven { url 'https://extranet.aoe.com/artifactory/remote-repos' } 5 | maven { url 'https://extranet.aoe.com/artifactory/libs-release' } 6 | } 7 | dependencies { 8 | classpath 'com.aoe.gradle:jenkins-job-dsl-gradle-plugin:1.2.0' 9 | } 10 | } 11 | 12 | apply plugin: 'com.aoe.jenkins-job-dsl' 13 | 14 | repositories.clear() 15 | repositories.addAll(buildscript.repositories) 16 | 17 | dependencies { 18 | jobDslExtension 'com.aoe.jenkins:scm-push-trigger:1.1.0@jar' 19 | } 20 | 21 | jobDsl { 22 | sourceDir 'jobs' 23 | resourceDir 'config' 24 | } 25 | -------------------------------------------------------------------------------- /jenkins-xml-to-jobdsl.rb: -------------------------------------------------------------------------------- 1 | require 'nokogiri' 2 | require 'pp' 3 | require 'optparse' 4 | 5 | # Bucket to dump any helpers multiple classes may need. 6 | module Helper 7 | 8 | # Escape Strings that are not valid groovy syntax. 9 | def escape str 10 | str.gsub(/\\/,"\\\\\\").gsub("'''", %q(\\\'\\\'\\\')) 11 | end 12 | 13 | def removeCarriage str 14 | str.tr " ", "\n" 15 | end 16 | 17 | def formatText str 18 | if str =~ /false|true/ 19 | truthy str 20 | else 21 | "'#{str}'" 22 | end 23 | end 24 | 25 | def truthy str 26 | str == 'true' 27 | end 28 | 29 | def toGroovyListOfStrings str 30 | str.split.map do |s| 31 | "'#{s}'" 32 | end.join ', ' 33 | end 34 | 35 | # Example input: 36 | # this=${thing} 37 | # another=${one} 38 | # duplicate=${one} 39 | # duplicate=${one} 40 | def propertiesToMap propertyNode 41 | propertyNode 42 | .split("\n") 43 | .map{|prop| prop.split '='} 44 | .inject({}){|hash, arr| hash[arr[0]] = arr[1] ; hash} 45 | .to_a 46 | .map{|propKV| "'#{propKV[0]}':'''#{escape propKV[1]}'''"} 47 | .flatten 48 | .join ', ' 49 | end 50 | 51 | end 52 | 53 | class SvnScmLocationNodeHandler < Struct.new(:node) 54 | def process(job_name, depth, indent) 55 | currentDepth = depth + indent 56 | svnurl='' 57 | node.elements.each do |i| 58 | case i.name 59 | when 'credentialsId', 'depthOption', 'local', 'ignoreExternalsOption' 60 | # do nothing 61 | when 'remote' 62 | svnurl = "#{i.text}" 63 | else 64 | pp i 65 | end 66 | end 67 | puts " " * depth + "location('#{svnurl}') {" 68 | node.elements.each do |i| 69 | case i.name 70 | when 'remote' 71 | # do nothing 72 | when 'credentialsId' 73 | puts " " * currentDepth + "credentials('#{i.text}')" 74 | when 'depthOption' 75 | puts " " * currentDepth + "depth(javaposse.jobdsl.dsl.helpers.scm.SvnDepth.#{i.text.upcase})" 76 | when 'local' 77 | puts " " * currentDepth + "directory('#{i.text}')" 78 | when 'ignoreExternalsOption' 79 | puts " " * currentDepth + "ignoreExternals(#{i.text})" 80 | else 81 | pp i 82 | end 83 | end 84 | puts " " * depth + "}" 85 | end 86 | end 87 | 88 | class SvnScmDefinitionNodeHandler < Struct.new(:node) 89 | def process(job_name, depth, indent) 90 | puts " " * depth + "svn {" 91 | currentDepth = depth + indent 92 | node.elements.each do |i| 93 | case i.name 94 | when 'locations' 95 | i.elements.each do |j| 96 | case j.name 97 | when 'hudson.scm.SubversionSCM_-ModuleLocation' 98 | SvnScmLocationNodeHandler.new(j).process(job_name, currentDepth, indent) 99 | else 100 | pp j 101 | end 102 | end 103 | when 'excludedRegions', 'includedRegions', 'excludedUsers', 'excludedCommitMessages' 104 | if i.elements.any? 105 | patterns = "[" 106 | i.elements.each do |p| 107 | patterns += "'#{p.text}'," 108 | end 109 | patterns[-1] = "]" 110 | puts " " * currentDepth + "#{i.name}(#{patterns})" 111 | end 112 | when 'excludedRevprop' 113 | puts " " * currentDepth + "excludedRevisionProperty('#{i.text}')" 114 | when 'workspaceUpdater' 115 | strategy = 'javaposse.jobdsl.dsl.helpers.scm.SvnCheckoutStrategy.' 116 | case i.attribute('class').value 117 | when 'hudson.scm.subversion.UpdateUpdater' 118 | strategy += 'UPDATE' 119 | when 'hudson.scm.subversion.CheckoutUpdater' 120 | strategy += 'CHECKOUT' 121 | when 'hudson.scm.subversion.UpdateWithCleanUpdater' 122 | strategy += 'UPDATE_WITH_CLEAN' 123 | when 'hudson.scm.subversion.UpdateWithRevertUpdater' 124 | strategy += 'UPDATE_WITH_REVERT' 125 | else 126 | pp i 127 | end 128 | puts " " * currentDepth + "checkoutStrategy(#{strategy})" 129 | when 'ignoreDirPropChanges', 'filterChangelog' 130 | # todo: figure out how to merge these into a configure {} block, since they aren't full supported yet 131 | else 132 | pp i 133 | end 134 | end 135 | puts " " * depth + "}" 136 | end 137 | end 138 | 139 | class MatrixAuthorizationNodeHandler < Struct.new(:node) 140 | def process(job_name, depth, indent) 141 | puts " " * depth + "authorization {" 142 | currentDepth = depth + indent 143 | node.elements.each do |i| 144 | case i.name 145 | when 'permission' 146 | if i.text.include? ":" 147 | p, u = i.text.split(":") 148 | puts " " * currentDepth + "permission(perm = '#{p}', user = '#{u}')" 149 | else 150 | pp i 151 | end 152 | else 153 | pp i 154 | end 155 | end 156 | puts " " * depth + "}" 157 | end 158 | end 159 | 160 | class RebuildNodeHandler < Struct.new(:node) 161 | def process(job_name, depth, indent) 162 | puts " " * depth + "rebuild {" 163 | currentDepth = depth + indent 164 | node.elements.each do |i| 165 | case i.name 166 | when 'autoRebuild', 'rebuildDisabled' 167 | puts " " * currentDepth + "#{i.name}(#{i.text})" 168 | else 169 | pp i 170 | end 171 | end 172 | puts " " * depth + "}" 173 | end 174 | end 175 | 176 | class LogRotatorNodeHandler < Struct.new(:node) 177 | def process(job_name, depth, indent) 178 | puts " " * depth + "logRotator {" 179 | currentDepth = depth + indent 180 | node.elements.each do |i| 181 | case i.name 182 | when 'daysToKeep', 'numToKeep', 'artifactDaysToKeep', 'artifactNumToKeep' 183 | puts " " * currentDepth + "#{i.name}(#{i.text})" 184 | else 185 | pp i 186 | end 187 | end 188 | puts " " * depth + "}" 189 | end 190 | end 191 | 192 | class BuildDiscarderNodeHandler < Struct.new(:node) 193 | def process(job_name, depth, indent) 194 | currentDepth = depth 195 | node.elements.each do |i| 196 | if i.attribute('class')&.value == 'hudson.tasks.LogRotator' 197 | LogRotatorNodeHandler.new(i).process(job_name, currentDepth, indent) 198 | else 199 | pp i 200 | end 201 | end 202 | end 203 | end 204 | 205 | class ParametersNodeHandler < Struct.new(:node) 206 | def nvd(i) 207 | name = "" 208 | value = "null" 209 | description = "null" 210 | i.elements.each do |p| 211 | case p.name 212 | when "name" 213 | name = "#{p.text}" 214 | when "description" 215 | if (!p.text.to_s.strip.empty? && "#{p.text}" != "null") 216 | description = "'''#{p.text}'''" 217 | else 218 | description = "null" 219 | end 220 | when "defaultValue" 221 | value = "#{p.text}" 222 | if (!p.text.to_s.strip.empty? && ("#{p.text}" == "true" || "#{p.text}" == "false")) 223 | value = "#{p.text}" 224 | elsif (!p.text.to_s.strip.empty? && "#{p.text}" != "null") 225 | value = "'#{p.text}'" 226 | else 227 | value = "null" 228 | end 229 | when 'choices' 230 | if p.attribute('class').value == 'java.util.Arrays$ArrayList' 231 | value = "[" 232 | p.elements.each do |k| 233 | case k.name 234 | when 'a' 235 | if k.attribute('class').value == 'string-array' 236 | value += k.elements.map{|s| "'#{s.text}'"}.join ', ' 237 | end 238 | else 239 | pp k 240 | end 241 | end 242 | value += "]" 243 | else 244 | pp p 245 | end 246 | else 247 | pp p 248 | end 249 | end 250 | return name, value, description 251 | end 252 | 253 | def process(job_name, depth, indent) 254 | param_block = [] 255 | param_block << " " * depth + "parameters {" 256 | currentDepth = depth + indent 257 | node.elements.each do |i| 258 | case i.name 259 | when 'com.seitenbau.jenkins.plugins.dynamicparameter.ChoiceParameterDefinition', 260 | 'hudson.plugins.jira.versionparameter.JiraVersionParameterDefinition' 261 | # these cannot be defined in this scope. Have to be defined on /properties. 262 | when 'hudson.model.TextParameterDefinition' 263 | name, value, description = nvd(i) 264 | param_block << " " * currentDepth + "textParam('#{name}', #{value}, #{description})" 265 | when 'hudson.model.StringParameterDefinition' 266 | name, value, description = nvd(i) 267 | param_block << " " * currentDepth + "stringParam('#{name}', #{value}, #{description})" 268 | when 'hudson.model.BooleanParameterDefinition' 269 | name, value, description = nvd(i) 270 | param_block << " " * currentDepth + "booleanParam('#{name}', #{value}, #{description})" 271 | when 'hudson.model.ChoiceParameterDefinition' 272 | name, value, description = nvd(i) 273 | param_block << " " * currentDepth + "choiceParam('#{name}', #{value}, #{description})" 274 | when 'hudson.model.PasswordParameterDefinition' 275 | name, value, description = nvd(i) 276 | param_block << "" 277 | param_block << " " * currentDepth + "/* Found a Password Parameter of:" 278 | param_block << "" 279 | param_block << " " * currentDepth + " Name: #{name}" 280 | param_block << " " * currentDepth + " Value: #{value}" 281 | param_block << " " * currentDepth + " description: #{description}" 282 | param_block << "" 283 | param_block << " " * currentDepth + " These are no longer supported and you will need to configure something like:" 284 | param_block << " " * currentDepth + " https://support.cloudbees.com/hc/en-us/articles/203802500-Injecting-Secrets-into-Jenkins-Build-Jobs */" 285 | param_block << "" 286 | else 287 | param_block << "#{pp i}" 288 | end 289 | end 290 | param_block << " " * depth + "}" 291 | return param_block 292 | end 293 | end 294 | 295 | class JiraVersionParameterDefinitionHandler < Struct.new(:node) 296 | include Helper 297 | 298 | def process(job_name, depth, indent) 299 | innerNode = [] 300 | node.elements.each do |i| 301 | case i.name 302 | when 'pattern' 303 | innerNode << { 304 | "'#{i.name}'" => i.elements.collect{|e| %W['#{e.name}'('#{escape e.text}')]} 305 | } 306 | else 307 | innerNode << "'#{i.name}'('#{i.text}')" 308 | end 309 | end 310 | 311 | unless innerNode.empty? 312 | ConfigureBlock.new([{ 313 | "it / #{configurePath} / '#{node.name}'" => innerNode 314 | }], 315 | indent: indent 316 | ).save! 317 | end 318 | end 319 | 320 | def configurePath 321 | node 322 | .path 323 | .split('/')[2..4] 324 | .collect{|n| "'#{n}'"} 325 | .join ' / ' 326 | end 327 | end 328 | 329 | class DynamicParameterHandler < Struct.new(:node) 330 | def process(job_name, depth, indent) 331 | puts " " * depth + "configure { project ->" 332 | 333 | currentDepth = depth + indent 334 | # Even though we are nested into properties already, we have to define it still. 335 | # The configure {} block in job dsl feels to be buggy and this works. 336 | puts " " * currentDepth + "project / 'properties' / 'hudson.model.ParametersDefinitionProperty' / 'parameterDefinitions' << '#{node.name}' {" 337 | node.elements.each do |i| 338 | case i.name 339 | when '__uuid', '__localBaseDirectory', '__remoteBaseDirectory' 340 | # nothing, dynamically created by the plugin. 341 | when '__remote', 'readonlyInputField' 342 | puts " " * (currentDepth + indent) + "'#{i.name}'(#{i.text})" unless i.text.empty? 343 | else 344 | puts " " * (currentDepth + indent) + "'#{i.name}'('''#{i.text}''')" unless i.text.empty? 345 | end 346 | end 347 | puts " " * currentDepth + "}" 348 | 349 | puts " " * depth + "}" 350 | end 351 | end 352 | 353 | class PropertiesNodeHandler < Struct.new(:node) 354 | def process(job_name, depth, indent) 355 | # hack... need to print parameter block outside of property block. :( 356 | parameter_node_block = nil 357 | puts " " * depth + "properties {" 358 | currentDepth = depth + indent 359 | node.elements.each do |i| 360 | case i.name 361 | when 'com.sonyericsson.rebuild.RebuildSettings' 362 | RebuildNodeHandler.new(i).process(job_name, currentDepth, indent) 363 | when 'hudson.security.AuthorizationMatrixProperty' 364 | MatrixAuthorizationNodeHandler.new(i).process(job_name, currentDepth, indent) 365 | when 'org.jenkinsci.plugins.workflow.job.properties.BuildDiscarderProperty' 366 | BuildDiscarderNodeHandler.new(i).process(job_name, currentDepth, indent) 367 | when 'hudson.model.ParametersDefinitionProperty' 368 | i.elements.each do |p| 369 | case p.name 370 | when 'parameterDefinitions' 371 | 372 | # These are not supported in jobdsl so have to be configured via ConfigureBlock 373 | p.elements.each do |pelement| 374 | case pelement.name 375 | when 'com.seitenbau.jenkins.plugins.dynamicparameter.ChoiceParameterDefinition' 376 | DynamicParameterHandler.new(pelement).process(job_name, currentDepth, indent) 377 | when 'hudson.plugins.jira.versionparameter.JiraVersionParameterDefinition' 378 | JiraVersionParameterDefinitionHandler.new(pelement).process(job_name, currentDepth, indent) 379 | when 'hudson.model.PasswordParameterDefinition' 380 | # handled by ParametersNodeHandler 381 | end 382 | end 383 | 384 | # hack... should really be nested under properties {} but jobdsl doesnt support this yet 385 | parameter_node_block = ParametersNodeHandler.new(p).process(job_name, depth, indent) 386 | else 387 | pp p 388 | end 389 | end 390 | when 'jenkins.model.BuildDiscarderProperty' 391 | BuildDiscarderNodeHandler.new(i).process(job_name, currentDepth, indent) 392 | when 'com.cloudbees.plugins.JobPrerequisites' 393 | ConfigureBlock.new([{ 394 | "it / properties / '#{i.name}'" => [ 395 | "'script'('''#{i.at_xpath("//#{i.name}/script")&.text}''')", 396 | "'interpreter'('#{i.at_xpath("//#{i.name}/interpreter")&.text}')" 397 | ] 398 | }], 399 | indent: indent 400 | ).save! 401 | when 'hudson.plugins.copyartifact.CopyArtifactPermissionProperty' 402 | CopyArtifactPermissionPropertyHandler.new(i).process(job_name, currentDepth, indent) 403 | else 404 | pp i 405 | end 406 | end 407 | puts " " * depth + "}" 408 | if parameter_node_block 409 | parameter_node_block.each do |i| 410 | puts "#{i}" 411 | end 412 | end 413 | end 414 | end 415 | 416 | class CopyArtifactPermissionPropertyHandler < Struct.new(:node) 417 | include Helper 418 | 419 | def process(job_name, depth, indent) 420 | innerNode = [] 421 | 422 | node.elements.each do |i| 423 | case i.name 424 | when 'projectNameList' 425 | unless i.text.empty? 426 | nestedInnerNode = i.elements.map do |e| 427 | "'#{e.name}'(#{formatText e.text})" unless e.text.empty? 428 | end 429 | innerNode << { "'#{i.name}'" => nestedInnerNode } 430 | end 431 | else 432 | pp i 433 | end 434 | end 435 | 436 | ConfigureBlock.new([ 437 | { 438 | "it / 'properties' / '#{node.name}'" => innerNode 439 | } 440 | ], indent: indent).save! 441 | end 442 | end 443 | 444 | class RemoteGitScmNodeHandler < Struct.new(:node) 445 | def process(job_name, depth, indent) 446 | puts " " * depth + "remote {" 447 | currentDepth = depth + indent 448 | node.elements.each do |i| 449 | case i.name 450 | when 'url' 451 | puts " " * currentDepth + "url('#{i.text}')" 452 | when 'credentialsId' 453 | puts " " * currentDepth + "credentials('#{i.text}')" 454 | when 'name', 'refspec' 455 | puts " " * currentDepth + "#{i.name}('#{i.text}')" unless i.text.empty? 456 | else 457 | pp i 458 | end 459 | end 460 | puts " " * depth + "}" 461 | end 462 | end 463 | 464 | class GitScmExtensionsNodeHandler < Struct.new(:node) 465 | def process(job_name, depth, indent) 466 | puts " " * depth + "extensions {" 467 | currentDepth = depth + indent 468 | puts " " * depth + "}" 469 | end 470 | end 471 | 472 | class GitScmDefinitionNodeHandler < Struct.new(:node) 473 | def process(job_name, depth, indent) 474 | puts " " * depth + "git {" 475 | currentDepth = depth + indent 476 | configureBlock = ConfigureBlock.new [], indent: indent, indent_times: (currentDepth / indent rescue 1) 477 | node.elements.each do |i| 478 | case i.name 479 | when 'configVersion' 480 | # nothing, generated by plugin 481 | when 'userRemoteConfigs' 482 | i.elements.each do |j| 483 | case j.name 484 | when 'hudson.plugins.git.UserRemoteConfig' 485 | RemoteGitScmNodeHandler.new(j).process(job_name, currentDepth, indent) 486 | else 487 | pp j 488 | end 489 | end 490 | when 'branches' 491 | i.elements.each do |j| 492 | case j.name 493 | when 'hudson.plugins.git.BranchSpec' 494 | branches = "" 495 | j.elements.each do |b| 496 | branches += "'#{b.text}'," 497 | end 498 | branches[-1] = "" 499 | puts " " * currentDepth + "branches(#{branches})" 500 | else 501 | end 502 | end 503 | when 'browser' 504 | puts " " * currentDepth + "browser {" 505 | if i.attribute('class').value == 'hudson.plugins.git.browser.Stash' 506 | puts " " * (currentDepth + indent) + "stash('#{i.at_xpath('//browser/url')&.text}')" 507 | else 508 | pp i 509 | end 510 | puts " " * currentDepth + "}" 511 | when 'gitTool', 'doGenerateSubmoduleConfigurations' 512 | configureBlock << "'#{i.name}'('#{i.text}')" unless i.text.empty? 513 | when'submoduleCfg' 514 | # todo: not yet implemented 515 | when 'extensions' 516 | GitScmExtensionsNodeHandler.new(i).process(job_name, currentDepth, indent) 517 | else 518 | pp i 519 | end 520 | end 521 | puts configureBlock 522 | puts " " * depth + "}" 523 | end 524 | end 525 | 526 | class ScmDefinitionNodeHandler < Struct.new(:node) 527 | def process(job_name, depth, indent) 528 | puts " " * depth + "scm {" 529 | currentDepth = depth + indent 530 | if node.attribute('class').value == 'hudson.plugins.git.GitSCM' 531 | GitScmDefinitionNodeHandler.new(node).process(job_name, currentDepth, indent) 532 | elsif node.attribute('class').value == 'hudson.scm.SubversionSCM' 533 | SvnScmDefinitionNodeHandler.new(node).process(job_name, currentDepth, indent) 534 | elsif node.attribute('class').value == 'hudson.scm.NullSCM' 535 | else 536 | pp node 537 | end 538 | puts " " * depth + "}" 539 | end 540 | end 541 | 542 | class CpsScmDefinitionNodeHandler < Struct.new(:node) 543 | def process(job_name, depth, indent) 544 | puts " " * depth + "cpsScm {" 545 | currentDepth = depth + indent 546 | node.elements.each do |i| 547 | case i.name 548 | when 'scm' 549 | ScmDefinitionNodeHandler.new(i).process(job_name, currentDepth, indent) 550 | when 'scriptPath' 551 | puts " " * currentDepth + "scriptPath('#{i.text}')" 552 | else 553 | pp i 554 | end 555 | end 556 | puts " " * depth + "}" 557 | end 558 | end 559 | 560 | class CpsDefinitionNodeHandler < Struct.new(:node) 561 | def process(job_name, depth, indent) 562 | puts " " * depth + "cps {" 563 | currentDepth = depth + indent 564 | node.elements.each do |i| 565 | case i.name 566 | when 'script' 567 | txt = i.text.gsub(/\\/,"\\\\\\").gsub("'''", %q(\\\'\\\'\\\')) 568 | puts " " * currentDepth + "script('''\\\n#{txt}\n\'''\n)" 569 | when 'sandbox' 570 | puts " " * currentDepth + "sandbox(#{i.text})" 571 | else 572 | pp i 573 | end 574 | end 575 | puts " " * depth + "}" 576 | end 577 | end 578 | 579 | class DefinitionNodeHandler < Struct.new(:node) 580 | def process(job_name, depth, indent) 581 | puts " " * depth + "definition {" 582 | currentDepth = depth + indent 583 | if node.attribute('class').value == 'org.jenkinsci.plugins.workflow.cps.CpsScmFlowDefinition' 584 | CpsScmDefinitionNodeHandler.new(node).process(job_name, currentDepth, indent) 585 | elsif node.attribute('class').value == 'org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition' 586 | CpsDefinitionNodeHandler.new(node).process(job_name, currentDepth, indent) 587 | else 588 | pp node 589 | end 590 | puts " " * depth + "}" 591 | end 592 | end 593 | 594 | class TriggerDefinitionNodeHandler < Struct.new(:node) 595 | def process(job_name, depth, indent) 596 | puts " " * depth + "triggers {" 597 | currentDepth = depth + indent 598 | puts " " * depth + "}" 599 | end 600 | end 601 | 602 | class FlowDefinitionNodeHandler < Struct.new(:node) 603 | include Helper 604 | 605 | def process(job_name, depth, indent) 606 | puts "pipelineJob('#{job_name}') {" 607 | currentDepth = depth + indent 608 | node.elements.each do |i| 609 | case i.name 610 | when 'actions' 611 | when 'description' 612 | if !(i.text.nil? || i.text.empty?) 613 | puts " " * currentDepth + "#{i.name}('''\\\n#{removeCarriage i.text}\n''')" 614 | end 615 | when 'keepDependencies', 'quietPeriod' 616 | puts " " * currentDepth + "#{i.name}(#{i.text})" 617 | when 'properties' 618 | PropertiesNodeHandler.new(i).process(job_name, currentDepth, indent) 619 | when 'definition' 620 | DefinitionNodeHandler.new(i).process(job_name, currentDepth, indent) 621 | when 'triggers' 622 | TriggerDefinitionNodeHandler.new(i).process(job_name, currentDepth, indent) 623 | when 'authToken' 624 | puts " " * currentDepth + "authenticationToken('#{i.text}')" 625 | when 'concurrentBuild' 626 | puts " " * currentDepth + "concurrentBuild(#{i.text})" 627 | when 'logRotator' 628 | LogRotatorNodeHandler.new(i).process(job_name, currentDepth, indent) 629 | else 630 | pp i 631 | end 632 | end 633 | ConfigureBlock.print 634 | puts "}" 635 | end 636 | end 637 | 638 | 639 | 640 | class TaskPropertiesHandler < Struct.new(:node) 641 | def process(job_name, depth, indent) 642 | logText = "#{node.at_xpath('//hudson.plugins.postbuildtask.TaskProperties/logTexts/hudson.plugins.postbuildtask.LogProperties/logText')&.text}" 643 | script = node.at_xpath('//hudson.plugins.postbuildtask.TaskProperties/script')&.text 644 | script = script.gsub(/\\/,"\\\\\\").gsub("'''", %q(\\\'\\\'\\\')) 645 | escalate = node.at_xpath('//hudson.plugins.postbuildtask.TaskProperties/EscalateStatus')&.text 646 | runIfSuccessful = node.at_xpath('//hudson.plugins.postbuildtask.TaskProperties/RunIfJobSuccessful')&.text 647 | puts " " * depth + "task('#{logText.to_s.empty? ? ".*" : logText.delete!("\C-M")}','''\\\n#{script.delete!("\C-M")}\n''',#{escalate},#{runIfSuccessful})" 648 | end 649 | end 650 | 651 | class TasksNodeHandler < Struct.new(:node) 652 | def process(job_name, depth, indent) 653 | currentDepth = depth 654 | node.elements.each do |i| 655 | case i.name 656 | when 'hudson.plugins.postbuildtask.TaskProperties' 657 | TaskPropertiesHandler.new(i).process(job_name, currentDepth, indent) 658 | else 659 | pp i 660 | end 661 | end 662 | end 663 | end 664 | 665 | class PostBuildTaskNodeHandler < Struct.new(:node) 666 | def process(job_name, depth, indent) 667 | puts " " * depth + "postBuildTask {" 668 | currentDepth = depth + indent 669 | node.elements.each do |i| 670 | case i.name 671 | when 'tasks' 672 | TasksNodeHandler.new(i).process(job_name, currentDepth, indent) 673 | else 674 | pp i 675 | end 676 | end 677 | puts " " * depth + "}" 678 | end 679 | end 680 | 681 | class ArchiverNodeHandler < Struct.new(:node) 682 | def process(job_name, depth, indent) 683 | puts " " * depth + "archiveArtifacts {" 684 | currentDepth = depth + indent 685 | node.elements.each do |i| 686 | case i.name 687 | when 'artifacts' 688 | puts " " * currentDepth + "pattern('#{i.text}')" 689 | when 'allowEmptyArchive' 690 | puts " " * currentDepth + "allowEmpty(#{i.text})" 691 | when 'onlyIfSuccessful', 'fingerprint', 'defaultExcludes' 692 | puts " " * currentDepth + "#{i.name}(#{i.text})" 693 | when 'caseSensitive' 694 | #unsupported 695 | else 696 | pp i 697 | end 698 | end 699 | puts " " * depth + "}" 700 | end 701 | end 702 | 703 | class SonarNodeHandler < Struct.new(:node) 704 | def process(job_name, depth, indent) 705 | puts " " * depth + "sonar {" 706 | currentDepth = depth + indent 707 | node.elements.each do |i| 708 | case i.name 709 | when 'branch' 710 | puts " " * currentDepth + "#{i.name}('#{i.text}')" 711 | when 'mavenOpts', 'jobAdditionalProperties', 'settings', 'globalSettings', 'usePrivateRepository' 712 | # unsupported 713 | else 714 | pp i 715 | end 716 | end 717 | puts " " * depth + "}" 718 | end 719 | end 720 | 721 | class IrcTargetsNodeHandler < Struct.new(:node) 722 | include Helper 723 | 724 | def process(job_name, depth, indent) 725 | node.elements.each do |i| 726 | case i.name 727 | when 'hudson.plugins.im.GroupChatIMMessageTarget' 728 | params = i.elements.collect {|e| 729 | "#{e.name}:#{formatText e.text}" 730 | }.join ', ' 731 | puts " " * depth + "channel(#{params})" 732 | else 733 | pp i 734 | end 735 | end 736 | end 737 | 738 | end 739 | 740 | class IrcPublisherNodeHandler < Struct.new(:node) 741 | def process(job_name, depth, indent) 742 | puts " " * depth + "irc {" 743 | currentDepth = depth + indent 744 | # ConfigureBlock has to be used here because jobdsl does not support 745 | # nesting configure within irc. 746 | configureBlock = ConfigureBlock.new [], indent: indent 747 | node.elements.each do |i| 748 | case i.name 749 | when 'buildToChatNotifier', 'channels' 750 | # dynamically created by IRC plugin, or cruft 751 | when 'targets' 752 | IrcTargetsNodeHandler.new(i).process(job_name, currentDepth, indent) 753 | when 'strategy' 754 | puts " " * currentDepth + "#{i.name}('#{i.text}')" 755 | when 'notifyUpstreamCommitters' 756 | puts " " * currentDepth + "#{i.name}(#{i.text})" 757 | when 'notifySuspects' 758 | puts " " * currentDepth + "notifyScmCommitters(#{i.text})" 759 | when 'notifyFixers' 760 | puts " " * currentDepth + "notifyScmFixers(#{i.text})" 761 | when 'notifyCulprits' 762 | puts " " * currentDepth + "notifyScmCulprits(#{i.text})" 763 | when 'notifyOnBuildStart' 764 | configureBlock << "(ircNode / '#{i.name}').setValue(#{i.text})" 765 | when 'matrixMultiplier' 766 | configureBlock << "(ircNode / '#{i.name}').setValue('#{i.text}')" 767 | else 768 | pp i 769 | end 770 | end 771 | 772 | unless configureBlock.empty? 773 | configureBlock.unshift "def ircNode = it / publishers / 'hudson.plugins.ircbot.IrcPublisher'" 774 | configureBlock.save! 775 | end 776 | 777 | puts " " * depth + "}" 778 | end 779 | end 780 | 781 | 782 | class ExtendedEmailNodeHandler < Struct.new(:node) 783 | def print_trigger_block(j, currentDepth, indent) 784 | j.elements.each do |k| 785 | case k.name 786 | when 'email' 787 | k.elements.each do |e| 788 | case e.name 789 | when 'attachmentsPattern' 790 | if !(e.text.nil? || e.text.empty?) 791 | puts " " * (currentDepth + indent * 2) + "attachmentPatterns('#{e.text}')" 792 | end 793 | when 'subject', 'recipientList' 794 | if !(e.text.nil? || e.text.empty?) 795 | puts " " * (currentDepth + indent * 2) + "#{e.name}('#{e.text}')" 796 | end 797 | when 'replyTo' 798 | puts " " * (currentDepth + indent * 2) + "replyToList('#{e.text}')" 799 | when 'compressBuildLog', 'attachBuildLog' 800 | puts " " * (currentDepth + indent * 2) + "#{e.name}(#{e.text})" 801 | when 'recipientProviders', 'contentType' 802 | # unsupported 803 | when 'body' 804 | puts " " * (currentDepth + indent * 2) + "content('''\\\n#{e.text}\n''')" 805 | else 806 | pp e 807 | end 808 | end 809 | when 'failureCount' 810 | #unsupported 811 | else 812 | pp k 813 | end 814 | end 815 | end 816 | 817 | def process(job_name, depth, indent) 818 | puts " " * depth + "extendedEmail {" 819 | currentDepth = depth + indent 820 | node.elements.each do |i| 821 | case i.name 822 | when 'saveOutput' 823 | puts " " * currentDepth + "saveToWorkspace(#{i.text})" 824 | when 'replyTo' 825 | puts " " * currentDepth + "replyToList('#{i.text}')" 826 | when 'presendScript' 827 | puts " " * currentDepth + "preSendScript('#{i.text}')" 828 | when 'recipientList', 'contentType', 'defaultSubject' 829 | puts " " * currentDepth + "#{i.name}('#{i.text}')" 830 | when 'defaultContent' 831 | puts " " * currentDepth + "#{i.name}('''\\\n#{i.text}\n''')" 832 | when 'attachBuildLog', 'compressBuildLog', 'disabled' 833 | puts " " * currentDepth + "#{i.name}(#{i.text})" 834 | when 'attachmentsPattern' 835 | # unsupported 836 | when 'configuredTriggers' 837 | puts " " * currentDepth + "triggers {" 838 | i.elements.each do |j| 839 | case j.name 840 | when 'hudson.plugins.emailext.plugins.trigger.FixedTrigger' 841 | puts " " * (currentDepth + indent) + "fixed {" 842 | print_trigger_block(j, currentDepth, indent) 843 | puts " " * (currentDepth + indent) + "}" 844 | when 'hudson.plugins.emailext.plugins.trigger.FirstFailureTrigger' 845 | puts " " * (currentDepth + indent) + "firstFailure {" 846 | print_trigger_block(j, currentDepth, indent) 847 | puts " " * (currentDepth + indent) + "}" 848 | when 'hudson.plugins.emailext.plugins.trigger.FailureTrigger' 849 | puts " " * (currentDepth + indent) + "failure {" 850 | print_trigger_block(j, currentDepth, indent) 851 | puts " " * (currentDepth + indent) + "}" 852 | when 'hudson.plugins.emailext.plugins.trigger.SuccessTrigger' 853 | puts " " * (currentDepth + indent) + "success {" 854 | print_trigger_block(j, currentDepth, indent) 855 | puts " " * (currentDepth + indent) + "}" 856 | when 'hudson.plugins.emailext.plugins.trigger.AlwaysTrigger' 857 | puts " " * (currentDepth + indent) + "always {" 858 | print_trigger_block(j, currentDepth, indent) 859 | puts " " * (currentDepth + indent) + "}" 860 | when 'hudson.plugins.emailext.plugins.trigger.StillFailingTrigger' 861 | puts " " * (currentDepth + indent) + "stillFailing {" 862 | print_trigger_block(j, currentDepth, indent) 863 | puts " " * (currentDepth + indent) + "}" 864 | when 'hudson.plugins.emailext.plugins.trigger.StatusChangedTrigger' 865 | puts " " * (currentDepth + indent) + "statusChanged {" 866 | print_trigger_block(j, currentDepth, indent) 867 | puts " " * (currentDepth + indent) + "}" 868 | when 'hudson.plugins.emailext.plugins.trigger.UnstableTrigger' 869 | puts " " * (currentDepth + indent) + "unstable {" 870 | print_trigger_block(j, currentDepth, indent) 871 | puts " " * (currentDepth + indent) + "}" 872 | else 873 | pp j 874 | end 875 | end 876 | puts " " * currentDepth + "}" 877 | else 878 | pp i 879 | end 880 | end 881 | puts " " * depth + "}" 882 | end 883 | end 884 | 885 | class TapPublisherHandler < Struct.new(:node) 886 | def process(job_name, depth, indent) 887 | innerNode = [] 888 | node.elements.each do |i| 889 | innerNode << "'#{i.name}'('#{i.text}')" unless i.text.empty? 890 | end 891 | 892 | unless innerNode.empty? 893 | ConfigureBlock.new([{ 894 | "it / 'publishers' / '#{node.name}'" => innerNode 895 | }], 896 | indent: indent 897 | ).save! 898 | end 899 | end 900 | end 901 | 902 | class JUnitResultArchiverHandler < Struct.new(:node) 903 | def process(job_name, depth, indent) 904 | innerNode = [] 905 | 906 | currentDepth = depth + indent 907 | 908 | node.elements.each do |i| 909 | case i.name 910 | when 'testResults' 911 | # Nothing, pulled out below because is in signature of archiveJunit method. 912 | when 'keepLongStdio' 913 | innerNode << ' ' * currentDepth + "retainLongStdout(#{i.text})" unless i.text.empty? 914 | when 'testDataPublishers' 915 | # TODO - don't have working example for this yet 916 | when 'healthScaleFactor' 917 | innerNode << ' ' * currentDepth + "#{i.name}(#{i.text})" unless i.text.empty? 918 | else 919 | pp i 920 | end 921 | end 922 | 923 | testResults = node.at_xpath("//publishers/#{node.name}/testResults")&.text 924 | archiveSig = ' ' * depth + "archiveJunit('#{testResults}')" 925 | if innerNode.empty? 926 | puts archiveSig 927 | else 928 | puts archiveSig + ' {' 929 | puts innerNode 930 | puts ' ' * depth + '}' 931 | end 932 | end 933 | end 934 | 935 | class MailerHandler < Struct.new(:node) 936 | def process(job_name, depth, indent) 937 | recipients = node.at_xpath("//#{node.name}/recipients")&.text 938 | dontNotifyEveryUnstableBuild = node.at_xpath("//#{node.name}/dontNotifyEveryUnstableBuild")&.text 939 | sendToIndividuals = node.at_xpath("//#{node.name}/sendToIndividuals")&.text 940 | 941 | unless recipients.empty? || dontNotifyEveryUnstableBuild.empty? || sendToIndividuals.empty? 942 | puts " " * depth + "mailer('#{recipients}', #{dontNotifyEveryUnstableBuild}, #{sendToIndividuals})" 943 | end 944 | end 945 | end 946 | 947 | class PublishersNodeHandler < Struct.new(:node) 948 | def process(job_name, depth, indent) 949 | puts " " * depth + "publishers {" 950 | currentDepth = depth + indent 951 | node.elements.each do |i| 952 | case i.name 953 | when 'hudson.plugins.postbuildtask.PostbuildTask' 954 | PostBuildTaskNodeHandler.new(i).process(job_name, currentDepth, indent) 955 | when 'hudson.tasks.ArtifactArchiver' 956 | ArchiverNodeHandler.new(i).process(job_name, currentDepth, indent) 957 | when 'org.jenkinsci.plugins.stashNotifier.StashNotifier' 958 | puts " " * currentDepth + "stashNotifier()" 959 | when 'hudson.plugins.sonar.SonarPublisher' 960 | SonarNodeHandler.new(i).process(job_name, currentDepth, indent) 961 | when 'hudson.plugins.emailext.ExtendedEmailPublisher' 962 | ExtendedEmailNodeHandler.new(i).process(job_name, currentDepth, indent) 963 | when 'hudson.plugins.ircbot.IrcPublisher' 964 | IrcPublisherNodeHandler.new(i).process(job_name, currentDepth, indent) 965 | when 'hudson.tasks.BuildTrigger' 966 | projects = "['#{i.at_xpath('//hudson.tasks.BuildTrigger/childProjects')&.text}']" 967 | threshold = "'#{i.at_xpath('//hudson.tasks.BuildTrigger/threshold/name')&.text}'" 968 | puts " " * currentDepth + "downstream(#{projects}, #{threshold})" 969 | when 'hudson.plugins.performance.PerformancePublisher' 970 | PerformancePublisherNodeHandler.new(i).process(job_name, currentDepth+indent, indent) 971 | when 'hudson.plugins.sitemonitor.SiteMonitorRecorder' 972 | SiteMonitorRecorderHandler.new(i).process(job_name, currentDepth, indent) 973 | when 'org.tap4j.plugin.TapPublisher' 974 | TapPublisherHandler.new(i).process(job_name, currentDepth, indent) 975 | when 'hudson.tasks.junit.JUnitResultArchiver' 976 | JUnitResultArchiverHandler.new(i).process(job_name, currentDepth, indent) 977 | when 'hudson.tasks.Mailer' 978 | MailerHandler.new(i).process(job_name, currentDepth, indent) 979 | when 'hudson.plugins.rubyMetrics.rcov.RcovPublisher' 980 | RcovPublisherHandler.new(i).process(job_name, currentDepth, indent) 981 | when 'hudson.plugins.testng.Publisher' 982 | TestNgHandler.new(i).process(job_name, currentDepth, indent) 983 | when 'com.pocketsoap.ChatterNotifier' 984 | ChatterNotifierHandler.new(i).process(job_name, currentDepth, indent) 985 | when 'hudson.plugins.parameterizedtrigger.BuildTrigger' 986 | TriggerNodeHandler.new(i).process(job_name, currentDepth, indent) 987 | else 988 | pp i 989 | end 990 | end 991 | puts " " * depth + "}" 992 | end 993 | end 994 | 995 | class ChatterNotifierHandler < Struct.new(:node) 996 | include Helper 997 | 998 | def process(job_name, depth, indent) 999 | innerNode = [] 1000 | 1001 | node.elements.each do |i| 1002 | innerNode << "'#{i.name}'(#{formatText i.text})" unless i.text.empty? 1003 | end 1004 | 1005 | ConfigureBlock.new([ 1006 | { 1007 | "it / 'publishers' / '#{node.name}'" => innerNode 1008 | } 1009 | ], indent: indent).save! 1010 | end 1011 | end 1012 | 1013 | class TestNgHandler < Struct.new(:node) 1014 | 1015 | def process(job_name, depth, indent) 1016 | reportFilenamePattern = node.at_xpath("//#{node.name}/reportFilenamePattern")&.text 1017 | puts " " * depth + "archiveTestNG('#{reportFilenamePattern}') {" 1018 | currentDepth = depth + indent 1019 | node.elements.each do |i| 1020 | case i.name 1021 | when 'reportFilenamePattern' 1022 | # handled above 1023 | when 'escapeTestDescp' 1024 | puts " " * currentDepth + "escapeTestDescription(#{i.text})" 1025 | when 'escapeExceptionMsg' 1026 | puts " " * currentDepth + "escapeExceptionMessages(#{i.text})" 1027 | when 'showFailedBuilds' 1028 | puts " " * currentDepth + "showFailedBuildsInTrendGraph(#{i.text})" 1029 | when 'unstableOnSkippedTests' 1030 | puts " " * currentDepth + "markBuildAsUnstableOnSkippedTests(#{i.text})" 1031 | when 'failureOnFailedTestConfig' 1032 | puts " " * currentDepth + "markBuildAsFailureOnFailedConfiguration(#{i.text})" 1033 | else 1034 | pp i 1035 | end 1036 | end 1037 | puts " " * depth + "}" 1038 | end 1039 | 1040 | end 1041 | 1042 | class RcovPublisherHandler < Struct.new(:node) 1043 | 1044 | def process(job_name, depth, indent) 1045 | puts " " * depth + "rcov {" 1046 | currentDepth = depth + indent 1047 | node.elements.each do |i| 1048 | case i.name 1049 | when 'reportDir' 1050 | puts " " * currentDepth + "reportDirectory('#{i.text}')" 1051 | when 'targets' 1052 | handleTargets i, currentDepth 1053 | else 1054 | pp i 1055 | end 1056 | end 1057 | puts " " * depth + "}" 1058 | end 1059 | 1060 | def handleTargets(i, depth) 1061 | i.elements.each do |target| 1062 | case target.name 1063 | when 'hudson.plugins.rubyMetrics.rcov.model.MetricTarget' 1064 | handleMetricTarget target, depth 1065 | else 1066 | pp target 1067 | end 1068 | end 1069 | end 1070 | 1071 | def handleMetricTarget(i, depth) 1072 | meth = '' 1073 | signature = [] 1074 | 1075 | i.elements.each do |target| 1076 | case target.name 1077 | when 'metric' 1078 | case target.text 1079 | when 'TOTAL_COVERAGE' 1080 | meth = 'totalCoverage' 1081 | when 'CODE_COVERAGE' 1082 | meth = 'codeCoverage' 1083 | else 1084 | pp target 1085 | end 1086 | when 'healthy' 1087 | signature[0] = target.text || 0 1088 | when 'unhealthy' 1089 | signature[1] = target.text || 0 1090 | when 'unstable' 1091 | signature[2] = target.text || 0 1092 | else 1093 | pp target 1094 | end 1095 | end 1096 | 1097 | unless meth.empty? && signature.empty? 1098 | puts ' ' * depth + "#{meth}(#{signature.join ', '})" 1099 | end 1100 | end 1101 | 1102 | end 1103 | 1104 | class SiteMonitorRecorderHandler < Struct.new(:node) 1105 | def process(job_name, depth, indent) 1106 | configureBlock = ConfigureBlock.new [], indent: indent 1107 | node.elements.each do |i| 1108 | case i.name 1109 | when 'mSites' 1110 | configureBlock << "def mSitesNode = it / publishers / '#{node.name}' / 'mSites'" 1111 | i.elements.each do |mSite| 1112 | innerNode = [] 1113 | mSite.elements.each do |s| 1114 | case s.name 1115 | when 'mUrl' 1116 | innerNode << "'#{s.name}'('#{s.text}')" 1117 | when 'timeout' 1118 | innerNode << "'#{s.name}'(#{s.text})" 1119 | when 'successResponseCodes' 1120 | srcsInnerNode = [] 1121 | s.elements.each do |sInner| 1122 | case sInner.name 1123 | when 'int' 1124 | srcsInnerNode << "'#{sInner.name}'(#{sInner.text})" 1125 | else 1126 | pp sInner 1127 | end 1128 | end 1129 | innerNode << { "'#{s.name}'" => srcsInnerNode } 1130 | end 1131 | end 1132 | 1133 | unless innerNode.empty? 1134 | configureBlock << { "mSitesNode << '#{mSite.name}'" => innerNode } 1135 | end 1136 | end 1137 | end 1138 | end 1139 | configureBlock.save! 1140 | end 1141 | end 1142 | 1143 | class PerformancePublisherNodeHandler < Struct.new(:node) 1144 | def process(job_name, depth, indent) 1145 | innerNode = [] 1146 | 1147 | node.elements.each do |i| 1148 | case i.name 1149 | when 'errorFailedThreshold', 'errorUnstableThreshold', 'relativeFailedThresholdPositive', 1150 | 'relativeFailedThresholdNegative', 'relativeUnstableThresholdPositive', 'relativeUnstableThresholdNegative', 1151 | 'nthBuildNumber', 'configType', 'modeOfThreshold', 'compareBuildPrevious', 'modePerformancePerTestCase', 1152 | 'errorUnstableResponseTimeThreshold', 'modeRelativeThresholds', 'failBuildIfNoResultFile', 'modeThroughput', 1153 | 'modeEvaluation', 'ignoreFailedBuilds', 'ignoreUnstableBuilds', 'persistConstraintLog' 1154 | innerNode << "#{i.name} '#{i.text}'" 1155 | when 'parsers' 1156 | innerParsers = [] 1157 | i.elements.each do |inner| 1158 | case inner.name 1159 | when 'hudson.plugins.performance.JMeterParser' 1160 | innerParsers << { 1161 | "'#{inner.name}'" => inner.elements.collect do |ie| 1162 | "'#{ie.name}'('#{ie.text}')" 1163 | end 1164 | } 1165 | else 1166 | pp i 1167 | end 1168 | end 1169 | innerNode << {"'parsers'" => innerParsers} 1170 | else 1171 | pp i 1172 | end 1173 | end 1174 | 1175 | unless innerNode.empty? 1176 | ConfigureBlock.new([{ 1177 | "it / publishers / '#{node.name}' <<" => innerNode 1178 | }], 1179 | indent: indent 1180 | ).save! 1181 | end 1182 | end 1183 | end 1184 | 1185 | class GoalsNodeHandler < Struct.new(:node) 1186 | def process(job_name, depth, indent) 1187 | node.children.each do |i| 1188 | puts " " * depth + "goals('#{i.text}')" 1189 | end 1190 | end 1191 | end 1192 | 1193 | class ArtifactNodeHandler < Struct.new(:node) 1194 | def process(job_name, depth, indent) 1195 | puts " " * depth + "artifact {" 1196 | currentDepth = depth + indent 1197 | node.elements.each do |i| 1198 | case i.name 1199 | when 'groupId', 'artifactId' 1200 | puts " " * currentDepth + "#{i.name}('#{i.text}')" 1201 | else 1202 | pp i 1203 | end 1204 | end 1205 | puts " " * depth + "}" 1206 | end 1207 | end 1208 | 1209 | class BlockNodeHandler < Struct.new(:node) 1210 | def process(job_name, depth, indent) 1211 | puts " " * depth + "block {" 1212 | currentDepth = depth + indent 1213 | node.elements.each do |i| 1214 | case i.name 1215 | when 'buildStepFailureThreshold' 1216 | puts " " * currentDepth + "buildStepFailure('#{i.at_xpath('//buildStepFailureThreshold/name')&.text}')" 1217 | when 'unstableThreshold' 1218 | puts " " * currentDepth + "unstable('#{i.at_xpath('//unstableThreshold/name')&.text}')" 1219 | when 'failureThreshold' 1220 | puts " " * currentDepth + "failure('#{i.at_xpath('//failureThreshold/name')&.text}')" 1221 | else 1222 | pp i 1223 | end 1224 | end 1225 | puts " " * depth + "}" 1226 | end 1227 | end 1228 | 1229 | class TriggerNodeHandler < Struct.new(:node) 1230 | include Helper 1231 | 1232 | def process(job_name, depth, indent) 1233 | puts " " * depth + "downstreamParameterized {" 1234 | projects = node.at_xpath('//configs/*/projects')&.text.split(',').map{|s|"'#{s}'"}.join(',') 1235 | nestedDepth = depth + indent 1236 | puts " " * nestedDepth + "trigger([#{projects}]) {" 1237 | currentDepth = nestedDepth + indent 1238 | node.elements.each do |i| 1239 | case i.name 1240 | when 'configs' 1241 | i.elements.each do |j| 1242 | case j.name 1243 | when 'hudson.plugins.parameterizedtrigger.BlockableBuildTriggerConfig' 1244 | j.elements.each do |k| 1245 | case k.name 1246 | when 'configs', 'projects' 1247 | # intentionally ignored 1248 | when 'condition' 1249 | #puts " " * currentDepth + "#{k.name}('#{k.text}')" 1250 | when 'triggerWithNoParameters', 'buildAllNodesWithLabel' 1251 | #puts " " * currentDepth + "#{k.name}(#{k.text})" 1252 | when 'block' 1253 | BlockNodeHandler.new(k).process(job_name, currentDepth, indent) 1254 | else 1255 | pp k 1256 | end 1257 | end 1258 | when 'hudson.plugins.parameterizedtrigger.BuildTriggerConfig' 1259 | j.elements.each do |k| 1260 | case k.name 1261 | when 'projects' 1262 | # handled at beginning of method 1263 | when 'configs' 1264 | k.elements.each do |l| 1265 | case l.name 1266 | when 'hudson.plugins.parameterizedtrigger.PredefinedBuildParameters' 1267 | l.elements.each do |m| 1268 | case m.name 1269 | when 'properties' 1270 | puts " " * currentDepth + "predefinedProps(#{propertiesToMap m.text})" unless i.text.empty? 1271 | else 1272 | pp m 1273 | end 1274 | end 1275 | else 1276 | pp l 1277 | end 1278 | end 1279 | when 'condition' 1280 | puts " " * currentDepth + "#{k.name}('#{k.text}')" 1281 | when 'triggerWithNoParameters' 1282 | puts " " * currentDepth + "#{k.name}(#{truthy k.text})" 1283 | else 1284 | pp k 1285 | end 1286 | end 1287 | else 1288 | pp j.name 1289 | end 1290 | end 1291 | else 1292 | pp i 1293 | end 1294 | end 1295 | puts " " * nestedDepth + "}" 1296 | puts " " * depth + "}" 1297 | end 1298 | end 1299 | 1300 | class BuildersNodeHandler < Struct.new(:node) 1301 | def process(job_name, depth, indent) 1302 | currentDepth = depth 1303 | node.elements.each do |i| 1304 | case i.name 1305 | when 'hudson.plugins.parameterizedtrigger.TriggerBuilder' 1306 | puts " " * currentDepth + "steps {" 1307 | TriggerNodeHandler.new(i).process(job_name, currentDepth, indent) 1308 | puts " " * currentDepth + "}" 1309 | when 'hudson.tasks.Shell' 1310 | puts " " * currentDepth + "steps {" 1311 | txt = i.at_xpath('//hudson.tasks.Shell/command')&.text 1312 | txt = txt&.gsub(/\\/,"\\\\\\").gsub("'''", %q(\\\'\\\'\\\')) 1313 | puts " " * (currentDepth + indent) + "shell('''\\\n#{txt}\n''')" 1314 | puts " " * currentDepth + "}" 1315 | when 'org.jvnet.hudson.plugins.SSHBuilder' 1316 | puts " " * currentDepth + "steps {" 1317 | puts " " * (currentDepth + depth) + "remoteShell('#{i.at_xpath("//#{i.name}/siteName")&.text}') {" 1318 | puts " " * (currentDepth + depth + depth) + "command('''#{i.at_xpath("//#{i.name}/command")&.text}''')" 1319 | puts " " * (currentDepth + depth) + "}" 1320 | puts " " * currentDepth + "}" 1321 | when 'hudson.tasks.Maven' 1322 | puts " " * currentDepth + "steps {" 1323 | MavenBuilderHandler.new(i).process(job_name, currentDepth + indent, indent) 1324 | puts " " * currentDepth + "}" 1325 | when 'hudson.plugins.copyartifact.CopyArtifact' 1326 | puts " " * currentDepth + "steps {" 1327 | CopyArtifactHandler.new(i).process(job_name, currentDepth + indent, indent) 1328 | puts " " * currentDepth + "}" 1329 | when 'hudson.tasks.Ant' 1330 | puts " " * currentDepth + "steps {" 1331 | AntHandler.new(i).process(job_name, currentDepth + indent, indent) 1332 | puts " " * currentDepth + "}" 1333 | else 1334 | pp i 1335 | end 1336 | end 1337 | end 1338 | end 1339 | 1340 | class AntHandler < Struct.new(:node) 1341 | include Helper 1342 | 1343 | def process(job_name, depth, indent) 1344 | puts " " * depth + "ant {" 1345 | currentDepth = depth + indent 1346 | node.elements.each do |i| 1347 | case i.name 1348 | when 'targets' 1349 | puts " " * currentDepth + "#{i.name}([#{toGroovyListOfStrings i.text}])" unless i.text.empty? 1350 | when 'antName' 1351 | puts " " * currentDepth + "antInstallation(#{formatText i.text})" unless i.text.empty? 1352 | when 'buildFile' 1353 | puts " " * currentDepth + "#{i.name}(#{formatText i.text})" unless i.text.empty? 1354 | when 'properties' 1355 | puts " " * currentDepth + "props(#{propertiesToMap i.text})" unless i.text.empty? 1356 | when 'antOpts' 1357 | puts " " * currentDepth + "javaOpts([#{toGroovyListOfStrings i.text}])" unless i.text.empty? 1358 | else 1359 | pp i 1360 | end 1361 | end 1362 | puts " " * depth + "}" 1363 | end 1364 | 1365 | end 1366 | 1367 | class CopyArtifactHandler < Struct.new(:node) 1368 | include Helper 1369 | 1370 | def process(job_name, depth, indent) 1371 | upstreamProject = node.at_xpath("//#{node.name}/project")&.text 1372 | puts " " * depth + "copyArtifacts('#{upstreamProject}') {" 1373 | currentDepth = depth + indent 1374 | node.elements.each do |i| 1375 | case i.name 1376 | when 'doNotFingerprintArtifacts' 1377 | puts " " * currentDepth + "fingerprintArtifacts(#{! truthy i.text})" unless i.text.empty? 1378 | when 'excludes' 1379 | puts " " * currentDepth + "excludePatterns(#{toGroovyListOfStrings i.text})" unless i.text.empty? 1380 | when 'target' 1381 | puts " " * currentDepth + "targetDirectory(#{formatText i.text})" unless i.text.empty? 1382 | when 'selector' 1383 | buildSelector i, currentDepth, indent 1384 | when 'filter' 1385 | puts " " * currentDepth + "includePatterns(#{toGroovyListOfStrings i.text})" unless i.text.empty? 1386 | when 'project' 1387 | # handled above 1388 | else 1389 | pp i 1390 | end 1391 | end 1392 | puts " " * depth + "}" 1393 | end 1394 | 1395 | def buildSelector currentNode, depth, indent 1396 | puts " " * depth + "buildSelector {" 1397 | currentDepth = depth + indent 1398 | case currentNode.attribute('class').value 1399 | when 'hudson.plugins.copyartifact.StatusBuildSelector' 1400 | stable = currentNode.at_xpath("//#{currentNode.name}/stable")&.text 1401 | puts " " * currentDepth + "latestSuccessful(#{truthy stable})" 1402 | end 1403 | puts " " * depth + "}" 1404 | end 1405 | 1406 | end 1407 | 1408 | class MavenBuilderHandler < Struct.new(:node) 1409 | def process(job_name, depth, indent) 1410 | innerNode = [] 1411 | currentDepth = depth + indent 1412 | configureBlock = ConfigureBlock.new [], indent: indent, indent_times: (currentDepth / indent rescue 1) 1413 | 1414 | node.elements.each do |i| 1415 | case i.name 1416 | when 'properties' 1417 | i.text.split("\n").each do |property| 1418 | key, value = *property.split('=') 1419 | innerNode << ' ' * currentDepth + "property('#{key}', '#{value}')" 1420 | end 1421 | when 'usePrivateRepository' 1422 | configureBlock << "'#{i.name}'(#{i.text})" unless i.text.empty? 1423 | when 'testDataPublishers' 1424 | # TODO - don't have working example for this yet 1425 | when 'settings' 1426 | next if i.text.empty? 1427 | path = i.at_xpath("//builders/#{node.name}/#{i.name}/path")&.text 1428 | configureBlock << {"it / '#{i.name}'(class: '#{i[:class]}')" => ["'path'('#{path}')"]} 1429 | when 'globalSettings' 1430 | next if i.text.empty? 1431 | path = i.at_xpath("//builders/#{node.name}/#{i.name}/path")&.text 1432 | configureBlock << {"it / '#{i.name}'(class: 'jenkins.mvn.FilePathGlobalSettingsProvider')" => ["'path'('#{path}')"]} 1433 | when 'targets' 1434 | innerNode << ' ' * currentDepth + "goals('#{i.text}')" unless i.text.empty? 1435 | when 'pom' 1436 | innerNode << ' ' * currentDepth + "rootPOM('#{i.text}')" unless i.text.empty? 1437 | when 'mavenName' 1438 | innerNode << ' ' * currentDepth + "mavenInstallation('#{i.text}')" unless i.text.empty? 1439 | else 1440 | pp i 1441 | end 1442 | end 1443 | 1444 | unless innerNode.empty? 1445 | puts ' ' * depth + "maven {" 1446 | puts innerNode 1447 | puts configureBlock unless configureBlock.empty? 1448 | puts ' ' * depth + '}' 1449 | end 1450 | end 1451 | end 1452 | 1453 | class MavenDefinitionNodeHandler < Struct.new(:node) 1454 | include Helper 1455 | 1456 | def process(job_name, depth, indent) 1457 | puts "mavenJob('#{job_name}') {" 1458 | currentDepth = depth + indent 1459 | node.elements.each do |i| 1460 | case i.name 1461 | when 'actions', 'reporters', 'buildWrappers', 'prebuilders', 'postbuilders', 1462 | 'aggregatorStyleBuild', 'ignoreUpstremChanges', 'processPlugins', 'mavenValidationLevel' 1463 | # todo: not yet implemented 1464 | when 'description' 1465 | if !(i.text.nil? || i.text.empty?) 1466 | puts " " * currentDepth + "#{i.name}('''\\\n#{removeCarriage i.text}\n''')" 1467 | end 1468 | when 'properties' 1469 | PropertiesNodeHandler.new(i).process(job_name, currentDepth, indent) 1470 | when 'definition' 1471 | DefinitionNodeHandler.new(i).process(job_name, currentDepth, indent) 1472 | when 'triggers' 1473 | TriggerDefinitionNodeHandler.new(i).process(job_name, currentDepth, indent) 1474 | when 'authToken' 1475 | puts " " * currentDepth + "authenticationToken(token = '#{i.text}')" 1476 | when 'mavenOpts', 'rootPOM', 'customWorkspace' 1477 | puts " " * currentDepth + "#{i.name}('#{i.text}')" 1478 | when 'keepDependencies', 'concurrentBuild', 'disabled', 'fingerprintingDisabled', 1479 | 'runHeadless', 'resolveDependencies', 'siteArchivingDisabled', 'archivingDisabled', 1480 | 'incrementalBuild', 'quietPeriod' 1481 | puts " " * currentDepth + "#{i.name}(#{i.text})" 1482 | when 'goals' 1483 | GoalsNodeHandler.new(i).process(job_name, currentDepth, indent) 1484 | when 'publishers' 1485 | PublishersNodeHandler.new(i).process(job_name, currentDepth, indent) 1486 | when 'scm' 1487 | ScmDefinitionNodeHandler.new(i).process(job_name, currentDepth, indent) 1488 | when 'canRoam', 'assignedNode' 1489 | if i.name == 'canRoam' and i.text == 'true' 1490 | puts " " * currentDepth + "label()" 1491 | elsif i.name == 'assignedNode' 1492 | puts " " * currentDepth + "label('#{i.text}')" 1493 | end 1494 | when 'blockBuildWhenDownstreamBuilding' 1495 | if i.text == 'true' 1496 | puts " " * currentDepth + "blockOnDownstreamProjects()" 1497 | end 1498 | when 'blockBuildWhenUpstreamBuilding' 1499 | if i.text == 'true' 1500 | puts " " * currentDepth + "blockOnUpstreamProjects()" 1501 | end 1502 | when 'disableTriggerDownstreamProjects' 1503 | puts " " * currentDepth + "disableDownstreamTrigger(#{i.text})" 1504 | when 'blockTriggerWhenBuilding' 1505 | # todo: do this when jobdsl supports it 1506 | when 'settings', 'globalSettings' 1507 | # todo: is this necessary? 1508 | when 'rootModule' 1509 | # todo: is this necessary? 1510 | when 'runPostStepsIfResult' 1511 | puts " " * currentDepth + "postBuildSteps('#{i.at_xpath('//runPostStepsIfResult/name')&.text}') {" 1512 | puts " " * currentDepth + "}" 1513 | else 1514 | pp i 1515 | end 1516 | end 1517 | ConfigureBlock.print 1518 | puts "}" 1519 | end 1520 | end 1521 | 1522 | class FreestyleDefinitionNodeHandler < Struct.new(:node) 1523 | include Helper 1524 | 1525 | def process(job_name, depth, indent) 1526 | puts "freeStyleJob('#{job_name}') {" 1527 | currentDepth = depth + indent 1528 | node.elements.each do |i| 1529 | case i.name 1530 | when 'actions', 'buildWrappers' 1531 | # todo: not yet implemented 1532 | when 'description' 1533 | if !(i.text.nil? || i.text.empty?) 1534 | puts " " * currentDepth + "#{i.name}('''\\\n#{removeCarriage i.text}\n''')" 1535 | end 1536 | when 'keepDependencies', 'quietPeriod' 1537 | puts " " * currentDepth + "#{i.name}(#{i.text})" 1538 | when 'properties' 1539 | PropertiesNodeHandler.new(i).process(job_name, currentDepth, indent) 1540 | when 'scm' 1541 | ScmDefinitionNodeHandler.new(i).process(job_name, currentDepth, indent) 1542 | when 'canRoam', 'assignedNode' 1543 | if i.name == 'canRoam' and i.text == 'true' 1544 | puts " " * currentDepth + "label()" 1545 | elsif i.name == 'assignedNode' 1546 | puts " " * currentDepth + "label('#{i.text}')" 1547 | end 1548 | # when 'keepDependencies', 'concurrentBuild', 'disabled', 'fingerprintingDisabled', 1549 | # 'runHeadless', 'resolveDependencies', 'siteArchivingDisabled', 'archivingDisabled', 'incrementalBuild' 1550 | when 'disabled', 'concurrentBuild' 1551 | puts " " * currentDepth + "#{i.name}(#{i.text})" 1552 | when 'blockBuildWhenDownstreamBuilding' 1553 | if i.text == 'true' 1554 | puts " " * currentDepth + "blockOnDownstreamProjects()" 1555 | end 1556 | when 'blockBuildWhenUpstreamBuilding' 1557 | if i.text == 'true' 1558 | puts " " * currentDepth + "blockOnUpstreamProjects()" 1559 | end 1560 | when 'triggers' 1561 | TriggerDefinitionNodeHandler.new(i).process(job_name, currentDepth, indent) 1562 | when 'publishers' 1563 | PublishersNodeHandler.new(i).process(job_name, currentDepth, indent) 1564 | when 'builders' 1565 | BuildersNodeHandler.new(i).process(job_name, currentDepth, indent) 1566 | when 'logRotator' 1567 | LogRotatorNodeHandler.new(i).process(job_name, currentDepth, indent) 1568 | else 1569 | pp i 1570 | end 1571 | end 1572 | ConfigureBlock.print 1573 | puts "}" 1574 | end 1575 | end 1576 | 1577 | # Used to implement JobDSL's `configure {...}` type syntax. This class 1578 | # approaches this problem as if the configure block is an array of lines and 1579 | # each line can be either String, Hash, or Array. This is implemented this 1580 | # way due to the way that JobDSL's configure block works and its flexibility. 1581 | # 1582 | # See their docs for details on this: 1583 | # https://github.com/jenkinsci/job-dsl-plugin/wiki/The-Configure-Block 1584 | # 1585 | # This can be used like: 1586 | # 1587 | # configureBlock = ConfigureBlock.new [], indent: 4 1588 | # configureBlock << '// this would be the very first line within the configure block' 1589 | # configureBlock << 'it / "this is the groovy reserved `it` to indicate the node we are on' 1590 | # configureBlock << {'it / "can use a hash as well to describe inner blocks" <<' => ["'inner'('element')]} 1591 | # 1592 | # configureBlock.save! #this will write `self` into the class constant that #print can use 1593 | # ConfigureBlock.print #this will print all ConfigureBlock's that have been #save!'d 1594 | # 1595 | # Another way to do this is like: 1596 | # 1597 | # arr = [ 1598 | # "def foo = it / 'inner' / 'xml'", 1599 | # "(foo / 'bar').setValue('bazz')", 1600 | # { 1601 | # "it / 'using' / 'block' <<" => [ 1602 | # "'inner'('element')", 1603 | # "'another'('element')", 1604 | # ] 1605 | # }, 1606 | # { 1607 | # "it / 'another' / 'using' / 'block'" => [ 1608 | # {"'further'" => ["'nested'('element')"]}, 1609 | # ] 1610 | # } 1611 | # ] 1612 | # 1613 | # configureBlock = ConfigureBlock.new arr, indent: 4 1614 | # configureBlock.save! 1615 | # ConfigureBlock.print 1616 | # 1617 | # You can also define multiple configure blocks just by instantiating a new one 1618 | # and calling #save! on that object. 1619 | class ConfigureBlock 1620 | NOT_SO_CONSTANT_CONFIGURE_BLOCKS = [] 1621 | 1622 | def self.print 1623 | return if NOT_SO_CONSTANT_CONFIGURE_BLOCKS.empty? 1624 | NOT_SO_CONSTANT_CONFIGURE_BLOCKS.each do |configureBlock| 1625 | puts configureBlock 1626 | end 1627 | end 1628 | 1629 | def initialize arr = [], opts = {} 1630 | @lines = arr 1631 | @indent = opts[:indent] || 4 1632 | @indent_times = opts[:indent_times] || 1 1633 | end 1634 | 1635 | def << e 1636 | @lines << e 1637 | end 1638 | 1639 | def unshift e 1640 | @lines.unshift e 1641 | end 1642 | 1643 | def empty? 1644 | @lines.empty? 1645 | end 1646 | 1647 | def save! 1648 | NOT_SO_CONSTANT_CONFIGURE_BLOCKS.push self 1649 | end 1650 | 1651 | def to_s 1652 | first = format 'configure {' 1653 | middle = @lines.inject('') do |ret, line| 1654 | ret = format line, @indent_times + 1 1655 | ret 1656 | end 1657 | last = format '}' 1658 | "#{first}#{middle}#{last}" 1659 | end 1660 | 1661 | def format line, indent_times = @indent_times 1662 | case line 1663 | when String 1664 | indention line, indent_times 1665 | when Hash 1666 | first = line.keys.first + ' {' 1667 | indention first, indent_times 1668 | format line.values.first, indent_times + 1 1669 | indention '}', indent_times 1670 | when Array 1671 | line.each do |l| 1672 | format l, indent_times 1673 | end 1674 | end 1675 | end 1676 | 1677 | def indention line, indent_times = 1 1678 | puts ' ' * @indent * indent_times + "#{line}\n" 1679 | end 1680 | 1681 | end 1682 | 1683 | depth = 0 1684 | indent = 4 1685 | 1686 | OptionParser.new do |opts| 1687 | opts.banner = "Usage: ruby jenkins-xml-to-jobdsl.rb [OPTIONS] path/to/config.xml" 1688 | 1689 | opts.on( 1690 | "-i indentation_level", 1691 | "--indent=indentation_level", 1692 | "Indentation level (default 4)", 1693 | ) do |indentation_level| 1694 | indent = indentation_level.to_i || 4 1695 | end 1696 | end.parse! 1697 | 1698 | f = ARGV.shift 1699 | if !File.file?(f) 1700 | exit 1 1701 | end 1702 | 1703 | f = File.absolute_path(f) 1704 | d = File.dirname(f) 1705 | job = d.split("/")[-1] 1706 | Nokogiri::XML::Reader(File.open(f)).each do |node| 1707 | if node.name == 'flow-definition' && node.node_type == Nokogiri::XML::Reader::TYPE_ELEMENT 1708 | FlowDefinitionNodeHandler.new( 1709 | Nokogiri::XML(node.outer_xml).at('./flow-definition') 1710 | ).process(job, depth, indent) 1711 | elsif node.name == 'maven2-moduleset' && node.node_type == Nokogiri::XML::Reader::TYPE_ELEMENT 1712 | MavenDefinitionNodeHandler.new( 1713 | Nokogiri::XML(node.outer_xml).at('./maven2-moduleset') 1714 | ).process(job, depth, indent) 1715 | elsif node.name == 'project' && node.node_type == Nokogiri::XML::Reader::TYPE_ELEMENT && node.depth == 0 1716 | FreestyleDefinitionNodeHandler.new( 1717 | Nokogiri::XML(node.outer_xml).at('./project') 1718 | ).process(job, depth, indent) 1719 | else 1720 | #pp node 1721 | end 1722 | end 1723 | 1724 | -------------------------------------------------------------------------------- /server.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require 'rubygems' 3 | require 'sinatra' 4 | require 'haml' 5 | require 'json' 6 | 7 | set :bind, '0.0.0.0' 8 | 9 | # Handle GET-request (Show the upload form) 10 | get "/" do 11 | haml :upload 12 | end 13 | 14 | # Handle POST-request (Receive and save the uploaded file) 15 | post "/" do 16 | unless (jobname = params[:name]) && 17 | (tempfile = params[:file][:tempfile]) && 18 | (filename = params[:file][:filename]) 19 | halt 422, JSON({ 20 | message: "Validation failed", 21 | errors: "parameters missing. file and name parameters required." 22 | }) 23 | end 24 | time = Time.now.strftime("%Y%m%d%H%M%S") 25 | dir = "/var/www/uploads/#{time}/#{jobname}" 26 | filepath = dir + "/#{filename}" 27 | FileUtils.mkdir_p(dir) 28 | FileUtils.cp(tempfile.path, filepath) 29 | output = `ruby jenkins-xml-to-jobdsl.rb #{filepath}` 30 | "#{output}" 31 | end 32 | -------------------------------------------------------------------------------- /tests/pipeline-example/config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | false 5 | 6 | 7 | 8 | 2 9 | 10 | 11 | https://github.com/kitconcept/jenkins-pipeline-examples.git 12 | 13 | 14 | 15 | 16 | */master 17 | 18 | 19 | false 20 | 21 | 22 | 23 | Jenkinsfile 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /views/upload.haml: -------------------------------------------------------------------------------- 1 | %html 2 | %body 3 | %h1 File uploader! 4 | %form(method="post" enctype='multipart/form-data') 5 | %input(type='text' name='name') 6 | %br 7 | %input(type='file' name='file') 8 | %br 9 | %input(type='submit' value='Upload!') 10 | --------------------------------------------------------------------------------