mamiya 0.0.1.alpha21 → 0.0.1.alpha22

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 (54) hide show
  1. checksums.yaml +4 -4
  2. data/example/.gitignore +5 -0
  3. data/example/Procfile +1 -1
  4. data/example/README.md +83 -0
  5. data/example/config.agent.rb +40 -0
  6. data/example/config.rb +20 -6
  7. data/example/deploy.rb +27 -11
  8. data/example/source/README.md +1 -0
  9. data/lib/mamiya/agent/actions.rb +8 -3
  10. data/lib/mamiya/agent/task_queue.rb +9 -0
  11. data/lib/mamiya/agent/tasks/abstract.rb +13 -0
  12. data/lib/mamiya/agent/tasks/clean.rb +36 -4
  13. data/lib/mamiya/agent/tasks/fetch.rb +1 -0
  14. data/lib/mamiya/agent/tasks/notifyable.rb +0 -1
  15. data/lib/mamiya/agent/tasks/prepare.rb +103 -0
  16. data/lib/mamiya/agent.rb +46 -12
  17. data/lib/mamiya/cli/client.rb +35 -7
  18. data/lib/mamiya/cli.rb +44 -5
  19. data/lib/mamiya/configuration.rb +12 -0
  20. data/lib/mamiya/dsl.rb +6 -2
  21. data/lib/mamiya/helpers/git.rb +24 -0
  22. data/lib/mamiya/master/agent_monitor.rb +22 -2
  23. data/lib/mamiya/master/agent_monitor_handlers.rb +17 -0
  24. data/lib/mamiya/master/web.rb +42 -3
  25. data/lib/mamiya/master.rb +4 -0
  26. data/lib/mamiya/script.rb +28 -8
  27. data/lib/mamiya/steps/abstract.rb +1 -0
  28. data/lib/mamiya/steps/build.rb +107 -19
  29. data/lib/mamiya/steps/extract.rb +1 -0
  30. data/lib/mamiya/steps/prepare.rb +60 -0
  31. data/lib/mamiya/steps/switch.rb +76 -0
  32. data/lib/mamiya/storages/filesystem.rb +92 -0
  33. data/lib/mamiya/storages/mock.rb +1 -0
  34. data/lib/mamiya/util/label_matcher.rb +7 -3
  35. data/lib/mamiya/version.rb +1 -1
  36. data/mamiya.gemspec +1 -1
  37. data/spec/agent/actions_spec.rb +25 -0
  38. data/spec/agent/task_queue_spec.rb +42 -6
  39. data/spec/agent/tasks/abstract_spec.rb +35 -0
  40. data/spec/agent/tasks/clean_spec.rb +94 -45
  41. data/spec/agent/tasks/fetch_spec.rb +1 -0
  42. data/spec/agent/tasks/prepare_spec.rb +127 -0
  43. data/spec/agent_spec.rb +75 -27
  44. data/spec/dsl_spec.rb +6 -8
  45. data/spec/master/agent_monitor_spec.rb +142 -4
  46. data/spec/master/web_spec.rb +43 -1
  47. data/spec/steps/build_spec.rb +101 -0
  48. data/spec/steps/prepare_spec.rb +125 -0
  49. data/spec/steps/switch_spec.rb +146 -0
  50. data/spec/storages/filesystem_spec.rb +305 -0
  51. data/spec/util/label_matcher_spec.rb +32 -0
  52. metadata +20 -6
  53. data/config.example.yml +0 -11
  54. data/example.rb +0 -74
@@ -7,6 +7,8 @@ require 'uri'
7
7
  require 'json'
8
8
  require 'thor'
9
9
 
10
+ require 'mamiya/util/label_matcher'
11
+
10
12
  module Mamiya
11
13
  class CLI < Thor
12
14
  class Client < Thor
@@ -42,8 +44,10 @@ module Mamiya
42
44
  end
43
45
 
44
46
  desc "list-agents", 'list agents'
47
+ method_option :labels, type: :string
45
48
  def list_agents
46
- payload = master_get("/agents")
49
+ params = options[:labels] ? {labels: options[:labels]} : {}
50
+ payload = master_get("/agents", params)
47
51
 
48
52
  agents = payload["agents"].keys
49
53
 
@@ -76,8 +80,10 @@ module Mamiya
76
80
  desc "show-distribution package", "Show package distribution status"
77
81
  method_option :format, aliases: %w(-f), type: :string, default: 'text'
78
82
  method_option :verbose, aliases: %w(-v), type: :boolean
83
+ method_option :labels, type: :string
79
84
  def show_distribution(package)
