mamiya 0.0.1.alpha21 → 0.0.1.alpha22

Sign up to get free protection for your applications and to get access to all the features.
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