is_it_done_yet 0.1.0 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 8cded42ede28e4279d6d57be770449cb57cd4370
4
- data.tar.gz: bdffab5e1c71d5781a93096d58f4ea10d0284588
2
+ SHA256:
3
+ metadata.gz: 3370c312f841a7cb85eacf8a0f52945c3cd662dce2e45788d3c9e60908866ae0
4
+ data.tar.gz: 481779c28ea71076b0c9dd9ffb902d53bf9aa4bc94896608ff6a433bc7c39f6d
5
5
  SHA512:
6
- metadata.gz: e92becf7e492523175a4bfc243b0ed44afe464a9143a425d8ccec1c69680e8f22fdd52c06229401f5dd910d99e9331061f303fad5467cf4a7ff84acb46ff5243
7
- data.tar.gz: bd8ffb0dfbd1c7b0cc517c48fa90add9942a6f673680d488b4b683fd8eac38e87a911107f895b740cf0d99b1267450be302f5a4987d5b13c5facd8b0e0b95031
6
+ metadata.gz: 7c9e4dc318b6ce443062e9d7dd796e36ead41989e7671ccbe3df0e0ebe99d5cbba4290c5b71408abcf83ff9f622facb6dd07c23194136d741339eb4f1e26ee7c
7
+ data.tar.gz: 032acfa618083162944e0d9253ce094a7682db09a18957b2d925363d1c886d376a1240cf1c9929e14984504d7503007c6f30838f0a18c96f6a8763e41a2d6543
@@ -0,0 +1,33 @@
1
+ # This workflow uses actions that are not certified by GitHub.
2
+ # They are provided by a third-party and are governed by
3
+ # separate terms of service, privacy policy, and support
4
+ # documentation.
5
+ # This workflow will download a prebuilt Ruby version, install dependencies and run tests with Rake
6
+ # For more information see: https://github.com/marketplace/actions/setup-ruby-jruby-and-truffleruby
7
+
8
+ name: Ruby
9
+
10
+ on:
11
+ push:
12
+ branches: [ master ]
13
+ pull_request:
14
+ branches: [ master ]
15
+
16
+ jobs:
17
+ test:
18
+
19
+ runs-on: ubuntu-latest
20
+
21
+ steps:
22
+ - uses: actions/checkout@v2
23
+ - name: Set up Ruby
24
+ # To automatically get bug fixes and new Ruby versions for ruby/setup-ruby,
25
+ # change this to (see https://github.com/ruby/setup-ruby#versioning):
26
+ uses: ruby/setup-ruby@v1
27
+ # uses: ruby/setup-ruby@ec106b438a1ff6ff109590de34ddc62c540232e0
28
+ with:
29
+ ruby-version: 2.6
30
+ - name: Install dependencies
31
+ run: bundle install
32
+ - name: Run tests
33
+ run: bundle exec rake
data/.gitignore CHANGED
@@ -1,6 +1,5 @@
1
- /.bundle/
1
+ git /.bundle/
2
2
  /.yardoc
3
- /Gemfile.lock
4
3
  /_yardoc/
5
4
  /coverage/
6
5
  /doc/
@@ -10,3 +9,7 @@
10
9
 
11
10
  # rspec failure tracking
12
11
  .rspec_status
12
+ *.gem
13
+ .byebug_history
14
+
15
+ .DS_Store
@@ -0,0 +1,32 @@
1
+ # 0.6.0
2
+
3
+ Bumping dependencies
4
+
5
+ # 0.5.0
6
+
7
+ Bumping dependencies
8
+
9
+
10
+ # 0.4.0
11
+
12
+ Features:
13
+
14
+ * Introduced `PUT /builds/:build_id/nodes/:node_id` which enables CI scripts that update node values for instance when rebuilding.
15
+
16
+ # 0.3.0
17
+
18
+ Features:
19
+
20
+ * Clear state for a build using `DELETE /builds/:build_id`. Useful when a CI build is restarted.
21
+
22
+ Bug fixes:
23
+
24
+ * Posting now adheres to standard REST conventions; returns 201 with no body if successful and 409 if the node already exists.
25
+
26
+
27
+ # 0.2.0
28
+
29
+ Features:
30
+
31
+ * Build ids and node ids may now contain any (url-escaped) character.
32
+ - Note that when all nodes of a build are queried, `GET /builds/:build_id`, node ids will appear in their url-unescaped form.
data/Gemfile CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  source 'https://rubygems.org'
2
4
 
3
5
  # Specify your gem's dependencies in is_it_done_yet.gemspec
