git-semaphore 0.0.9 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +9 -19
  3. data/.pryrc +6 -0
  4. data/.ruby-version +1 -1
  5. data/CODE_OF_CONDUCT.md +49 -0
  6. data/LICENSE.txt +17 -18
  7. data/README.md +166 -40
  8. data/Rakefile +8 -9
  9. data/bin/console +14 -0
  10. data/bin/setup +10 -0
  11. data/exe/git-semaphore +79 -0
  12. data/git-semaphore.gemspec +15 -19
  13. data/lib/git/semaphore/api.rb +133 -0
  14. data/lib/git/semaphore/api_cache.rb +59 -0
  15. data/lib/git/semaphore/api_enrich.rb +40 -0
  16. data/lib/git/semaphore/app.rb +121 -0
  17. data/lib/{git-semaphore → git/semaphore}/banner.rb +0 -0
  18. data/lib/{git-semaphore → git/semaphore}/copyright.rb +1 -1
  19. data/lib/{git-semaphore → git/semaphore}/version.rb +1 -1
  20. data/lib/git/semaphore.rb +77 -0
  21. metadata +55 -181
  22. data/.bundle/config +0 -3
  23. data/.irbrc +0 -13
  24. data/bin/git-semaphore +0 -115
  25. data/features/cassettes/cucumber_tags/api_branch_status.json +0 -20
  26. data/features/cassettes/cucumber_tags/api_project_branches.json +0 -14
  27. data/features/cassettes/cucumber_tags/api_projects.json +0 -46
  28. data/features/cassettes/cucumber_tags/vcr_api_branches.yml +0 -70
  29. data/features/cassettes/cucumber_tags/vcr_api_projects.yml +0 -102
  30. data/features/cassettes/cucumber_tags/vcr_api_rebuild_last_revision.yml +0 -55
  31. data/features/cassettes/cucumber_tags/vcr_api_status.yml +0 -76
  32. data/features/coming_soon.feature +0 -13
  33. data/features/env_config.feature +0 -11
  34. data/features/git_config.feature +0 -11
  35. data/features/help_and_version.feature +0 -33
  36. data/features/semaphore_app_api.feature +0 -29
  37. data/features/semaphore_app_config.feature +0 -37
  38. data/features/semaphore_auth_token.feature +0 -32
  39. data/features/semaphore_project_token.feature +0 -32
  40. data/features/step_definitions/git-semaphore_steps.rb +0 -121
  41. data/features/step_definitions/patch_methadone_steps.rb +0 -5
  42. data/features/step_definitions/vcr_semaphore_steps.rb +0 -25
  43. data/features/support/env.rb +0 -7
  44. data/features/support/semaphoreapp.rb +0 -2
  45. data/features/support/vcr.rb +0 -14
  46. data/features/working_directory.feature +0 -22
  47. data/lib/git-semaphore/api.rb +0 -60
  48. data/lib/git-semaphore/app.rb +0 -123
  49. data/lib/git-semaphore.rb +0 -15
  50. data/spec_helper.rb +0 -6
  51. data/vendor/bundle/.gitignore +0 -2
