Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

Refactored from singleton and added first class models for job and build #2

Merged
merged 3 commits into from

2 participants

@dblock
Collaborator

I want to propose a substantial change to the structure of the gem. If you like it, would love if you merged it. If you don't like it, I'll be happy to release the fork under a new name. No hard feelings :)

The biggest gripe is that it's a singleton. There's no need to have one global connection to Jenkins for the entire program, you should be able to use the client against multiple jenkins servers for starters and with multiple authentications.

The second issue is that jobs and builds aren't encapsulated. So adding functionality to a "job" or a "build" becomes difficult. Refactored the code to have a Job and a Build class with their respective methods such as start!.

Added a couple of pieces that I need for my project, notably console output from the last failed/succeeded build.

@dblock
Collaborator

Btw, this pull also merges @aimak's work, which fixed the URL issue I was having.

@john-griffin

Thanks for getting this refactored. I had started on something similar but hadn't gotten round to completing it so this is great. I'll merge this is in and do a new release in a few days.

@john-griffin john-griffin merged commit 6664802 into john-griffin:master
@dblock
Collaborator

Awesome. I'll make some more pulls to move this forward.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Mar 15, 2012
  1. upgrade to latest webmock

    authored
Commits on Apr 26, 2012
  1. @dblock
  2. @dblock