80
- dist = master_get("/packages/#{application}/#{package}/distribution")
85
+ params = options[:labels] ? {labels: options[:labels]} : {}
86
+ dist = master_get("/packages/#{application}/#{package}/distribution", params)
81
87
 
82
88
  case options[:format]
83
89
  when 'json'
@@ -122,8 +128,21 @@ not distributed: #{dist['not_distributed_count']} agents
122
128
  end
123
129
 
124
130
  desc "distribute package", "order distributing package to agents"
131
+ method_option :labels, type: :string
125
132
  def distribute(package)
126
- p master_post("/packages/#{application}/#{package}/distribute")
133
+ params = options[:labels] ?
134
+ {labels: Mamiya::Util::LabelMatcher.parse_string_expr(options[:labels])} : {}
135
+
136
+ p master_post("/packages/#{application}/#{package}/distribute", params, type: :json)
137
+ end
138
+
139
+ desc "prepare PACKAGE", "order preparing package to agents"
140
+ method_option :labels, type: :string
141
+ def prepare(package)
142
+ params = options[:labels] ?
143
+ {labels: Mamiya::Util::LabelMatcher.parse_string_expr(options[:labels])} : {}
144
+
145
+ p master_post("/packages/#{application}/#{package}/prepare", params, type: :json)
127
146
  end
128
147
 
129
148
  desc "refresh", "order refreshing agent status"
@@ -155,20 +174,29 @@ not distributed: #{dist['not_distributed_count']} agents
155
174
  options[:application] or fatal!('specify application')
156
175
  end
157
176
 
158
- def master_get(path)
177
+ def master_get(path, params={})
178
+ path += "?#{Rack::Utils.build_query(params)}" unless params.empty?
159
179
  master_http.start do |http|
160
180
  JSON.parse http.get(path).tap(&:value).body
161
181
  end
162
182
  end
163
183
 
164
- def master_post(path, data='')
184
+ def master_post(path, data='', type: :text)
165
185
  response = nil
166
186
  master_http.start do |http|
187
+ headers = {}
188
+
167
189
  if Hash === data
168
- data = Rack::Utils.build_nested_query(data)
190
+ case type
191
+ when :json
192
+ data = data.to_json
193
+ headers['Content-Type'] = 'application/json'
194
+ when :text
195
+ data = Rack::Utils.build_nested_query(data)
196
+ end
169
197
  end
170
198
 
171
- response = http.post(path, data)
199
+ response = http.post(path, data, headers)
172
200
  response.value
173
201
  response.code == '204' ? true : JSON.parse(response.body)
174
202
  end
data/lib/mamiya/cli.rb CHANGED
@@ -6,8 +6,11 @@ require 'mamiya/logger'
6
6
 
7
7
  require 'mamiya/steps/build'
8
8
  require 'mamiya/steps/push'
9
+
9
10
  require 'mamiya/steps/fetch'
10
11
  require 'mamiya/steps/extract'
12
+ require 'mamiya/steps/prepare'
13
+ require 'mamiya/steps/switch'
11
14
 
12
15
  require 'mamiya/agent'
13
16
  require 'mamiya/master'
@@ -159,15 +162,30 @@ module Mamiya
159
162
  def distribute
160
163
  end
161
164
 
162
- desc "prepare", "Prepare package on clients."
163
- def prepare
165
+ desc "prepare TARGET", "Prepare package."
166
+ method_option :labels, type: :string
167
+ def prepare(target)
168
+ Mamiya::Steps::Prepare.new(
169
+ script: nil,
170
+ config: config,
171
+ target: target,
172
+ labels: labels,
173
+ ).run!
164
174
  end
165
175
 
166
- desc "finalize", "Finalize (start) prepared package on clients."
167
- def finalize
176
+ desc "switch TARGET", "Switch current dir then release."
177
+ method_option :no_release, type: :boolean, default: false
178
+ method_option :labels, type: :string
179
+ def switch(target)
180
+ Mamiya::Steps::Switch.new(
181
+ script: nil,
182
+ config: config,
183
+ target: target,
184
+ labels: labels,
185
+ no_release: options[:no_release],
186
+ ).run!
168
187
  end
169
188
 
170
-
171
189
  # ---
172
190
 
173
191
  desc "agent", "Start agent."
@@ -175,9 +193,11 @@ module Mamiya
175
193
  method_option :daemonize, aliases: '-D', type: :boolean, default: false
176
194
  method_option :log, aliases: '-l', type: :string
177
195
  method_option :pidfile, aliases: '-p', type: :string
196
+ method_option :labels, type: :array
178
197
  def agent
179
198
  prepare_agent_behavior!