@@ -0,0 +1,79 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ is_it_done_yet (0.6.0)
5
+ concurrent-ruby
6
+ json
7
+ rack-contrib
8
+ rack-token_auth
9
+ sinatra (~> 2.0)
10
+ thin
11
+
12
+ GEM
13
+ remote: https://rubygems.org/
14
+ specs:
15
+ byebug (11.0.0)
16
+ coderay (1.1.2)
17
+ concurrent-ruby (1.1.7)
18
+ daemons (1.3.1)
19
+ diff-lcs (1.3)
20
+ eventmachine (1.2.7)
21
+ json (2.3.1)
22
+ knapsack (1.17.1)
23
+ rake
24
+ method_source (0.9.2)
25
+ mustermann (1.1.1)
26
+ ruby2_keywords (~> 0.0.1)
27
+ pry (0.12.2)
28
+ coderay (~> 1.1.0)
29
+ method_source (~> 0.9.0)
30
+ rack (2.2.3)
31
+ rack-contrib (2.2.0)
32
+ rack (~> 2.0)
33
+ rack-protection (2.0.8.1)
34
+ rack
35
+ rack-test (1.1.0)
36
+ rack (>= 1.0, < 3)
37
+ rack-token_auth (0.2.0)
38
+ rack
39
+ rake (13.0.1)
40
+ rspec (3.8.0)
41
+ rspec-core (~> 3.8.0)
42
+ rspec-expectations (~> 3.8.0)
43
+ rspec-mocks (~> 3.8.0)
44
+ rspec-core (3.8.0)
45
+ rspec-support (~> 3.8.0)
46
+ rspec-expectations (3.8.2)
47
+ diff-lcs (>= 1.2.0, < 2.0)
48
+ rspec-support (~> 3.8.0)
49
+ rspec-mocks (3.8.0)
50
+ diff-lcs (>= 1.2.0, < 2.0)
51
+ rspec-support (~> 3.8.0)
52
+ rspec-support (3.8.0)
53
+ ruby2_keywords (0.0.2)
54
+ sinatra (2.0.8.1)
55
+ mustermann (~> 1.0)
56
+ rack (~> 2.0)
57
+ rack-protection (= 2.0.8.1)
58
+ tilt (~> 2.0)
59
+ thin (1.7.2)
60
+ daemons (~> 1.0, >= 1.0.9)
61
+ eventmachine (~> 1.0, >= 1.0.4)
62
+ rack (>= 1, < 3)
63
+ tilt (2.0.10)
64
+
65
+ PLATFORMS
66
+ ruby
67
+
68
+ DEPENDENCIES
69
+ bundler
70
+ byebug
71
+ is_it_done_yet!
72
+ knapsack
73
+ pry
74
+ rack-test
75
+ rake
76
+ rspec
77
+
78
+ BUNDLED WITH
79
+ 2.1.0.pre.1
data/README.md CHANGED
@@ -6,6 +6,8 @@ The intended use case is when you want to use one of the build nodes as a master
6
6
 
7
7
  Specifically I built it to allow a travis job at work to do parallel tests with Knapsack and do a deploy if all tests pass.
8
8
 
9
+ An example Travis build configuration that takes advantage of a deployed `is_it_done_yet` service is available in [examples](exampels/.travis.yml).
10
+
9
11
  ## Installation
10
12
 
11
13
  Add this line to your application's Gemfile:
@@ -26,6 +28,19 @@ Or install it yourself as:
26
28
 
27
29
  This library includes a Rack application which you can instantiate by doing `IsItDoneYet.build_app`. In the [example Rack config](examples/config.ru) there's a setup with token-based auth middleware. Include a similar `config.ru` at the root of your application and run `bundle exec rackup` to start the web app locally. If you're running it on a cloud service, follow your service's guidelines on deploying Rack applications.
28
30
 
31
+ The web app provides five endpoints:
32
+ * `POST /builds/:build_id/nodes/:node_id` to write value for node, failing to overwrite.
33
+ - example request body: `{ "build_state" : "ok" }`
34
+ * `PUT /builds/:build_id/nodes/:node_id` to write or overwrite value for node.
35
+ - example request body: `{ "build_state" : "building", "build_state_on_overwrite" : "rebuilding" }`
36
+ * `GET /builds/:build_id`
37
+ - example response body: `{ "build_states": { "51": "bad", "52": "ok" } }`
38
+ * `GET /builds/:build_id/nodes/:node_id`
39
+ - example response body: `{ "build_state": "ok" }`
40
+ * `DELETE /builds/:build_id` to clear state for a build
41
+
42
+ The app keeps the build states in memory. This means that values will be lost after a restart of the service. Further, load balancing across multiple instances will only work if routing to the individual instance is consistent for the same `:build_id` path parameter, but scaling beyond one app instance is not currently a known use case.
43
+
29
44
  ## Development
30
45
 
31
46
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
data/Rakefile CHANGED
@@ -1,10 +1,11 @@
1
- require "bundler/gem_tasks"
2
- require "rspec/core/rake_task"
3
- require 'knapsack'
1
+ # frozen_string_literal: true
4
2
 
3
+ require 'bundler/gem_tasks'
4
+ require 'rspec/core/rake_task'
5
+ require 'knapsack'
5
6
 
6
7
  RSpec::Core::RakeTask.new(:spec)
7
8
 
8
- task :default => :spec
9
+ task default: :spec
9
10
 
10
11
  Knapsack.load_tasks if defined?(Knapsack)
@@ -1,7 +1,8 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
2
3
 
