teamcity_ruby 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (32) hide show
  1. data/.gitignore +17 -0
  2. data/.ruby-version +1 -0
  3. data/Gemfile +4 -0
  4. data/LICENSE.txt +22 -0
  5. data/README.md +42 -0
  6. data/Rakefile +5 -0
  7. data/lib/teamcity_ruby/build_configuration.rb +86 -0
  8. data/lib/teamcity_ruby/element_builder.rb +27 -0
  9. data/lib/teamcity_ruby/project.rb +52 -0
  10. data/lib/teamcity_ruby/resource.rb +26 -0
  11. data/lib/teamcity_ruby/vcs_root.rb +49 -0
  12. data/lib/teamcity_ruby/version.rb +3 -0
  13. data/lib/teamcity_ruby.rb +28 -0
  14. data/spec/cassettes/TeamcityRuby_BuildConfiguration/a_existing_build_configuration/can_get_a_build_step_added_to_it.yml +331 -0
  15. data/spec/cassettes/TeamcityRuby_BuildConfiguration/a_existing_build_configuration/can_get_a_build_trigger_added_to_it.yml +298 -0
  16. data/spec/cassettes/TeamcityRuby_BuildConfiguration/a_existing_build_configuration/can_get_a_vcs_root_attached_with_specific_checkout_rules.yml +335 -0
  17. data/spec/cassettes/TeamcityRuby_BuildConfiguration/fetches_a_specific_build_configuration_by_id.yml +261 -0
  18. data/spec/cassettes/TeamcityRuby_BuildConfiguration/lists_all_build_configurations.yml +397 -0
  19. data/spec/cassettes/TeamcityRuby_Project/creates_a_project_copying_its_settings_from_a_source_project.yml +301 -0
  20. data/spec/cassettes/TeamcityRuby_Project/creates_a_project_with_a_parent_project.yml +154 -0
  21. data/spec/cassettes/TeamcityRuby_Project/destroys_a_specific_project.yml +223 -0
  22. data/spec/cassettes/TeamcityRuby_Project/fetches_a_specific_project_by_id_locator.yml +220 -0
  23. data/spec/cassettes/TeamcityRuby_Project/fetches_a_specific_project_by_name_locator.yml +187 -0
  24. data/spec/cassettes/TeamcityRuby_Project/lists_all_projects_including_root_built_in_on_TeamCity_.yml +224 -0
  25. data/spec/cassettes/TeamcityRuby_VcsRoot/lists_all_vcs_roots.yml +151 -0
  26. data/spec/spec_helper.rb +32 -0
  27. data/spec/teamcity_ruby/build_configuration_spec.rb +59 -0
  28. data/spec/teamcity_ruby/element_builder_spec.rb +29 -0
  29. data/spec/teamcity_ruby/project_spec.rb +59 -0
  30. data/spec/teamcity_ruby/vcs_root_spec.rb +21 -0
  31. data/teamcity_ruby.gemspec +31 -0
  32. metadata +242 -0
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 1.8.7-p357
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in teamcity_ruby.gemspec
4
+ gemspec
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,5 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ require "rspec/core/rake_task"
4
+ RSpec::Core::RakeTask.new(:spec)
5
+ task :default => :spec
@@ -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,3 @@
1
+ module TeamcityRuby
2
+ VERSION = "0.0.1"
3
+ 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')