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.
- checksums.yaml +4 -4
- data/.gitignore +9 -19
- data/.pryrc +6 -0
- data/.ruby-version +1 -1
- data/CODE_OF_CONDUCT.md +49 -0
- data/LICENSE.txt +17 -18
- data/README.md +166 -40
- data/Rakefile +8 -9
- data/bin/console +14 -0
- data/bin/setup +10 -0
- data/exe/git-semaphore +79 -0
- data/git-semaphore.gemspec +15 -19
- data/lib/git/semaphore/api.rb +133 -0
- data/lib/git/semaphore/api_cache.rb +59 -0
- data/lib/git/semaphore/api_enrich.rb +40 -0
- data/lib/git/semaphore/app.rb +121 -0
- data/lib/{git-semaphore → git/semaphore}/banner.rb +0 -0
- data/lib/{git-semaphore → git/semaphore}/copyright.rb +1 -1
- data/lib/{git-semaphore → git/semaphore}/version.rb +1 -1
- data/lib/git/semaphore.rb +77 -0
- metadata +55 -181
- data/.bundle/config +0 -3
- data/.irbrc +0 -13
- data/bin/git-semaphore +0 -115
- data/features/cassettes/cucumber_tags/api_branch_status.json +0 -20
- data/features/cassettes/cucumber_tags/api_project_branches.json +0 -14
- data/features/cassettes/cucumber_tags/api_projects.json +0 -46
- data/features/cassettes/cucumber_tags/vcr_api_branches.yml +0 -70
- data/features/cassettes/cucumber_tags/vcr_api_projects.yml +0 -102
- data/features/cassettes/cucumber_tags/vcr_api_rebuild_last_revision.yml +0 -55
- data/features/cassettes/cucumber_tags/vcr_api_status.yml +0 -76
- data/features/coming_soon.feature +0 -13
- data/features/env_config.feature +0 -11
- data/features/git_config.feature +0 -11
- data/features/help_and_version.feature +0 -33
- data/features/semaphore_app_api.feature +0 -29
- data/features/semaphore_app_config.feature +0 -37
- data/features/semaphore_auth_token.feature +0 -32
- data/features/semaphore_project_token.feature +0 -32
- data/features/step_definitions/git-semaphore_steps.rb +0 -121
- data/features/step_definitions/patch_methadone_steps.rb +0 -5
- data/features/step_definitions/vcr_semaphore_steps.rb +0 -25
- data/features/support/env.rb +0 -7
- data/features/support/semaphoreapp.rb +0 -2
- data/features/support/vcr.rb +0 -14
- data/features/working_directory.feature +0 -22
- data/lib/git-semaphore/api.rb +0 -60
- data/lib/git-semaphore/app.rb +0 -123
- data/lib/git-semaphore.rb +0 -15
- data/spec_helper.rb +0 -6
- 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-
|
4
|
+
COPYRIGHT = "git-semaphore v#{Git::Semaphore::VERSION} Copyright (c) 2012-2016 Peter Vandenberk"
|
5
5
|
end
|
6
6
|
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'
|