3
- require "bundler/setup"
4
- require "is_it_done_yet"
4
+ require 'bundler/setup'
5
+ require 'is_it_done_yet'
5
6
 
6
7
  # You can add fixtures and/or initialization code here to make experimenting
7
8
  # with your gem easier. You can also use a different console, if you like.
@@ -10,5 +11,5 @@ require "is_it_done_yet"
10
11
  # require "pry"
11
12
  # Pry.start
12
13
 
13
- require "irb"
14
+ require 'irb'
14
15
  IRB.start(__FILE__)
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler'
4
+ require 'rack/contrib'
5
+ require 'rack/token_auth'
6
+ require 'is_it_done_yet'
7
+ Bundler.require
8
+
9
+ # Shared secret authentication
10
+ use Rack::TokenAuth do |token, _options, _env|
11
+ token == ENV['IIDY_TOKEN']
12
+ end
13
+
14
+ # Heartbeat endpoint
15
+ map '/health_check' do
16
+ run ->(_env) { [200, { 'Content-Type' => 'text/plain' }, ['success']] }
17
+ end
18
+
19
+ run IsItDoneYet.build_app
@@ -0,0 +1,47 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # Script that clears build status of
5
+ # travis nodes from synchronising service
6
+
7
+ require 'net/http'
8
+ require 'uri'
9
+ require 'json'
10
+
11
+ SLEEP_TIME_SECONDS = 30
12
+
13
+ env_ok = ENV['CI_NODE_INDEX'] &&
14
+ ENV['CI_NODE_TOTAL'] &&
15
+ ENV['IIDY_ENDPOINT'] &&
16
+ ENV['TRAVIS_BUILD_NUMBER'] &&
17
+ ENV['TRAVIS_JOB_NUMBER'] &&
18
+ ENV['IIDY_TOKEN']
19
+
20
+ abort 'Bad env' unless env_ok
21
+
22
+ main_node = ENV['CI_NODE_INDEX'] == '0'
23
+ auth_token = "Token token=\"#{ENV['IIDY_TOKEN']}\""
24
+
25
+ if main_node
26
+ build_id = "is_it_done_yet--#{ENV['TRAVIS_BUILD_NUMBER']}"
27
+
28
+ url = ENV['IIDY_ENDPOINT'] + "/builds/#{build_id}"
29
+
30
+ puts "URL: #{url}"
31
+
32
+ uri = URI(url)
33
+
34
+ request = Net::HTTP::Delete.new(uri)
35
+ request['Authorization'] = auth_token
36
+
37
+ response = Net::HTTP.start(
38
+ uri.hostname,
39
+ uri.port,
40
+ use_ssl: uri.scheme == 'https'
41
+ ) { |http| http.request(request) }
42
+
43
+ abort 'Failed to clear results' unless response.code == '204'
44
+ puts 'Successfully cleared results!'
45
+ else
46
+ puts 'Not main node!'
47
+ end
@@ -1,4 +1,6 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
2
4
  main_node = ENV['CI_NODE_INDEX'] == '0'
3
5
 
4
6
  if main_node
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
2
3
 
3
4
  # Script that posts build status of
4
5
  # travis nodes (from ARGV[0]) to synchronising service
@@ -46,5 +47,5 @@ else
46
47
  use_ssl: uri.scheme == 'https'
47
48
  ) { |http| http.request(request) }
48
49
 
49
- abort "Failed to post build status: #{response.code}" unless response.code == '200'
50
+ abort "Failed to post build status: #{response.code}" unless response.code == '201'
50
51
  end
@@ -0,0 +1,54 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # Script that puts build and rebuild status of
5
+ # travis nodes (from ARGV[0] and ARGV[1]) to synchronising service
6
+ require 'net/http'
7
+ require 'uri'
8
+ require 'json'
9
+
10
+ env_ok = ENV['CI_NODE_INDEX'] &&
11
+ ENV['CI_NODE_TOTAL'] &&
12
+ ENV['IIDY_ENDPOINT'] &&
13
+ ENV['TRAVIS_BUILD_NUMBER'] &&
14
+ ENV['TRAVIS_JOB_NUMBER'] &&
15
+ ENV['IIDY_TOKEN']
16
+
17
+ args_ok = !ARGV[0].nil?
18
+
19
+ abort 'Bad env' unless env_ok
20
+ abort 'Bad args' unless args_ok
21
+
22
+ main_node = ENV['CI_NODE_INDEX'] == '0'
23
+ auth_token = "Token token=\"#{ENV['IIDY_TOKEN']}\""
24
+
25
+ if main_node
26
+ puts 'Main node!'
27
+ else
28
+ # publish progress
29
+ build_id = "is_it_done_yet--#{ENV['TRAVIS_BUILD_NUMBER']}"
30
+ node_id = ENV['TRAVIS_JOB_NUMBER']
31
+
32
+ url = ENV['IIDY_ENDPOINT'] +
33
+ "/builds/#{build_id}/nodes/#{node_id}"
34
+
35
+ puts "URL: #{url}"
36
+
37
+ uri = URI(url)
38
+
39
+ request = Net::HTTP::Put.new(uri)
40
+ request['Authorization'] = auth_token
41
+ request.body = {
42
+ build_state: ARGV[0],
43
+ build_state_on_overwrite: ARGV[1]
44
+ }.compact.to_json
45
+ request.content_type = 'application/json'
46
+
47
+ response = Net::HTTP.start(
48
+ uri.hostname,
49
+ uri.port,
50
+ use_ssl: uri.scheme == 'https'
51
+ ) { |http| http.request(request) }
52
+
53
+ abort "Failed to put build status: #{response.code}" unless response.code == '204'
54
+ end
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
2
3
 