180
199
  merge_serf_option!
200
+ override_labels!
181
201
 
182
202
  @agent = Agent.new(config, logger: logger)
183
203
  @agent.run!
@@ -256,6 +276,11 @@ module Mamiya
256
276
  end
257
277
  end
258
278
 
279
+ def labels
280
+ c = config(:no_error)
281
+ options[:labels] ? options[:labels].split(/,/).map(&:to_sym) : (c ? c.labels[[]] : [])
282
+ end
283
+
259
284
  def fatal!(message)
260
285
  logger.fatal message
261
286
  exit 1
@@ -272,6 +297,20 @@ module Mamiya
272
297
  end
273
298
  end
274
299
 
300
+ def override_labels!
301
+ return unless config(:no_error)
302
+ return unless options[:labels]
303
+
304
+ labels = options[:labels].flat_map{ |_| _.split(/,/) }.map(&:to_sym)
305
+ return if labels.empty?
306
+
307
+ config.labels do
308
+ labels
309
+ end
310
+
311
+ logger.info "Overriding labels: #{labels.inspect}"
312
+ end
313
+
275
314
  def application
276
315
  options[:application] || config[:application] || script.application
277
316
  end
@@ -14,16 +14,28 @@ module Mamiya
14
14
 
15
15
  # agent
16
16
  set_default :packages_dir, nil
17
+ set_default :prereleases_dir, nil
17
18
  set_default :fetch_sleep, 12
18
19
  set_default :keep_packages, 3
20
+ set_default :keep_prereleases, 3
19
21
 
20
22
  # master
21
23
  set_default :master, {monitor: {refresh_interval: nil}} # TODO: don't nest
22
24
  set_default :web, {port: 7761, bind: '0.0.0.0', environment: :development} # TODO: IPv6
23
25
 
26
+ add_hook :labels, chain: true
27
+
24
28
  def storage_class
25
29
  Storages.find(self[:storage][:type])
26
30
  end
31
+
32
+ def packages_dir
33
+ self[:packages_dir] && Pathname.new(self[:packages_dir])
34
+ end
35
+
36
+ def prereleases_dir
37
+ self[:prereleases_dir] && Pathname.new(self[:prereleases_dir])
38
+ end
27
39
  end
28
40
  end
29
41
 
data/lib/mamiya/dsl.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  require 'mamiya/util/label_matcher'
2
2
  require 'thread'
3
+ require 'pathname'
3
4
 
4
5
  module Mamiya
5
6
  class DSL
@@ -40,6 +41,8 @@ module Mamiya
40
41
  self.define_variable_accessor(k)
41
42
  end
42
43
 
44
+ set_default :_file, nil
45
+
43
46
  ##
44
47
  # Add hook point with name +name+.
45
48
  # This defines method with same name in class to call and define hooks.
@@ -101,9 +104,9 @@ module Mamiya
101
104
  @file = filename if filename
102
105
 
103
106
  if str && filename && lineno
104
- self.instance_eval(str, filename, lineno)
107
+ self.instance_eval(str, filename.to_s, lineno)
105
108
  elsif str && filename
106
- self.instance_eval(str, filename)
109
+ self.instance_eval(str, filename.to_s)
107
110
  elsif str
108
111
  self.instance_eval(str)
109
112
  end
@@ -118,6 +121,7 @@ module Mamiya
118
121
  ##
119
122
  # Evaluates specified file +file+ in DSL environment.
120
123
  def load!(file)
124
+ set :_file, Pathname.new(file)
121
125
  evaluate! File.read(file), file, 1
122
126
  end
123
127
 
@@ -3,6 +3,10 @@ require 'time'
3
3
  set_default :git_remote, 'origin'
4
4
  set_default :commit, "#{self.git_remote}/HEAD"
5
5
 
6
+ def git_managed?
7
+ system *%w(git rev-parse --git-dir), err: File::NULL, out: File::NULL
8
+ end
9
+
6
10
  def git_ignored_files
7
11
  git_clean_out = `git clean -ndx`.lines
8
12
  prefix = /^Would (?:remove|skip repository) /
@@ -73,3 +77,23 @@ if options[:include_head_commit_to_meta]
73
77
  candidate.merge(git: git_head())
74
78
  end
75
79
  end