This page is out of date. Refresh to see the latest.
View
50 README.md
@@ -21,26 +21,28 @@ Or install it yourself as:
In rails add an initialiser like this
``` ruby
-Jenkins::Client.configure do |c|
- c.username = "user"
- c.password = "pass"
- c.url = "http://jenkinsurl.com"
-end
+client = Jenkins::Client.new
+client.username = "user"
+client.password = "pass"
+client.url = "http://jenkinsurl.com"
```
Then you can issue the following commands in your app.
-### All
+### Jobs
-`Jenkins::Client::Job.all` pulls back a list of objects that represent all the Jenkins jobs on the server.
+`client.jobs` returns a hash of jobs with the job name as key.
-### Find
+#### Create
-`Jenkins::Client::Job.find("job_name")` pulls back a single Jenkins job based on the job name.
+Create a new Jenkins job on the server with a given configuration.
-### Create
+``` ruby
+job = Jenkins::Client::Job.new({name: "job_name"})
+job.create!(config)
+```
-`Jenkins::Client::Job.create("job_name", config).should be_true` will create a new Jenkins job on the server based on the config you pass in. Jenkins uses XML config files on the server and this is what you should send as the config. Example
+Jenkins uses XML config files on the server and this is what you should send as the config. Example:
``` xml
<?xml version='1.0' encoding='UTF-8'?>
@@ -64,13 +66,31 @@ Then you can issue the following commands in your app.
To export an existing config simply look in the jobs path inside your Jenkins server and pull back a job's `config.xml` file.
-### Start
+#### Start
+
+Starts a job.
+
+job = Jenkins::Client::Job.new({name: "job_name"})
+job.start!
+
+#### Delete
+
+Delete a Jenkins job on the server.
+
+``` ruby
+job = Jenkins::Client::Job.new({name: "job_name"})
+job.delete!
+```
+
+#### Last Build
-`Jenkins::Client::Job.start("job_name")` will start a build for the job whose name is passed by parameter
+`job.last_build` will return the last build.
+`job.last_successful_build` will return the last successful build.
+`job.last_failed_build` will return the last failed build.
-### LastBuild
+### Build
-`Jenkins::Client::Job.lastBuild("job_name")` will return information about the last build of the job whose name is passed by parameter
+`build.console_text` will return the build's console text.
## Contributing
View
2  jenkins-client.gemspec
@@ -21,6 +21,6 @@ Gem::Specification.new do |gem|
gem.add_development_dependency "rspec", "~> 2.8.0"
gem.add_development_dependency "rake"
- gem.add_development_dependency "webmock", "~> 1.7.10"
+ gem.add_development_dependency "webmock", "~> 1.8.2"
gem.add_development_dependency "guard-rspec", "~> 0.6.0"
end
View
7 lib/jenkins-client.rb
@@ -1,2 +1,7 @@
+require "faraday"
+require "faraday_middleware"
+require 'rash'
require "jenkins-client/version"
-require "jenkins-client/job"
+require "jenkins-client/client"
+require "jenkins-client/job"
+require "jenkins-client/build"
View
11 lib/jenkins-client/build.rb
@@ -0,0 +1,11 @@
+module Jenkins
+ class Client
+ class Build < Hashie::Rash
+ attr_accessor :job
+
+ def console_text
+ job.client.get("#{url}consoleText", job.client.raw_connection).body
+ end
+ end
+ end
+end
View
78 lib/jenkins-client/client.rb
@@ -1,47 +1,61 @@
-require "faraday"
-require "faraday_middleware"
-require 'rash'
-
module Jenkins
class Client
- class << self
- attr_accessor :username, :password, :url, :connection
+ attr_accessor :username, :password, :url
- def configure
- yield self
- setup_connection
- end
+ def configure
+ yield self
+ end
- @connection
- def setup_connection
- @connection = Faraday.new(:url => url) do |builder|
+ def connection
+ @connection ||= begin
+ c = Faraday.new(:url => url) do |builder|
builder.use FaradayMiddleware::Rashify
builder.use FaradayMiddleware::ParseJson
- builder.adapter :net_http
+ builder.adapter :net_http
end
- @connection.basic_auth username, password
+ c.basic_auth username, password
+ c
end
-
- def get(path)
- normalized_path = normalize_path path
- connection.get(normalized_path)
- end
-
- def post(path, body)
- normalized_path = normalize_path path
- resp = @connection.post do |req|
- req.headers['Content-Type'] = 'application/xml'
- req.url normalized_path
- req.body = body
+ end
+
+ def raw_connection
+ @raw_connection ||= begin
+ c = Faraday.new(:url => url) do |builder|
+ builder.adapter :net_http
end
- resp.status == 200
+ c.basic_auth username, password
+ c
end
+ end
+
+ def get(path, use_connection = connection)
+ normalized_path = normalize_path path
+ use_connection.get(normalized_path)
+ end
- private
- def normalize_path path
- return @connection.path_prefix+path unless @connection.path_prefix == '/'
- path
+ def post(path, body, use_connection = connection)
+ normalized_path = normalize_path path
+ resp = use_connection.post do |req|
+ req.headers['Content-Type'] = 'application/xml'
+ req.url normalized_path
+ req.body = body
end
+ resp.status == 200
+ end
+
+ def jobs
+ Hash[get("/api/json").body.jobs.map do |data|
+ job = Jenkins::Client::Job.new(data)
+ job.client = self
+ [ job.name, job ]
+ end]
+ end
+
+ private
+
+ def normalize_path path
+ return @connection.path_prefix+path unless @connection.path_prefix == '/'
+ path
end
end
View
44 lib/jenkins-client/job.rb
@@ -1,32 +1,34 @@
-require "jenkins-client/client"
-
module Jenkins
class Client
- class Job
- def self.all
- resp = Jenkins::Client.get "/api/json"
- resp.body.jobs
+ class Job < Hashie::Rash
+ attr_accessor :client
+
+ def create!(config)
+ client.post("/createItem/api/xml?name=#{CGI.escape(name)}", config)
end
-
- def self.find(name)
- all.select{|j| j.name == name}.first
+
+ def delete!
+ client.post("/job/#{name}/doDelete", "")
end
-
- def self.create(name, config)
- Jenkins::Client.post("/createItem/api/xml?name=#{CGI.escape(name)}", config)
+
+ def start!
+ client.post("/job/#{name}/build", "")
end
-
- def self.delete(name)
- Jenkins::Client.post("/job/#{name}/doDelete", "")
+
+ def last_build(status = "")
+ build = Jenkins::Client::Build.new(client.get("/job/#{name}/last#{status.capitalize}Build/api/json").body)
+ build.job = self
+ build
end
-
- def self.start(name)
- Jenkins::Client.post("/job/#{name}/build", "")
+
+ def last_failed_build
+ last_build(:failed)
end
-
- def self.lastBuild(name)
- Jenkins::Client::get("/job/#{name}/lastBuild/api/json")
+
+ def last_successful_build
+ last_build(:successful)
end
+
end
end
end
View
0  spec/jenkins-client/build_spec.rb
No changes.
View
57 spec/jenkins-client/client_spec.rb
@@ -0,0 +1,57 @@
+require "spec_helper"
+
+describe Jenkins::Client do
+
+ before(:each) do
+ jenkins_config("https://jenkinstest.com")
+ end
+
+ before(:each) do
+ body = '{"assignedLabels":[{}],"mode":"NORMAL","nodeDescription":"the master Jenkins node","nodeName":"","numExecutors":2,"description":null,"jobs":[
+ {"name":"foo-bar","url":"https://testjenkins.com/job/foo-bar/","color":"blue"},
+ {"name":"woohoo","url":"https://jenkinstest.com/job/woohoo/","color":"red"},
+ {"name":"wat","url":"https://jenkinstest.com/job/wat/","color":"red"},
+ {"name":"fudge","url":"https://jenkinstest.com/job/fudge/","color":"blue"},
+ {"name":"amaze","url":"https://jenkinstest.com/job/amaze/","color":"blue"}]}'
+ stub_request(:get, "https://testuser:testpass@jenkinstest.com/api/json").
+ with(:headers => {'Accept'=>'*/*', 'Accept-Encoding'=>'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'User-Agent'=>'Ruby'}).
+ to_return(:status => 200, :body => body, :headers => {})
+ end
+
+ before(:each) do
+ body = '{"assignedLabels":[{}],"mode":"NORMAL","nodeDescription":"the master Jenkins node","nodeName":"","numExecutors":2,"description":null,"jobs":[]}'
+ stub_request(:get, "https://testuser:testpass@emptyjenkinstest.com/api/json").
+ with(:headers => {'Accept'=>'*/*', 'Accept-Encoding'=>'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'User-Agent'=>'Ruby'}).
+ to_return(:status => 200, :body => body, :headers => {})
+ end
+
+ describe ".jobs" do
+ context "given some jobs are available" do
+ let(:jobs) { @client.jobs }
+
+ it "will return a list of jobs" do
+ jobs.should have(5).items
+ end
+
+ it "will contain an expected job" do
+ jobs.should include(
+ "foo-bar" => {
+ "name"=>"foo-bar",
+ "url"=>"https://testjenkins.com/job/foo-bar/",
+ "color"=>"blue"
+ })
+ end
+ end
+
+ context "given no jobs" do
+ before(:each) do
+ empty_jenkins_config
+ end
+
+ it "will have no jobs" do
+ @client.jobs.should be_empty
+ end
+ end
+ end
+
+end
View
121 spec/jenkins-client/job_spec.rb
@@ -1,97 +1,12 @@
require "spec_helper"
-require "jenkins-client"
-require "jenkins-client/job"
describe Jenkins::Client::Job do
- def jenkins_config(url)
- Jenkins::Client.configure do |c|
- c.username = "testuser"
- c.password = "testpass"
- c.url = url
- end
- end
-
- def empty_jenkins_config
- jenkins_config("https://emptyjenkinstest.com")
- end
before(:each) do
jenkins_config("https://jenkinstest.com")
end
-
- before(:each) do
- body = '{"assignedLabels":[{}],"mode":"NORMAL","nodeDescription":"the master Jenkins node","nodeName":"","numExecutors":2,"description":null,"jobs":[
- {"name":"foo-bar","url":"https://testjenkins.com/job/foo-bar/","color":"blue"},
- {"name":"woohoo","url":"https://jenkinstest.com/job/woohoo/","color":"red"},
- {"name":"wat","url":"https://jenkinstest.com/job/wat/","color":"red"},
- {"name":"fudge","url":"https://jenkinstest.com/job/fudge/","color":"blue"},
- {"name":"amaze","url":"https://jenkinstest.com/job/amaze/","color":"blue"}]}'
- stub_request(:get, "https://testuser:testpass@jenkinstest.com/api/json").
- with(:headers => {'Accept'=>'*/*', 'Accept-Encoding'=>'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'User-Agent'=>'Ruby'}).
- to_return(:status => 200, :body => body, :headers => {})
- end
-
- before(:each) do
- body = '{"assignedLabels":[{}],"mode":"NORMAL","nodeDescription":"the master Jenkins node","nodeName":"","numExecutors":2,"description":null,"jobs":[]}'
- stub_request(:get, "https://testuser:testpass@emptyjenkinstest.com/api/json").
- with(:headers => {'Accept'=>'*/*', 'Accept-Encoding'=>'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'User-Agent'=>'Ruby'}).
- to_return(:status => 200, :body => body, :headers => {})
- end
-
- describe ".all" do
- context "given some jobs are available" do
- let(:jobs){Jenkins::Client::Job.all}
-
- it "will return a list of jobs" do
- jobs.should have(5).items
- end
-
- it "will contain an expected job" do
- jobs.should include({
- "name"=>"foo-bar",
- "url"=>"https://testjenkins.com/job/foo-bar/",
- "color"=>"blue"
- })
- end
- end
-
- context "given no jobs" do
- before(:each) do
- empty_jenkins_config
- end
-
- it "will have no jobs" do
- Jenkins::Client::Job.all.should be_empty
- end
- end
- end
-
- describe ".find" do
- context "given some jobs are available" do
- it "will find the specified job" do
- job = Jenkins::Client::Job.find("woohoo")
- job.url.should eq("https://jenkinstest.com/job/woohoo/")
- end
- end
-
- context "given a match isn't found" do
- it "will be nil" do
- Jenkins::Client::Job.find("badname").should be_nil
- end
- end
-
- context "given no jobs" do
- before(:each) do
- empty_jenkins_config
- end
-
- it "will be nil" do
- Jenkins::Client::Job.find("woohoo").should be_nil
- end
- end
- end
-
- describe ".create" do
+
+ describe ".create!" do
before(:each) do
stub_request(:post, "https://testuser:testpass@jenkinstest.com/createItem/api/xml?name=excellent").
to_return(:status => 200, :body => "", :headers => {})
@@ -100,7 +15,9 @@ def empty_jenkins_config
context "given an xml config file" do
it "will be able to create a new jenkins job" do
config = File.open("spec/fixtures/jenkins_config.xml").read
- Jenkins::Client::Job.create("excellent", config).should be_true
+ job = Jenkins::Client::Job.new({ name: "excellent" })
+ job.client = @client
+ job.create!(config).should be_true
end
end
@@ -113,12 +30,14 @@ def empty_jenkins_config
it "should be able to create a new job" do
config = File.read("spec/fixtures/jenkins_config.xml")
- Jenkins::Client::Job.create("excellent", config).should be_true
+ job = Jenkins::Client::Job.new({ name: "excellent" })
+ job.client = @client
+ job.create!(config).should be_true
end
end
end
- describe ".delete" do
+ describe ".delete!" do
context "there is a job to delete" do
before(:each) do
stub_request(:post, "https://testuser:testpass@jenkinstest.com/createItem/api/xml?name=excellent").
@@ -130,12 +49,14 @@ def empty_jenkins_config
end
it "should delete the job" do
- Jenkins::Client::Job.delete("excellent").should be_true
+ job = Jenkins::Client::Job.new({ name: "excellent" })
+ job.client = @client
+ job.delete!.should be_true
end
end
end
- describe ".start" do
+ describe ".start!" do
context "there is a job to start" do
before(:each) do
stub_request(:post, "https://testuser.testpass@jenkinstest.com/createItem/api/xml?name=excellent").
@@ -146,13 +67,15 @@ def empty_jenkins_config
end
it "should start the build for this job" do
- Jenkins::Client::Job.start("excellent").should be_true
+ job = Jenkins::Client::Job.new({ name: "excellent" })
+ job.client = @client
+ job.start!
end
end
end
- describe ".lastBuild" do
+ describe ".last_build" do
context "a build has been started" do
before(:each) do
stub_request(:post, "https://testuser.testpass@jenkinstest.com/createItem/api/xml?name=excellent").
@@ -165,14 +88,20 @@ def empty_jenkins_config
end
it "should return 1 as buildNumber" do
- resp = Jenkins::Client::Job.lastBuild("excellent")
- resp.body.number.should == 1
+ job = Jenkins::Client::Job.new({ name: "excellent" })
+ job.client = @client
+ job.last_build.number.should == 1
end
+
end
end
+
+ pending ".last_successful_build"
+ pending ".last_failed_build"
def resp_lastbuild
current_dir = File.expand_path(File.dirname((__FILE__)))
IO.read(File.join(current_dir, "..", "support", "resp_last_build.json"))
end
+
end
View
6 spec/spec_helper.rb
@@ -1,5 +1,11 @@
+$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
+$LOAD_PATH.unshift(File.dirname(__FILE__))
+
+require 'jenkins-client'
require 'webmock/rspec'
+require 'support/jenkins_config'
+
RSpec.configure do |config|
config.treat_symbols_as_metadata_keys_with_true_values = true
config.run_all_when_everything_filtered = true
View
13 spec/support/jenkins_config.rb
@@ -0,0 +1,13 @@
+def jenkins_config(url)
+ @client = Jenkins::Client.new
+ @client.configure do |c|
+ c.username = "testuser"
+ c.password = "testpass"
+ c.url = url
+ end
+end
+
+def empty_jenkins_config
+ jenkins_config("https://emptyjenkinstest.com")
+end
+
Something went wrong with that request. Please try again.