3
4
  # Script that queries build status of
4
5
  # travis nodes from synchronising service
@@ -8,7 +9,9 @@ require 'net/http'
8
9
  require 'uri'
9
10
  require 'json'
10
11
 
11
- OK_STATES = %w(OK GOOD).freeze
12
+ OK_STATES = %w[OK GOOD].freeze
13
+ BUILDING_STATE = 'BUILDING'.freeze
14
+ REBUILDING_STATE = 'REBUILDING'.freeze
12
15
  SLEEP_TIME_SECONDS = 30
13
16
 
14
17
  env_ok = ENV['CI_NODE_INDEX'] &&
@@ -39,10 +42,11 @@ if main_node
39
42
  expected_size = ENV['CI_NODE_TOTAL'].to_i - 1
40
43
  attempts = 0
41
44
  success = false
45
+ rebuilding = false
42
46
 
43
47
  loop do
44
48
  attempts += 1
45
- abort 'Failed to fetch results' if attempts > 10
49
+ abort 'Failed to fetch results' if attempts > 10 && !rebuilding
46
50
 
47
51
  puts "Attempt no. #{attempts} at fetching build states"
48
52
 
@@ -52,13 +56,25 @@ if main_node
52
56
  use_ssl: uri.scheme == 'https'
53
57
  ) { |http| http.request(request) }
54
58
 
55
- next unless response.code == '200'
59
+ unless response.code == '200'
60
+ sleep SLEEP_TIME_SECONDS
61
+ next
62
+ end
63
+
56
64
  build_states = JSON.parse(response.body)['build_states']
57
65
 
58
66
  if build_states.size < expected_size
59
67
  puts "Some nodes have not reported in yet, \
60
- found #{build_states.size}, \
61
- expected #{expected_size}, waiting a bit"
68
+ found #{build_states.size}, \
69
+ expected #{expected_size}, waiting a bit"
70
+ sleep SLEEP_TIME_SECONDS
71
+ next
72
+ end
73
+
74
+ rebuilding = build_states.any?{ |(_k, value)| REBUILDING_STATE == value.upcase }
75
+ building = build_states.any?{ |(_k, value)| BUILDING_STATE == value.upcase }
76
+ if rebuilding || building
77
+ puts 'Nodes are building or rebuilding'
62
78
  sleep SLEEP_TIME_SECONDS
63
79
  next
64
80
  end
@@ -1,11 +1,21 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'bundler'
2
4
  require 'rack/contrib'
3
5
  require 'rack/token_auth'
4
6
  require 'is_it_done_yet'
5
7
  Bundler.require
6
8
 
7
- use Rack::TokenAuth do |token, options, env|
9
+ # Shared secret authentication
10
+ use Rack::TokenAuth do |token, _options, _env|
8
11
  token == ENV['IIDY_TOKEN']
9
12
  end
10
13
 
14
+ # Heartbeat endpoint
15
+ use Rack::Builder.new do
16
+ map '/health_check' do
17
+ run ->(_env) { "success" }
18
+ end
19
+ end
20
+
11
21
  run IsItDoneYet.build_app
@@ -1,5 +1,6 @@
1
- # coding: utf-8
2
- lib = File.expand_path('../lib', __FILE__)
1
+ # frozen_string_literal: true
2
+
3
+ lib = File.expand_path('lib', __dir__)
3
4
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
5
  require 'is_it_done_yet/version'
5
6
 
@@ -9,20 +10,16 @@ Gem::Specification.new do |spec|
9
10
  spec.authors = ['Erik Madsen']
10
11
  spec.email = ['beatmadsen@gmail.com']
11
12
 
12
- spec.summary = 'Tracks status of Travis matrix build nodes'
13
- spec.description = '
14
- In case you need to carry out build steps
15
- in one of the nodes after the other nodes finish, e.g. deploying,
16
- then you need a way of ascertaining the status of the other nodes.
17
- This is what is_it_done_yet is for.
18
- '
13
+ spec.summary = 'Tracks statuses of CI build nodes'
14
+ spec.description = 'In case you need to carry out build steps in one of the nodes of your CI project after the other nodes finish, e.g. deploying, then you need a way of ascertaining the status of the other nodes. This is what is_it_done_yet is for.'
19
15
  spec.homepage = 'http://www.github.com/beatmadsen/is_it_done_yet'
20
16
  spec.license = 'MIT'
17
+ spec.required_ruby_version = '>= 2.0'
21
18
 
22
19
  # Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
