From e39c9541b7473cf3725ab858e22aad2b42a93eb3 Mon Sep 17 00:00:00 2001 From: Igor Moochnick Date: Tue, 4 Mar 2014 22:19:09 -0500 Subject: [PATCH] Initial commit. --- .ruby-gemset | 1 + .ruby-version | 1 + Gemfile | 4 + README.md | 134 ++++++- Rakefile | 68 ++++ bin/generate | 28 ++ config/login.yml | 24 ++ jenkins_pipeline_builder.gemspec | 42 +++ lib/jenksin_pipeline_builder.rb | 39 ++ lib/jenksin_pipeline_builder/builders.rb | 72 ++++ lib/jenksin_pipeline_builder/cli/base.rb | 69 ++++ lib/jenksin_pipeline_builder/cli/helper.rb | 68 ++++ lib/jenksin_pipeline_builder/cli/pipeline.rb | 40 ++ lib/jenksin_pipeline_builder/cli/view.rb | 40 ++ lib/jenksin_pipeline_builder/compiler.rb | 81 ++++ lib/jenksin_pipeline_builder/generator.rb | 346 ++++++++++++++++++ lib/jenksin_pipeline_builder/job_builder.rb | 82 +++++ .../module_registry.rb | 82 +++++ lib/jenksin_pipeline_builder/publishers.rb | 113 ++++++ lib/jenksin_pipeline_builder/triggers.rb | 38 ++ lib/jenksin_pipeline_builder/utils.rb | 46 +++ lib/jenksin_pipeline_builder/version.rb | 25 ++ lib/jenksin_pipeline_builder/view.rb | 259 +++++++++++++ lib/jenksin_pipeline_builder/wrappers.rb | 91 +++++ lib/jenksin_pipeline_builder/xml_helper.rb | 40 ++ spec/func_tests/spec_helper.rb | 18 + spec/func_tests/view_spec.rb | 93 +++++ spec/unit_tests/compiler_spec.rb | 19 + .../fixtures/files/Job-Build-Flow.xml | 57 +++ .../fixtures/files/Job-Build-Flow.yaml | 22 ++ .../fixtures/files/Job-Build-Maven.xml | 90 +++++ .../fixtures/files/Job-Build-Maven.yaml | 26 ++ spec/unit_tests/fixtures/files/Job-DSL.yaml | 14 + spec/unit_tests/fixtures/files/Job-DSL1.xml | 27 ++ spec/unit_tests/fixtures/files/Job-DSL2.xml | 25 ++ .../fixtures/files/Job-Gem-Build.xml | 142 +++++++ .../fixtures/files/Job-Gem-Build.yaml | 41 +++ .../files/Job-Generate-From-Template.xml | 32 ++ .../files/Job-Generate-From-Template.yaml | 8 + .../fixtures/files/Job-Multi-Project.xml | 117 ++++++ .../fixtures/files/Job-Multi-Project.yaml | 36 ++ spec/unit_tests/fixtures/files/project.yaml | 15 + spec/unit_tests/generator_spec.rb | 67 ++++ spec/unit_tests/module_registry_spec.rb | 19 + spec/unit_tests/resolve_dependencies_spec.rb | 230 ++++++++++++ spec/unit_tests/spec_helper.rb | 29 ++ 46 files changed, 2959 insertions(+), 1 deletion(-) create mode 100644 .ruby-gemset create mode 100644 .ruby-version create mode 100644 Gemfile create mode 100644 Rakefile create mode 100755 bin/generate create mode 100644 config/login.yml create mode 100644 jenkins_pipeline_builder.gemspec create mode 100644 lib/jenksin_pipeline_builder.rb create mode 100644 lib/jenksin_pipeline_builder/builders.rb create mode 100644 lib/jenksin_pipeline_builder/cli/base.rb create mode 100644 lib/jenksin_pipeline_builder/cli/helper.rb create mode 100644 lib/jenksin_pipeline_builder/cli/pipeline.rb create mode 100644 lib/jenksin_pipeline_builder/cli/view.rb create mode 100644 lib/jenksin_pipeline_builder/compiler.rb create mode 100644 lib/jenksin_pipeline_builder/generator.rb create mode 100644 lib/jenksin_pipeline_builder/job_builder.rb create mode 100644 lib/jenksin_pipeline_builder/module_registry.rb create mode 100644 lib/jenksin_pipeline_builder/publishers.rb create mode 100644 lib/jenksin_pipeline_builder/triggers.rb create mode 100644 lib/jenksin_pipeline_builder/utils.rb create mode 100644 lib/jenksin_pipeline_builder/version.rb create mode 100644 lib/jenksin_pipeline_builder/view.rb create mode 100644 lib/jenksin_pipeline_builder/wrappers.rb create mode 100644 lib/jenksin_pipeline_builder/xml_helper.rb create mode 100644 spec/func_tests/spec_helper.rb create mode 100644 spec/func_tests/view_spec.rb create mode 100644 spec/unit_tests/compiler_spec.rb create mode 100644 spec/unit_tests/fixtures/files/Job-Build-Flow.xml create mode 100644 spec/unit_tests/fixtures/files/Job-Build-Flow.yaml create mode 100644 spec/unit_tests/fixtures/files/Job-Build-Maven.xml create mode 100644 spec/unit_tests/fixtures/files/Job-Build-Maven.yaml create mode 100644 spec/unit_tests/fixtures/files/Job-DSL.yaml create mode 100644 spec/unit_tests/fixtures/files/Job-DSL1.xml create mode 100644 spec/unit_tests/fixtures/files/Job-DSL2.xml create mode 100644 spec/unit_tests/fixtures/files/Job-Gem-Build.xml create mode 100644 spec/unit_tests/fixtures/files/Job-Gem-Build.yaml create mode 100644 spec/unit_tests/fixtures/files/Job-Generate-From-Template.xml create mode 100644 spec/unit_tests/fixtures/files/Job-Generate-From-Template.yaml create mode 100644 spec/unit_tests/fixtures/files/Job-Multi-Project.xml create mode 100644 spec/unit_tests/fixtures/files/Job-Multi-Project.yaml create mode 100644 spec/unit_tests/fixtures/files/project.yaml create mode 100644 spec/unit_tests/generator_spec.rb create mode 100644 spec/unit_tests/module_registry_spec.rb create mode 100644 spec/unit_tests/resolve_dependencies_spec.rb create mode 100644 spec/unit_tests/spec_helper.rb diff --git a/.ruby-gemset b/.ruby-gemset new file mode 100644 index 0000000..b45f591 --- /dev/null +++ b/.ruby-gemset @@ -0,0 +1 @@ +jenkins_pipeline_builder diff --git a/.ruby-version b/.ruby-version new file mode 100644 index 0000000..1dbd866 --- /dev/null +++ b/.ruby-version @@ -0,0 +1 @@ +ruby-2.0.0-p247 \ No newline at end of file diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000..434273b --- /dev/null +++ b/Gemfile @@ -0,0 +1,4 @@ +source "https://rubygems.org" + +# Specify your gem's dependencies in jenkins_pipeline_builder.gemspec +gemspec diff --git a/README.md b/README.md index 73afffb..9ea7b63 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,136 @@ jenkins-pipeline-builder ======================== -YAML driven CI pipeline generator enabling to version your artifact pipelines alongside with the artifact source itself. +YAML driven CI Jenkins Pipeline Builder enabling to version your artifact pipelines alongside with the artifact source itself. + + +# JenkinsPipeline::Generator + +TODO: Write a gem description + +This is how you can bootstrap a pipeline: + + generate pipeline -d -c config/login.yml bootstrap ./pipeline-archetype + +## Installation + +Add this line to your application's Gemfile: + + gem 'jenkins_pipeline_builder' + +And then execute: + + $ bundle + +Or install it yourself as: + + $ gem install jenkins_pipeline_builder + + brew install libxml2 libxslt + [optional] brew link libxml2 libxslt + gem install nokogiri + +## Usage + +TODO: Write usage instructions here + +## Contributing + +1. Fork it +2. Create your feature branch (`git checkout -b my-new-feature`) +3. Commit your changes (`git commit -am 'Add some feature'`) +4. Push to the branch (`git push origin my-new-feature`) +5. Create new Pull Request + +## Job DSL +Here's a high level overview of what's available: + +```yaml +- job: + name: nameStr # Name of your Job + job_type: free_style # Optional [free_style|multi_project] + parameters: + - name: param_name + type: string + default: default value + description: text + scm_provider: git # See more info on Jenkins Api Client + scm_url: git@github.com:your_url_here + scm_branch: master + scm_params: + excuded_users: user + local_branch: branch_name + recursive_update: true + wipe_workspace: true + shell_command: '. commit_build.sh' + hipchat: + room: room name here + start-notify: true + builders: + - job_builder: + child_jobs: + - job-name-1 + - job-name-2 + mark_phase: SUCCESSFUL + - inject_vars_file: build_job_info + - shell_command: | + echo 'Doing some work' + run command1 + - maven3: + goals: -B clean + wrappers: + - timestamp: true + - ansicolor: true + - artifactory: + url: 'https://url.com/path' + artifactory-name: 'key' + target-repo: gems-local + publish: 'pkg/*.gem' + publish-build-info: true + - inject_env_var: | + VAR1 = value_1 + VAR2 = value_2 + - inject_passwords: + - name: pwd_name + value: some_encrypted_password + - rvm: "ruby-version@ruby-gemset" + publishers: + - junit_result: + test_results: 'out/**/*.xml' + - git: + push-merge: true + push-only-if-success: false + - hipchat: + jenkinsUrl: 'https://jenkins_url/' + authToken: 'auth_token' + room: 'room name' + - coverage_result: + report_dir: out/coverage/rcov + total: + healthy: 80 + unhealthy: 0 + unstable: 0 + code: + healthy: 80 + unhealthy: 0 + unstable: 0 + - description_setter: + regexp: See the build details at (.*) + description: 'Build Details: \1' + - downstream: + project: project_name + data: + - params: | + PARAM1=value1 + PARAM2=value2 + - file: promote-job-params + triggers: + - git_push: true + - scm_polling: 'H/5 * * * *' + build_flow: | + guard { + build("job_name1", param1: params["param1"]); + } rescue { + build("job_name2", param1: build21.environment.get("some_var")) + } +``` diff --git a/Rakefile b/Rakefile new file mode 100644 index 0000000..f40e46b --- /dev/null +++ b/Rakefile @@ -0,0 +1,68 @@ +# +# Copyright (c) 2014 Igor Moochnick +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# + +#!/usr/bin/env rake +require 'rspec/core/rake_task' +require 'yard' + +RSpec::Core::RakeTask.new(:spec, :tag) do |t, task_args| + t.pattern = ".spec/**/*_spec.rb" + t.verbose = true + t.fail_on_error = false + t.rspec_opts = spec_output 'spec.xml' +end + +def spec_output(filename) + "--color --format documentation --format RspecJunitFormatter --out out/#{filename}" +end + +RSpec::Core::RakeTask.new(:unit_tests) do |spec| + spec.pattern = FileList['spec/unit_tests/*_spec.rb'] + spec.rspec_opts = ['--color', '--format documentation'] +end + +RSpec::Core::RakeTask.new(:func_tests) do |spec| + spec.pattern = FileList['spec/func_tests/*_spec.rb'] + spec.rspec_opts = ['--color', '--format documentation'] +end + +RSpec::Core::RakeTask.new(:test) do |spec| + spec.pattern = FileList['spec/*/*.rb'] + spec.rspec_opts = ['--color', '--format documentation'] +end + +YARD::Config.load_plugin 'thor' +YARD::Rake::YardocTask.new do |t| + t.files = ['lib/**/*.rb', 'lib/**/**/*.rb'] +end + +namespace :doc do + # This task requires that graphviz is installed locally. For more info: + # http://www.graphviz.org/ + desc "Generates the class diagram using the yard generated dot file" + task :generate_class_diagram do + puts "Generating the dot file..." + `yard graph --file jenkins_api_client.dot` + puts "Generating class diagram from the dot file..." + `dot jenkins_api_client.dot -Tpng -o jenkins_api_client_class_diagram.png` + end +end diff --git a/bin/generate b/bin/generate new file mode 100755 index 0000000..a15e34e --- /dev/null +++ b/bin/generate @@ -0,0 +1,28 @@ +# +# Copyright (c) 2014 Igor Moochnick +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# + +#!/usr/bin/env ruby + +$LOAD_PATH.unshift(File.expand_path(File.dirname(__FILE__) + "/../lib")) +require 'jenksin_pipeline_builder' +JenkinsPipelineBuilder::CLI::Base.start + diff --git a/config/login.yml b/config/login.yml new file mode 100644 index 0000000..983397f --- /dev/null +++ b/config/login.yml @@ -0,0 +1,24 @@ +# The Jenkins server and login information can be stored in a YAML file like this +# so we don't have to pass in the parameters every we login to the server +# through this API client. + +# This file contains the following four parameters + +# The IP address of the Jenkins CI Server + +:server_ip: server_ip +#:server_url: https://server_url/ + +# The port number on which the Jenkins listens on. The default is 8080 + +#:server_port: 8080 + +# If there is a web server routing your Jenkins requests you might need this + +#:jenkins_path: "" + +# The username and password to authenticate to the server + +:username: username +#:password: my_password +:password_base64: my_base64_password diff --git a/jenkins_pipeline_builder.gemspec b/jenkins_pipeline_builder.gemspec new file mode 100644 index 0000000..7632728 --- /dev/null +++ b/jenkins_pipeline_builder.gemspec @@ -0,0 +1,42 @@ +# coding: utf-8 +lib = File.expand_path('../lib', __FILE__) +$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) +require 'jenksin_pipeline_builder/version' + +Gem::Specification.new do |spec| + spec.name = 'jenkins_pipeline_builder' + spec.version = JenkinsPipelineBuilder::VERSION + spec.authors = ['Igor Moochnick'] + spec.email = %w(igor.moochnick@gmail.com) + spec.description = %q{This is a simple and easy-to-use Jenkins Pipeline generator with features focused on +automating Job & Pipeline creation from the YAML files checked-in with your application source code} + spec.summary = %q{This gem is will boostrap your Jenkins pipelines} + spec.homepage = 'https://github.com/IgorShare/jenkins_pipeline_builder' + spec.license = 'MIT' + + spec.files = `git ls-files`.split($/) + spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) } + spec.test_files = spec.files.grep(%r{^(test|spec|features)/}) + spec.require_paths = ["lib"] + + ENV['NOKOGIRI_USE_SYSTEM_LIBRARIES']='true' + + spec.add_dependency 'nokogiri', '~> 1.5.0' + spec.add_dependency 'jenkins_api_client', '~> 0.14.1' + spec.add_dependency 'thor', '>= 0.18.0' + + spec.add_development_dependency 'bundler', '~> 1.3' + spec.add_development_dependency 'rake' + spec.add_development_dependency 'rspec', '~> 2.14' + spec.add_development_dependency 'bump' + spec.add_development_dependency 'gem-release' + spec.add_development_dependency 'pry' + spec.add_development_dependency 'simplecov' + spec.add_development_dependency 'simplecov-rcov' + spec.add_development_dependency 'kwalify' + spec.add_development_dependency 'equivalent-xml' + spec.add_development_dependency 'yard-thor' + spec.add_development_dependency 'yard' + spec.add_development_dependency 'rspec_junit_formatter' + spec.add_development_dependency 'webmock' +end diff --git a/lib/jenksin_pipeline_builder.rb b/lib/jenksin_pipeline_builder.rb new file mode 100644 index 0000000..3c7388d --- /dev/null +++ b/lib/jenksin_pipeline_builder.rb @@ -0,0 +1,39 @@ +# +# Copyright (c) 2014 Igor Moochnick +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# + +require 'jenksin_pipeline_builder/version' +require 'jenksin_pipeline_builder/utils' +require 'jenksin_pipeline_builder/xml_helper' +require 'jenksin_pipeline_builder/compiler' +require 'jenksin_pipeline_builder/job_builder' +require 'jenksin_pipeline_builder/module_registry' +require 'jenksin_pipeline_builder/builders' +require 'jenksin_pipeline_builder/wrappers' +require 'jenksin_pipeline_builder/triggers' +require 'jenksin_pipeline_builder/publishers' +require 'jenksin_pipeline_builder/view' +require 'jenksin_pipeline_builder/generator' + +require 'jenksin_pipeline_builder/cli/helper' +require 'jenksin_pipeline_builder/cli/view' +require 'jenksin_pipeline_builder/cli/pipeline' +require 'jenksin_pipeline_builder/cli/base' diff --git a/lib/jenksin_pipeline_builder/builders.rb b/lib/jenksin_pipeline_builder/builders.rb new file mode 100644 index 0000000..3241288 --- /dev/null +++ b/lib/jenksin_pipeline_builder/builders.rb @@ -0,0 +1,72 @@ +# +# Copyright (c) 2014 Igor Moochnick +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# + +module JenkinsPipelineBuilder + class Builders + def self.build_multijob_builder(params, xml) + xml.send('com.tikal.jenkins.plugins.multijob.MultiJobBuilder') { + xml.phaseName 'Child Jobs' + xml.phaseJobs { + proc = lambda do |xml, name| + xml.send('com.tikal.jenkins.plugins.multijob.PhaseJobsConfig') { + xml.jobName name + xml.currParams false + xml.exposedSCM false + xml.configs { + xml.send('hudson.plugins.parameterizedtrigger.PredefinedBuildParameters') { + xml.properties 'PARENT_WORKSPACE=${WORKSPACE}' + } + } + } + end + params[:child_jobs].each do |name| + proc.call xml, name + end + } + xml.continuationCondition params[:mark_phase] + } + end + + def self.build_maven3(params, xml) + xml.send('org.jfrog.hudson.maven3.Maven3Builder') { + xml.mavenName 'tools-maven-3.0.3' + xml.rootPom params[:rootPom] + xml.goals params[:goals] + xml.mavenOpts params[:options] + } + end + + def self.build_shell_command(param, xml) + xml.send('hudson.tasks.Shell') { + xml.command param + } + end + + def self.build_environment_vars_injector(params, xml) + xml.EnvInjectBuilder { + xml.info { + xml.propertiesFilePath params + } + } + end + end +end diff --git a/lib/jenksin_pipeline_builder/cli/base.rb b/lib/jenksin_pipeline_builder/cli/base.rb new file mode 100644 index 0000000..21d917c --- /dev/null +++ b/lib/jenksin_pipeline_builder/cli/base.rb @@ -0,0 +1,69 @@ +# +# Copyright (c) 2014 Igor Moochnick +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# + +require 'thor' +#require "#{File.dirname(__FILE__)}/pipeline.rb" + +module JenkinsPipelineBuilder + module CLI + class Base < Thor + + class_option :username, :aliases => "-u", :desc => "Name of Jenkins user" + class_option :password, :aliases => "-p", + :desc => "Password of Jenkins user" + class_option :password_base64, :aliases => "-b", + :desc => "Base 64 encoded password of Jenkins user" + class_option :server_ip, :aliases => "-s", + :desc => "Jenkins server IP address" + class_option :server_port, :aliases => "-o", :desc => "Jenkins port" + class_option :creds_file, :aliases => "-c", + :desc => "Credentials file for communicating with Jenkins server" + class_option :debug, :type => :boolean, :aliases => "-d", :desc => "Run in debug mode (no Jenkins changes)", + :default => false + + + map "-v" => :version + + desc "version", "Shows current version" + # CLI command that returns the version of Jenkins API Client + def version + puts JenkinsPipelineBuilder::VERSION + end + + # Register the CLI::Pipeline class as "pipeline" subcommand to CLI + register( + CLI::Pipeline, + 'pipeline', + 'pipeline [subcommand]', + 'Provides functions to access pipeline functions of the Jenkins CI server' + ) + + # Register the CLI::Job class as "view" subcommand to CLI + register( + CLI::View, + 'view', + 'view [subcommand]', + 'Provides functions to access view interface of Jenkins CI server' + ) + end + end +end \ No newline at end of file diff --git a/lib/jenksin_pipeline_builder/cli/helper.rb b/lib/jenksin_pipeline_builder/cli/helper.rb new file mode 100644 index 0000000..f3ebf65 --- /dev/null +++ b/lib/jenksin_pipeline_builder/cli/helper.rb @@ -0,0 +1,68 @@ +# +# Copyright (c) 2014 Igor Moochnick +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# + +require 'fileutils' +require 'yaml' +require 'jenkins_api_client' + +module JenkinsPipelineBuilder + module CLI + # This is the helper class that sets up the credentials from the command + # line parameters given and initializes the Jenkins Pipeline Builder. + class Helper + # Sets up the credentials and initializes the Jenkins Pipeline Builder + # + # @param [Hash] options Options obtained from the command line + # + # @return [JenkinsPipelineBuilder::Generator] A new Client object + # + def self.setup(options) + if options[:username] && options[:server_ip] && \ + (options[:password] || options[:password_base64]) + creds = options + elsif options[:creds_file] + creds = YAML.load_file( + #File.expand_path(options[:creds_file], __FILE__) + options[:creds_file] + ) + elsif File.exist?("#{ENV['HOME']}/.jenkins_api_client/login.yml") + creds = YAML.load_file( + File.expand_path( + "#{ENV['HOME']}/.jenkins_api_client/login.yml", __FILE__ + ) + ) + else + msg = "Credentials are not set. Please pass them as parameters or" + msg << " set them in the default credentials file" + puts msg + exit 1 + end + + #creds[:log_level] = Logger::DEBUG + client = JenkinsApi::Client.new(creds) + generator = JenkinsPipelineBuilder::Generator.new(options, client) + generator.debug = options[:debug] #== 'debug' + return generator + end + end + end +end \ No newline at end of file diff --git a/lib/jenksin_pipeline_builder/cli/pipeline.rb b/lib/jenksin_pipeline_builder/cli/pipeline.rb new file mode 100644 index 0000000..f9218f9 --- /dev/null +++ b/lib/jenksin_pipeline_builder/cli/pipeline.rb @@ -0,0 +1,40 @@ +# +# Copyright (c) 2014 Igor Moochnick +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# + +module JenkinsPipelineBuilder + module CLI + # This class provides various command line operations related to jobs. + class Pipeline < Thor + include Thor::Actions + + desc 'dump', 'Dump job' + def dump(job_name) + Helper.setup(parent_options).dump(job_name) + end + + desc 'bootstrap Path', 'Generates pipeline from folder or a file' + def bootstrap(path) + Helper.setup(parent_options).bootstrap(path) + end + end + end +end \ No newline at end of file diff --git a/lib/jenksin_pipeline_builder/cli/view.rb b/lib/jenksin_pipeline_builder/cli/view.rb new file mode 100644 index 0000000..28ef6ce --- /dev/null +++ b/lib/jenksin_pipeline_builder/cli/view.rb @@ -0,0 +1,40 @@ +# +# Copyright (c) 2014 Igor Moochnick +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# + +module JenkinsPipelineBuilder + module CLI + # This class provides various command line operations related to jobs. + class View < Thor + include Thor::Actions + + desc 'dump', 'Dump view' + def dump(job_name) + Helper.setup(parent_options).dump(job_name) + end + + desc 'bootstrap Path', 'Generates view from folder or a file' + def create(path) + Helper.setup(parent_options).view.generate(path) + end + end + end +end \ No newline at end of file diff --git a/lib/jenksin_pipeline_builder/compiler.rb b/lib/jenksin_pipeline_builder/compiler.rb new file mode 100644 index 0000000..d4e981a --- /dev/null +++ b/lib/jenksin_pipeline_builder/compiler.rb @@ -0,0 +1,81 @@ +# +# Copyright (c) 2014 Igor Moochnick +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# + +module JenkinsPipelineBuilder + class Compiler + def self.resolve_value(value, settings) + value_s = value.to_s.clone + vars = value_s.scan(/{{([^}]+)}}/).flatten + vars.select! do |var| + var_val = settings[var.to_sym] + value_s.gsub!("{{#{var.to_s}}}", var_val) unless var_val.nil? + var_val.nil? + end + return nil if vars.count != 0 + return value_s + end + + def self.get_settings_bag(item_bag, settings_bag = {}) + item = item_bag[:value] + bag = {} + return unless item.kind_of?(Hash) + item.keys.each do |k| + val = item[k] + if val.kind_of? String + new_value = resolve_value(val, settings_bag) + return nil if new_value.nil? + bag[k] = new_value + end + end + my_settings_bag = settings_bag.clone + return my_settings_bag.merge(bag) + end + + def self.compile(item, settings = {}) + case item + when String + new_value = resolve_value(item, settings) + puts "Failed to resolve #{item}" if new_value.nil? + return new_value + when Hash + result = {} + item.each do |key, value| + new_value = compile(value, settings) + puts "Failed to resolve #{value}" if new_value.nil? + return nil if new_value.nil? + result[key] = new_value + end + return result + when Array + result = [] + item.each do |value| + new_value = compile(value, settings) + puts "Failed to resolve #{value}" if new_value.nil? + return nil if new_value.nil? + result << new_value + end + return result + end + return item + end + end +end \ No newline at end of file diff --git a/lib/jenksin_pipeline_builder/generator.rb b/lib/jenksin_pipeline_builder/generator.rb new file mode 100644 index 0000000..22cc1a4 --- /dev/null +++ b/lib/jenksin_pipeline_builder/generator.rb @@ -0,0 +1,346 @@ +# +# Copyright (c) 2014 Igor Moochnick +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# + +require 'yaml' +require 'pp' + +module JenkinsPipelineBuilder + class Generator + # Initialize a Client object with Jenkins Api Client + # + # @param args [Hash] Arguments to connect to Jenkins server + # + # @option args [String] :something some option description + # + # @return [JenkinsPipelineBuilder::Generator] a client generator + # + # @raise [ArgumentError] when required options are not provided. + # + def initialize(args, client) + @client = client + @logger = @client.logger + @job_templates = {} + @job_collection = {} + + @module_registry = ModuleRegistry.new ({ + job: { + description: JobBuilder.method(:change_description), + scm_params: JobBuilder.method(:apply_scm_params), + hipchat: JobBuilder.method(:hipchat_notifier), + parameters: JobBuilder.method(:build_parameters), + builders: { + registry: { + job_builder: Builders.method(:build_multijob_builder), + inject_vars_file: Builders.method(:build_environment_vars_injector), + shell_command: Builders.method(:build_shell_command), + maven3: Builders.method(:build_maven3) + }, + method: + lambda { |registry, params, n_xml| @module_registry.run_registry_on_path('//builders', registry, params, n_xml) } + }, + publishers: { + registry: { + git: Publishers.method(:push_to_git), + hipchat: Publishers.method(:push_to_hipchat), + description_setter: Publishers.method(:description_setter), + downstream: Publishers.method(:push_to_projects), + junit_result: Publishers.method(:publish_junit), + coverage_result: Publishers.method(:publish_rcov) + }, + method: + lambda { |registry, params, n_xml| @module_registry.run_registry_on_path('//publishers', registry, params, n_xml) } + }, + wrappers: { + registry: { + timestamp: Wrappers.method(:console_timestamp), + ansicolor: Wrappers.method(:ansicolor), + artifactory: Wrappers.method(:publish_to_artifactory), + rvm: Wrappers.method(:run_with_rvm), + inject_env_var: Wrappers.method(:inject_env_vars), + inject_passwords: Wrappers.method(:inject_passwords) + }, + method: + lambda { |registry, params, n_xml| @module_registry.run_registry_on_path('//buildWrappers', registry, params, n_xml) } + }, + triggers: { + registry: { + git_push: Triggers.method(:enable_git_push), + scm_polling: Triggers.method(:enable_scm_polling) + }, + method: + lambda { |registry, params, n_xml| @module_registry.run_registry_on_path('//triggers', registry, params, n_xml) } + } + } + }) + end + + attr_accessor :client + attr_accessor :debug + # TODO: WTF? + attr_accessor :no_files + attr_accessor :job_collection + + # Creates an instance to the View class by passing a reference to self + # + # @return [JenkinsApi::Client::System] An object to System subclass + # + def view + JenkinsPipelineBuilder::View.new(self) + end + + def load_collection_from_path(path, recursively = false) + if File.directory?(path) + @logger.info "Generating from folder #{path}" + Dir.glob(File.join(path, '/*.yaml')).each do |file| + if File.directory?(file) + if recursively + load_collection_from_path(File.join(path, file), recursively) + else + next + end + end + @logger.info "Loading file #{file}" + yaml = YAML.load_file(file) + load_job_collection(yaml) + end + else + @logger.info "Loading file #{path}" + yaml = YAML.load_file(path) + load_job_collection(yaml) + end + end + + def load_job_collection(yaml) + yaml.each do |section| + Utils.symbolize_keys_deep!(section) + key = section.keys.first + value = section[key] + name = value[:name] + raise "Duplicate item with name '#{name}' was detected." if @job_collection.has_key?(name) + @job_collection[name.to_s] = { name: name.to_s, type: key, value: value } + end + end + + def get_item(name) + @job_collection[name.to_s] + end + + def resolve_project(project) + defaults = get_item('global') + settings = defaults.nil? ? {} : defaults[:value] || {} + + project[:settings] = Compiler.get_settings_bag(project, settings) unless project[:settings] + project_body = project[:value] + + # Process jobs + jobs = project_body[:jobs] || [] + jobs.map! do |job| + job.kind_of?(String) ? { job.to_sym => {} } : job + end + @logger.info project + jobs.each do |job| + job_id = job.keys.first + settings = project[:settings].clone.merge(job[job_id]) + job[:result] = resolve_job_by_name(job_id, settings) + end + + # Process views + views = project_body[:views] || [] + views.map! do |view| + view.kind_of?(String) ? { view.to_sym => {} } : view + end + views.each do |view| + view_id = view.keys.first + settings = project[:settings].clone.merge(view[view_id]) + # TODO: rename resolve_job_by_name properly + view[:result] = resolve_job_by_name(view_id, settings) + end + + project + end + + def resolve_job_by_name(name, settings = {}) + job = get_item(name) + raise "Failed to locate job by name '#{name}'" if job.nil? + job_value = job[:value] + compiled_job = Compiler.compile(job_value, settings) + return compiled_job + end + + def projects + result = [] + @job_collection.values.each do |item| + result << item if item[:type] == :project + end + return result + end + + def bootstrap(path) + @logger.info "Bootstrapping pipeline from path #{path}" + load_collection_from_path(path) + + projects.each do |project| + compiled_project = resolve_project(project) + pp compiled_project + + if compiled_project[:value][:jobs] + compiled_project[:value][:jobs].each do |i| + job = i[:result] + xml = compile_job_to_xml(job) + create_or_update(job, xml) + end + end + + if compiled_project[:value][:views] + compiled_project[:value][:views].each do |v| + _view = v[:result] + view.create(_view) + end + end + end + + end + + def dump(job_name) + @logger.info "Debug #{@debug}" + @logger.info "Dumping #{job_name} into #{job_name}.xml" + xml = @client.job.get_config(job_name) + File.open(job_name + '.xml', 'w') { |f| f.write xml } + end + + def create_or_update(job, xml) + job_name = job[:name] + if @debug + @logger.info "Will create job #{job}" + @logger.info "#{xml}" + File.open(job_name + '.xml', 'w') { |f| f.write xml } + return + end + + if @client.job.exists?(job_name) + @client.job.update(job_name, xml) + else + @client.job.create(job_name, xml) + end + end + + def compile_job_to_xml(job) + raise 'Job name is not specified' unless job[:name] + + @logger.info "Creating Yaml Job #{job}" + job[:job_type] = 'free_style' unless job[:job_type] + case job[:job_type] + when 'job_dsl' + xml = compile_freestyle_job_to_xml(job) + update_job_dsl(job, xml) + when 'multi_project' + xml = compile_freestyle_job_to_xml(job) + adjust_multi_project xml + when 'build_flow' + xml = compile_freestyle_job_to_xml(job) + add_job_dsl(job, xml) + when 'free_style' + compile_freestyle_job_to_xml job + else + @logger.info 'Unknown job type' + '' + end + + end + + def adjust_multi_project(xml) + n_xml = Nokogiri::XML(xml) + root = n_xml.root() + root.name = 'com.tikal.jenkins.plugins.multijob.MultiJobProject' + n_xml.to_xml + end + + def compile_freestyle_job_to_xml(params) + if params.has_key?(:template) + template_name = params[:template] + raise "Job template '#{template_name}' can't be resolved." unless @job_templates.has_key?(template_name) + params.delete(:template) + template = @job_templates[template_name] + puts "Template found: #{template}" + params = template.deep_merge(params) + puts "Template merged: #{template}" + end + + xml = @client.job.build_freestyle_config(params) + n_xml = Nokogiri::XML(xml) + + @module_registry.traverse_registry_path('job', params, n_xml) + + n_xml.to_xml + end + + def add_job_dsl(job, xml) + n_xml = Nokogiri::XML(xml) + n_xml.root.name = 'com.cloudbees.plugins.flow.BuildFlow' + Nokogiri::XML::Builder.with(n_xml.root) do |xml| + xml.dsl job[:build_flow] + end + n_xml.to_xml + end + + # TODO: make sure this is tested + def update_job_dsl(job, xml) + n_xml = Nokogiri::XML(xml) + n_builders = n_xml.xpath('//builders').first + Nokogiri::XML::Builder.with(n_builders) do |xml| + build_job_dsl(job, xml) + end + n_xml.to_xml + end + + def generate_job_dsl_body(params) + @logger.info "Generating pipeline" + + xml = @client.job.build_freestyle_config(params) + + n_xml = Nokogiri::XML(xml) + if n_xml.xpath('//javaposse.jobdsl.plugin.ExecuteDslScripts').empty? + p_xml = Nokogiri::XML::Builder.new(:encoding => 'UTF-8') do |b_xml| + build_job_dsl(params, b_xml) + end + + n_xml.xpath('//builders').first.add_child("\r\n" + p_xml.doc.root.to_xml(:indent => 4) + "\r\n") + xml = n_xml.to_xml + end + xml + end + + def build_job_dsl(job, xml) + xml.send('javaposse.jobdsl.plugin.ExecuteDslScripts') { + if job.has_key?(:job_dsl) + xml.scriptText job[:job_dsl] + xml.usingScriptText true + else + xml.targets job[:job_dsl_targets] + xml.usingScriptText false + end + xml.ignoreExisting false + xml.removedJobAction 'IGNORE' + } + end + end +end diff --git a/lib/jenksin_pipeline_builder/job_builder.rb b/lib/jenksin_pipeline_builder/job_builder.rb new file mode 100644 index 0000000..2364860 --- /dev/null +++ b/lib/jenksin_pipeline_builder/job_builder.rb @@ -0,0 +1,82 @@ +# +# Copyright (c) 2014 Igor Moochnick +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# + +module JenkinsPipelineBuilder + class JobBuilder + def self.change_description(description, n_xml) + desc = n_xml.xpath("//description").first + desc.content = "#{description}" + end + + def self.apply_scm_params(params, n_xml) + XmlHelper.update_node_text(n_xml, '//scm/localBranch', params[:local_branch]) if params[:local_branch] + XmlHelper.update_node_text(n_xml, '//scm/recursiveSubmodules', params[:recursive_update]) if params[:recursive_update] + XmlHelper.update_node_text(n_xml, '//scm/wipeOutWorkspace', params[:wipe_workspace]) if params[:wipe_workspace] + XmlHelper.update_node_text(n_xml, '//scm/excludedUsers', params[:excuded_users]) if params[:excuded_users] + end + + def self.hipchat_notifier(params, n_xml) + raise "No HipChat room specified" unless params[:room] + + properties = n_xml.xpath("//properties").first + Nokogiri::XML::Builder.with(properties) do |xml| + xml.send('jenkins.plugins.hipchat.HipChatNotifier_-HipChatJobProperty') { + xml.room params[:room] + xml.startNotification params[:'start-notify'] || false + } + end + end + + def self.build_parameters(params, n_xml) + n_builders = n_xml.xpath('//properties').first + Nokogiri::XML::Builder.with(n_builders) do |xml| + xml.send('hudson.model.ParametersDefinitionProperty') { + xml.parameterDefinitions { + param_proc = lambda do |xml, name, type, default, description| + xml.send(type) { + xml.name name + xml.description description + xml.defaultValue default + } + end + params.each do |param| + case param[:type] + when 'string' + paramType = 'hudson.model.StringParameterDefinition' + when 'bool' + paramType = 'hudson.model.BooleanParameterDefinition' + when 'text' + paramType = 'hudson.model.TextParameterDefinition' + when 'password' + paramType = 'hudson.model.PasswordParameterDefinition' + else + paramType = 'hudson.model.StringParameterDefinition' + end + + param_proc.call xml, param[:name], paramType, param[:default], param[:description] + end + } + } + end + end + end +end \ No newline at end of file diff --git a/lib/jenksin_pipeline_builder/module_registry.rb b/lib/jenksin_pipeline_builder/module_registry.rb new file mode 100644 index 0000000..6190a5d --- /dev/null +++ b/lib/jenksin_pipeline_builder/module_registry.rb @@ -0,0 +1,82 @@ +# +# Copyright (c) 2014 Igor Moochnick +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# + +module JenkinsPipelineBuilder + class ModuleRegistry + attr_accessor :registry + + def initialize(registry) + @registry = registry + end + + def get(path) + parts = path.split('/') + self.get_by_path_collection(parts, @registry) + end + + def get_by_path_collection(path, registry) + item = registry[path.shift.to_sym] + if path.count == 0 + return item + end + + get_by_path_collection(path, item) + end + + def traverse_registry_path(path, params, n_xml) + registry = get(path) + traverse_registry(registry, params, n_xml) + end + + def traverse_registry(registry, params, n_xml) + params.each do |key, value| + execute_registry_item(registry, key, value, n_xml) + end + end + + def traverse_registry_param_array(registry, params, n_xml) + params.each do |item| + key = item.keys.first + next if key.nil? + execute_registry_item(registry, key, item[key], n_xml) + end + end + + def execute_registry_item(registry, key, value, n_xml) + registry_item = registry[key] + if registry_item.kind_of?(Hash) + sub_registry = registry_item[:registry] + method = registry_item[:method] + method.call(sub_registry, value, n_xml) + elsif registry_item.kind_of?(Method) + registry_item.call(value, n_xml) unless registry_item.nil? + end + end + + def run_registry_on_path(path, registry, params, n_xml) + n_builders = n_xml.xpath(path).first + Nokogiri::XML::Builder.with(n_builders) do |xml| + traverse_registry_param_array(registry, params, xml) + end + end + end +end diff --git a/lib/jenksin_pipeline_builder/publishers.rb b/lib/jenksin_pipeline_builder/publishers.rb new file mode 100644 index 0000000..af2c3d1 --- /dev/null +++ b/lib/jenksin_pipeline_builder/publishers.rb @@ -0,0 +1,113 @@ +# +# Copyright (c) 2014 Igor Moochnick +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# + +module JenkinsPipelineBuilder + class Publishers + def self.description_setter(params, xml) + xml.send('hudson.plugins.descriptionsetter.DescriptionSetterPublisher') { + xml.regexp params[:regexp] + xml.regexpForFailed params[:regexp] + xml.description params[:description] + xml.descriptionForFailed params[:description] + xml.setForMatrix false + } + end + + def self.push_to_projects(params, xml) + xml.send('hudson.plugins.parameterizedtrigger.BuildTrigger') { + xml.configs { + xml.send('hudson.plugins.parameterizedtrigger.BuildTriggerConfig') { + xml.configs { + params[:data] = [ { params: "" } ] unless params[:data] + params[:data].each do |config| + if config[:params] + xml.send('hudson.plugins.parameterizedtrigger.PredefinedBuildParameters') { + xml.properties config[:params] + } + end + if config[:file] + xml.send('hudson.plugins.parameterizedtrigger.FileBuildParameters') { + xml.propertiesFile config[:file] + xml.failTriggerOnMissing false + } + end + end + } + xml.projects params[:project] + xml.condition 'SUCCESS' + xml.triggerWithNoParameters false + } + } + } + end + + def self.push_to_hipchat(params, xml) + params = {} if params == true + xml.send('jenkins.plugins.hipchat.HipChatNotifier') { + xml.jenkinsUrl params[:jenkinsUrl] || '' + xml.authToken params[:authToken] || '' + xml.room params[:room] || '' + } + end + + def self.push_to_git(params, xml) + xml.send('hudson.plugins.git.GitPublisher') { + xml.configVersion params[:configVersion] || 2 + xml.pushMerge params[:'push-merge'] || false + xml.pushOnlyIfSuccess params[:'push-only-if-success'] || false + xml.branchesToPush { + xml.send('hudson.plugins.git.GitPublisher_-BranchToPush') { + xml.targetRepoName params[:targetRepoName] || 'origin' + xml.branchName params[:branchName] || 'master' + } + } + } + end + + def self.publish_junit(params, xml) + xml.send('hudson.tasks.junit.JUnitResultArchiver') { + xml.testResults params[:test_results] || '' + xml.keepLongStdio false + xml.testDataPublishers + } + end + + def self.coverage_metric(name, params, xml) + xml.send('hudson.plugins.rubyMetrics.rcov.model.MetricTarget') { + xml.metric name + xml.healthy params[:healthy] + xml.unhealthy params[:unhealthy] + xml.unstable params[:unstable] + } + end + + def self.publish_rcov(params, xml) + xml.send('hudson.plugins.rubyMetrics.rcov.RcovPublisher') { + xml.reportDir params[:report_dir] + xml.targets { + coverage_metric('TOTAL_COVERAGE', params[:total], xml) + coverage_metric('CODE_COVERAGE', params[:code], xml) + } + } + end + end +end \ No newline at end of file diff --git a/lib/jenksin_pipeline_builder/triggers.rb b/lib/jenksin_pipeline_builder/triggers.rb new file mode 100644 index 0000000..e8be968 --- /dev/null +++ b/lib/jenksin_pipeline_builder/triggers.rb @@ -0,0 +1,38 @@ +# +# Copyright (c) 2014 Igor Moochnick +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# + +module JenkinsPipelineBuilder + class Triggers + def self.enable_git_push(git_push, xml) + xml.send('com.cloudbees.jenkins.GitHubPushTrigger') { + xml.spec + } + end + + def self.enable_scm_polling(scm_polling, xml) + xml.send('hudson.triggers.SCMTrigger') { + xml.spec scm_polling + xml.ignorePostCommitHooks false + } + end + end +end \ No newline at end of file diff --git a/lib/jenksin_pipeline_builder/utils.rb b/lib/jenksin_pipeline_builder/utils.rb new file mode 100644 index 0000000..52661d6 --- /dev/null +++ b/lib/jenksin_pipeline_builder/utils.rb @@ -0,0 +1,46 @@ +# +# Copyright (c) 2014 Igor Moochnick +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# + +module JenkinsPipelineBuilder + class ::Hash + def deep_merge(second) + merger = proc { |key, v1, v2| Hash === v1 && Hash === v2 ? v1.merge(v2, &merger) : v2 } + self.merge(second, &merger) + end + end + + class Utils + # Code was duplicated from jeknins_api_client + def self.symbolize_keys_deep!(h) + return unless h.kind_of?(Hash) + h.keys.each do |k| + ks = k.respond_to?(:to_sym) ? k.to_sym : k + h[ks] = h.delete k # Preserve order even when k == ks + symbolize_keys_deep! h[ks] if h[ks].kind_of? Hash + if h[ks].kind_of? Array + #puts "Array #{h[ks]}" + h[ks].each { |item| symbolize_keys_deep!(item) } + end + end + end + end +end \ No newline at end of file diff --git a/lib/jenksin_pipeline_builder/version.rb b/lib/jenksin_pipeline_builder/version.rb new file mode 100644 index 0000000..ac6a2fd --- /dev/null +++ b/lib/jenksin_pipeline_builder/version.rb @@ -0,0 +1,25 @@ +# +# Copyright (c) 2014 Igor Moochnick +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# + +module JenkinsPipelineBuilder + VERSION = "0.2.0" +end diff --git a/lib/jenksin_pipeline_builder/view.rb b/lib/jenksin_pipeline_builder/view.rb new file mode 100644 index 0000000..25d951b --- /dev/null +++ b/lib/jenksin_pipeline_builder/view.rb @@ -0,0 +1,259 @@ +# +# Copyright (c) 2014 Igor Moochnick +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# + +module JenkinsPipelineBuilder + class View + # Initializes a new View object. + # + # @param generator [Generator] the client object + # + # @return [View] the view object + # + def initialize(generator) + @generator = generator + @client = generator.client + @logger = @client.logger + end + + def generate(path) + yaml = YAML.load_file(path) + + yaml.each do |item| + Utils.symbolize_keys_deep!(item) + create(item[:view]) if item[:view] + end + end + + def get_mode(type) + case type + when 'listview' + 'hudson.model.ListView' + when 'myview' + 'hudson.model.MyView' + when 'nestedView' + 'hudson.plugins.nested_view.NestedView' + when 'categorizedView' + 'org.jenkinsci.plugins.categorizedview.CategorizedJobsView' + when 'dashboardView' + 'hudson.plugins.view.dashboard.Dashboard' + when 'multijobView' + 'com.tikal.jenkins.plugins.multijob.views.MultiJobView' + else + raise "Type #{type} is not supported by Jenkins." + end + end + + # Creates a new empty view of the given type + # + # @param [String] view_name Name of the view to be created + # @param [String] type Type of view to be created. Valid options: + # listview, myview. Default: listview + # + def create_base_view(view_name, type = 'listview', parent_view_name = nil) + @logger.info "Creating a view '#{view_name}' of type '#{type}'" + mode = get_mode(type) + initial_post_params = { + 'name' => view_name, + 'mode' => mode, + 'json' => { + 'name' => view_name, + 'mode' => mode + }.to_json + } + + if @generator.debug + pp initial_post_params + return + end + + view_path = parent_view_name.nil? ? '' : "/view/#{path_encode parent_view_name}" + view_path += '/createView' + + @client.api_post_request(view_path, initial_post_params) + end + + # Creates a listview by accepting the given parameters hash + # + # @param [Hash] params options to create the new view + # @option params [String] :name Name of the view + # @option params [String] :type Description of the view + # @option params [String] :description Description of the view + # @option params [String] :status_filter Filter jobs based on the status. + # Valid options: all_selected_jobs, enabled_jobs_only, + # disabled_jobs_only. Default: all_selected_jobs + # @option params [Boolean] :filter_queue true or false + # @option params [Boolean] :filter_executors true or false + # @option params [String] :regex Regular expression to filter jobs that + # are to be added to the view + # + # @raise [ArgumentError] if the required parameter +:name+ is not + # specified + # + def create(params) + # Name is a required parameter. Raise an error if not specified + raise ArgumentError, "Name is required for creating view" \ + unless params.is_a?(Hash) && params[:name] + unless @generator.debug + if @client.view.exists?(params[:name]) + @client.view.delete(params[:name]) + end + end + params[:type] = 'listview' unless params[:type] + create_base_view(params[:name], params[:type], params[:parent_view]) + @logger.debug "Creating a #{params[:type]} view with params: #{params.inspect}" + status_filter = case params[:status_filter] + when "all_selected_jobs" + "" + when "enabled_jobs_only" + "1" + when "disabled_jobs_only" + "2" + else + "" + end + + json = { + "name" => params[:name], + "description" => params[:description], + "mode" => get_mode(params[:type]), + "statusFilter" => "", + "columns" => get_columns(params[:type]) + } + json.merge!("groupingRules" => params[:groupingRules]) if params[:groupingRules] + post_params = { + "name" => params[:name], + "mode" => get_mode(params[:type]), + "description" => params[:description], + "statusFilter" => status_filter, + "json" => json.to_json + } + post_params.merge!("filterQueue" => "on") if params[:filter_queue] + post_params.merge!("filterExecutors" => "on") if params[:filter_executors] + post_params.merge!("useincluderegex" => "on", + "includeRegex" => params[:regex]) if params[:regex] + + if @generator.debug + pp post_params + return + end + + view_path = params[:parent_view].nil? ? '' : "/view/#{path_encode params[:parent_view]}" + view_path += "/view/#{path_encode params[:name]}/configSubmit" + + @client.api_post_request(view_path, post_params) + end + + def get_columns(type) + columns_repository = { + 'Status' => + { + 'stapler-class' => 'hudson.views.StatusColumn', + 'kind' => 'hudson.views.StatusColumn' + }, + 'Weather' => + { + "stapler-class" => "hudson.views.WeatherColumn", + "kind" => "hudson.views.WeatherColumn" + }, + 'Name' => + { + "stapler-class" => "hudson.views.JobColumn", + "kind" => "hudson.views.JobColumn" + }, + 'Last Success' => + { + "stapler-class" => "hudson.views.LastSuccessColumn", + "kind" => "hudson.views.LastSuccessColumn" + }, + 'Last Failure' => + { + "stapler-class" => "hudson.views.LastFailureColumn", + "kind" => "hudson.views.LastFailureColumn" + }, + 'Last Duration' => + { + "stapler-class" => "hudson.views.LastDurationColumn", + "kind" => "hudson.views.LastDurationColumn" + }, + 'Build Button' => + { + 'stapler-class' => 'hudson.views.BuildButtonColumn', + 'kind' => 'hudson.views.BuildButtonColumn' + }, + 'Categorized - Job' => + { + 'stapler-class' => 'org.jenkinsci.plugins.categorizedview.IndentedJobColumn', + 'kind' => 'org.jenkinsci.plugins.categorizedview.IndentedJobColumn' + } + } + + column_names = case type + when 'categorizedView' + ['Status', 'Weather', 'Categorized - Job', 'Last Success', 'Last Failure', 'Last Duration', 'Build Button'] + else + ['Status', 'Weather', 'Name', 'Last Success', 'Last Failure', 'Last Duration', 'Build Button'] + end + + result = [] + column_names.each do |name| + result << columns_repository[name] + end + return result + end + + def path_encode(path) + URI.escape(path.encode(Encoding::UTF_8)) + end + + # This method lists all views + # + # @param [String] parent_view a name of the parent view + # @param [String] filter a regex to filter view names + # @param [Bool] ignorecase whether to be case sensitive or not + # + def list_children(parent_view = nil, filter = "", ignorecase = true) + @logger.info "Obtaining children views of parent #{parent_view} based on filter '#{filter}'" + view_names = [] + path = parent_view.nil? ? '' : "/view/#{path_encode parent_view}" + response_json = @client.api_get_request(path) + response_json["views"].each { |view| + if ignorecase + view_names << view["name"] if view["name"] =~ /#{filter}/i + else + view_names << view["name"] if view["name"] =~ /#{filter}/ + end + } + view_names + end + + # Delete a view + # + # @param [String] view_name + # + def delete(view_name, parent_view = nil) + @logger.info "Deleting view '#{view_name}'" + path = parent_view.nil? ? '' : "/view/#{path_encode parent_view}" + path += "/view/#{path_encode view_name}/doDelete" + @client.api_post_request(path) + end + end +end \ No newline at end of file diff --git a/lib/jenksin_pipeline_builder/wrappers.rb b/lib/jenksin_pipeline_builder/wrappers.rb new file mode 100644 index 0000000..0fa3d94 --- /dev/null +++ b/lib/jenksin_pipeline_builder/wrappers.rb @@ -0,0 +1,91 @@ +# +# Copyright (c) 2014 Igor Moochnick +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# + +module JenkinsPipelineBuilder + class Wrappers + def self.ansicolor(wrapper, xml) + xml.send('hudson.plugins.ansicolor.AnsiColorBuildWrapper') { + xml.colorMapName 'xterm' + } + end + + def self.console_timestamp(wrapper, xml) + xml.send('hudson.plugins.timestamper.TimestamperBuildWrapper') + end + + def self.run_with_rvm(wrapper, xml) + xml.send('ruby-proxy-object') { + xml.send('ruby-object', 'ruby-class' => 'Jenkins::Plugin::Proxies::BuildWrapper', 'pluginid' => 'rvm') { + xml.object('ruby-class' => 'RvmWrapper', 'pluginid' => 'rvm') { + xml.impl('pluginid' => "rvm", 'ruby-class' => 'String') { xml.text wrapper } + } + xml.pluginid(:pluginid => 'rvm', 'ruby-class' => 'String') { xml.text 'rvm' } + } + } + end + + def self.inject_passwords(passwords, xml) + xml.EnvInjectPasswordWrapper { + xml.injectGlobalPasswords false + xml.passwordEntries { + passwords.each do |password| + xml.EnvInjectPasswordEntry { + xml.name password[:name] + xml.value password[:value] + } + end + } + } + end + + def self.inject_env_vars(wrapper, xml) + xml.EnvInjectBuildWrapper { + xml.info { + xml.propertiesContent wrapper + xml.loadFilesFromMaster false + } + } + end + + def self.publish_to_artifactory(wrapper, xml) + xml.send('org.jfrog.hudson.generic.ArtifactoryGenericConfigurator') { + xml.details { + xml.artifactoryUrl wrapper[:url] + xml.artifactoryName wrapper[:'artifactory-name'] + xml.repositoryKey wrapper[:'target-repo'] + xml.snapshotsRepositoryKey wrapper[:'target-repo'] + } + xml.deployPattern wrapper[:publish] + xml.resolvePattern + xml.matrixParams + xml.deployBuildInfo wrapper[:'publish-build-info'] + xml.includeEnvVars false + xml.envVarsPatterns { + xml.includePatterns + xml.excludePatterns '*password*,*secret*' + } + xml.discardOldBuilds false + xml.discardBuildArtifacts true + } + end + end +end \ No newline at end of file diff --git a/lib/jenksin_pipeline_builder/xml_helper.rb b/lib/jenksin_pipeline_builder/xml_helper.rb new file mode 100644 index 0000000..6106ec5 --- /dev/null +++ b/lib/jenksin_pipeline_builder/xml_helper.rb @@ -0,0 +1,40 @@ +# +# Copyright (c) 2014 Igor Moochnick +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# + +module JenkinsPipelineBuilder + class XmlHelper + def self.update_node_text(n_xml, path, value) + n_node = n_xml.xpath(path).first + if n_node.nil? + left, right = path.match(/^(.*)\/([^\/]*)$/).captures + parent_node = n_xml.xpath(left).first + Nokogiri::XML::Builder.with(parent_node) do |xml| + xml.send(right) { + xml.text value + } + end + else + n_node.content = value + end + end + end +end \ No newline at end of file diff --git a/spec/func_tests/spec_helper.rb b/spec/func_tests/spec_helper.rb new file mode 100644 index 0000000..02b513d --- /dev/null +++ b/spec/func_tests/spec_helper.rb @@ -0,0 +1,18 @@ +require 'logger' +require 'rspec' + +require 'simplecov' +require 'simplecov-rcov' + +SimpleCov.start if ENV["COVERAGE"] + +require File.expand_path('../../../lib/jenksin_pipeline_builder', __FILE__) + +RSpec.configure do |config| + config.treat_symbols_as_metadata_keys_with_true_values = true + config.run_all_when_everything_filtered = true + config.filter_run_excluding :broken => true + + config.before(:each) do + end +end diff --git a/spec/func_tests/view_spec.rb b/spec/func_tests/view_spec.rb new file mode 100644 index 0000000..6606319 --- /dev/null +++ b/spec/func_tests/view_spec.rb @@ -0,0 +1,93 @@ +require File.expand_path('../spec_helper', __FILE__) + +describe JenkinsPipelineBuilder::View do + context "With properly initialized client" do + before(:all) do + @creds_file = '~/.jenkins_api_client/login.yml' + @valid_post_responses = [200, 201, 302] + begin + @client = JenkinsApi::Client.new( + YAML.load_file(File.expand_path(@creds_file, __FILE__)) + ) + @client.logger.level = Logger::DEBUG + @generator = JenkinsPipelineBuilder::Generator.new(nil, @client) + @generator.no_files = true + rescue Exception => e + puts 'WARNING: Credentials are not set properly.' + puts e.message + end + end + + describe 'InstanceMethods' do + describe '#create' do + def create_and_validate(params) + name = params[:name] + @valid_post_responses.should include( + @generator.view.create(params).to_i + ) + @generator.view.list_children(params[:parent_view], name).include?(name).should be_true + end + + def destroy_and_validate(params) + name = params[:name] + @valid_post_responses.should include( + @generator.view.delete(name, params[:parent_view]).to_i + ) + @generator.view.list_children(params[:parent_view], name).include?(name).should be_false + end + + def test_and_validate(params) + create_and_validate(params) + destroy_and_validate(params) + end + + it 'accepts the name of the view and creates the view' do + params = { + :name => 'test_list_view' + } + + test_and_validate(params) + end + + it 'creates a Nested view with a child' do + params_parent = { + name: 'test_nested_view', + type: 'nestedView' + } + + create_and_validate(params_parent) + + params_child = { + name: 'test_list_view', + parent_view: params_parent[:name] + } + + test_and_validate(params_child) + + destroy_and_validate(params_parent) + end + + it 'creates a categorized view with columns' do + params = { + name: 'test_category_view', + type: 'categorizedView', + description: 'Blah blah', + regex: 'Job-.*', + groupingRules: [{ + groupRegex: 'Step-1.*', + namingRule: '1. Commit' + },{ + groupRegex: 'Step-2.*', + namingRule: '2. Acceptance' + },{ + groupRegex: 'Step-3.*', + namingRule: '3. Release' + }] + } + + test_and_validate(params) + end + end + end + end +end \ No newline at end of file diff --git a/spec/unit_tests/compiler_spec.rb b/spec/unit_tests/compiler_spec.rb new file mode 100644 index 0000000..d303475 --- /dev/null +++ b/spec/unit_tests/compiler_spec.rb @@ -0,0 +1,19 @@ +require File.expand_path('../spec_helper', __FILE__) + +describe 'Compiler' do + it 'transforms hash into hash' do + hash = { + a: 'A sentence', + b: 'B sentence', + hash: { + c: 5, + d: true + }, + z: false + } + + result = JenkinsPipelineBuilder::Compiler.compile(hash) + + result.should == hash + end +end \ No newline at end of file diff --git a/spec/unit_tests/fixtures/files/Job-Build-Flow.xml b/spec/unit_tests/fixtures/files/Job-Build-Flow.xml new file mode 100644 index 0000000..4bcecd9 --- /dev/null +++ b/spec/unit_tests/fixtures/files/Job-Build-Flow.xml @@ -0,0 +1,57 @@ + + + + + false + + + HipChat Room Name + true + + + + + param1 + + + + + + + + true + false + false + false + + false + + + + + + + + + + + project_name + SUCCESS + false + + + + + https://jenkins_url/ + auth_token + room name + + + + guard { + build("job_name1", param1: params["param1"]); + } rescue { + build("job_name2", param1: build21.environment.get("some_var")) + } + + diff --git a/spec/unit_tests/fixtures/files/Job-Build-Flow.yaml b/spec/unit_tests/fixtures/files/Job-Build-Flow.yaml new file mode 100644 index 0000000..38e2164 --- /dev/null +++ b/spec/unit_tests/fixtures/files/Job-Build-Flow.yaml @@ -0,0 +1,22 @@ +--- +- job: + name: Job-Build-Flow + job_type: build_flow + hipchat: + room: 'HipChat Room Name' + start-notify: true + parameters: + - name: param1 + publishers: + - downstream: + project: project_name + - hipchat: + jenkinsUrl: 'https://jenkins_url/' + authToken: 'auth_token' + room: 'room name' + build_flow: | + guard { + build("job_name1", param1: params["param1"]); + } rescue { + build("job_name2", param1: build21.environment.get("some_var")) + } diff --git a/spec/unit_tests/fixtures/files/Job-Build-Maven.xml b/spec/unit_tests/fixtures/files/Job-Build-Maven.xml new file mode 100644 index 0000000..008de17 --- /dev/null +++ b/spec/unit_tests/fixtures/files/Job-Build-Maven.xml @@ -0,0 +1,90 @@ + + + + + false + + + 2 + + + + + git@github.com:devops/repo.git + + + + + release + + + release + false + false + false + false + false + true + false + false + false + false + + Default + + + + + user + + + false + + + + true + false + false + false + + false + + + echo 'Doing some work' + run command1 + + + + tools-maven-3.0.3 + + -B clean + + + + + + See the QE build details at (.*) + See the QE build details at (.*) + QE Build Details: <a href="\1">\1</a> + QE Build Details: <a href="\1">\1</a> + false + + + + + + + VM_NAME=${VM_NAME} + + + project_name + SUCCESS + false + + + + + + + + \ No newline at end of file diff --git a/spec/unit_tests/fixtures/files/Job-Build-Maven.yaml b/spec/unit_tests/fixtures/files/Job-Build-Maven.yaml new file mode 100644 index 0000000..9f0001e --- /dev/null +++ b/spec/unit_tests/fixtures/files/Job-Build-Maven.yaml @@ -0,0 +1,26 @@ +--- +- job: + name: Job-Build-Maven + scm_provider: git + scm_url: git@github.com:devops/repo.git + scm_branch: release + scm_params: + local_branch: release + wipe_workspace: true + excuded_users: user + builders: + - shell_command: | + echo 'Doing some work' + run command1 + - maven3: + goals: -B clean + publishers: + - description_setter: + regexp: See the QE build details at (.*) + description: 'QE Build Details: \1' + - downstream: + project: project_name + data: + - params: VM_NAME=${VM_NAME} + wrappers: + - timestamp: true diff --git a/spec/unit_tests/fixtures/files/Job-DSL.yaml b/spec/unit_tests/fixtures/files/Job-DSL.yaml new file mode 100644 index 0000000..dff2af2 --- /dev/null +++ b/spec/unit_tests/fixtures/files/Job-DSL.yaml @@ -0,0 +1,14 @@ +--- +- job: + name: Job-DSL1 + job_type: job_dsl + job_dsl: | + job { + name 'DSL-Job' + } + +- job: + name: Job-DSL2 + job_type: job_dsl + job_dsl_targets: | + **/*.groovy diff --git a/spec/unit_tests/fixtures/files/Job-DSL1.xml b/spec/unit_tests/fixtures/files/Job-DSL1.xml new file mode 100644 index 0000000..fde70f2 --- /dev/null +++ b/spec/unit_tests/fixtures/files/Job-DSL1.xml @@ -0,0 +1,27 @@ + + + + + false + + + true + false + false + false + + false + + + job { + name 'DSL-Job' + } + + true + false + IGNORE + + + + + diff --git a/spec/unit_tests/fixtures/files/Job-DSL2.xml b/spec/unit_tests/fixtures/files/Job-DSL2.xml new file mode 100644 index 0000000..59ee7f0 --- /dev/null +++ b/spec/unit_tests/fixtures/files/Job-DSL2.xml @@ -0,0 +1,25 @@ + + + + + false + + + true + false + false + false + + false + + + **/*.groovy + + false + false + IGNORE + + + + + diff --git a/spec/unit_tests/fixtures/files/Job-Gem-Build.xml b/spec/unit_tests/fixtures/files/Job-Gem-Build.xml new file mode 100644 index 0000000..26f8536 --- /dev/null +++ b/spec/unit_tests/fixtures/files/Job-Gem-Build.xml @@ -0,0 +1,142 @@ + + + + + false + + + 2 + + + + + git@github.com:devops/repo.git + + + + + master + + + false + false + false + false + false + false + false + false + false + false + + Default + + + + + user + + + false + + + + true + false + false + false + + + H/5 * * * * + false + + + false + + + run.sh + + + + + out/**/*.xml + false + + + + 2 + true + false + + + origin + master + + + + + out/coverage/rcov + + + TOTAL_COVERAGE + 80 + 0 + 0 + + + CODE_COVERAGE + 80 + 0 + 0 + + + + + + + xterm + + +
+ https://artifactory.com/artifactory + key + gems-local + gems-local +
+ pkg/*.gem + + + true + false + + + *password*,*secret* + + false + true +
+ + + VAR1 = value_1 + false + + + + false + + + OS_PASSWORD + some_encrypted_password + + + + + + + `cat .ruby-version`@`cat .ruby-gemset` + + rvm + + +
+
\ No newline at end of file diff --git a/spec/unit_tests/fixtures/files/Job-Gem-Build.yaml b/spec/unit_tests/fixtures/files/Job-Gem-Build.yaml new file mode 100644 index 0000000..fd8846e --- /dev/null +++ b/spec/unit_tests/fixtures/files/Job-Gem-Build.yaml @@ -0,0 +1,41 @@ +- job: + name: 'Job-Gem-Build' + job_type: free_style + scm_provider: git + scm_url: git@github.com:devops/repo.git + scm_branch: master + scm_params: + excuded_users: user + shell_command: 'run.sh' + triggers: + - scm_polling: 'H/5 * * * *' + wrappers: + - ansicolor: true + - artifactory: + url: 'https://artifactory.com/artifactory' + artifactory-name: 'key' + target-repo: gems-local + publish: 'pkg/*.gem' + publish-build-info: true + - inject_env_var: > + VAR1 = value_1 + - inject_passwords: + - name: OS_PASSWORD + value: some_encrypted_password + - rvm: "`cat .ruby-version`@`cat .ruby-gemset`" + publishers: + - junit_result: + test_results: 'out/**/*.xml' + - git: + push-merge: true + push-only-if-success: false + - coverage_result: + report_dir: out/coverage/rcov + total: + healthy: 80 + unhealthy: 0 + unstable: 0 + code: + healthy: 80 + unhealthy: 0 + unstable: 0 diff --git a/spec/unit_tests/fixtures/files/Job-Generate-From-Template.xml b/spec/unit_tests/fixtures/files/Job-Generate-From-Template.xml new file mode 100644 index 0000000..c9205e4 --- /dev/null +++ b/spec/unit_tests/fixtures/files/Job-Generate-From-Template.xml @@ -0,0 +1,32 @@ + + + + Do not edit this job through the web! + false + + + + + param1 + Some description + + + + + + + true + false + false + false + + false + + + run.sh + + + + + + \ No newline at end of file diff --git a/spec/unit_tests/fixtures/files/Job-Generate-From-Template.yaml b/spec/unit_tests/fixtures/files/Job-Generate-From-Template.yaml new file mode 100644 index 0000000..44ab4cd --- /dev/null +++ b/spec/unit_tests/fixtures/files/Job-Generate-From-Template.yaml @@ -0,0 +1,8 @@ +--- +- job-template: + name: Job-{{name}}-From-Template + description: '{{description}}' + parameters: + - name: param1 + description: Some description + shell_command: '{{command}}' diff --git a/spec/unit_tests/fixtures/files/Job-Multi-Project.xml b/spec/unit_tests/fixtures/files/Job-Multi-Project.xml new file mode 100644 index 0000000..0b713f9 --- /dev/null +++ b/spec/unit_tests/fixtures/files/Job-Multi-Project.xml @@ -0,0 +1,117 @@ + + + + + false + + + + + Param1 + The git branch to be used when building application + release + + + + + + 2 + + + + + git@github:org/repo + + + + + release + + + false + true + false + false + false + true + false + false + false + false + + Default + + + + + user + + + false + + + ${GIT_BRANCH} + + true + false + false + false + + + + + + H/5 * * * * + false + + + false + + + Child Jobs + + + Child-Job + false + false + + + PARENT_WORKSPACE=${WORKSPACE} + + + + + SUCCESSFUL + + + + build_job_info + + + + + + + + + + PARAM1=value1 + PARAM2=value2 + + + + promote-job-params + false + + + Downstream-Job + SUCCESS + false + + + + + + + + diff --git a/spec/unit_tests/fixtures/files/Job-Multi-Project.yaml b/spec/unit_tests/fixtures/files/Job-Multi-Project.yaml new file mode 100644 index 0000000..49dbc93 --- /dev/null +++ b/spec/unit_tests/fixtures/files/Job-Multi-Project.yaml @@ -0,0 +1,36 @@ +--- +- job: + name: Job-Multi-Project + job_type: multi_project + parameters: + - name: Param1 + type: string + default: release + description: The git branch to be used when building application + scm_provider: git + scm_url: git@github:org/repo + scm_branch: release + scm_params: + local_branch: ${GIT_BRANCH} + recursive_update: true + wipe_workspace: true + excuded_users: user + triggers: + - git_push: true + - scm_polling: "H/5 * * * *" + builders: + - job_builder: + child_jobs: + - Child-Job + mark_phase: SUCCESSFUL + - inject_vars_file: build_job_info + publishers: + - downstream: + project: Downstream-Job + data: + - params: | + PARAM1=value1 + PARAM2=value2 + - file: promote-job-params + wrappers: + - timestamp: true diff --git a/spec/unit_tests/fixtures/files/project.yaml b/spec/unit_tests/fixtures/files/project.yaml new file mode 100644 index 0000000..2e5ea5c --- /dev/null +++ b/spec/unit_tests/fixtures/files/project.yaml @@ -0,0 +1,15 @@ +- defaults: + name: global + description: 'Do not edit this job through the web!' + hipchat_room: HipChat Room Name + os_username: admin + os_priv_key: /home/user/qa-oskey.priv + os_password: some_encrypted_password + +- project: + name: Generate + jobs: + - Job-{{name}}-From-Template: + command: run.sh + - Job-DSL1 + - Job-DSL2 diff --git a/spec/unit_tests/generator_spec.rb b/spec/unit_tests/generator_spec.rb new file mode 100644 index 0000000..474872c --- /dev/null +++ b/spec/unit_tests/generator_spec.rb @@ -0,0 +1,67 @@ +require File.expand_path('../spec_helper', __FILE__) +require 'equivalent-xml' + +describe 'Test YAML jobs conversion to XML' do + context 'Loading YAML files' do + before do + @client = JenkinsApi::Client.new( + :server_ip => '127.0.0.1', + :server_port => 8080, + :username => 'username', + :password => 'password', + :log_location => '/dev/null' + ) + @generator = JenkinsPipelineBuilder::Generator.new(nil, @client) + @generator.debug = true + @generator.no_files = true + end + + def compare_jobs(job, path) + xml = @generator.compile_job_to_xml(job) + doc1 = Nokogiri::XML(xml) + + sample_job_xml = File.read(path + '.xml') + + doc2 = Nokogiri::XML(sample_job_xml) + + doc1.should be_equivalent_to(doc2) + end + + [ + 'Job-Multi-Project', + 'Job-Build-Maven', + 'Job-Build-Flow', + 'Job-Gem-Build' + ].each do |file| + it "should create expected XML from YAML '#{file}'" do + path = File.expand_path('../fixtures/files/' + file, __FILE__) + + @generator.load_collection_from_path path + '.yaml' + job_name = @generator.job_collection.keys.first + job = @generator.resolve_job_by_name(job_name) + + compare_jobs job, path + end + end + + it "should create expected XML from YAML collection" do + path = File.expand_path('../fixtures/files/', __FILE__) + + @generator.load_collection_from_path(path) + + project_name = @generator.projects.first[:name] + + project = @generator.resolve_project(@generator.get_item(project_name)) + + project[:value][:jobs].should_not be_nil + + project[:value][:jobs].each do |i| + job = i[:result] + job.should_not be_nil + + file_name = File.join(path, job[:name]) + compare_jobs job, file_name + end + end + end +end \ No newline at end of file diff --git a/spec/unit_tests/module_registry_spec.rb b/spec/unit_tests/module_registry_spec.rb new file mode 100644 index 0000000..600aef9 --- /dev/null +++ b/spec/unit_tests/module_registry_spec.rb @@ -0,0 +1,19 @@ +require File.expand_path('../spec_helper', __FILE__) + +describe 'ModuleRegistry' do + + it 'should return item by a specified path' do + + registry = JenkinsPipelineBuilder::ModuleRegistry.new ({ + zz: { + aa: 'aa', + bb: 'bb', + cc: { + dd: 'dd' + } + } + }) + + registry.get('zz/aa').should be == 'aa' + end +end \ No newline at end of file diff --git a/spec/unit_tests/resolve_dependencies_spec.rb b/spec/unit_tests/resolve_dependencies_spec.rb new file mode 100644 index 0000000..693db4b --- /dev/null +++ b/spec/unit_tests/resolve_dependencies_spec.rb @@ -0,0 +1,230 @@ +require File.expand_path('../spec_helper', __FILE__) + +describe 'Templates resolver' do + before(:each) do + @client = JenkinsApi::Client.new( + :server_ip => '127.0.0.1', + :server_port => 8080, + :username => 'username', + :password => 'password', + :log_location => '/dev/null' + ) + @generator = JenkinsPipelineBuilder::Generator.new(nil, @client) + @generator.debug = true + @generator.no_files = true + end + + describe 'resolving settings bags' do + it 'gives a bag when all the variables can be resolved' do + str = %{ +- project: + name: project-name + db: my_own_db_{{else}} + } + project = YAML.load(str) + @generator.load_job_collection project + + #@generator.resolve_item('project-name') + settings = JenkinsPipelineBuilder::Compiler.get_settings_bag(@generator.get_item('project-name'), { db: 'blah', else: "bum" }) + settings.should == { name: "project-name", db: "my_own_db_bum", else: "bum", } + end + + it 'returns nil when all the variables cant be resolved' do + str = %{ +- project: + name: project-name + db: my_own_db_{{else}}_{{blah}} + } + project = YAML.load(str) + @generator.load_job_collection project + + #@generator.resolve_item('project-name') + settings = JenkinsPipelineBuilder::Compiler.get_settings_bag(@generator.get_item('project-name'), { db: 'blah', else: "bum" }) + settings.should be_nil + end + end + + it 'starts with the defaults section for settings bag' do + str = %{ +- defaults: + name: global + description: 'Do not edit this job through the web!' +- job-template: + name: 'foo-bar' + description: '{{description}}' + builders: + - shell: perftest +- project: + name: project-name + db: my_own_db + jobs: + - 'foo-bar' + } + project = YAML.load(str) + @generator.load_job_collection project + + @generator.resolve_project(@generator.get_item('project-name')).should == + {:name=>"project-name", + :type=>:project, + :value=> + {:name=>"project-name", + :db=>"my_own_db", + :jobs=> + [{:"foo-bar"=>{}, + :result=> + {:name=>"foo-bar", + :description=>"Do not edit this job through the web!", + :builders=>[{:shell=>"perftest"}]}}]}, + :settings=> + {:name=>"project-name", + :description=>"Do not edit this job through the web!", + :db=>"my_own_db"}} + end + + it 'should build project collection from jobs templates' do + str = %{ +- job-template: + name: '{{name}}-unit-tests' + builders: + - shell: unittest + publishers: + - email: + recipients: '{{mail-to}}' + +- job-template: + name: '{{name}}-perf-tests' + builders: + - shell: perftest + publishers: + - email: + recipients: '{{mail-to}}' + +- project: + name: project-name + db: my_own_db + jobs: + - '{{name}}-unit-tests': + mail-to: developer@nowhere.net + - '{{name}}-perf-tests': + mail-to: projmanager@nowhere.net +} + + project = YAML.load(str) + @generator.load_job_collection project + + @generator.resolve_project(@generator.get_item('project-name')).should == + {:name=>"project-name", + :type=>:project, + :value=> + {:name=>"project-name", + :db=>"my_own_db", + :jobs=> + [{:"{{name}}-unit-tests"=>{:"mail-to"=>"developer@nowhere.net"}, + :result=> + {:name=>"project-name-unit-tests", + :builders=>[{:shell=>"unittest"}], + :publishers=>[{:email=>{:recipients=>"developer@nowhere.net"}}]}}, + {:"{{name}}-perf-tests"=>{:"mail-to"=>"projmanager@nowhere.net"}, + :result=> + {:name=>"project-name-perf-tests", + :builders=>[{:shell=>"perftest"}], + :publishers=>[{:email=>{:recipients=>"projmanager@nowhere.net"}}]}}]}, + :settings=>{:name=>"project-name", :db=>"my_own_db"}} + end + + it 'should build project collection from jobs and jobs templates' do + str = %{ +- job-template: + name: '{{name}}-unit-tests' + builders: + - shell: unittest + publishers: + - email: + recipients: '{{mail-to}}' + +- job: + name: 'foo-bar' + builders: + - shell: perftest + +- project: + name: project-name + db: my_own_db + jobs: + - 'foo-bar' + - '{{name}}-unit-tests': + mail-to: projmanager@nowhere.net +} + + project = YAML.load(str) + @generator.load_job_collection project + + @generator.resolve_project(@generator.get_item('project-name')).should == + {:name=>"project-name", + :type=>:project, + :value=> + {:name=>"project-name", + :db=>"my_own_db", + :jobs=> + [{:"foo-bar"=>{}, + :result=> + {:name=>"foo-bar", + :builders=>[{:shell=>"perftest"}]}}, + {:"{{name}}-unit-tests"=>{:"mail-to"=>"projmanager@nowhere.net"}, + :result=> + {:name=>"project-name-unit-tests", + :builders=>[{:shell=>"unittest"}], + :publishers=>[{:email=>{:recipients=>"projmanager@nowhere.net"}}]}}]}, + :settings=>{:name=>"project-name", :db=>"my_own_db"}} + end + + + describe 'compilation of templates' do + it 'compiles String' do + JenkinsPipelineBuilder::Compiler.compile('blah', { item1: 'data1'}).should == 'blah' + end + + it 'compiles simple Hash' do + JenkinsPipelineBuilder::Compiler.compile({ name: 'item-{{item1}}', value: 'item1-data'}, { item1: 'data1'}).should == + { name: 'item-data1', value: 'item1-data'} + end + + it 'compiles nested Hash' do + JenkinsPipelineBuilder::Compiler.compile({ name: 'item-{{item1}}', value: { house: 'house-{{item1}}'}}, { item1: 'data1'}).should == + { name: 'item-data1', value: { house: 'house-data1'}} + end + + it 'compiles complex Hash' do + template = {:name=>"{{name}}-unit-tests", + :builders=>[{:shell=>"unittest"}], + :publishers=>[{:email=>{:recipients=>"{{mail-to}}"}}]} + settings = {:name=>"project-name", :db=>"my_own_db", :'mail-to' => 'developer@nowhere.net'} + + JenkinsPipelineBuilder::Compiler.compile(template, settings).should == + {:name=>"project-name-unit-tests", + :builders=>[{:shell=>"unittest"}], + :publishers=>[{:email=>{:recipients=>"developer@nowhere.net"}}]} + end + end + + it 'shoult resolve job template into a job' do + file = 'project_simple' + path = File.expand_path('../fixtures/templates/' + file, __FILE__) + project = YAML.load_file(path + '.yaml') + + @generator.load_job_collection project + + @generator.resolve_job_by_name('{{name}}-unit-tests', { name: 'project-name', db: 'my_own_db', :'mail-to' => 'developer@nowhere.net' }).should == + {:name=>"project-name-unit-tests", + :builders=>[{:shell=>"unittest"}], + :publishers=>[{:email=>{:recipients=>"developer@nowhere.net"}}]} + end + + it 'should load from folder' do + path = File.expand_path('../fixtures/templates/', __FILE__) + @generator.load_collection_from_path(path) + + @generator.job_collection.count.should == 4 + @generator.projects.count == 1 + end +end \ No newline at end of file diff --git a/spec/unit_tests/spec_helper.rb b/spec/unit_tests/spec_helper.rb new file mode 100644 index 0000000..51fdb60 --- /dev/null +++ b/spec/unit_tests/spec_helper.rb @@ -0,0 +1,29 @@ +require 'logger' +require 'rspec' + +require 'simplecov' +require 'simplecov-rcov' + +SimpleCov.profiles.define 'spec' do + add_group 'jenksin_pipeline_builder', '/lib/' + coverage_dir 'out/coverage' + formatter SimpleCov::Formatter::MultiFormatter[ + SimpleCov::Formatter::Console, + SimpleCov::Formatter::RcovFormatter, + ] +end + +class SimpleCov::Formatter::Console + def format(result) + print "COVERAGE: #{result.covered_percent.round(2)}%\n" + end +end + +SimpleCov.start 'spec' #if ENV["COVERAGE"] + +require File.expand_path('../../../lib/jenksin_pipeline_builder', __FILE__) + +RSpec.configure do |config| + config.before(:each) do + end +end