80
+
81
+ options[:manage_script] = true unless options.key?(:manage_script)
82
+ options[:script_auto_additionals] = true unless options.key?(:script_auto_additionals)
83
+ if options[:manage_script] && _file
84
+ Dir.chdir(File.dirname(_file)) do
85
+ break unless git_managed?
86
+
87
+ script_git_head = git_head()
88
+ package_meta do |candidate|
89
+ candidate.merge(script_git: script_git_head)
90
+ end
91
+
92
+ if options[:script_auto_additionals]
93
+ files = `git ls-files`.lines.map(&:chomp).reject { |_| _ == File.basename(_file) }
94
+ set :script_additionals, (script_additionals || []) + files
95
+ end
96
+ end
97
+ end
98
+
99
+
@@ -30,9 +30,22 @@ module Mamiya
30
30
  @failed_agents = [].freeze
31
31
  @statuses = {}
32
32
  @commit_lock = Mutex.new
33
+ @last_refresh_at = nil
33
34
  end
34
35
 
35
- attr_reader :statuses, :agents, :failed_agents
36
+ attr_reader :agents, :failed_agents, :last_refresh_at
37
+
38
+ def statuses(labels: nil)
39
+ if labels
40
+ @statuses.select { |name, status|
41
+ status['labels'] &&
42
+ Mamiya::Util::LabelMatcher::Simple.new(status['labels']).
43
+ match?(labels)
44
+ }
45
+ else
46
+ @statuses
47
+ end
48
+ end
36
49
 
37
50
  def start!
38
51
  @thread ||= Thread.new do
@@ -119,7 +132,13 @@ module Mamiya
119
132
  next unless new_statuses[name]
120
133
 
121
134
  begin
122
- new_statuses[name]['packages'] = JSON.parse(json)
135
+ resp = JSON.parse(json)
136
+ if resp.keys.sort == ['packages', 'prereleases']
137
+ new_statuses[name]['packages'] = resp['packages']
138
+ new_statuses[name]['prereleases'] = resp['prereleases']
139
+ else # TODO: Compatibility; should remove soon
140
+ new_statuses[name]['packages'] = resp
141
+ end
123
142
  rescue JSON::ParserError => e
124
143
  logger.warn "Failed to parse packages from #{name}: #{e.message}"
125
144
  next
@@ -153,6 +172,7 @@ module Mamiya
153
172
  @agents = new_agents.freeze
154
173
  @failed_agents = new_failed_agents.freeze
155
174
  @statuses = new_statuses
175
+ @last_refresh_at = Time.now
156
176
  }
157
177
 
158
178
  self
@@ -63,11 +63,28 @@ module Mamiya
63
63
  end
64
64
  end
65
65
 
66
+ def task___prepare__finish(status, task)
67
+ status['prereleases'] ||= {}
68
+ status['prereleases'][task['app']] ||= []
69
+
70
+ unless status['prereleases'][task['app']].include?(task['pkg'])
71
+ status['prereleases'][task['app']] << task['pkg']
72
+ end
73
+ end
74
+
75
+
76
+
66
77
  def pkg__remove(status, payload, event)
67
78
  status['packages'] ||= {}
68
79
  packages = status['packages'][payload['application']]
69
80
  packages.delete(payload['package']) if packages
70
81
  end
82
+
83
+ def prerelease__remove(status, payload, event)
84
+ status['prereleases'] ||= {}
85
+ prereleases = status['prereleases'][payload['app']]
86
+ prereleases.delete(payload['pkg']) if prereleases
87
+ end
71
88
  end
72
89
  end
73
90
  end
@@ -1,6 +1,7 @@
1
1
  require 'mamiya/version'
2
2
  require 'mamiya/agent'
3
3
  require 'sinatra/base'
4
+ require 'mamiya/util/label_matcher'
4
5
  require 'json'
5
6
 
6
7
  module Mamiya
@@ -18,6 +19,20 @@ module Mamiya
18
19
  def storage(app)
19
20
  master.storage(app)
20
21
  end
22
+
23
+ def parse_label_matcher_expr(str)
24
+ Mamiya::Util::LabelMatcher.parse_string_expr(str)
25
+ end
26
+ end
27
+
28
+ before do
29
+ if request.content_type == 'application/json'
30
+ begin
31
+ params.merge! JSON.parse(request.body.read)
32
+ rescue JSON::ParserError
33
+ halt :bad_request
34
+ end
35
+ end
21
36
  end
22
37
 
23
38
  get '/' do
@@ -58,7 +73,19 @@ module Mamiya
58
73
  # TODO: filter with label
59
74
  if storage(params[:application]).meta(params[:package])
60
75
  status 204
61
- master.distribute(params[:application], params[:package])
76
+ master.distribute(params[:application], params[:package], labels: params['labels'])
77
+ else
78
+ status 404
79
+ content_type :json
80
+ {error: 'not found'}.to_json
81
+ end
82
+ end
83
+
84
+ post '/packages/:application/:package/prepare' do
85
+ # TODO: filter with label
86
+ if storage(params[:application]).meta(params[:package])
87
+ status 204
88
+ master.prepare(params[:application], params[:package], labels: params['labels'])
62
89
  else