23
20
  # to allow pushing to a single host or delete this section to allow pushing to any host.
24
21
  if spec.respond_to?(:metadata)
25
- spec.metadata['allowed_push_host'] = "https://rubygems.org"
22
+ spec.metadata['allowed_push_host'] = 'https://rubygems.org'
26
23
  else
27
24
  raise 'RubyGems 2.0 or newer is required to protect against ' \
28
25
  'public gem pushes.'
@@ -30,23 +27,26 @@ Gem::Specification.new do |spec|
30
27
 
31
28
  spec.files = `git ls-files -z`.split("\x0").reject do |f|
32
29
  f.match(%r{^(test|spec|features)/}) ||
33
- f.match(/knapsack/)
30
+ f.match(/knapsack/) ||
31
+ f.match(/travis/)
34
32
  end
35
33
  spec.bindir = 'bin'
36
34
  spec.require_paths = ['lib']
37
35
 
38
36
  # Always
39
- spec.add_dependency 'concurrent-ruby', '~> 1'
40
- spec.add_dependency 'sinatra', '~> 1.4'
41
- spec.add_dependency 'thin', '~> 1.7'
42
- spec.add_dependency 'rack-contrib', '~> 1.4'
43
- spec.add_dependency 'rack-token_auth', '~> 0.1'
44
- spec.add_dependency 'json', '~> 2'
37
+ spec.add_dependency 'concurrent-ruby'
38
+ spec.add_dependency 'json'
39
+ spec.add_dependency 'rack-contrib'
40
+ spec.add_dependency 'rack-token_auth'
41
+ spec.add_dependency 'sinatra', '~> 2.0'
42
+ spec.add_dependency 'thin'
45
43
 
46
44
  # Development
47
- spec.add_development_dependency 'bundler', '~> 1.14'
48
- spec.add_development_dependency 'rake', '~> 10.0'
49
- spec.add_development_dependency 'rspec', '~> 3.0'
50
- spec.add_development_dependency 'rack-test', '~> 0.6'
51
- spec.add_development_dependency 'knapsack', '~> 1.13'
45
+ spec.add_development_dependency 'bundler'
46
+ spec.add_development_dependency 'byebug'
47
+ spec.add_development_dependency 'knapsack'
48
+ spec.add_development_dependency 'pry'
49
+ spec.add_development_dependency 'rack-test'
50
+ spec.add_development_dependency 'rake'
51
+ spec.add_development_dependency 'rspec'
52
52
  end
@@ -1,96 +1,11 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'is_it_done_yet/version'
2
- require 'sinatra'
3
- require 'concurrent'
4
- require 'json'
5
- require 'time'
4
+ require 'is_it_done_yet/web_app'
6
5
  require 'rack/contrib'
6
+ require 'rack'
7
7
 
8
8
  module IsItDoneYet
9
- class WebApp < ::Sinatra::Base
10
- UNKNOWN_NODE = { errors: ['Unknown Node'] }.to_json
11
- UNKNOWN_BUILD = { errors: ['Unknown Build'] }.to_json
12
- NO_BUILD_STATE = { errors: ['You forgot build state'] }.to_json
13
- TTL_SECONDS = 24 * 60 * 60
14
-
15
- configure do
16
- set :state, ::Concurrent::Map.new
17
- end
18
-
19
- helpers do
20
- private
21
-
22
- def key(build_id, node_id)
23
- "#{build_id}|#{node_id}"
24
- end
25
-
26
- def house_keeping
27
- expired_keys =
28
- settings.state.each_pair.with_object([]) do |(key, (_b, time)), acc|
29
- acc << key if time < Time.now - TTL_SECONDS
30
- end
31
-
32
- expired_keys.each { |key| settings.state.delete(key) }
33
- end
34
-
35
- def store(key, value)
36
- settings.state[key] = [value, Time.now]
37
- end
38
-
39
- def retrieve(key)
40
- build_state, _t = settings.state[key]
41
- build_state
42
- end
43
-
44
- def retrieve_all(prefix)
45
- settings.state
46
- .each_pair
47
- .select { |(key, _v)| key.start_with?(prefix) }
48
- .map { |(key, (build_state, _t))| [key.split('|')[1], build_state] }
49
- .to_h
50
- end
51
- end
52
-
53
- before do
54
- house_keeping
55
-
56
- content_type :json
57
- end
58
-
59
- get '/builds/:build_id/nodes/:node_id' do
60
- build_id, node_id = params.values_at('build_id', 'node_id')
61
- k = key(build_id, node_id)
62
-
63
- build_state = retrieve(k)
64
-
65
- halt 404, UNKNOWN_NODE unless build_state
66
-
67
- content_type :json
68
- { build_state: build_state }.to_json
69
- end
70
-
71
- get '/builds/:build_id' do
72
- build_id = params['build_id']
73
-
74
- build_states = retrieve_all(build_id)
75
-
76
- halt 404, UNKNOWN_BUILD unless build_states
77
-
78
- content_type :json
79
- { build_states: build_states }.to_json
80
- end
81
-
82
- post '/builds/:build_id/nodes/:node_id' do
83
- build_state = params['build_state']
84
- halt 400, NO_BUILD_STATE unless build_state
85
-
86
- build_id, node_id = params.values_at('build_id', 'node_id')
87
- k = key(build_id, node_id)
88
- store(k, build_state)
89
-
90
- 200
91
- end
92
- end
93
-
94
9
  def self.build_app