@@ -0,0 +1,133 @@
1
+ module Git
2
+ module Semaphore
3
+ class API
4
+
5
+ def self.projects auth_token
6
+ get_json projects_uri(auth_token)
7
+ end
8
+
9
+ def self.branches project_hash_id, auth_token
10
+ get_json branches_uri(project_hash_id, auth_token)
11
+ end
12
+
13
+ def self.status project_hash_id, branch_id, auth_token
14
+ get_json status_uri(project_hash_id, branch_id, auth_token)
15
+ end
16
+
17
+ def self.history project_hash_id, branch_id, auth_token
18
+ get_paginated_response(history_uri(project_hash_id, branch_id, auth_token)) do |previous_page, next_page|
19
+ previous_page['builds'] += next_page['builds']
20
+ end
21
+ end
22
+
23
+ def self.rebuild project_hash_id, branch_id, auth_token
24
+ get_json last_revision_uri(project_hash_id, branch_id, auth_token), :post
25
+ end
26
+
27
+ # private helper functions
28
+
29
+ def self.projects_uri auth_token
30
+ # https://semaphoreci.com/docs/projects-api.html
31
+ # GET /api/v1/projects
32
+ request_uri(auth_token, :path => File.join('projects'))
33
+ end
34
+
35
+ private_class_method :projects_uri
36
+
37
+ def self.branches_uri project_hash_id, auth_token
38
+ # https://semaphoreci.com/docs/branches-and-builds-api.html#project_branches
39
+ # GET /api/v1/projects/:hash_id/branches
40
+ request_uri(auth_token, :path => File.join('projects', project_hash_id, 'branches'))
41
+ end
42
+
43
+ private_class_method :branches_uri
44
+
45
+ def self.status_uri project_hash_id, branch_id, auth_token
46
+ # https://semaphoreci.com/docs/branches-and-builds-api.html#branch_status
47
+ # GET /api/v1/projects/:hash_id/:id/status
48
+ request_uri(auth_token, :path => File.join('projects', project_hash_id, branch_id, 'status'))
49
+ end
50
+
51
+ private_class_method :status_uri
52
+
53
+ def self.history_uri project_hash_id, branch_id, auth_token, page = 1
54
+ # https://semaphoreci.com/docs/branches-and-builds-api.html#branch_history
55
+ # GET /api/v1/projects/:hash_id/:id
56
+ request_uri(auth_token, :path => File.join('projects', project_hash_id, branch_id), :page => page)
57
+ end
58
+
59
+ private_class_method :history_uri
60
+
61
+ def self.last_revision_uri project_hash_id, branch_id, auth_token
62
+ # https://semaphoreci.com/docs/branches-and-builds-api.html#rebuild
63
+ # POST /api/v1/projects/:project_hash_id/:branch_id/build
64
+ request_uri(auth_token, :path => File.join('projects', project_hash_id, branch_id, 'build'))
65
+ end
66
+
67
+ private_class_method :last_revision_uri
68
+
69
+ # more private helper functions
70
+
71
+ def self.get_response uri, action = :get
72
+ response = ::Net::HTTP.start(uri.host, uri.port, :use_ssl => (uri.scheme == 'https'), :verify_mode => ::OpenSSL::SSL::VERIFY_NONE) do |net_http|
73
+ case action
74
+ when :get
75
+ net_http.get uri.request_uri
76
+ when :post
77
+ net_http.post uri.request_uri, uri.query
78
+ else
79
+ raise 'Unsupported action'
80
+ end
81
+ end
82
+
83
+ def response.json_body
84
+ raise "JSON response expected" unless self['content-type'] =~ %r{application/json}
85
+ JSON.parse(body)
86
+ end
87
+
88
+ response
89
+ end
90
+
91
+ private_class_method :get_response
92
+
93
+ def self.get_paginated_response uri, action = :get
94
+ response = get_response(uri, action)
95
+ body = response.json_body
96
+ loop do
97
+ pagination_header = response['Pagination']
98
+ break unless pagination_header
99
+ pagination = JSON.parse(pagination_header)
100
+ break if pagination['last_page']
101
+ uri.query = uri.query.sub(/page=(\d+)/, "page=#{pagination['next_page']}")
102
+ response = get_response(uri, action)
103
+ yield(body, response.json_body)
104
+ end
105
+ body
106
+ end
107
+
108
+ private_class_method :get_paginated_response
109
+
110
+ def self.get_json uri, action = :get
111
+ get_response(uri, action).json_body
112
+ end
113
+
114
+ private_class_method :get_json
115
+
116
+ # https://semaphoreci.com/docs/
117
+ SEMAPHORE_API_HOST = 'semaphoreci.com'.freeze
118
+ SEMAPHORE_API_URI = '/api/v1/'.freeze
119
+
120
+ def self.request_uri auth_token, options = {}
121
+ page = options.delete(:page) # API pagination
122
+ options[:host] ||= SEMAPHORE_API_HOST
123
+ options[:path].prepend SEMAPHORE_API_URI
124
+ options[:query] = "auth_token=#{auth_token}"
125
+ options[:query] << "&page=#{page}" if page
126
+ URI::HTTPS.build(options)
127
+ end
128
+
129
+ private_class_method :request_uri
130
+
131
+ end
132
+ end
133
+ end
@@ -0,0 +1,59 @@
1
+ module Git
2
+ module Semaphore
3
+ class API
4
+ class Cache
5
+
6
+ def self.projects auth_token
7
+ @projects ||= Git::Semaphore.from_json_cache(projects_cache) do
8
+ API::Enrich.projects auth_token
9
+ end
10
+ end
11
+
12
+ def self.branches project_hash_id, auth_token
13
+ @branches ||= Git::Semaphore.from_json_cache(branches_cache(project_hash_id)) do
14
+ API.branches project_hash_id, auth_token
15
+ end
16
+ end
17
+
18
+ def self.status project_hash_id, branch_id, auth_token
19
+ @status ||= Git::Semaphore.from_json_cache(status_cache(project_hash_id, branch_id)) do
20
+ API.status project_hash_id, branch_id, auth_token
21
+ end
22
+ end
23
+
24
+ def self.history project_hash_id, branch_id, auth_token
25
+ @history ||= Git::Semaphore.from_json_cache(history_cache(project_hash_id, branch_id)) do
26
+ API::Enrich.history project_hash_id, branch_id, auth_token
27
+ end
28
+ end
29
+
30
+ # private helper functions
31
+
32
+ def self.projects_cache
33
+ File.join(Git::Semaphore.cache_dir, 'projects.json')
34
+ end
35
+
36
+ private_class_method :projects_cache
37
+
38
+ def self.branches_cache project_hash_id
39
+ File.join(Git::Semaphore.cache_dir_for(project_hash_id), 'branches.json')
40
+ end
41
+
42
+ private_class_method :branches_cache
43
+
44
+ def self.status_cache project_hash_id, branch_id
45
+ File.join(Git::Semaphore.cache_dir_for(project_hash_id), "#{branch_id}_status.json")
46
+ end
47
+
48
+ private_class_method :status_cache
49
+
50
+ def self.history_cache project_hash_id, branch_id
51
+ File.join(Git::Semaphore.cache_dir_for(project_hash_id), "#{branch_id}_history.json")
52
+ end
53
+
54
+ private_class_method :history_cache
55
+
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,40 @@
1
+ module Git
2
+ module Semaphore
3
+ class API
4
+ class Enrich
5
+
6
+ def self.projects auth_token
7
+ API.projects(auth_token).tap do |results|
8
+ results.each do |project|
9
+ # full repository name on github.com: 'pvdb/git-semaphore'
10
+ project['full_name'] = [project['owner'], project['name']].join('/')
11
+ # https://semaphoreci.com/pvdb/git-semaphore -> https://github.com/pvdb/git-semaphore
12
+ project['github_url'] = project['html_url'].sub(/semaphoreci\.com/, 'github.com')
13
+ end
14
+ end
15
+ end
16
+
17
+ def self.history project_hash_id, branch_id, auth_token
18
+ API.history(project_hash_id, branch_id, auth_token).tap do |results|
19
+ results['builds'].each do |build|
20
+ # build['result'] = "passed", "failed", "stopped" or "pending"
21
+ next unless started_at = build['started_at']
22
+ next unless finished_at = build['finished_at']
23
+ started_at = DateTime.parse(started_at).to_time
24
+ finished_at = DateTime.parse(finished_at).to_time
25
+ build['date'] = {
26
+ :started_at => started_at.to_date,
27
+ :finished_at => finished_at.to_date,
28
+ }
29
+ build['duration'] = {
30
+ :seconds => (finished_at - started_at).to_i,
31
+ :minutes => "%0.2f" % ((finished_at - started_at) / 60),
32
+ }
33
+ end
34
+ end
35
+ end
36
+
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,121 @@
1
+ class Git::Semaphore::App
2
+
3
+ attr_writer :project_name
4
+ attr_writer :branch_name
5
+ attr_writer :commit_sha
6
+
7
+ attr_reader :branch_url
8
+
9
+ def initialize auth_token, git_repo, config = ENV
10
+ @auth_token = auth_token
11
+ @git_repo = git_repo
12
+
13
+ self.project_name = config['SEMAPHORE_PROJECT_NAME']
14
+ self.branch_name = config['SEMAPHORE_BRANCH_NAME']
15
+ self.commit_sha = config['SEMAPHORE_COMMIT_SHA']
16
+ end
17
+
18
+ def to_json
19
+ {
20
+ semaphore_auth_token: @auth_token,
21
+ semaphore_project_name: self.project_name,
22
+ semaphore_branch_name: self.branch_name,
23
+ semaphore_commit_sha: self.commit_sha,
24
+ }.to_json
25
+ end
26
+
27
+ def project_name
28
+ return @project_name unless @project_name.nil?
29
+ File.basename @git_repo.git.work_tree if @git_repo
30
+ end
31
+
32
+ def branch_name
33
+ return @branch_name unless @branch_name.nil?
34
+ @git_repo.head.name if @git_repo
35
+ end
36
+
37
+ def commit_sha
38
+ return @commit_sha unless @commit_sha.nil?
39
+ @git_repo.head.commit.id if @git_repo
40
+ end
41
+
42
+ def projects
43
+ Git::Semaphore::API::Cache.projects(@auth_token)
44
+ end
45
+
46
+ def branches
47
+ Git::Semaphore::API::Cache.branches(project_hash_id, @auth_token)
48
+ end
49
+
50
+ def status
51
+ Git::Semaphore::API::Cache.status(project_hash_id, branch_id, @auth_token)
52
+ end
53
+
54
+ def history
55
+ Git::Semaphore::API::Cache.history(project_hash_id, branch_id, @auth_token)
56
+ end
57
+
58
+ def rebuild
59
+ Git::Semaphore::API.rebuild(project_hash_id, branch_id, @auth_token)
60
+ end
61
+
62
+ def branch_url
63
+ branch_hash = project_hash['branches'].find { |hash|
64
+ hash['branch_name'] == branch_name
65
+ }
66
+ branch_hash['branch_url']
67
+ end
68
+
69
+ private
70
+
71
+ def project_hash_for project_name
72
+ projects.find { |project_hash|
73
+ project_hash['name'] == project_name
74
+ }
75
+ end
76
+
77
+ def project_hash
78
+ project_hash_for(project_name)
79
+ end
80
+
81
+ def project_hash_id_for project_name
82
+ project_hash_for(project_name)['hash_id']
83
+ end
84
+
85
+ def project_hash_id
86
+ project_hash_id_for(project_name)
87
+ end
88
+
89
+ def branch_hash_for branch_name
90
+ branches.find { |branch_hash|
91
+ branch_hash['name'] == branch_name
92
+ }
93
+ end
94
+
95
+ def branch_hash
96
+ branch_hash_for(branch_name)
97
+ end
98
+
99
+ def branch_id_for branch_name
100
+ branch_hash_for(branch_name)['id'].to_s
101
+ end
102
+
103
+ def branch_id
104
+ branch_id_for(branch_name)
105
+ end
106
+
107
+ def build_statuses
108
+ history['builds']
109
+ end
110
+
111
+ def build_status_for commit_sha
112
+ build_statuses.find { |build_status|
113
+ build_status['commit']['id'] == commit_sha
114
+ }
115
+ end
116
+
117
+ def build_status
118
+ build_status_for(commit_sha)
119
+ end
120
+
121
+ end
File without changes
@@ -1,6 +1,6 @@
1
1
  module Git
