cult 0.1.3.pre → 0.1.4.pre

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +53 -47
  3. data/cult.gemspec +6 -5
  4. data/exe/cult +19 -4
  5. data/lib/cult/cli/common.rb +1 -2
  6. data/lib/cult/cli/console_cmd.rb +2 -2
  7. data/lib/cult/cli/cri_extensions.rb +66 -13
  8. data/lib/cult/cli/init_cmd.rb +6 -7
  9. data/lib/cult/cli/node_cmd.rb +233 -67
  10. data/lib/cult/cli/provider_cmd.rb +16 -13
  11. data/lib/cult/cli/role_cmd.rb +25 -26
  12. data/lib/cult/cli/task_cmd.rb +13 -13
  13. data/lib/cult/commander.rb +53 -17
  14. data/lib/cult/commander_sync.rb +29 -0
  15. data/lib/cult/definition.rb +21 -49
  16. data/lib/cult/driver.rb +1 -1
  17. data/lib/cult/drivers/common.rb +12 -11
  18. data/lib/cult/drivers/digital_ocean_driver.rb +2 -2
  19. data/lib/cult/drivers/virtual_box_driver.rb +156 -0
  20. data/lib/cult/drivers/vultr_driver.rb +3 -3
  21. data/lib/cult/named_array.rb +103 -15
  22. data/lib/cult/node.rb +139 -12
  23. data/lib/cult/paramap.rb +209 -0
  24. data/lib/cult/project.rb +2 -17
  25. data/lib/cult/provider.rb +3 -1
  26. data/lib/cult/role.rb +12 -8
  27. data/lib/cult/task.rb +73 -45
  28. data/lib/cult/template.rb +3 -4
  29. data/lib/cult/transaction.rb +11 -5
  30. data/lib/cult/user_refinements.rb +1 -1
  31. data/lib/cult/version.rb +1 -1
  32. data/lib/cult.rb +32 -3
  33. data/skel/roles/{all → base}/role.json +0 -0
  34. data/skel/roles/{all/tasks/00000-do-something-cool → base/tasks/000-do-something-cool} +0 -0
  35. data/skel/roles/{all/tasks/sync → base/tasks/sync-host-map} +5 -5
  36. data/skel/roles/base/tasks/sync-leader-of +11 -0
  37. data/skel/roles/bootstrap/files/cult-motd +15 -3
  38. data/skel/roles/bootstrap/tasks/{00000-set-hostname → 000-set-hostname} +1 -1
  39. data/skel/roles/bootstrap/tasks/{00001-add-cult-user → 001-add-cult-user} +0 -6
  40. data/skel/roles/bootstrap/tasks/002-disable-root-user +7 -0
  41. data/skel/roles/bootstrap/tasks/{00002-install-cult-motd → 002-install-cult-motd} +1 -1
  42. metadata +29 -11
  43. data/lib/cult/cli/fleet_cmd.rb +0 -37
data/lib/cult/role.rb CHANGED
@@ -72,12 +72,12 @@ module Cult
72
72
 
73
73
 
74
74
  def build_tasks
75
- tasks.select(&:build_task?)
75
+ tasks.select { |t| t.is_a?(Cult::BuildTask) }
76
76
  end
77
77
 
78
78
 
79
79
  def event_tasks
80
- tasks.select(&:event_task?)
80
+ tasks.select { |t| t.is_a?(Cult::EventTask) }
81
81
  end
82
82
 
83
83
 
@@ -93,7 +93,8 @@ module Cult
93
93
 
94
94
 
95
95
  def definition_path
96
- File.join(path, "role")
96
+ [ File.join(path, "extra.json"),
97
+ File.join(path, "role.json") ]
97
98
  end
98
99
 
99
100
 
@@ -108,7 +109,7 @@ module Cult
108
109
 
109
110
 
110
111
  def includes
111
- definition.direct('includes') || ['all']
112
+ definition.direct('includes') || ['base']
112
113
  end
113
114
 
114
115
 
@@ -154,11 +155,10 @@ module Cult
154
155
 
155
156
 
156
157
  def self.all(project)