95
10
  Rack::Builder.app do
96
11
  use Rack::PostBodyContentTypeParser
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module IsItDoneYet
2
- VERSION = "0.1.0"
4
+ VERSION = '0.6.0'
3
5
  end
@@ -0,0 +1,139 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'is_it_done_yet/version'
4
+ require 'sinatra'
5
+ require 'concurrent'
6
+ require 'json'
7
+ require 'time'
8
+ require 'rack/contrib'
9
+
10
+ module IsItDoneYet
11
+ class WebApp < ::Sinatra::Base
12
+ UNKNOWN_NODE = { errors: ['Unknown Node'] }.to_json
13
+ ALREADY_EXISTS = { errors: ['Node already exists'] }.to_json
14
+ UNKNOWN_BUILD = { errors: ['Unknown Build'] }.to_json
15
+ NO_BUILD_STATE = { errors: ['You forgot build state'] }.to_json
16
+ TTL_SECONDS = 24 * 60 * 60
17
+
18
+ configure do
19
+ set :state, ::Concurrent::Map.new
20
+ end
21
+
22
+ helpers do
23
+ private
24
+
25
+ def key(build_id, node_id)
26
+ [build_id, node_id].freeze
27
+ end
28
+
29
+ def house_keeping
30
+ expired_keys =
31
+ settings.state.each_pair.with_object([]) do |(key, (_b, time)), acc|
32
+ acc << key if time < Time.now - TTL_SECONDS
33
+ end
34
+
35
+ expired_keys.each { |key| settings.state.delete(key) }
36
+ end
37
+
38
+ def store(key, value)
39
+ settings.state[key] = [value, Time.now]
40
+ end
41
+
42
+ def merge(key:, value_if_absent:, value_if_present:)
43
+ settings.state.merge_pair(key, [value_if_absent, Time.now]) do
44
+ [value_if_present, Time.now]
45
+ end
46
+ end
47
+
48
+ def retrieve(key)
49
+ build_state, _t = settings.state[key]
50
+ build_state
51
+ end
52
+
53
+ def retrieve_all(build_id)
54
+ settings.state
55
+ .each_pair
56
+ .select { |((b, _n), _v)| build_id == b }
57
+ .map { |((_b, node_id), (build_state, _t))| [node_id, build_state] }
58
+ .to_h
59
+ end
60
+
61
+ def clear_nodes(build_id)
62
+ state = settings.state
63
+ keys_for_build = state.keys.select { |(b, _n)| b == build_id }
64
+ keys_for_build.each { |key| state.delete(key) }
65
+ end
66
+ end
67
+
68
+ before do
69
+ house_keeping
70
+
71
+ content_type :json
72
+ end
73
+
74
+ get '/builds/:build_id/nodes/:node_id' do
75
+ build_id, node_id = params.values_at('build_id', 'node_id')
76
+ k = key(build_id, node_id)
77
+
78
+ build_state = retrieve(k)
79
+
80
+ halt 404, UNKNOWN_NODE unless build_state
81
+
82
+ { build_state: build_state }.to_json
83
+ end
84
+
85
+ get '/builds/:build_id' do
86
+ build_id = params['build_id']
87
+
88
+ build_states = retrieve_all(build_id)
89
+
90
+ halt 404, UNKNOWN_BUILD if build_states.to_a.empty?
91
+
92
+ { build_states: build_states }.to_json
93
+ end
94
+
95
+ delete '/builds/:build_id' do
96
+ build_id = params['build_id']
97
+
98
+ clear_nodes(build_id)
99
+
100
+ 204
101
+ end
102
+
103
+ post '/builds/:build_id/nodes/:node_id' do
104
+ build_state = params['build_state']
105
+ halt 400, NO_BUILD_STATE unless build_state
106
+
107
+ build_id, node_id = params.values_at('build_id', 'node_id')
108
+ k = key(build_id, node_id)
109
+ halt 409, ALREADY_EXISTS if retrieve(k)
110
+
111
+ store(k, build_state)
112
+
113
+ 201
114
+ end
115
+
116
+ # Example:
117
+ # PUT /builds/42/nodes/13?build_state=BUILDING&build_state_on_overwrite=REBUILDING
118
+ put '/builds/:build_id/nodes/:node_id' do
119
+ build_state, build_state_on_overwrite =
120
+ params.values_at('build_state', 'build_state_on_overwrite')
121
+ halt 400, NO_BUILD_STATE unless build_state
122
+
123
+ build_id, node_id = params.values_at('build_id', 'node_id')
124
+ k = key(build_id, node_id)
125
+
126
+ if build_state_on_overwrite
127
+ merge(
128
+ key: k,
129
+ value_if_absent: build_state,
130
+ value_if_present: build_state_on_overwrite
131
+ )
132
+ else
133
+ store(k, build_state)
134
+ end
135
+
136
+ 204
137
+ end
138
+ end
139
+ end
metadata CHANGED
@@ -1,196 +1,229 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: is_it_done_yet
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Erik Madsen
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-04-21 00:00:00.000000000 Z
11
+ date: 2020-08-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: concurrent-ruby
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - "~>"
17
+ - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: '1'
19
+ version: '0'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - "~>"
24
+ - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: '1'
26
+ version: '0'
27
27
  - !ruby/object:Gem::Dependency