2
2
  module Semaphore
3
3
  # TODO replace hard-coded details with data retrieved from the gemspec
4
- COPYRIGHT = "git-semaphore v#{Git::Semaphore::VERSION} Copyright (c) 2012-2015 Peter Vandenberk"
4
+ COPYRIGHT = "git-semaphore v#{Git::Semaphore::VERSION} Copyright (c) 2012-2016 Peter Vandenberk"
5
5
  end
6
6
  end
@@ -1,5 +1,5 @@
1
1
  module Git
2
2
  module Semaphore
3
- VERSION = "0.0.9"
3
+ VERSION = "1.0.0"
4
4
  end
5
5
  end
@@ -0,0 +1,77 @@
1
+ require 'uri'
2
+ require 'json'
3
+ require 'openssl'
4
+ require 'net/http'
5
+ require 'fileutils'
6
+
7
+ require 'grit'
8
+
9
+ module Git
10
+ module Semaphore
11
+
12
+ def self.home_dir
13
+ @home_dir ||= begin
14
+ ENV['HOME'] || File.expand_path("~#{Etc.getlogin}")
15
+ end
16
+ end
17
+
18
+ def self.cache_dir
19
+ @cache_dir ||= begin
20
+ File.join(self.home_dir, '.git', 'semaphore').tap do |cache_dir|
21
+ FileUtils.mkdir_p(cache_dir)
22
+ end
23
+ end
24
+ end
25
+
26
+ def self.cache_dir_for identifier
27
+ File.join(self.cache_dir, identifier).tap do |cache_dir|
28
+ FileUtils.mkdir_p(cache_dir)
29
+ end
30
+ end
31
+
32
+ def self.empty_cache_dir
33
+ FileUtils.rm_r Dir.glob(File.join(self.cache_dir, '*'))
34
+ end
35
+
36
+ def self.from_json_cache path
37
+ if File.exists? path
38
+ JSON.parse(File.read(path))
39
+ else
40
+ yield.tap do |content|
41
+ File.open(path, 'w') { |file| file.write content.to_json }
42
+ end
43
+ end
44
+ end
45
+
46
+ def self.git_repo
47
+ @git_repo ||= begin
48
+ Grit::Repo.new(Dir.pwd)
49
+ rescue Grit::InvalidGitRepositoryError
50
+ nil
51
+ end
52
+ end
53
+
54
+ def self.env_auth_token
55
+ @env_auth_token ||= ENV['SEMAPHORE_AUTH_TOKEN']
56
+ end
57
+
58
+ def self.git_auth_token
59
+ git_repo && git_repo.config['semaphore.authtoken']
60
+ end
61
+
62
+ def self.auth_token
63
+ git_auth_token || env_auth_token
64
+ end
65
+
66
+ end
67
+ end
68
+
69
+ require 'git/semaphore/version'
70
+ require 'git/semaphore/banner'
71
+ require 'git/semaphore/copyright'
72
+
73
+ require 'git/semaphore/api'
74
+ require 'git/semaphore/api_cache'
75
+ require 'git/semaphore/api_enrich'
76
+
77
+ require 'git/semaphore/app'