63
90
  status 404
64
91
  content_type :json
@@ -83,7 +110,12 @@ module Mamiya
83
110
  queued: [],
84
111
  not_distributed: []
85
112
  }
86
- statuses = agent_monitor.statuses
113
+ if params[:labels]
114
+ expr = parse_label_matcher_expr(params[:labels])
115
+ statuses = agent_monitor.statuses(labels: expr)
116
+ else
117
+ statuses = agent_monitor.statuses
118
+ end
87
119
 
88
120
  pkg_array = [params[:application], params[:package]]
89
121
 
@@ -148,7 +180,10 @@ module Mamiya
148
180
  end
149
181
 
150
182
  get '/agents' do
151
- statuses = agent_monitor.statuses
183
+ expr = params[:labels] ? parse_label_matcher_expr(params[:labels]) : nil
184
+
185
+ last_refresh_at = agent_monitor.last_refresh_at
186
+ statuses = agent_monitor.statuses(labels: expr)
152
187
  members = agent_monitor.agents
153
188
  failed_agents = agent_monitor.failed_agents
154
189
 
@@ -170,10 +205,14 @@ module Mamiya
170
205
  agents[name]["status"] = status
171
206
  end
172
207
 
208
+ if params[:labels]
209
+ agents.select! { |k,v| v['status'] }
210
+ end
173
211
 
174
212
  content_type :json
175
213
 
176
214
  {
215
+ last_refresh_at: agent_monitor.last_refresh_at,
177
216
  agents: agents,
178
217
  failed_agents: failed_agents,
179
218
  }.to_json
data/lib/mamiya/master.rb CHANGED
@@ -56,6 +56,10 @@ module Mamiya
56
56
  {name: serf.name, master: true}
57
57
  end
58
58
 
59
+ def labels
60
+ []
61
+ end
62
+
59
63
  private
60
64
 
61
65
  def init_serf
data/lib/mamiya/script.rb CHANGED
@@ -12,16 +12,16 @@ module Mamiya
12
12
  add_hook :build
13
13
  add_hook :after_build
14
14
 
15
- add_hook :before_distribute
16
- add_hook :after_distribute
15
+ #add_hook :before_distribute
16
+ #add_hook :after_distribute
17
17
 
18
18
  add_hook :before_prepare
19
19
  add_hook :prepare
20
20
  add_hook :after_prepare
21
21
 
22
- add_hook :before_finalize
23
- add_hook :finalize
24
- add_hook :after_finalize
22
+ add_hook :before_switch
23
+ add_hook :release
24
+ add_hook :after_switch
25
25
 
26
26
  add_hook :before_rollback
27
27
  add_hook :rollback
@@ -43,16 +43,16 @@ module Mamiya
43
43
  set_default :exclude_from_package, []
44
44
  set_default :dereference_symlinks, true
45
45
 
46
- set_default :package_to, nil
47
-
48
46
  # TODO: use variable in config.yml
49
47
  set_default :deploy_to, nil
50
- set_default :prepare_to, nil
51
48
 
52
49
  set_default :logger, Mamiya::Logger.new(outputs: [])
53
50
 
54
51
  set_default :skip_prepare_build, false
55
52
 
53
+ set_default :script_file, nil
54
+ set_default :script_additionals, []
55
+
56
56
  def run(*args, allow_failure: false)
57
57
  # TODO: Stop when fail
58
58
  actual = -> do
@@ -113,5 +113,25 @@ module Mamiya
113
113
  logger.info "$ cd #{args[0]}"
114
114
  Dir.chdir *args
115
115
  end
116
+
117
+ def build_from
118
+ self[:build_from] && Pathname.new(self[:build_from])
119
+ end
120
+
121
+ def deploy_to
122
+ self[:deploy_to] && Pathname.new(self[:deploy_to])
123
+ end
124
+
125
+ def release_path
126
+ self[:release_path] && Pathname.new(self[:release_path])
127
+ end
128
+
129
+ def shared_path
130
+ deploy_to && deploy_to.join('shared')
131
+ end
132
+
133
+ def current_path
134
+ deploy_to && deploy_to.join('current')
135
+ end
116
136
  end
117
137
  end
@@ -1,5 +1,6 @@
1
1
  require 'mamiya/script'
2
2
  require 'mamiya/logger'
3
+ require 'mamiya/configuration'
3
4
 
4
5
  module Mamiya
5
6
  module Steps