28
- name: sinatra
28
+ name: json
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - "~>"
31
+ - - ">="
32
32
  - !ruby/object:Gem::Version
33
- version: '1.4'
33
+ version: '0'
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - "~>"
38
+ - - ">="
39
39
  - !ruby/object:Gem::Version
40
- version: '1.4'
40
+ version: '0'
41
41
  - !ruby/object:Gem::Dependency
42
- name: thin
42
+ name: rack-contrib
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
- - - "~>"
45
+ - - ">="
46
46
  - !ruby/object:Gem::Version
47
- version: '1.7'
47
+ version: '0'
48
48
  type: :runtime
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
- - - "~>"
52
+ - - ">="
53
53
  - !ruby/object:Gem::Version
54
- version: '1.7'
54
+ version: '0'
55
55
  - !ruby/object:Gem::Dependency
56
- name: rack-contrib
56
+ name: rack-token_auth
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
- - - "~>"
59
+ - - ">="
60
60
  - !ruby/object:Gem::Version
61
- version: '1.4'
61
+ version: '0'
62
62
  type: :runtime
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
- - - "~>"
66
+ - - ">="
67
67
  - !ruby/object:Gem::Version
68
- version: '1.4'
68
+ version: '0'
69
69
  - !ruby/object:Gem::Dependency
70
- name: rack-token_auth
70
+ name: sinatra
71
71
  requirement: !ruby/object:Gem::Requirement
72
72
  requirements:
73
73
  - - "~>"
74
74
  - !ruby/object:Gem::Version
75
- version: '0.1'
75
+ version: '2.0'
76
76
  type: :runtime
77
77
  prerelease: false
78
78
  version_requirements: !ruby/object:Gem::Requirement
79
79
  requirements:
80
80
  - - "~>"
81
81
  - !ruby/object:Gem::Version
82
- version: '0.1'
82
+ version: '2.0'
83
83
  - !ruby/object:Gem::Dependency
84
- name: json
84
+ name: thin
85
85
  requirement: !ruby/object:Gem::Requirement
86
86
  requirements:
87
- - - "~>"
87
+ - - ">="
88
88
  - !ruby/object:Gem::Version
89
- version: '2'
89
+ version: '0'
90
90
  type: :runtime
91
91
  prerelease: false
92
92
  version_requirements: !ruby/object:Gem::Requirement
93
93
  requirements:
94
- - - "~>"
94
+ - - ">="
95
95
  - !ruby/object:Gem::Version
96
- version: '2'
96
+ version: '0'
97
97
  - !ruby/object:Gem::Dependency
98
98
  name: bundler
99
99
  requirement: !ruby/object:Gem::Requirement
100
100
  requirements:
101
- - - "~>"
101
+ - - ">="
102
102
  - !ruby/object:Gem::Version
103
- version: '1.14'
103
+ version: '0'
104
104
  type: :development
105
105
  prerelease: false
106
106
  version_requirements: !ruby/object:Gem::Requirement
107
107
  requirements:
108
- - - "~>"
108
+ - - ">="
109
109
  - !ruby/object:Gem::Version
110
- version: '1.14'
110
+ version: '0'
111
111
  - !ruby/object:Gem::Dependency
112
- name: rake
112
+ name: byebug
113
113
  requirement: !ruby/object:Gem::Requirement
114
114
  requirements:
115
- - - "~>"
115
+ - - ">="
116
116
  - !ruby/object:Gem::Version
117
- version: '10.0'
117
+ version: '0'
118
118
  type: :development
119
119
  prerelease: false
120
120
  version_requirements: !ruby/object:Gem::Requirement
121
121
  requirements:
122
- - - "~>"
122
+ - - ">="
123
123
  - !ruby/object:Gem::Version
124
- version: '10.0'
124
+ version: '0'
125
125
  - !ruby/object:Gem::Dependency
126
- name: rspec
126
+ name: knapsack
127
127
  requirement: !ruby/object:Gem::Requirement
128
128
  requirements:
129
- - - "~>"
129
+ - - ">="
130
130
  - !ruby/object:Gem::Version
131
- version: '3.0'
131
+ version: '0'
132
132
  type: :development
133
133
  prerelease: false
134
134
  version_requirements: !ruby/object:Gem::Requirement
135
135
  requirements:
136
- - - "~>"
136
+ - - ">="
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
139
+ - !ruby/object:Gem::Dependency
140
+ name: pry
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - ">="
144
+ - !ruby/object:Gem::Version
145
+ version: '0'
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - ">="
137
151
  - !ruby/object:Gem::Version
