git-semaphore 0.0.9 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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'