jpi 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,6 @@
1
+ *.gem
2
+ .bundle
3
+ .idea
4
+ Gemfile.lock
5
+ pkg/*
6
+ target/
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in jenkins-plugins.gemspec
4
+ gemspec
data/README.md ADDED
@@ -0,0 +1,76 @@
1
+ # Jenkins plugins
2
+
3
+ Provide the facility to create, develop and release extensions for [Jenkins](http://jenkins-ci.org) with nothing but knowledge of the language, tools and best practices of the Ruby community.
4
+
5
+ [read more](http://blog.thefrontside.net/2011/05/12/what-it-take-to-bring-ruby-to-jenkins)...
6
+
7
+ # Get started
8
+
9
+ Using JRuby, install the plugin tools
10
+
11
+ $ gem install jpi
12
+
13
+ The gem provides the `jpi` executeable
14
+
15
+ $ jpi -h
16
+
17
+ jpi- tools to create, build, develop and release Jenkins plugins
18
+
19
+ Usage: jpi command [arguments] [options]
20
+
21
+ Commands:
22
+ jpi help [COMMAND] # get help for COMMAND, or for jpi itself
23
+ jpi new NAME # create a new plugin called NAME
24
+ jpi generate # generate code for extensions points
25
+ jpi build # build plugin into .hpi file suitable for distribution
26
+ jpi server # run a test server with plugin
27
+ jpi version # show jpi version information
28
+
29
+ The first thing you'll probably want to do is create a new ruby plugin.
30
+
31
+ $ jpi new one-great-plugin
32
+ create one-great-plugin/Gemfile
33
+ create one-great-plugin/one-great-plugin.pluginspec
34
+
35
+ This will create a minimal plugin project structure, to which you can add later.
36
+ Once you have your plugin created, you can run a server with it loaded
37
+
38
+ $ cd one-great-plugin
39
+ $ jpi server
40
+
41
+ Listening for transport dt_socket at address: 8000
42
+ webroot: System.getProperty("JENKINS_HOME")
43
+ [Winstone 2011/09/19 12:01:36] - Beginning extraction from war file
44
+ [Winstone 2011/09/19 12:01:37] - HTTP Listener started: port=8080
45
+ [Winstone 2011/09/19 12:01:37] - AJP13 Listener started: port=8009
46
+ [Winstone 2011/09/19 12:01:37] - Winstone Servlet Engine v0.9.10 running: controlPort=disabled
47
+ Sep 19, 2011 12:01:37 PM jenkins.model.Jenkins$6 onAttained
48
+ INFO: Started initialization
49
+ Sep 19, 2011 12:01:38 PM hudson.PluginManager$1$3$1 isDuplicate
50
+ Sep 19, 2011 12:01:39 PM jenkins.model.Jenkins$6 onAttained
51
+ INFO: Listed all plugins
52
+ Sep 19, 2011 12:01:39 PM ruby.RubyRuntimePlugin start
53
+ INFO: Injecting JRuby into XStream
54
+ Sep 19, 2011 12:01:49 PM jenkins.model.Jenkins$6 onAttained
55
+ INFO: Prepared all plugins
56
+ Sep 19, 2011 12:01:49 PM jenkins.model.Jenkins$6 onAttained
57
+ INFO: Started all plugins
58
+ Sep 19, 2011 12:01:49 PM jenkins.model.Jenkins$6 onAttained
59
+ INFO: Augmented all extensions
60
+ Sep 19, 2011 12:01:49 PM jenkins.model.Jenkins$6 onAttained
61
+ INFO: Loaded all jobs
62
+ Sep 19, 2011 12:01:51 PM jenkins.model.Jenkins$6 onAttained
63
+ INFO: Completed initialization
64
+ Sep 19, 2011 12:01:51 PM hudson.TcpSlaveAgentListener <init>
65
+ INFO: JNLP slave agent listener started on TCP port 52262
66
+ Sep 19, 2011 12:02:01 PM hudson.WebAppMain$2 run
67
+ INFO: Jenkins is fully up and running
68
+
69
+ Of course, this plugin isn't actually doing anything because we haven't defined any extension
70
+ points. Let's go ahead and create one of the most common extension points: a `Builder`
71
+
72
+ $ jpi generate builder logging
73
+
74
+
75
+
76
+
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
3
+
4
+ require 'rspec/core/rake_task'
5
+ RSpec::Core::RakeTask.new(:spec)
6
+
data/bin/jpi ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'jenkins/plugin/cli'
4
+
5
+ Jenkins::Plugin::CLI.start ARGV
@@ -0,0 +1,18 @@
1
+
2
+ Feature: Generating a new Jenkins Ruby Plugin
3
+
4
+ Creating a new Ruby plugin for Jenkins needs to be as simple as running a single command
5
+ that will generate a project skeleton. This skeleton will come complete with git repository and all
6
+ the goodies that you need to do your plugin develompent.
7
+
8
+ Scenario: The directory skeleton is generated
9
+ When I run "jpi new newplugin"
10
+ # Then I should see this structure
11
+ # """
12
+ # [-] newplugin
13
+ # | [+] .git
14
+ # | .gitignore
15
+ # | Gemfile
16
+ # | Rakefile
17
+ # | newplugin.pluginspec
18
+ # """
@@ -0,0 +1,7 @@
1
+ When /^I run "([^"]*)"$/ do |cmd|
2
+ run cmd
3
+ end
4
+
5
+ Then /^I should see this structure$/ do |structure|
6
+ fail "no match" unless DirectoryStructure.new(structure).matches? work_dir
7
+ end
@@ -0,0 +1,49 @@
1
+
2
+ class DirectoryStructure
3
+ def initialize(structure)
4
+
5
+ @root = context = DirChild.new('.')
6
+
7
+ structure.each_line do |line|
8
+ if line =~ /(\[[-+]\]|\|)?\s+(\.?\w+)$/
9
+ op, name = $1, $2
10
+ case op
11
+ when "[+]"
12
+ context.add(DirChild.new name)
13
+ when "[-]"
14
+ new_context = DirChild.new name
15
+ context.add(new_context)
16
+ context = new_context
17
+ when "|"
18
+ context.add(FileChild.new name)
19
+ end
20
+ end
21
+ end
22
+ end
23
+
24
+ def matches?(dir)
25
+ @root.matches?(dir)
26
+ end
27
+
28
+ Entry = Struct.new(:name)
29
+
30
+ class DirChild < Entry
31
+ def initialize(name)
32
+ super(name)
33
+ @entries = []
34
+ end
35
+
36
+ def add(entry)
37
+ @entries << entries
38
+ end
39
+
40
+ def matches?(realdir)
41
+ entries = Dir.new(realdir).entries
42
+ !@entries.detect {|e| !entries.map{|e| File.basename(e)}.member?(e)}
43
+ end
44
+ end
45
+
46
+ class FileChild < Entry
47
+
48
+ end
49
+ end
@@ -0,0 +1,20 @@
1
+ module Work
2
+ def run cmd
3
+ Dir.chdir(work_dir) do
4
+ root = Pathname(__FILE__).join('..', '..', '..')
5
+ full_cmd = "ruby -rubygems -I #{root.join('lib')} -S #{root.join('bin',cmd)}"
6
+ system(full_cmd) or fail "failed to run command #{cmd}"
7
+ end
8
+ end
9
+
10
+ def work_dir
11
+ @work_dir ||= File.expand_path("tmp/work")
12
+ end
13
+ end
14
+
15
+ Before do
16
+ FileUtils.rm_rf work_dir
17
+ FileUtils.mkdir_p work_dir
18
+ end
19
+
20
+ World(Work)
data/jpi.gemspec ADDED
@@ -0,0 +1,28 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "jenkins/plugin/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "jpi"
7
+ s.version = Jenkins::Plugin::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ["Charles Lowell", "Jørgen P. Tjernø", "Kohsuke Kawaguchi"]
10
+ s.email = ["cowboyd@thefrontside.net"]
11
+ s.homepage = "https://github.com/jenkinsci/jpi.rb"
12
+ s.summary = %q{Tools for creating and building Jenkins Ruby plugins}
13
+ s.description = %q{Allows you to generate a new Ruby plugin project, build it, test it in Jenkins and release it to the Jenkins Update Center.}
14
+
15
+ s.files = `git ls-files`.split("\n")
16
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
17
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
18
+ s.require_paths = ["lib"]
19
+
20
+ s.add_dependency "rubyzip"
21
+ s.add_dependency "thor"
22
+ s.add_dependency "jenkins-war", ">= 1.427"
23
+ s.add_dependency "bundler", "~> 1.1.rc2"
24
+ s.add_dependency "jenkins-plugin-runtime", "~> 0.1.13"
25
+
26
+ s.add_development_dependency "rspec", "~> 2.0"
27
+ s.add_development_dependency "cucumber", "~> 1.0"
28
+ end
@@ -0,0 +1,39 @@
1
+ module Jenkins
2
+ class CiOrg
3
+ # credential to access jenkins-ci.org
4
+ # TODO: move it elsewhere
5
+ class Credential
6
+ CREDENTIAL = File.expand_path("~/.jenkins-ci.org")
7
+
8
+ def initialize
9
+ @props = {}
10
+
11
+ if File.exists?(CREDENTIAL) then
12
+ File.open(CREDENTIAL,'r') do |f|
13
+ f.each_line do |l|
14
+ if l[0]=='#' then
15
+ return # comment
16
+ end
17
+
18
+ k,v = l.split("=",2)
19
+ @props[k]=v.strip
20
+ end
21
+ end
22
+ end
23
+ end
24
+
25
+ # do we already have the credential?
26
+ def has_credential?
27
+ @props["userName"] && @props["password"]
28
+ end
29
+
30
+ def user_name
31
+ @props["userName"]
32
+ end
33
+
34
+ def password
35
+ @props["password"]
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,66 @@
1
+
2
+ require 'thor'
3
+ require 'jenkins/plugin/specification'
4
+ require 'jenkins/plugin/cli/formatting'
5
+ require 'jenkins/plugin/cli/new'
6
+ require 'jenkins/plugin/cli/generate'
7
+
8
+
9
+ module Jenkins
10
+ class Plugin
11
+ class CLI < Thor
12
+ extend Formatting
13
+
14
+ register New, "new", "new NAME", "create a new plugin called NAME"
15
+ register Generate, "generate", "generate [options] [arguments]", "add new classes/templates and views to your project"
16
+ map "g" => "generate"
17
+
18
+
19
+ desc "build", "build plugin into .hpi file suitable for distribution"
20
+ def build
21
+ require 'jenkins/plugin/tools/package'
22
+ pkg = Tools::Package.new(spec, "pkg")
23
+ pkg.build
24
+ pkg
25
+ end
26
+
27
+ desc "server", "run a test server with plugin"
28
+ method_option :home, :desc => "set server work directory", :default => 'work'
29
+ method_option :port, :desc => "server http port (currently ignored)", :default => 8080
30
+ method_option :war, :desc => "specify a custom jenkins.war to run the plugin with"
31
+ def server
32
+ require 'jenkins/plugin/tools/server'
33
+ server = Tools::Server.new(spec, options[:home], options[:war])
34
+ server.run!
35
+ end
36
+ map "s" => "server"
37
+
38
+ desc "release", "release to jenkins-ci.org"
39
+ method_option :release, :desc => "deploy as a release (as opposed to a snapshot)", :type => :boolean
40
+ def release
41
+ require 'jenkins/plugin/tools/release'
42
+
43
+ Tools::Release.new(spec,build().file_name, !options[:release]).run
44
+ end
45
+
46
+ desc "version", "show jpi version information"
47
+ def version
48
+ require 'jenkins/plugin/version'
49
+ shell.say Jenkins::Plugin::VERSION
50
+ end
51
+ map ["-v","--version"] => "version"
52
+
53
+ desc "help [COMMAND]", "get help for COMMAND, or for jpi itself"
54
+ def help(command = nil)
55
+ super
56
+ end
57
+
58
+ private
59
+
60
+ def spec
61
+ Specification.find!
62
+ end
63
+
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,54 @@
1
+
2
+ module Jenkins
3
+ class Plugin
4
+ class CLI < Thor
5
+ module Formatting
6
+ def task_help(shell, task_name)
7
+ meth = normalize_task_name(task_name)
8
+ task = all_tasks[meth]
9
+ handle_no_task_error(meth) unless task
10
+
11
+ shell.say "usage: #{banner(task)}"
12
+ shell.say
13
+ class_options_help(shell, nil => task.options.map { |_, o| o })
14
+ end
15
+
16
+
17
+ def print_options(shell, options, grp = nil)
18
+ return if options.empty?
19
+ table = options.map do |option|
20
+ prototype = if option.default
21
+ " [#{option.default}]"
22
+ elsif option.type == :boolean
23
+ ""
24
+ elsif option.required?
25
+ " #{option.banner}"
26
+ else
27
+ " [#{option.banner}]"
28
+ end
29
+ aliases = option.aliases.empty? ? "" : option.aliases.join(" ") + ","
30
+ [aliases, "--#{option.name}#{prototype}", "\t",option.description]
31
+ end
32
+ shell.print_table(table, :ident => 2)
33
+ shell.say
34
+ end
35
+
36
+ def help(shell, task)
37
+ list = printable_tasks
38
+ print shell.set_color("jpi", :black, true)
39
+ shell.say <<-USEAGE
40
+ - tools to create, build, develop and release Jenkins plugins
41
+
42
+ Usage: jpi command [arguments] [options]
43
+
44
+ USEAGE
45
+
46
+ shell.say "Commands:"
47
+ shell.print_table(list, :ident => 2, :truncate => true)
48
+ shell.say
49
+ class_options_help(shell)
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,27 @@
1
+
2
+ module Jenkins
3
+ class Plugin
4
+ class CLI
5
+ class Generate < Thor
6
+ include Thor::Actions
7
+
8
+ source_root File.dirname(__FILE__)
9
+
10
+ argument :name
11
+
12
+ desc "publisher", "publisher NAME", :desc => "generate a publish step definition"
13
+ def publisher
14
+ @step_class = "Publisher"
15
+ template('templates/build_step.tt', "models/#{name.downcase}_publisher.rb")
16
+ end
17
+
18
+ desc "builder", "builder NAME", :desc => "generate a build step definition"
19
+ def builder
20
+ @step_class = "Builder"
21
+ template('templates/build_step.tt', "models/#{name.downcase}_builder.rb")
22
+ end
23
+
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,39 @@
1
+
2
+ require 'thor/group'
3
+
4
+ module Jenkins
5
+ class Plugin
6
+ class CLI
7
+ class New < Thor::Group
8
+ include Thor::Actions
9
+
10
+ source_root File.dirname(__FILE__)
11
+
12
+ argument :name
13
+
14
+ def create_gemfile
15
+ template('templates/Gemfile.tt', "#{name}/Gemfile")
16
+ end
17
+
18
+ def create_pluginspec
19
+ git_name = %x[git config user.name].chomp
20
+ git_email = %x[git config user.email].chomp
21
+
22
+ developer_id = git_email.split('@', 2).first || ''
23
+
24
+ # Fallback values.
25
+ git_name = 'TODO: Put your realname here' if git_name.empty?
26
+ git_email = 'email@example.com' if git_email.empty?
27
+
28
+ opts = {
29
+ :developer_id => developer_id.empty? ? 'TODO: Put your jenkins-ci.org username here.' : developer_id,
30
+ :developer_name => "#{git_name} <#{git_email}>"
31
+ }
32
+
33
+ template('templates/pluginspec.tt', "#{name}/#{name}.pluginspec", opts)
34
+ end
35
+
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,4 @@
1
+
2
+ source :rubygems
3
+
4
+ gem "jenkins-plugin-runtime", "~> 0.1.13"
@@ -0,0 +1,25 @@
1
+
2
+ class <%= name.capitalize %><%= @step_class %> < Jenkins::Tasks::<%= @step_class %>
3
+
4
+ display_name "<%= name.capitalize %> <%= @step_class.downcase %>"
5
+
6
+ ##
7
+ # Runs before the build begins
8
+ #
9
+ # @param [Jenkins::Model::Build] build the build which will begin
10
+ # @param [Jenkins::Model::Listener] listener the listener for this build.
11
+ def prebuild(build, listener)
12
+ # do any setup that needs to be done before this build runs.
13
+ end
14
+
15
+ ##
16
+ # Runs the step over the given build and reports the progress to the listener.
17
+ #
18
+ # @param [Jenkins::Model::Build] build on which to run this step
19
+ # @param [Jenkins::Launcher] launcher the launcher that can run code on the node running this build
20
+ # @param [Jenkins::Model::Listener] listener the listener for this build.
21
+ def perform(build, launcher, listener)
22
+ # actually perform the build step
23
+ end
24
+
25
+ end
@@ -0,0 +1,28 @@
1
+
2
+ Jenkins::Plugin::Specification.new do |plugin|
3
+ plugin.name = <%= name.inspect %>
4
+ plugin.display_name = <%= (name.capitalize + " Plugin").inspect %>
5
+ plugin.version = '0.0.1'
6
+ plugin.description = 'enter description here'
7
+
8
+ # You should create a wiki-page for your plugin when you publish it, see
9
+ # https://wiki.jenkins-ci.org/display/JENKINS/Hosting+Plugins#HostingPlugins-AddingaWikipage
10
+ # This line makes sure it's listed in your POM.
11
+ plugin.url = 'https://wiki.jenkins-ci.org/display/JENKINS/My+Plugin'
12
+
13
+ # The first argument is your user name for jenkins-ci.org.
14
+ plugin.developed_by <%= config[:developer_id].inspect %>, <%= config[:developer_name].inspect %>
15
+
16
+ # This specifies where your code is hosted.
17
+ # Alternatives include:
18
+ # :github => 'myuser/my-plugin' (without myuser it defaults to jenkinsci)
19
+ # :git => 'git://repo.or.cz/my-plugin.git'
20
+ # :svn => 'https://svn.jenkins-ci.org/trunk/hudson/plugins/my-plugin'
21
+ plugin.uses_repository :github => 'my-plugin'
22
+
23
+ # This is a required dependency for every ruby plugin.
24
+ plugin.depends_on 'ruby-runtime', '0.4'
25
+
26
+ # This is a sample dependency for a Jenkins plugin, 'git'.
27
+ plugin.depends_on 'git', '1.1.11'
28
+ end
@@ -0,0 +1,30 @@
1
+
2
+ module Jenkins
3
+ class Plugin
4
+ module Tools
5
+ class Bundle
6
+
7
+ def initialize(target)
8
+ @target = target
9
+ end
10
+
11
+ def install
12
+ require 'java'
13
+ require 'bundler'
14
+ puts "bundling..."
15
+
16
+ # We set these in ENV instead of passing the --without and --path
17
+ # options because the CLI options are remembered in .bundle/config and
18
+ # will interfere with regular usage of bundle exec / install.
19
+ Bundler.with_clean_env {
20
+ ENV['BUNDLE_APP_CONFIG'] = "#{@target}/vendor/bundle"
21
+ ENV['BUNDLE_WITHOUT'] = "development"
22
+ ENV['BUNDLE_PATH'] = "#{@target}/vendor/gems"
23
+ ENV.delete 'RUBYOPT'
24
+ system('bundle --standalone')
25
+ }
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,107 @@
1
+ require 'zip/zip'
2
+ require 'net/http'
3
+ require 'uri'
4
+ require 'fileutils'
5
+
6
+ module Jenkins
7
+ class Plugin
8
+ module Tools
9
+ # class for parsing hpi file and its manifests
10
+ class Hpi
11
+ attr_reader :file, :manifest
12
+
13
+ # take the path name to the plugin file
14
+ def initialize(file)
15
+ @file = file
16
+
17
+ # load and parse manifests
18
+ Zip::ZipFile.open(@file) do |zip|
19
+ zip.get_input_stream("META-INF/MANIFEST.MF") do |m|
20
+ # main section of the manifest
21
+ @manifest = parse_manifest(m.read)[0]
22
+ end
23
+ end
24
+
25
+ # parse dependencies into hash
26
+ @dependencies = {}
27
+ deps = @manifest["Plugin-Dependencies"]
28
+ if deps
29
+ deps.split(",").each do |token|
30
+ token = token.gsub(/;.+/,"") # trim off the optional portions
31
+ name,ver = token.split(":")
32
+ @dependencies[name] = ver
33
+ end
34
+ end
35
+ end
36
+
37
+ # given the plugin short name and the version number,
38
+ # return the path name of the .hpi file by either locating the plugin locally or downloading it.
39
+ def self.resolve(short_name,version)
40
+ # this is where we expect the retrieved file to be
41
+ cache = File.expand_path "~/.jenkins/cache/plugins/#{short_name}/#{version}/#{short_name}.hpi"
42
+
43
+ return cache if File.exists?(cache)
44
+
45
+ # now we start looking for places to find them
46
+
47
+ # is it in the local maven2 repository?
48
+ maven = File.expand_path "~/.m2/repository/org/jenkins-ci/plugins/#{short_name}/#{version}/#{short_name}-#{version}.hpi"
49
+ return maven if File.exists?(maven)
50
+
51
+ # download from the community update center
52
+ FileUtils.mkdir_p(File.dirname(cache))
53
+ open(cache+".tmp","wb") do |f|
54
+ puts "Downloading #{short_name} #{version}"
55
+ url = "http://updates.jenkins-ci.org/download/plugins/#{short_name}/#{version}/#{short_name}.hpi?for=ruby-plugin"
56
+ puts " from #{url}"
57
+ f.write fetch(url).body
58
+ end
59
+ FileUtils.mv cache+".tmp", cache
60
+
61
+ return cache
62
+ end
63
+
64
+ # download with redirect support
65
+ def self.fetch(uri, limit = 10)
66
+ # You should choose better exception.
67
+ raise ArgumentError, 'HTTP redirect too deep' if limit == 0
68
+
69
+ response = Net::HTTP.get_response(URI.parse(uri))
70
+ case response
71
+ when Net::HTTPSuccess then response
72
+ when Net::HTTPRedirection then fetch(response['location'], limit - 1)
73
+ else
74
+ response.error!
75
+ end
76
+ end
77
+
78
+ # parse manifest file text into a hash
79
+ def parse_manifest(txt)
80
+ # separators
81
+ nl = /\r\n|\n|\r[^\n]/
82
+ secsep = /(#{nl}){2}/
83
+
84
+ txt.split(secsep).reject { |s| s.chomp.length==0 }.map do |section|
85
+ lines = []
86
+ section.split(nl).each do |line|
87
+ if line[0]==0x20
88
+ lines.last << line[1..-1] # continuation of the previous line
89
+ else
90
+ lines << line
91
+ end
92
+ end
93
+
94
+ # convert to hash
95
+ hash = {}
96
+ lines.each do |l|
97
+ (k,v) = l.split(/: /,2)
98
+ hash[k] = v
99
+ end
100
+
101
+ hash
102
+ end
103
+ end
104
+ end
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,29 @@
1
+ module Jenkins
2
+ class Plugin
3
+ module Tools
4
+ class Loadpath
5
+ def initialize(*groups)
6
+ require 'bundler'
7
+ @groups = groups.empty? ? [:default] : groups
8
+ end
9
+
10
+ def to_path
11
+ to_a.join(File::PATH_SEPARATOR)
12
+ end
13
+
14
+ def to_a
15
+ [].tap do |paths|
16
+ specs = Bundler.definition.specs_for @groups.map {|g| g.to_sym}
17
+ for spec in specs
18
+ next if spec.name == "bundler"
19
+ for path in spec.require_paths
20
+ paths << File.join(spec.full_gem_path, path)
21
+ end
22
+ end
23
+ end
24
+ end
25
+
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,80 @@
1
+
2
+ #require 'jenkins/plugin/version'
3
+
4
+ require 'jenkins/plugin/version'
5
+ require 'etc'
6
+
7
+ module Jenkins
8
+ class Plugin
9
+ module Tools
10
+ class Manifest
11
+
12
+ def initialize(spec)
13
+ @spec = spec
14
+ end
15
+
16
+ def write_hpi(io)
17
+ w = Writer.new(io)
18
+ w.put "Manifest-Version", "1.0"
19
+ w.put "Created-By", Jenkins::Plugin::VERSION
20
+ w.put "Build-Ruby-Platform", RUBY_PLATFORM
21
+ w.put "Build-Ruby-Version", RUBY_VERSION
22
+ w.put "Built-By", Etc.getlogin()
23
+
24
+ w.put "Group-Id", "org.jenkins-ci.plugins"
25
+ w.put "Short-Name", @spec.name
26
+ w.put "Long-Name", @spec.display_name
27
+ w.put "Url", @spec.url if @spec.url
28
+
29
+ w.put "Plugin-Class", "ruby.RubyPlugin"
30
+ w.put "Plugin-Version", @spec.version
31
+ w.put "Jenkins-Version", "1.432"
32
+
33
+ w.put "Plugin-Dependencies", @spec.dependencies.map{|k,v| "#{k}:#{v}"}.join(",")
34
+ end
35
+
36
+ def write_hpl(io, loadpath)
37
+ write_hpi(io)
38
+
39
+ w = Writer.new(io)
40
+ w.put "Load-Path", loadpath.to_a.join(':')
41
+ w.put "Lib-Path", "#{Dir.pwd}/lib/"
42
+ w.put "Models-Path", "#{Dir.pwd}/models"
43
+ # Stapler expects view erb/haml scripts to be in the JVM ClassPath
44
+ w.put "Class-Path", "#{Dir.pwd}/views" if File.exists?("#{Dir.pwd}/views")
45
+ # Directory for static images, javascript, css, etc. of this plugin.
46
+ # The static resources are mapped under #CONTEXTPATH/plugin/SHORTNAME/
47
+ w.put "Resource-Path", "#{Dir.pwd}/static"
48
+ end
49
+
50
+ class Writer
51
+
52
+ MAX_LENGTH = 72.to_i
53
+
54
+ def initialize(io)
55
+ @io = io
56
+ end
57
+
58
+ def put(key, value)
59
+ @io.puts "#{key}: #{manifest_truncate(value)}"
60
+ end
61
+
62
+ def manifest_truncate(message)
63
+ if message.length < MAX_LENGTH
64
+ return message
65
+ end
66
+
67
+ line = message[0 ... MAX_LENGTH] + "\n"
68
+ offset = MAX_LENGTH
69
+
70
+ while offset < message.length
71
+ line += " #{message[offset ... (offset + MAX_LENGTH - 1)]}\n"
72
+ offset += (MAX_LENGTH - 1)
73
+ end
74
+ return line
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,60 @@
1
+ require 'jenkins/plugin/tools/bundle'
2
+ require 'jenkins/plugin/tools/manifest'
3
+ require 'zip/zip'
4
+
5
+ module Jenkins
6
+ class Plugin
7
+ module Tools
8
+ class Package
9
+
10
+ def initialize(spec,target)
11
+ @target = target
12
+ @spec = spec
13
+ end
14
+
15
+ # where to generate the package?
16
+ def file_name
17
+ file_name = "#{@target}/#{@spec.name}.hpi"
18
+ end
19
+
20
+ def build
21
+ FileUtils.mkdir_p @target
22
+
23
+ Bundle.new(@target).install
24
+
25
+ manifest = Manifest.new(@spec)
26
+
27
+ File.delete file_name if File.exists?(file_name)
28
+
29
+ Zip::ZipFile.open(file_name, Zip::ZipFile::CREATE) do |zipfile|
30
+ zipfile.get_output_stream("META-INF/MANIFEST.MF") do |f|
31
+ manifest.write_hpi(f)
32
+ f.puts "Bundle-Path: vendor/gems"
33
+ end
34
+ zipfile.mkdir("WEB-INF/classes")
35
+
36
+ ["lib","models","#{@target}/vendor"].each do |d|
37
+ Dir.glob("#{d}/**/*") do |f|
38
+ if !File.directory? f
39
+ p = f.gsub("#{@target}/",'')
40
+ if p !~ %r{/cache/}
41
+ zipfile.add("WEB-INF/classes/#{p}",f)
42
+ end
43
+ end
44
+ end
45
+ end
46
+
47
+ # stapler expects views to be directly in the classpath without any prefix
48
+ Dir.glob("views/**/*") do |f|
49
+ if !File.directory? f
50
+ zipfile.add("WEB-INF/classes/#{f[6..-1]}",f)
51
+ end
52
+ end
53
+ end
54
+ puts "#{@spec.name} plugin #{@spec.version} built to #{file_name}"
55
+
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,70 @@
1
+ require 'jenkins/plugin/tools/bundle'
2
+ require 'jenkins/plugin/tools/manifest'
3
+ require 'jenkins/jenkins-ci.org/credential'
4
+ require 'net/http'
5
+ require 'erb'
6
+
7
+ module Jenkins
8
+ class Plugin
9
+ module Tools
10
+ # task for deploying a plugin
11
+ class Release
12
+
13
+ def initialize(spec,hpi,snapshot)
14
+ @spec = spec
15
+ @hpi = hpi # hpi file to release
16
+ @snapshot = snapshot # if true, deploy as a snapshot, otherwise as release
17
+ end
18
+
19
+ def check_error(rsp)
20
+ # in case of 401 Unauthorized, the server just resets the connection and Net::HTTP fails to parse the response,
21
+ # so we don't really get any meaningful error message.
22
+ rsp.value # TODO: is this how we check for the error?
23
+ end
24
+
25
+ def each_developer
26
+ @spec.developers.each do |id, name|
27
+ email = ''
28
+ if name =~ /^(.*)<([^>]+)>$/
29
+ name = $1
30
+ email = $2.strip
31
+ end
32
+
33
+ yield id, name.strip, email
34
+ end
35
+ end
36
+
37
+ def run
38
+ cred = Jenkins::CiOrg::Credential.new
39
+ if !cred.has_credential? then
40
+ raise Exception.new("no credential available to connect to jenkins-ci.org. Please create ~/.jenkins-ci.org. See https://wiki.jenkins-ci.org/display/JENKINS/Dot+Jenkins+Ci+Dot+Org")
41
+ end
42
+
43
+ http = Net::HTTP.new("maven.jenkins-ci.org",8081)
44
+
45
+ puts @snapshot ? "deploying as a snapshot" : "deploying as a release"
46
+ puts "Generating POM"
47
+ version = @snapshot ? @spec.version+"-SNAPSHOT" : @spec.version
48
+ pom = ERB.new(File.read(File.dirname(__FILE__)+"/templates/release-pom.xml.erb")).result(binding)
49
+
50
+ path = "/content/repositories/#{@snapshot?'snapshots':'releases'}/org/jenkins-ci/ruby-plugins/#{@spec.name}/#{version}/#{@spec.name}-#{version}"
51
+ req = Net::HTTP::Put.new("#{path}.pom")
52
+ req.body = pom
53
+ req.basic_auth(cred.user_name,cred.password)
54
+ check_error(http.request(req))
55
+
56
+ puts "Uploading #{@hpi}"
57
+ File.open(@hpi,'r') do |f|
58
+ req = Net::HTTP::Put.new("#{path}.hpi")
59
+ req.body_stream = f
60
+ req.basic_auth(cred.user_name,cred.password)
61
+ req.content_length = File.size(@hpi)
62
+ check_error(http.request(req))
63
+ end
64
+
65
+ puts "See http://maven.jenkins-ci.org"+File.dirname(path)
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,61 @@
1
+ require 'net/http'
2
+
3
+ module Jenkins
4
+ class Plugin
5
+ module Tools
6
+ class Resolver
7
+
8
+ def initialize(spec, dir)
9
+ @spec = spec
10
+ @dir = dir
11
+ FileUtils.mkdir_p(dir) unless File.directory? @dir
12
+ end
13
+
14
+ def resolve!
15
+ @spec.dependencies.each do |name, version|
16
+ FileUtils.cp resolve(name, version), @dir
17
+ end
18
+ end
19
+
20
+ def resolve(short_name,version)
21
+ # this is where we expect the retrieved file to be
22
+ cache = File.expand_path "~/.jenkins/cache/plugins/#{short_name}/#{version}/#{short_name}.hpi"
23
+
24
+ return cache if File.exists?(cache)
25
+
26
+ # now we start looking for places to find them
27
+
28
+ # is it in the local maven2 repository?
29
+ maven = File.expand_path "~/.m2/repository/org/jenkins-ci/plugins/#{short_name}/#{version}/#{short_name}-#{version}.hpi"
30
+ return maven if File.exists?(maven)
31
+
32
+ # download from the community update center
33
+ FileUtils.mkdir_p(File.dirname(cache))
34
+ open(cache+".tmp","wb") do |f|
35
+ puts "Downloading #{short_name} #{version}"
36
+ url = "http://updates.jenkins-ci.org/download/plugins/#{short_name}/#{version}/#{short_name}.hpi?for=ruby-plugin"
37
+ puts " from #{url}"
38
+ f.write fetch(url).body
39
+ end
40
+ FileUtils.mv cache+".tmp", cache
41
+
42
+ return cache
43
+ end
44
+
45
+ # download with redirect support
46
+ def fetch(uri, limit = 10)
47
+ # You should choose better exception.
48
+ raise ArgumentError, 'HTTP redirect too deep' if limit == 0
49
+
50
+ response = Net::HTTP.get_response(URI.parse(uri))
51
+ case response
52
+ when Net::HTTPSuccess then response
53
+ when Net::HTTPRedirection then fetch(response['location'], limit - 1)
54
+ else
55
+ response.error!
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,49 @@
1
+ require 'jenkins/plugin/tools/loadpath'
2
+ require 'jenkins/plugin/tools/resolver'
3
+ require 'jenkins/plugin/tools/manifest'
4
+ require 'jenkins/war'
5
+ require 'fileutils'
6
+
7
+ module Jenkins
8
+ class Plugin
9
+ module Tools
10
+ class Server
11
+
12
+ def initialize(spec, workdir, war)
13
+ @spec = spec
14
+ @workdir = workdir
15
+ @plugindir = "#{workdir}/plugins"
16
+ @war = war || Jenkins::War::LOCATION
17
+ end
18
+
19
+ def run!
20
+ FileUtils.mkdir_p(@plugindir)
21
+ loadpath = Jenkins::Plugin::Tools::Loadpath.new
22
+ manifest = Jenkins::Plugin::Tools::Manifest.new(@spec)
23
+ resolver = Jenkins::Plugin::Tools::Resolver.new(@spec, @plugindir)
24
+
25
+ resolver.resolve!
26
+ # generate the plugin manifest
27
+
28
+ File.open("#{@plugindir}/#{@spec.name}.hpl",mode="w+") do |f|
29
+ manifest.write_hpl(f, loadpath)
30
+ end
31
+
32
+
33
+ # execute Jenkins
34
+ args = []
35
+ args << "java"
36
+ args << "-Xrunjdwp:transport=dt_socket,server=y,address=8000,suspend=n"
37
+ args << "-DJENKINS_HOME=#{@workdir}"
38
+ args << "-Dstapler.trace=true"
39
+ args << "-Ddebug.YUI=true"
40
+ # args << "-Djruby.debug.loadService=true"
41
+ # args << "-Djruby.debug.loadService.timing=true"
42
+ args << "-jar"
43
+ args << @war
44
+ exec *args
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,54 @@
1
+ <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
2
+ <modelVersion>4.0.0</modelVersion>
3
+ <parent>
4
+ <groupId>org.jenkins-ci.plugins</groupId>
5
+ <artifactId>plugin</artifactId>
6
+ <version>1.420</version>
7
+ </parent>
8
+
9
+ <groupId>org.jenkins-ci.ruby-plugins</groupId>
10
+ <artifactId><%= @spec.name %></artifactId>
11
+ <version><%= version %></version>
12
+ <name><%= @spec.display_name %></name>
13
+ <description><%= @spec.description %></description>
14
+ <packaging>hpi</packaging>
15
+
16
+ <% if @spec.url %>
17
+ <url><%= @spec.url %></url>
18
+ <% end %>
19
+
20
+ <repositories>
21
+ <repository>
22
+ <id>m.g.o-public</id>
23
+ <url>http://maven.glassfish.org/content/groups/public/</url>
24
+ </repository>
25
+ </repositories>
26
+
27
+ <developers>
28
+ <% each_developer do |id, name, email| %>
29
+ <developer>
30
+ <id><%= id %></id>
31
+ <name><%= name %></name>
32
+ <% if not email.empty? %>
33
+ <email><%= email %></email>
34
+ <% end %>
35
+ </developer>
36
+ <% end %>
37
+ </developers>
38
+
39
+ <dependencies>
40
+ <% @spec.dependencies.each do |k,v| %>
41
+ <dependency>
42
+ <groupId>org.jenkins-ci.plugins</groupId><!-- TODO: needs to figure out correct groupId -->
43
+ <artifactId><%= k %></artifactId>
44
+ <version><%= v %></version>
45
+ </dependency>
46
+ <% end %>
47
+ </dependencies>
48
+
49
+ <% if @spec.repository %>
50
+ <scm>
51
+ <connection>scm:<%= @spec.repository[:type] %>:<%= @spec.repository[:url] %></connection>
52
+ </scm>
53
+ <% end %>
54
+ </project>
@@ -0,0 +1,5 @@
1
+ module Jenkins
2
+ class Plugin
3
+ VERSION = "0.3.0"
4
+ end
5
+ end
@@ -0,0 +1,52 @@
1
+ require 'jenkins/plugin/version'
2
+ require 'jenkins/plugin/specification'
3
+ require 'jenkins/plugin/tools/hpi'
4
+ require 'jenkins/plugin/tools/loadpath'
5
+ require 'zip/zip'
6
+
7
+ module Jenkins
8
+ # given the IO handle, produce the basic manifest entries that are common between hpi and hpl formats
9
+
10
+ def self.spec
11
+ @spec ||= Jenkins::Plugin::Specification.load(Dir['*.pluginspec'].first)
12
+ end
13
+
14
+ class Rake
15
+ def self.install_tasks
16
+ self.new.install
17
+ end
18
+
19
+ include ::Rake::DSL if defined? ::Rake::DSL
20
+
21
+ def install
22
+ desc "Directory used as JENKINS_HOME during 'rake server'"
23
+ directory work = "work"
24
+
25
+ desc "remove built artifacts"
26
+ task :clean do
27
+ sh "rm -rf pkg"
28
+ sh "rm -rf vendor"
29
+ end
30
+
31
+ desc "output the development servers loadpath"
32
+ task :loadpath do
33
+ loadpath = Jenkins::Plugin::Tools::Loadpath.new(:default)
34
+ puts loadpath.to_path
35
+ end
36
+
37
+ desc "package up stuff into HPI file"
38
+ task :package do
39
+ require 'jenkins/plugin/tools/package'
40
+ Jenkins::Plugin::Tools::Package.new(Jenkins.spec,"pkg").build
41
+ end
42
+
43
+ desc "run a Jenkins server with this plugin"
44
+ task :server do
45
+ require 'jenkins/plugin/tools/server'
46
+
47
+ server = Jenkins::Plugin::Tools::Server.new(Jenkins.spec, "work")
48
+ server.run!
49
+ end
50
+ end
51
+ end
52
+ end
metadata ADDED
@@ -0,0 +1,164 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: jpi
3
+ version: !ruby/object:Gem::Version
4
+ prerelease:
5
+ version: 0.3.0
6
+ platform: ruby
7
+ authors:
8
+ - Charles Lowell
9
+ - "J\xC3\xB8rgen P. Tjern\xC3\xB8"
10
+ - Kohsuke Kawaguchi
11
+ autorequire:
12
+ bindir: bin
13
+ cert_chain: []
14
+
15
+ date: 2011-12-19 00:00:00 Z
16
+ dependencies:
17
+ - !ruby/object:Gem::Dependency
18
+ name: rubyzip
19
+ prerelease: false
20
+ requirement: &id001 !ruby/object:Gem::Requirement
21
+ none: false
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: "0"
26
+ type: :runtime
27
+ version_requirements: *id001
28
+ - !ruby/object:Gem::Dependency
29
+ name: thor
30
+ prerelease: false
31
+ requirement: &id002 !ruby/object:Gem::Requirement
32
+ none: false
33
+ requirements:
34
+ - - ">="
35
+ - !ruby/object:Gem::Version
36
+ version: "0"
37
+ type: :runtime
38
+ version_requirements: *id002
39
+ - !ruby/object:Gem::Dependency
40
+ name: jenkins-war
41
+ prerelease: false
42
+ requirement: &id003 !ruby/object:Gem::Requirement
43
+ none: false
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: "1.427"
48
+ type: :runtime
49
+ version_requirements: *id003
50
+ - !ruby/object:Gem::Dependency
51
+ name: bundler
52
+ prerelease: false
53
+ requirement: &id004 !ruby/object:Gem::Requirement
54
+ none: false
55
+ requirements:
56
+ - - ~>
57
+ - !ruby/object:Gem::Version
58
+ version: 1.1.rc2
59
+ type: :runtime
60
+ version_requirements: *id004
61
+ - !ruby/object:Gem::Dependency
62
+ name: jenkins-plugin-runtime
63
+ prerelease: false
64
+ requirement: &id005 !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ~>
68
+ - !ruby/object:Gem::Version
69
+ version: 0.1.13
70
+ type: :runtime
71
+ version_requirements: *id005
72
+ - !ruby/object:Gem::Dependency
73
+ name: rspec
74
+ prerelease: false
75
+ requirement: &id006 !ruby/object:Gem::Requirement
76
+ none: false
77
+ requirements:
78
+ - - ~>
79
+ - !ruby/object:Gem::Version
80
+ version: "2.0"
81
+ type: :development
82
+ version_requirements: *id006
83
+ - !ruby/object:Gem::Dependency
84
+ name: cucumber
85
+ prerelease: false
86
+ requirement: &id007 !ruby/object:Gem::Requirement
87
+ none: false
88
+ requirements:
89
+ - - ~>
90
+ - !ruby/object:Gem::Version
91
+ version: "1.0"
92
+ type: :development
93
+ version_requirements: *id007
94
+ description: Allows you to generate a new Ruby plugin project, build it, test it in Jenkins and release it to the Jenkins Update Center.
95
+ email:
96
+ - cowboyd@thefrontside.net
97
+ executables:
98
+ - jpi
99
+ extensions: []
100
+
101
+ extra_rdoc_files: []
102
+
103
+ files:
104
+ - .gitignore
105
+ - Gemfile
106
+ - README.md
107
+ - Rakefile
108
+ - bin/jpi
109
+ - features/create-new-plugin.feature
110
+ - features/support/create_new_plugin_steps.rb
111
+ - features/support/directory_structure.rb
112
+ - features/support/work.rb
113
+ - jpi.gemspec
114
+ - lib/jenkins/jenkins-ci.org/credential.rb
115
+ - lib/jenkins/plugin/cli.rb
116
+ - lib/jenkins/plugin/cli/formatting.rb
117
+ - lib/jenkins/plugin/cli/generate.rb
118
+ - lib/jenkins/plugin/cli/new.rb
119
+ - lib/jenkins/plugin/cli/templates/Gemfile.tt
120
+ - lib/jenkins/plugin/cli/templates/build_step.tt
121
+ - lib/jenkins/plugin/cli/templates/pluginspec.tt
122
+ - lib/jenkins/plugin/tools/bundle.rb
123
+ - lib/jenkins/plugin/tools/hpi.rb
124
+ - lib/jenkins/plugin/tools/loadpath.rb
125
+ - lib/jenkins/plugin/tools/manifest.rb
126
+ - lib/jenkins/plugin/tools/package.rb
127
+ - lib/jenkins/plugin/tools/release.rb
128
+ - lib/jenkins/plugin/tools/resolver.rb
129
+ - lib/jenkins/plugin/tools/server.rb
130
+ - lib/jenkins/plugin/tools/templates/release-pom.xml.erb
131
+ - lib/jenkins/plugin/version.rb
132
+ - lib/jenkins/rake.rb
133
+ homepage: https://github.com/jenkinsci/jpi.rb
134
+ licenses: []
135
+
136
+ post_install_message:
137
+ rdoc_options: []
138
+
139
+ require_paths:
140
+ - lib
141
+ required_ruby_version: !ruby/object:Gem::Requirement
142
+ none: false
143
+ requirements:
144
+ - - ">="
145
+ - !ruby/object:Gem::Version
146
+ version: "0"
147
+ required_rubygems_version: !ruby/object:Gem::Requirement
148
+ none: false
149
+ requirements:
150
+ - - ">="
151
+ - !ruby/object:Gem::Version
152
+ version: "0"
153
+ requirements: []
154
+
155
+ rubyforge_project:
156
+ rubygems_version: 1.8.9
157
+ signing_key:
158
+ specification_version: 3
159
+ summary: Tools for creating and building Jenkins Ruby plugins
160
+ test_files:
161
+ - features/create-new-plugin.feature
162
+ - features/support/create_new_plugin_steps.rb
163
+ - features/support/directory_structure.rb
164
+ - features/support/work.rb