138
- version: '3.0'
152
+ version: '0'
139
153
  - !ruby/object:Gem::Dependency
140
154
  name: rack-test
141
155
  requirement: !ruby/object:Gem::Requirement
142
156
  requirements:
143
- - - "~>"
157
+ - - ">="
144
158
  - !ruby/object:Gem::Version
145
- version: '0.6'
159
+ version: '0'
146
160
  type: :development
147
161
  prerelease: false
148
162
  version_requirements: !ruby/object:Gem::Requirement
149
163
  requirements:
150
- - - "~>"
164
+ - - ">="
151
165
  - !ruby/object:Gem::Version
152
- version: '0.6'
166
+ version: '0'
153
167
  - !ruby/object:Gem::Dependency
154
- name: knapsack
168
+ name: rake
155
169
  requirement: !ruby/object:Gem::Requirement
156
170
  requirements:
157
- - - "~>"
171
+ - - ">="
158
172
  - !ruby/object:Gem::Version
159
- version: '1.13'
173
+ version: '0'
160
174
  type: :development
161
175
  prerelease: false
162
176
  version_requirements: !ruby/object:Gem::Requirement
163
177
  requirements:
164
- - - "~>"
178
+ - - ">="
179
+ - !ruby/object:Gem::Version
180
+ version: '0'
181
+ - !ruby/object:Gem::Dependency
182
+ name: rspec
183
+ requirement: !ruby/object:Gem::Requirement
184
+ requirements:
185
+ - - ">="
165
186
  - !ruby/object:Gem::Version
166
- version: '1.13'
167
- description: "\n In case you need to carry out build steps\n in one of the
168
- nodes after the other nodes finish, e.g. deploying,\n then you need a way of
169
- ascertaining the status of the other nodes.\n This is what is_it_done_yet is
170
- for.\n "
187
+ version: '0'
188
+ type: :development
189
+ prerelease: false
190
+ version_requirements: !ruby/object:Gem::Requirement
191
+ requirements:
192
+ - - ">="
193
+ - !ruby/object:Gem::Version
194
+ version: '0'
195
+ description: In case you need to carry out build steps in one of the nodes of your
196
+ CI project after the other nodes finish, e.g. deploying, then you need a way of
197
+ ascertaining the status of the other nodes. This is what is_it_done_yet is for.
171
198
  email:
172
199
  - beatmadsen@gmail.com
173
200
  executables: []
174
201
  extensions: []
175
202
  extra_rdoc_files: []
176
203
  files:
204
+ - ".github/workflows/ruby.yml"
177
205
  - ".gitignore"
178
206
  - ".rspec"
179
- - ".travis.yml"
207
+ - CHANGELOG.md
180
208
  - CODE_OF_CONDUCT.md
181
209
  - Gemfile
210
+ - Gemfile.lock
182
211
  - LICENSE.txt
183
212
  - README.md
184
213
  - Rakefile
185
214
  - bin/console
186
215
  - bin/setup
216
+ - config.ru
217
+ - examples/ci-clear
187
218
  - examples/ci-deploy
188
219
  - examples/ci-post
220
+ - examples/ci-put
189
221
  - examples/ci-sync
190
222
  - examples/config.ru
191
223
  - is_it_done_yet.gemspec
192
224
  - lib/is_it_done_yet.rb
193
225
  - lib/is_it_done_yet/version.rb
226
+ - lib/is_it_done_yet/web_app.rb
194
227
  homepage: http://www.github.com/beatmadsen/is_it_done_yet
195
228
  licenses:
196
229
  - MIT
@@ -204,16 +237,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
204
237
  requirements:
205
238
  - - ">="
206
239
  - !ruby/object:Gem::Version
207
- version: '0'
240
+ version: '2.0'
208
241
  required_rubygems_version: !ruby/object:Gem::Requirement
209
242
  requirements:
210
243
  - - ">="
211
244
  - !ruby/object:Gem::Version
212
245
  version: '0'
213
246
  requirements: []
214
- rubyforge_project:
215
- rubygems_version: 2.6.11
247
+ rubygems_version: 3.1.0.pre1
216
248
  signing_key:
217
249
  specification_version: 4
218
- summary: Tracks status of Travis matrix build nodes
250
+ summary: Tracks statuses of CI build nodes
219
251
  test_files: []
@@ -1,20 +0,0 @@
1
- sudo: false
2
- language: ruby
3
- cache: bundler
4
- rvm:
5
- - 2.4.1
6
- before_install: gem install bundler -v 1.14.6
7
- bundler_args: --without production
8
-
9
- env:
10
- global:
11
- - MY_GLOBAL_VAR=123
12
- - CI_NODE_TOTAL=2
13
- matrix:
14
- - CI_NODE_INDEX=0
15
- - CI_NODE_INDEX=1
16
-
17
- script: bundle exec rake knapsack:rspec && examples/ci-sync && examples/ci-deploy
18
-
19
- after_success: examples/ci-post OK
20
- after_failure: examples/ci-post FAILED