158
+ fail if block_given?
157
159
  all_files(project).map do |filename|
158
- new(project, filename).tap do |new_role|
159
- yield new_role if block_given?
160
- end
161
- end.to_named_array
160
+ new(project, filename)
161
+ end.select(&:exist?).to_named_array
162
162
  end
163
163
 
164
164
 
@@ -185,6 +185,10 @@ module Cult
185
185
  build_order.map(&:name)
186
186
  end
187
187
 
188
+ def query_for_role
189
+ build_order
190
+ end
191
+
188
192
  def names_for_task
189
193
  tasks.map(&:name)
190
194
  end
data/lib/cult/task.rb CHANGED
@@ -3,36 +3,74 @@ module Cult
3
3
  include Transferable
4
4
  include SingletonInstances
5
5
 
6
- attr_reader :path
7
6
  attr_reader :role
8
- attr_reader :serial
7
+ attr_reader :path
9
8
  attr_reader :name
10
- attr_reader :type
11
-
12
- LEADING_ZEROS = 3
13
- BASENAME_RE = /\A(\d{#{LEADING_ZEROS},})-([\w-]+)(\..+)?\z/i
14
- EVENTS = [:sync]
15
-
16
9
 
17
10
  def initialize(role, path)
18
11
  @role = role
19
12
  @path = path
20
- @basename = File.basename(path)
13
+ @name = File.basename(path)
14
+ end
15
+
16
+
17
+ def self.collection_name
18
+ "tasks"
19
+ end
20
+
21
+
22
+ def relative_path
23
+ File.basename(path)
24
+ end
25
+
26
+
27
+ # Task files are executable by anyone: this makes re-exec'ing
28
+ # tasks as another user trivial.
29
+ def file_mode
30
+ super | 0111
31
+ end
32
+
21
33
 
22
- unless self.class.valid_task_name?(@basename)
23
- fail ArgumentError, "invalid task name: #{path}"
34
+ def self.spawn(role, path)
35
+ [BuildTask, EventTask].each do |task_cls|
36
+ if task_cls.valid_name?(File.basename(path))
37
+ return task_cls.new(role, path)
38
+ end
24
39
  end
40
+ nil
41
+ end
42
+
43
+
44
+ def self.all_for_role(project, role)
45
+ fail ArgumentError if block_given?
46
+
47
+ Dir.glob(File.join(role.path, "tasks", "*")).sort.map do |path|
48
+ spawn(role, path)
49
+ end.compact.to_named_array
50
+ end
51
+ end
52
+
53
+
54
+ class BuildTask < Task
55
+ LEADING_ZEROS = 3
56
+ BASENAME_RE = /\A(\d{#{LEADING_ZEROS},})-([\w-]+)(\..+)?\z/i
57
+
58
+
59
+ def self.valid_name?(basename)
60
+ !! basename.match(BASENAME_RE)
61
+ end
62
+
63
+
64
+ attr_reader :serial
65
+
66
+ def initialize(role, path)
67
+ super
25
68
 
26
- if (m = @basename.match(BASENAME_RE))
27
- @type = :build
69
+ if (m = BASENAME_RE.match(name))
28
70
  @serial = m[1].to_i
29
71
  @name = m[2]
30
- elsif EVENTS.map(&:to_s).include?(@basename)
31
- @type = :event
32
- @serial = nil
33
- @name = @basename
34
72
  else
35
- fail "WTF"
73
+ fail ArgumentError
36
74
  end
37
75
  end
38
76
 
@@ -41,46 +79,36 @@ module Cult
41
79
  basename = sprintf("%0#{LEADING_ZEROS}d-%s", serial, name)
42
80
  new(role, File.join(role.path, collection_name, basename))
43
81
  end
82
+ end
44
83
 
45
84
 
46
- def relative_path
47
- File.basename(path)
48
- end
49
-
50
-
51
- def build_task?
52
- type == :build
53
- end
85
+ class EventTask < Task
86
+ EVENT_TYPES = [:sync]
87
+ EVENT_RE = /^(#{EVENT_TYPES.join('|')})(?:\-P(\d+))?\-?/
54
88
 
89
+ attr_reader :event
90
+ attr_reader :pass
55
91
 
56
- def event_task?
57
- type != :build
92
+ def self.valid_name?(basename)
93
+ !! basename.match(EVENT_RE)
58
94
  end
59
95
 
60
96
 
61
- def inspect
62
- "\#<#{self.class.name} type: #{type} role:#{role&.name.inspect} " +
63
- "serial:#{serial} name:#{name.inspect}>"
97
+ def initialize(role, path)
98
+ super
99
+ @event = event_name(name)
100
+ @pass = pass_name(name)
64
101
  end
65
- alias_method :to_s, :inspect
66
102
 
67
103
 
68
- def file_mode
69
- super | 0100
104
+ private
105
+ def event_name(basename)
106
+ basename.match(EVENT_RE)[1].to_sym
70
107
  end
71
108
 
72
- def self.valid_task_name?(basename)
73
- EVENTS.map(&:to_s).include?(basename) || basename.match(BASENAME_RE)
109
+ def pass_name(basename)
110
+ basename.match(EVENT_RE)[2].to_i
74
111
  end
75
112
 
76
-
77
- def self.all_for_role(project, role)
78
- Dir.glob(File.join(role.path, "tasks", "*")).map do |filename|
79
- next unless valid_task_name?(File.basename(filename))
80
- new(role, filename).tap do |new_task|
81
- yield new_task if block_given?
82
- end
83
- end.compact.to_named_array
84
- end
85
113
  end
86
114
  end
data/lib/cult/template.rb CHANGED
@@ -1,5 +1,4 @@
1
- require 'erb'
2
- require 'json'
1
+ require 'erubis'
3
2
  require 'cult/user_refinements'
4
3
 
5
4
  module Cult
@@ -12,9 +11,9 @@ module Cult
12
11
  super(project, **kw)
13
12
  end
14
13
 
15
- def _process(template, filename: nil)
14
+ def _process(input, filename: nil)
16
15
  Dir.chdir(@pwd || Dir.pwd) do
17
- erb = ::ERB.new(template)
16
+ erb = Erubis::Eruby.new(input)
18
17
  erb.filename = filename
19
18
  erb.result(binding)
20
19
  end
@@ -9,11 +9,17 @@ module Cult
9
9
 
10
10
  def unwind
11
11
  begin
12
- while (step = steps.pop)
12
+ # We stop rolling back when we read entries created in our parent
13
+ # process
14
+ while !steps.empty?
15
+ pid, step = steps.last
16
+ break if pid != Process.pid
17
+ steps.pop
13
18
  step.call
14
19
  end
15
20
  rescue Exception => e
16
- puts "Error raised while rolling back: #{e.inspect}\n#{e.backtrace}"
21
+ $stderr.puts "Execption raised while rolling back: #{e.inspect}\n" +
22
+ e.backtrace
17
23
  retry
18
24
  end
19
25
  end
@@ -21,15 +27,15 @@ module Cult
21
27
  def protect(&block)
22
28
  begin
23
29
  yield
24
- rescue Exception
25
- $stderr.puts "Rolling back actions"
30
+ rescue Exception => e
31
+ $stderr.puts "Rolling back actions due to: #{e.inspect}"
26
32
  unwind
27
33
  raise
28
34
  end
29
35
  end
30
36
 
31
37
  def rollback(&block)
32
- steps.push(block)
38
+ steps.push([Process.pid, block])
33
39
  end
34
40
  end
35
41
 
@@ -27,7 +27,7 @@ module Cult
27
27
 
28
28
  refine String do
29
29
  def dquote
30
- Util.squote(self)
30
+ Util.dquote(self)
31
31
  end
32
32
  alias_method :dq, :dquote
33
33
  alias_method :q, :dquote
data/lib/cult/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Cult
2
- VERSION = '0.1.3.pre'
2
+ VERSION = '0.1.4.pre'
3
3
  end
data/lib/cult.rb CHANGED
@@ -1,12 +1,14 @@
1
1
  require 'cult/artifact'
2
2
  require 'cult/bundle'
3
+ require 'cult/commander_sync'
3
4
  require 'cult/commander'
4
5
  require 'cult/definition'
5
6
  require 'cult/driver'
6
7
  require 'cult/named_array'
7
8
  require 'cult/node'
8
- require 'cult/project'
9
+ require 'cult/paramap'
9
10
  require 'cult/project_context'
11
+ require 'cult/project'
10
12
  require 'cult/provider'
11
13
  require 'cult/role'
12
14
  require 'cult/singleton_instances'
@@ -17,14 +19,41 @@ require 'cult/transaction'
17
19
  require 'cult/transferable'
18
20
  require 'cult/version'
19
21
 
20
-
21
22
  module Cult
22
23
  class << self
23
24
  attr_accessor :project
24
25
 
25
26
  attr_writer :singletons
26
27
  def singletons?
27
- @singletons
28
+ defined?(@singletons) ? @singletons : env_flag('CULT_SINGLETONS', true)
29
+ end
30
+
31
+
32
+ def concurrency=(v)
33
+ unless v == :max || (v.is_a?(Integer) && v >= 0)
34
+ fail CLI::CLIError, "concurrency must be a positive integer or :max"
35
+ end
36
+ v = 1 if v == 0
37
+ @concurrency = v
38
+ end
39
+
40
+
41
+ def concurrency
42
+ defined?(@concurrency) ? @concurrency : :max
43
+ end
44
+
45
+
46
+ def env_flag(s, default = false)
47
+ case (v = ENV[s])
48
+ when /^0|false|no|n$/i
49
+ false
50
+ when /^1|true|yes|y$/i
51
+ true
52
+ when nil
53
+ default
54
+ else
55
+ fail CLI::CLIError, "Invalid value for boolean #{s}: #{v}"
56
+ end
28
57
  end
29
58
  end
30
59
  end
File without changes
@@ -2,16 +2,16 @@
2
2
  set -e
3
3
 
4
4
  # This file generates a map of all hosts on each node, in an /etc/hosts like
5
- # format. Because it's included in all, it'll be executed before your custom
6
- # roles' sync task. That means you can parse the cultmap instead of using
7
- # custom Ruby templating, if that's not your thing.
5
+ # format. Because it's included in `base`, it'll be executed before your
6
+ # custom roles' sync task. That means you can parse the cultmap instead of
7
+ # using custom Ruby templating, if that's not your thing.
8
8
  #
9
9
  # Keep in mind that this file is evaluated on the local machine, and its result
10
10
  # is sent to the remote host.
11
11
  #
12
12
  # The output format is:
13
13
  #
14
- # 192.168.1.1 node-name # all role1 role2 role3
14
+ # 192.168.1.1 node-name # base role1 role2 role3
15
15
  #
16
16
 
17
17
  CULTMAP="$HOME/cult/hosts"
@@ -20,5 +20,5 @@ sudo rm -f "$CULTMAP"
20
20
 
21
21
  <% nodes.each do |n| %>
22
22
  <% roles = n.build_order.map(&:name).join " " %>
23
- echo "<%= n.ipv4_private %> <%= n.name %> # <%= roles %>" | sudo tee -a "$CULTMAP"
23
+ echo "<%= n.addr_from(node) %> <%= n.name %> # <%= roles %>" | sudo tee -a "$CULTMAP"
24
24
  <% end %>
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env bash
2
+ set -e
3
+ LEADER_FILE=~cult/cult/leader-of
4
+
5
+ <% leader_of = roles.select {|r| node.zone_leader?(r) }.map(&:name) %>
6
+
7
+ <% if leader_of.empty? %>
8
+ rm -f "$LEADER_FILE"
9
+ <% else %>
10
+ echo <%= leader_of.join(" ").sq %> | tee "$LEADER_FILE"
11
+ <% end %>
@@ -2,8 +2,16 @@
2
2
  set -e
3
3
 
4
4
  NODE=<%= node.name.sq %>
5
- ROLES=<%= node.roles.map(&:name).sq %>
6
- SPAWN=<%= Time.now.to_s.sq %>
5
+ PROVIDER=<%= node.provider.name.sq %>
6
+ ZONE=<%= node.zone.sq %>
7
+ ROLES=<%= node.build_order.map(&:name).join(' ').sq %>
8
+ CREATED_AT=<%= Time.now.to_s.sq %>
9
+ SIZE=<%= node.size.sq %>
10
+ IMAGE=<%= node.image.sq %>
11
+ IPV4_PUBLIC=<%= node.ipv4_public.sq %>
12
+ IPV4_PRIVATE=<%= node.ipv4_private.sq %>
13
+
14
+ LEADER_FILE=~cult/cult/leader-of
7
15
 
8
16
  colorize() {
9
17
  i=124
@@ -38,8 +46,12 @@ cat <<EOD | colorize
38
46
  ::: ::: ::::: :: :: :::: ::
39
47
  :: :: : : : : : :: : : :
40
48
 
49
+
41
50
  EOD
42
51
 
43
- inf "node: $NODE, since $SPAWN"
52
+ inf "node: $NODE@$PROVIDER/$ZONE:$SIZE/$IMAGE"
53
+ inf "addr: $IPV4_PUBLIC, $IPV4_PRIVATE"
54
+ inf "created: $CREATED_AT"
44
55
  inf "roles: $ROLES"
56
+ [ -f "$LEADER_FILE" ] && inf "leader: $(cat "$LEADER_FILE")"
45
57
  echo
@@ -13,7 +13,7 @@ HOSTS=$(cat /etc/hosts | sed -e 's/#.*//g' | sed -e 's/\t/ /g' | tr -s ' ' | \
13
13
  grep -v '^\s*$' | cut -d' ' -f2-)
14
14
 
15
15
  # See if we're in hosts
16
- if ! echo "$HOSTS" | grep -q "$NODE_NAME" ; then
16
+ if ! echo "$HOSTS" | grep -E -q "\b$NODE_NAME\b" ; then
17
17
  echo "127.0.1.1 $NODE_NAME" >> /etc/hosts
18
18
  echo "::1 $NODE_NAME" >> /etc/hosts
19
19
  fi
@@ -13,9 +13,3 @@ chmod -R 0700 /home/cult/.ssh
13
13
  chmod -R 0600 /home/cult/.ssh/*
14
14
 
15
15
  echo 'cult ALL=(ALL) NOPASSWD:ALL' > /etc/sudoers.d/cult-nopasswd
16
-
17
- # disable root account
18
- passwd -l root
19
- sed -i.bak -e 's/^PermitRootLogin.*/PermitRootLogin no/' /etc/ssh/sshd_config
20
-
21
- systemctl reload sshd
@@ -0,0 +1,7 @@
1
+ #!/bin/sh
2
+ set -e
3
+
4
+ passwd -l root
5
+ sed -i.bak -e 's/^PermitRootLogin.*/PermitRootLogin no/' /etc/ssh/sshd_config
6
+
7
+ systemctl reload sshd
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env bash
1
+ #!/bin/bash
2
2
  set -e
3
3
 
4
4
  if [ -d "/etc/update-motd.d" ]; then
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cult
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.3.pre
4
+ version: 0.1.4.pre
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mike Owens
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2016-09-11 00:00:00.000000000 Z
11
+ date: 2016-09-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: cri
@@ -66,6 +66,20 @@ dependencies:
66
66
  - - "~>"
67
67
  - !ruby/object:Gem::Version
68
68
  version: '2.1'
69
+ - !ruby/object:Gem::Dependency
70
+ name: erubis
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: 2.7.0
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: 2.7.0
69
83
  - !ruby/object:Gem::Dependency
70
84
  name: bundler
71
85
  requirement: !ruby/object:Gem::Requirement
@@ -117,7 +131,6 @@ files:
117
131
  - lib/cult/cli/common.rb
118
132
  - lib/cult/cli/console_cmd.rb
119
133
  - lib/cult/cli/cri_extensions.rb
120
- - lib/cult/cli/fleet_cmd.rb
121
134
  - lib/cult/cli/init_cmd.rb
122
135
  - lib/cult/cli/load.rb
123
136
  - lib/cult/cli/node_cmd.rb
@@ -125,15 +138,18 @@ files:
125
138
  - lib/cult/cli/role_cmd.rb
126
139
  - lib/cult/cli/task_cmd.rb
127
140
  - lib/cult/commander.rb
141
+ - lib/cult/commander_sync.rb
128
142
  - lib/cult/definition.rb
129
143
  - lib/cult/driver.rb
130
144
  - lib/cult/drivers/common.rb
131
145
  - lib/cult/drivers/digital_ocean_driver.rb
132
146
  - lib/cult/drivers/linode_driver.rb
133
147
  - lib/cult/drivers/load.rb
148
+ - lib/cult/drivers/virtual_box_driver.rb
134
149
  - lib/cult/drivers/vultr_driver.rb
135
150
  - lib/cult/named_array.rb
136
151
  - lib/cult/node.rb
152
+ - lib/cult/paramap.rb
137
153
  - lib/cult/project.rb
138
154
  - lib/cult/project_context.rb
139
155
  - lib/cult/provider.rb
@@ -151,14 +167,16 @@ files:
151
167
  - skel/README.md.erb
152
168
  - skel/nodes/.keep
153
169
  - skel/providers/.keep
154
- - skel/roles/all/role.json
155
- - skel/roles/all/tasks/00000-do-something-cool
156
- - skel/roles/all/tasks/sync
170
+ - skel/roles/base/role.json
171
+ - skel/roles/base/tasks/000-do-something-cool
172
+ - skel/roles/base/tasks/sync-host-map
173
+ - skel/roles/base/tasks/sync-leader-of
157
174
  - skel/roles/bootstrap/files/cult-motd
158
175
  - skel/roles/bootstrap/role.json
159
- - skel/roles/bootstrap/tasks/00000-set-hostname
160
- - skel/roles/bootstrap/tasks/00001-add-cult-user
161
- - skel/roles/bootstrap/tasks/00002-install-cult-motd
176
+ - skel/roles/bootstrap/tasks/000-set-hostname
177
+ - skel/roles/bootstrap/tasks/001-add-cult-user
178
+ - skel/roles/bootstrap/tasks/002-disable-root-user
179
+ - skel/roles/bootstrap/tasks/002-install-cult-motd
162
180
  homepage: https://github.com/metermd/cult
163
181
  licenses:
164
182
  - MIT
@@ -170,9 +188,9 @@ require_paths:
170
188
  - lib
171
189
  required_ruby_version: !ruby/object:Gem::Requirement
172
190
  requirements:
173
- - - "~>"
191
+ - - ">="
174
192
  - !ruby/object:Gem::Version
175
- version: '2.3'
193
+ version: '2.2'
176
194
  required_rubygems_version: !ruby/object:Gem::Requirement
177
195
  requirements:
178
196
  - - ">"
@@ -1,37 +0,0 @@
1
- module Cult
2
- module CLI
3
- module_function
4
- def fleet_cmd
5
- fleet = Cri::Command.define do
6
- name 'fleet'
7
- summary 'Fleet commands'
8
-
9
- run(arguments: 0) do |opts, args, cmd|
10
- puts cmd.help
11
- end
12
- end
13
-
14
- fleet_sync = Cri::Command.define do
15
- name 'sync'
16
- summary 'Synchronize host information across fleet'
17
- description <<~EOD.format_description
18
- Processes generates and executes tasks/sync on every node with a
19
- current network setup.
20
- EOD
21
-
22
- run(arguments: 0..-1) do |opts, args, cmd|
23
- nodes = args.empty? ? Cult.project.nodes
24
- : CLI.fetch_items(args, from: Node)
25
- nodes.each do |node|
26
- c = Commander.new(project: Cult.project, node: node)
27
- c.sync!
28
- puts "SYNCING #{node}"
29
- end
30
- end
31
- end
32
- fleet.add_command(fleet_sync)
33
-
34
- fleet
35
- end
36
- end
37
- end