teamcity_ruby 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +17 -0
- data/.ruby-version +1 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +42 -0
- data/Rakefile +5 -0
- data/lib/teamcity_ruby/build_configuration.rb +86 -0
- data/lib/teamcity_ruby/element_builder.rb +27 -0
- data/lib/teamcity_ruby/project.rb +52 -0
- data/lib/teamcity_ruby/resource.rb +26 -0
- data/lib/teamcity_ruby/vcs_root.rb +49 -0
- data/lib/teamcity_ruby/version.rb +3 -0
- data/lib/teamcity_ruby.rb +28 -0
- data/spec/cassettes/TeamcityRuby_BuildConfiguration/a_existing_build_configuration/can_get_a_build_step_added_to_it.yml +331 -0
- data/spec/cassettes/TeamcityRuby_BuildConfiguration/a_existing_build_configuration/can_get_a_build_trigger_added_to_it.yml +298 -0
- data/spec/cassettes/TeamcityRuby_BuildConfiguration/a_existing_build_configuration/can_get_a_vcs_root_attached_with_specific_checkout_rules.yml +335 -0
- data/spec/cassettes/TeamcityRuby_BuildConfiguration/fetches_a_specific_build_configuration_by_id.yml +261 -0
- data/spec/cassettes/TeamcityRuby_BuildConfiguration/lists_all_build_configurations.yml +397 -0
- data/spec/cassettes/TeamcityRuby_Project/creates_a_project_copying_its_settings_from_a_source_project.yml +301 -0
- data/spec/cassettes/TeamcityRuby_Project/creates_a_project_with_a_parent_project.yml +154 -0
- data/spec/cassettes/TeamcityRuby_Project/destroys_a_specific_project.yml +223 -0
- data/spec/cassettes/TeamcityRuby_Project/fetches_a_specific_project_by_id_locator.yml +220 -0
- data/spec/cassettes/TeamcityRuby_Project/fetches_a_specific_project_by_name_locator.yml +187 -0
- data/spec/cassettes/TeamcityRuby_Project/lists_all_projects_including_root_built_in_on_TeamCity_.yml +224 -0
- data/spec/cassettes/TeamcityRuby_VcsRoot/lists_all_vcs_roots.yml +151 -0
- data/spec/spec_helper.rb +32 -0
- data/spec/teamcity_ruby/build_configuration_spec.rb +59 -0
- data/spec/teamcity_ruby/element_builder_spec.rb +29 -0
- data/spec/teamcity_ruby/project_spec.rb +59 -0
- data/spec/teamcity_ruby/vcs_root_spec.rb +21 -0
- data/teamcity_ruby.gemspec +31 -0
- metadata +242 -0
data/.gitignore
ADDED
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
1.8.7-p357
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Jefferson Girao
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
# TeamcityRuby
|
2
|
+
|
3
|
+
TeamcityRuby is an abstraction layer for the TeamCity API. It does not simply wrap TeamCity API functionality, it adds some sugar on top of it.
|
4
|
+
Be aware of the difference between Abstraction vs Wrapping and the tradeoffs related to them reading [what makes a good api wrapper](http://wynnnetherland.com/journal/what-makes-a-good-api-wrapper) by @pengwynn
|
5
|
+
|
6
|
+
If you want an implementation that lean more to a simple wrapper take a look into [teamcity-ruby-client](https://github.com/jperry/teamcity-ruby-client)
|
7
|
+
by @jperry
|
8
|
+
|
9
|
+
## Installation
|
10
|
+
|
11
|
+
Add this line to your application's Gemfile:
|
12
|
+
|
13
|
+
gem 'teamcity_ruby'
|
14
|
+
|
15
|
+
And then execute:
|
16
|
+
|
17
|
+
$ bundle
|
18
|
+
|
19
|
+
Or install it yourself as:
|
20
|
+
|
21
|
+
$ gem install teamcity_ruby
|
22
|
+
|
23
|
+
## Usage
|
24
|
+
|
25
|
+
TODO: Write usage instructions here
|
26
|
+
|
27
|
+
## Contributing
|
28
|
+
|
29
|
+
1. Fork it
|
30
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
31
|
+
3. Commit your changes with tests(`git commit -am 'Add some feature'`)
|
32
|
+
4. Run the tests(`bundle exec rake`)
|
33
|
+
5. Push to the branch (`git push origin my-new-feature`)
|
34
|
+
6. Create new Pull Request
|
35
|
+
|
36
|
+
|
37
|
+
If your changes involve talking with the API in a new or different way, you perhaps will
|
38
|
+
need to remove the affected HTTP interactions from spec/cassetes and record them again.
|
39
|
+
The tests assume that TeamCity is running on localhost at the port 8111
|
40
|
+
and that user `teamcity` and password `teamcity` are valid admin credentials.
|
41
|
+
If you want to spin up a TeamCity instance quickly with Vagrant,
|
42
|
+
checkout my [ansible_teamcity](http://github.com/jeffersongirao/ansible_teamcity) project
|
data/Rakefile
ADDED
@@ -0,0 +1,86 @@
|
|
1
|
+
module TeamcityRuby
|
2
|
+
class BuildConfiguration
|
3
|
+
extend TeamcityRuby::Resource
|
4
|
+
|
5
|
+
attr_accessor :teamcity_id, :name, :project_id
|
6
|
+
|
7
|
+
def self.all
|
8
|
+
client.get("/buildTypes")["buildType"].map do |b|
|
9
|
+
new(b)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.find(options = {})
|
14
|
+
response = client.get("/buildTypes/#{locator(options)}")
|
15
|
+
return nil if ( response.body =~ /No buildType found/ )
|
16
|
+
new(response)
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.create(name, options = {})
|
20
|
+
payload = { "name" => name }.to_json
|
21
|
+
project_id = options.fetch(:project_id) { raise ":project_id is required" }
|
22
|
+
response = client.post("/projects/id:#{project_id}/buildTypes", :body => payload)
|
23
|
+
new(response)
|
24
|
+
end
|
25
|
+
|
26
|
+
def initialize(options = {})
|
27
|
+
@teamcity_id = options["id"]
|
28
|
+
@name = options["name"]
|
29
|
+
@project_id = options["projectId"]
|
30
|
+
end
|
31
|
+
|
32
|
+
def attach_vcs_root(vcs_root_id, checkout_rules)
|
33
|
+
payload = { "vcs-root" => { "id" => vcs_root_id }, "checkout-rules" => checkout_rules }
|
34
|
+
response = client.post("/buildTypes/id:#{teamcity_id}/vcs-root-entries", :body => payload.to_json)
|
35
|
+
response["id"]
|
36
|
+
end
|
37
|
+
|
38
|
+
def vcs_root_entries
|
39
|
+
response = client.get("/buildTypes/id:#{teamcity_id}/vcs-root-entries")["vcs-root-entry"]
|
40
|
+
response.map do |i|
|
41
|
+
{ :vcs_root_id => i["vcs-root"]["id"], :checkout_rules => i["checkout-rules"] }
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def add_step(options = {}, &block)
|
46
|
+
attributes = { "name" => options[:name], "type" => options[:type] }
|
47
|
+
post_with_properties("/buildTypes/id:#{teamcity_id}/steps", attributes, &block)
|
48
|
+
end
|
49
|
+
|
50
|
+
def steps
|
51
|
+
get_with_properties("/buildTypes/id:#{teamcity_id}/steps")
|
52
|
+
end
|
53
|
+
|
54
|
+
def add_trigger(options = {}, &block)
|
55
|
+
attributes = { "type" => options[:type] }
|
56
|
+
post_with_properties("/buildTypes/id:#{teamcity_id}/triggers", attributes, &block)
|
57
|
+
end
|
58
|
+
|
59
|
+
def triggers
|
60
|
+
get_with_properties("/buildTypes/id:#{teamcity_id}/triggers")
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
|
65
|
+
def with_normalized_properties(hash)
|
66
|
+
Hashie::Mash.new(hash).tap do |mash|
|
67
|
+
mash[:properties] = mash[:properties].delete(:property)
|
68
|
+
mash[:properties] = mash[:properties].map do |p|
|
69
|
+
{ p["name"] => p["value"] }
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def get_with_properties(url)
|
75
|
+
response = client.get(url)
|
76
|
+
root = response.keys.first
|
77
|
+
response[root].map { |i| with_normalized_properties(i) }
|
78
|
+
end
|
79
|
+
|
80
|
+
def post_with_properties(url, attributes, &block)
|
81
|
+
builder = TeamCity::ElementBuilder.new(attributes, &block)
|
82
|
+
response = client.post(url, :body => builder.to_request_body)
|
83
|
+
with_normalized_properties(response)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module TeamCity
|
2
|
+
class ElementBuilder
|
3
|
+
def initialize(attributes = {}, &block)
|
4
|
+
@payload = attributes
|
5
|
+
|
6
|
+
@payload['properties'] ||= {}
|
7
|
+
@payload['properties']['property'] ||= []
|
8
|
+
|
9
|
+
if block_given?
|
10
|
+
properties = {}
|
11
|
+
|
12
|
+
yield(properties)
|
13
|
+
|
14
|
+
properties.each do |name, value|
|
15
|
+
@payload['properties']['property'] << {
|
16
|
+
:name => name,
|
17
|
+
:value => value
|
18
|
+
}
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def to_request_body
|
24
|
+
@payload.to_json
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module TeamcityRuby
|
2
|
+
class Project
|
3
|
+
extend TeamcityRuby::Resource
|
4
|
+
|
5
|
+
attr_reader :teamcity_id, :name, :parent_id
|
6
|
+
|
7
|
+
def self.all
|
8
|
+
client.get("/projects")["project"].map do |p|
|
9
|
+
new(p)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.find(options = {})
|
14
|
+
response = client.get("/projects/#{locator(options)}")
|
15
|
+
return nil if ( response.body =~ /No project found/ )
|
16
|
+
new(response)
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.create(name, options = {})
|
20
|
+
payload = { "name" => name }
|
21
|
+
payload["parentProject"] = { "id" => options[:parent_id] } if options[:parent_id]
|
22
|
+
|
23
|
+
if options[:source_id]
|
24
|
+
payload["sourceProject"] = { "id" => options[:source_id] }
|
25
|
+
payload["copyAllAssociatedSettings"] = 'true'
|
26
|
+
end
|
27
|
+
|
28
|
+
response = client.post("/projects", :body => payload.to_json)
|
29
|
+
new(response)
|
30
|
+
end
|
31
|
+
|
32
|
+
def initialize(options = {})
|
33
|
+
@teamcity_id = options["id"]
|
34
|
+
@name = options["name"]
|
35
|
+
@parent_id = options["parentProjectId"]
|
36
|
+
end
|
37
|
+
|
38
|
+
def destroy!
|
39
|
+
client.delete("/projects/id:#{teamcity_id}")
|
40
|
+
end
|
41
|
+
|
42
|
+
def description=(value)
|
43
|
+
headers = { "Accept" => "text/plain", "Content-Type" => "text/plain" }
|
44
|
+
client.put("/projects/id:#{teamcity_id}/description", :body => value, :headers => headers)
|
45
|
+
end
|
46
|
+
|
47
|
+
def description
|
48
|
+
headers = { "Accept" => "text/plain", "Content-Type" => "text/plain" }
|
49
|
+
client.get("/projects/id:#{teamcity_id}/description", :headers => headers, :format => :text).parsed_response
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module TeamcityRuby
|
2
|
+
module Resource
|
3
|
+
def client
|
4
|
+
TeamcityRuby
|
5
|
+
end
|
6
|
+
|
7
|
+
def locator(options)
|
8
|
+
raise(ArgumentError, "the Locator must be a Hash") unless options.is_a? Hash
|
9
|
+
options.map { |key, value| "#{key}:#{URI.escape(value)}" }.join
|
10
|
+
end
|
11
|
+
|
12
|
+
module InstanceMethods
|
13
|
+
def client
|
14
|
+
self.class.client
|
15
|
+
end
|
16
|
+
|
17
|
+
def ==(other)
|
18
|
+
self.class == other.class && self.teamcity_id == other.teamcity_id
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.extended(base)
|
23
|
+
base.send(:include, InstanceMethods)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module TeamcityRuby
|
2
|
+
class VcsRoot
|
3
|
+
extend TeamcityRuby::Resource
|
4
|
+
|
5
|
+
VCS_TYPES = { :git => 'jetbrains.git' }
|
6
|
+
|
7
|
+
DEFAULT_PROJECT = "_Root"
|
8
|
+
DEFAULT_VCS_TYPE = :git
|
9
|
+
|
10
|
+
attr_reader :teamcity_id, :name, :parent_id
|
11
|
+
|
12
|
+
def self.all
|
13
|
+
client.get("/vcs-roots")["vcs-root"].map do |p|
|
14
|
+
VcsRoot.new(p)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.find(options = {})
|
19
|
+
response = client.get("/vcs-roots/#{locator(options)}")
|
20
|
+
return nil if ( response.body =~ /No vcsRoot found/ )
|
21
|
+
VcsRoot.new(response)
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.create(options = {}, &block)
|
25
|
+
vcs_type = VCS_TYPES[options.fetch(:type) { DEFAULT_VCS_TYPE }]
|
26
|
+
project_id = options.fetch(:project_id) { DEFAULT_PROJECT }
|
27
|
+
|
28
|
+
attributes = {
|
29
|
+
"name" => options[:name],
|
30
|
+
"vcsName" => vcs_type,
|
31
|
+
"projectLocator" => "id:#{project_id}",
|
32
|
+
}
|
33
|
+
|
34
|
+
builder = TeamCity::ElementBuilder.new(attributes, &block)
|
35
|
+
|
36
|
+
response = client.post("/vcs-roots", :body => builder.to_request_body)
|
37
|
+
VcsRoot.new(response)
|
38
|
+
end
|
39
|
+
|
40
|
+
def initialize(options = {})
|
41
|
+
@teamcity_id = options["id"]
|
42
|
+
@name = options["name"]
|
43
|
+
end
|
44
|
+
|
45
|
+
def destroy!
|
46
|
+
client.delete("/vcs-roots/id:#{self.teamcity_id}")
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
directory = File.expand_path(File.dirname(__FILE__))
|
2
|
+
|
3
|
+
require "teamcity_ruby/version"
|
4
|
+
require "httparty"
|
5
|
+
require "hashie"
|
6
|
+
|
7
|
+
module TeamcityRuby
|
8
|
+
include HTTParty
|
9
|
+
format :json
|
10
|
+
base_uri 'http://localhost:8111/httpAuth/app/rest/'
|
11
|
+
headers 'Accept' => 'application/json'
|
12
|
+
headers 'Content-Type' => 'application/json'
|
13
|
+
basic_auth "teamcity", "teamcity"
|
14
|
+
|
15
|
+
def self.client
|
16
|
+
self
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.configure
|
20
|
+
yield self
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
require File.join(directory, 'teamcity_ruby', 'resource')
|
25
|
+
require File.join(directory, 'teamcity_ruby', 'element_builder')
|
26
|
+
require File.join(directory, 'teamcity_ruby', 'project')
|
27
|
+
require File.join(directory, 'teamcity_ruby', 'vcs_root')
|
28
|
+
require File.join(directory, 'teamcity_ruby', 'build_configuration')
|