cult 0.1.1.pre

Sign up to get free protection for your applications and to get access to all the features.
Files changed (55) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +9 -0
  3. data/Gemfile +4 -0
  4. data/LICENSE.txt +21 -0
  5. data/README.md +240 -0
  6. data/Rakefile +6 -0
  7. data/cult +1 -0
  8. data/cult.gemspec +38 -0
  9. data/doc/welcome.txt +1 -0
  10. data/exe/cult +86 -0
  11. data/lib/cult/artifact.rb +45 -0
  12. data/lib/cult/cli/common.rb +265 -0
  13. data/lib/cult/cli/console_cmd.rb +124 -0
  14. data/lib/cult/cli/cri_extensions.rb +84 -0
  15. data/lib/cult/cli/init_cmd.rb +116 -0
  16. data/lib/cult/cli/load.rb +26 -0
  17. data/lib/cult/cli/node_cmd.rb +205 -0
  18. data/lib/cult/cli/provider_cmd.rb +123 -0
  19. data/lib/cult/cli/role_cmd.rb +149 -0
  20. data/lib/cult/cli/task_cmd.rb +140 -0
  21. data/lib/cult/commander.rb +103 -0
  22. data/lib/cult/config.rb +22 -0
  23. data/lib/cult/definition.rb +112 -0
  24. data/lib/cult/driver.rb +88 -0
  25. data/lib/cult/drivers/common.rb +192 -0
  26. data/lib/cult/drivers/digital_ocean_driver.rb +179 -0
  27. data/lib/cult/drivers/linode_driver.rb +282 -0
  28. data/lib/cult/drivers/load.rb +26 -0
  29. data/lib/cult/drivers/script_driver.rb +27 -0
  30. data/lib/cult/drivers/vultr_driver.rb +217 -0
  31. data/lib/cult/named_array.rb +129 -0
  32. data/lib/cult/node.rb +62 -0
  33. data/lib/cult/project.rb +169 -0
  34. data/lib/cult/provider.rb +134 -0
  35. data/lib/cult/role.rb +213 -0
  36. data/lib/cult/skel.rb +85 -0
  37. data/lib/cult/task.rb +64 -0
  38. data/lib/cult/template.rb +92 -0
  39. data/lib/cult/transferable.rb +61 -0
  40. data/lib/cult/version.rb +3 -0
  41. data/lib/cult.rb +4 -0
  42. data/skel/.cultconsolerc +4 -0
  43. data/skel/.cultrc.erb +29 -0
  44. data/skel/README.md.erb +22 -0
  45. data/skel/keys/.keep +0 -0
  46. data/skel/nodes/.keep +0 -0
  47. data/skel/providers/.keep +0 -0
  48. data/skel/roles/all/role.json +4 -0
  49. data/skel/roles/all/tasks/00000-do-something-cool +27 -0
  50. data/skel/roles/bootstrap/files/cult-motd +45 -0
  51. data/skel/roles/bootstrap/role.json +4 -0
  52. data/skel/roles/bootstrap/tasks/00000-set-hostname +22 -0
  53. data/skel/roles/bootstrap/tasks/00001-add-cult-user +21 -0
  54. data/skel/roles/bootstrap/tasks/00002-install-cult-motd +9 -0
  55. metadata +183 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: cea2fc50c8b92985e08159d77c53f16cb71569ce
4
+ data.tar.gz: cba93e78130a741980b6f3be07038edb2a9035fc
5
+ SHA512:
6
+ metadata.gz: 70ccf020691c38b75dce07f9e408cce0f0849a1d54b06a96038de5b0f4c459be024822f6931b433c115e04fa318b461e05f937faad60f7b85ce492474efa72bc
7
+ data.tar.gz: 98370627f82c3c4aa5c240e66aa2dfce29c17f4d8368e509c205651dfd0fdcd97d4a0aef2f9711fe03f28c5d9e158206ee10771108bc04c5a6b01b8965c02525
data/.gitignore ADDED
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+ .tags
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in cult.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2016 Mike Owens, meter.md
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,240 @@
1
+ # Cult
2
+
3
+ ## Introduction
4
+
5
+ Cult is a tool to manage fleets of servers. It tries to work in an obvious
6
+ way, and doesn't intend on you learning a bunch of new metaphors, languages,
7
+ and terminology.
8
+
9
+ Cult may be what you're looking for if:
10
+
11
+ * You like the transparency of shell-script setup but have outgrown it, and
12
+ it's turning into a tangled mess
13
+ * "Configuration Management Systems" and "Known Configuration State" stuff
14
+ rubs you the wrong way
15
+ * You're not worried about abstracting away Unix, as if you'll be deploying
16
+ on a herd of Amigas next year
17
+ * You're not looking to find a cloud-hosted meta-script to effectively
18
+ `apt-get -y install nginx`
19
+ * You have no ceremony around spinning up and killing servers. Cult can
20
+ manage real metal, but its default mindset is: if you fuck up too bad, you
21
+ can spin up a fresh instance
22
+ * When you think of a forward migration, the first thing that comes to mind
23
+ is `#!...`
24
+ * You don't get why you need more than a working SSH to configure a server,
25
+ and why you'd want an agent running thereafter.
26
+ * It doesn't creep you out that Cult can look at your git branch name to
27
+ decide if you're in production or development mode. There are some
28
+ conventions going on.
29
+
30
+
31
+ Cult's probably not your bourbon and ginger if:
32
+
33
+ * You see value in "converging toward a configuration", as if you're guiding
34
+ your precious children through the path of life, and helping them evolve
35
+ into better people.
36
+ * You have one big-ass old server that's gets conservatively upgraded via
37
+ efforts big enough to have a project name. Cult can help you *out* of
38
+ that, though.
39
+ * You're sold on image-in-a-container-in-a-KVM deployment. That's totally
40
+ reasonable, but Cult doesn't really do anything particularly special
41
+ to help you if you're happy with the process you have building containers.
42
+ * You expect to have the same configuration abstractly work on a totally
43
+ diverse set of platforms (e.g., one "formula" that works on Ubuntu,
44
+ FreeBSD, and SCO OpenServer 5). Cult can manage these absolutely fine, but
45
+ you lose a lot of the benefit of its role-based sharing, and will find
46
+ yourself in the script-equivalent of `#ifdef`-hell.
47
+ * You've already got a working and complicated network, managed by years
48
+ worth of highly-tuned inputs into a Configuration Management System. Cult
49
+ itself does less for you, but requires a lot less of you.
50
+
51
+ But, what you gain via Cult is transparency, repeatability, and obviousness.
52
+
53
+
54
+ ## Installation
55
+
56
+ Cult is written in Ruby and available as a gem, but it is an application, not a
57
+ library. It's not written in, and has no relation to Rails. It depends on
58
+ Ruby 2.3 or greater. If you've got Ruby installed, via any means, the following
59
+ command should handle everything:
60
+
61
+ $ gem install cult
62
+
63
+ Most provider drivers require outside gems, for example, the Linode driver
64
+ requires the Linode API gem, the DigitalOcean driver requires DropletKit,
65
+ etc. Cult will ask, and then install these required gems only when you go to
66
+ use the driver.
67
+
68
+ Cult requires nothing to be installed on each node, other than an operating SSH
69
+ server and Bourne Shell. If you know you've got Bash on the other end, feel
70
+ free to write your tasks in Bash. If you want to write tasks in Ruby, Python,
71
+ Node or Perl, etc, one of your firsts Tasks in the `all` role should be to
72
+ `apt-get -y install {ruby,python,node}`. All subsequent tasks will have that
73
+ interpreter available.
74
+
75
+
76
+ ## General Theory
77
+
78
+ I think a reasonable level of abstraction for cloud deployments is such:
79
+
80
+ 1. *Nodes*: Actual machines, virtual or otherwise, running somewhere.
81
+ 2. *Roles*: The purpose of a node. e.g., `web-frontend`, `db-master`, or
82
+ `redis-server`.
83
+ 3. *Tasks*: Roles are made of tasks, which are basically scripts, called
84
+ things like `install-postgres` or `configure-nginx`. They're written in
85
+ your language of choice. When you being up a Node with a Role, all of the
86
+ Role's tasks are executed to get it up and running.
87
+ 4. *Providers*: Your VPS provider, e.g., Linode, DigitalOcean, etc. These
88
+ allow you to spawn and destroy nodes, and typically charge you a few cents
89
+ per node per hour. I guess there's also *Drivers*, but a Provider is
90
+ really just an instance of a *Driver* with an API key configured.
91
+
92
+ We hate adding anything on top of this because it increases complexity.
93
+
94
+ Sometimes, what you need is not baked into Cult. So Cult is full of escape
95
+ hatches like ERB templating on shell scripts and JSON files to do weird stuff.
96
+
97
+
98
+ ### Drivers
99
+
100
+ Cult provides a handful of Drivers which can talk to common VPS providers,
101
+ initially DigitalOcean, Linode, and Vultr. A driver is typically 200-300 lines
102
+ of Ruby code, and is pretty well isolated from the rest of Cult, so feel free
103
+ to open a PR to add your provider of choice. You can get a current list with:
104
+
105
+ $ cult provider drivers
106
+
107
+ The goal of a driver is to talk to your VPS provider, start a server with a
108
+ size/instance type, zone/region, distribution, and ssh key you provide. It
109
+ then makes sure it's configured to allow a root login with that SSH key.
110
+
111
+ The Driver gets you to a "pre-bootstrap" stage where your server exists and
112
+ you can connect to it. The `bootstrap` role can then kick in. Your `boostrap`
113
+ role will typically create the `cult` user, disable the root account, etc (the
114
+ default bootstrap role indeed does exactly this.)
115
+
116
+ ### Nodes
117
+
118
+ A node is a physical instance running somewhere. A node has a name, like
119
+ 'web1', but also has a full description, that looks something like:
120
+
121
+ web1@ubuntu-16-01.2gb:digitalocean.nyc1
122
+
123
+ A node named `web1` is an idea, but hasn't spawned. A node named with its full
124
+ description should represent a real server somewhere, that is (hopefully)
125
+ running.
126
+
127
+ ### Roles
128
+ A role is a collection of files (usually configuration files), Tasks (usually
129
+ shell scripts), and a configuration (`role.json`). A role can include other
130
+ roles via `includes:`. A role can state it conflicts with another role via
131
+ `conflicts:`.
132
+
133
+ A Role's tasks are named like `00000-a-descriptive-name`, because the only
134
+ ordering Cult does is asciibetical.
135
+
136
+ Every task, file, and even `role.json` is pre-processed with ERB before it gets
137
+ processed by Cult or shipped to a node. This lets you customize behavior based
138
+ on the node, the role, the provider, the project, or anything else you'd like.
139
+
140
+ #### Special Roles
141
+ There are two Roles generated by default that you should think of as
142
+ special-ish:
143
+
144
+ 1. `all`: If a Role does not explicitly list an `includes` value, Cult will
145
+ act as if it specified `includes: ['all']`. In practice, this means tasks
146
+ in the `all` role are common to all nodes that haven't explicitly opted
147
+ out of it. A task can opt-out of including `all` with an explicit
148
+ `includes: []`.
149
+ 2. `bootstrap`: The generator creates this role to be the first one ran on a
150
+ new node. Before it starts, we have a root account with an SSH key, when
151
+ `bootstrap` finishes, we have a `cult` user on the node with `sudo` access
152
+ who uses the same SSH key, and the root account is disabled. `bootstrap`
153
+ opts-out of `all`. The generator also installs a MOTD banner so you'll
154
+ know Cult was enabled on the server, has a demo script that sets the
155
+ hostname.
156
+
157
+ ## UI
158
+
159
+ Even though you aren't required to use it, Cult includes the terminal-mode GUI
160
+ packages (`$ cult ui`). Because it's 2016, and we're not worried about a few
161
+ MB of extra packages hanging around anymore. We're steadily trying to improve
162
+ this cool-ass tmux-based UI, but it's still new right now.
163
+
164
+ ## Usage
165
+
166
+ We're going to put together a complete usage guide, tutorial, and example repo
167
+ once Cult has settled down a bit. It's still pre-1.0 software, and we still
168
+ like breaking things to make it work better for us.
169
+
170
+ ### 👻 Spooky Secrets
171
+
172
+ * `cult console` is built to be really nice to use. If you're not afraid of
173
+ Ruby, the method names are chosen to read almost like pseudo-code. It
174
+ supports IRB, Pry, and Ripl with command-line flags.
175
+ * Anything that Cult keeps "an Array of", that you'd maybe want to reference
176
+ by name is stored in a NamedArray, which shares some features with a Hash.
177
+ This is for convenience. For example, in `cult console`, you can find the
178
+ first driver with `drivers[0]`, find it by name with `drivers['linode']`,
179
+ or (*get ready for fancy stuff:*) look it up by a Regexp with
180
+ `drivers[/ocean/i]`.
181
+ * The NamedArray stuff even works on the command-line with String arguments,
182
+ and will convert strings that start with '/' to Regexps to search by name.
183
+ * Although Cult will only *generate* JSON, not having comments and other
184
+ stuff is a pain. If you don't care about JSON-readability of your
185
+ `node.json`s or `role.json`s, you can just rename it to `node.yaml` or
186
+ `node.yml` and it'll get parsed with a YAML parser. Cult keeps transient
187
+ state in separate files for this reason: so it doesn't overwrite your
188
+ long-lived YAML replacements with JSON equivalents.
189
+
190
+
191
+ ## Development
192
+
193
+ ### History
194
+
195
+ Cult was developed to basically avoid Puppet, Chef, and Ansible. I know there
196
+ are some really large, successful deployments of all of these. I just think
197
+ they just do too much for me to feel safe with when shit goes crazy.
198
+
199
+ Some of the things Cult doesn't do are things we haven't had time to implement,
200
+ like fully building out 'cult ui'. A lot of the things Cult doesn't do are
201
+ the reason Cult exists. Some of the limitations are designed to force a
202
+ certain mindset I think is healthy for building resilient systems. In
203
+ particular:
204
+
205
+ 1. Exercising the server bring-up process from bottom-up as the normal mode
206
+ of operation. This is why Cult makes you bring up a node from provision/
207
+ bootstrap each time, instead of using snapshots, a feature virtually every
208
+ VPS provider supports.
209
+ 2. Making it less reasonable to have nodes hanging around that have ended up
210
+ in their current state via baby-step migrations for too long. Cult does
211
+ this fine, but makes it easier to just test a new node with a clean build.
212
+ This way you're not worried about idempotent transforms, or a lot of
213
+ conditional logic. If a node is not where you want, bring up another that
214
+ is, and kill the old one.
215
+
216
+ We can be convinced otherwise, but these are sort of core tenets of what Cult
217
+ is about.
218
+
219
+ ### Contributing
220
+
221
+ We greatly appreciate bug reports, pull-requests, questions, and general
222
+ commentary in the GitHub Issues. These are all *contributions*. However,
223
+ before opening an issue demanding us to work on your feature that Cult *has to
224
+ have to be taken seriously*, note:
225
+
226
+ * Cult was built by meter.md nerds to make managing our infrastructure
227
+ better. Its utility is measured by that metric. We've released the
228
+ project because we believe in the value of open *collaboration*, not so we
229
+ can be unpaid software contractors working on the demands of strangers on
230
+ the internet.
231
+ * If your contribution consists of instructing the team how to run the
232
+ project: Thanks, but we've got that handled.
233
+
234
+ If you're contributing code, asking a question, or reporting a bug, don't let
235
+ the above items you away.
236
+
237
+ ## License
238
+
239
+ Cult is available as open source software under the terms of the
240
+ [MIT License](http://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ task :default => :spec
3
+
4
+ task :tags do
5
+ sh "ctags -R ."
6
+ end
data/cult ADDED
@@ -0,0 +1 @@
1
+ ./exe/cult
data/cult.gemspec ADDED
@@ -0,0 +1,38 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'cult/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "cult"
8
+ spec.version = Cult::VERSION
9
+ spec.authors = ["Mike Owens"]
10
+ spec.email = ["mike@meter.md"]
11
+
12
+ spec.summary = "Fleet Management like its 1990"
13
+ spec.homepage = "https://github.com/metermd/cult"
14
+ spec.license = "MIT"
15
+
16
+ # Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
17
+ # to allow pushing to a single host or delete this section to allow pushing to any host.
18
+ if spec.respond_to?(:metadata)
19
+ spec.metadata['allowed_push_host'] = "https://rubygems.org"
20
+ else
21
+ raise "RubyGems 2.0 or newer is required to protect against public gem pushes."
22
+ end
23
+
24
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
25
+ spec.bindir = "exe"
26
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
27
+ spec.require_paths = ["lib"]
28
+
29
+ spec.required_ruby_version = '~> 2.3'
30
+
31
+ spec.add_dependency "cri", "~> 2.7.0"
32
+ spec.add_dependency "net-ssh", "~> 3.2.0"
33
+ spec.add_dependency "net-scp", "~> 1.2.1"
34
+ spec.add_dependency "rainbow", "~> 2.1.0"
35
+
36
+ spec.add_development_dependency "bundler", "~> 1.12"
37
+ spec.add_development_dependency "rake", "~> 10.0"
38
+ end
data/doc/welcome.txt ADDED
@@ -0,0 +1 @@
1
+ Welcome to cult.
data/exe/cult ADDED
@@ -0,0 +1,86 @@
1
+ #!/usr/bin/env ruby
2
+ $LOAD_PATH << File.expand_path(File.join(__dir__, '../lib'))
3
+
4
+ require 'cri'
5
+
6
+ require 'cult/version'
7
+ require 'cult/config'
8
+ require 'cult/project'
9
+ require 'cult/node'
10
+ require 'cult/cli/load'
11
+ require 'cult/drivers/load'
12
+
13
+ cult = Cri::Command.define do
14
+ optional_project
15
+ name 'cult'
16
+ usage 'cult [options] [command [options...]]'
17
+ summary 'Control a Fleet of Obedient Zomboid Machines'
18
+ description <<~EOD.format_description
19
+ Cult is a tool for creating and then managing a fleet of servers you
20
+ control. It operates on a few simple concepts:
21
+
22
+ * Nodes: actual servers out there somewhere. The purpose of using Cult
23
+ is to end up with nodes doing useful work for you.
24
+
25
+ * Roles: Every node has one or more roles, things it plans on being.
26
+ Roles are composed of...
27
+
28
+ * Tasks: Basically shell scripts that run in a specific order.
29
+
30
+ Cult has a few more convenience concepts, like Keys and Providers, but you
31
+ don't end up thinking about them too often.
32
+
33
+ To create a new Cult project, use 'cult init DIRECTORY', but see the 'init'
34
+ help first, with 'cult init --help'
35
+ EOD
36
+
37
+ required :C, :directory, 'Specify a project path' do |value|
38
+ Cult::CLI.set_project(value)
39
+ end
40
+
41
+ flag :h, :help, 'Show this help' do |value, cmd|
42
+ puts cmd.help
43
+ end
44
+
45
+ flag :y, :yes, 'Answer "yes" to any questions' do
46
+ Cult::CLI.yes = true
47
+ end
48
+
49
+ flag :q, :quiet, "Don't show any unnecessary information" do
50
+ Cult::CLI.quiet = true
51
+ end
52
+
53
+ flag :v, :version, 'Show version information' do
54
+ puts "Cult #{Cult::VERSION}"
55
+ puts 'Copyright (C) 2016 Mike A. Owens, meter.md, and Contributors'
56
+ end
57
+
58
+ run(arguments: 0) do |opts, args, cmd|
59
+ puts cmd.help
60
+ end
61
+ end
62
+
63
+ Cult::CLI.commands.each do |m|
64
+ cult.add_command(m)
65
+ end
66
+
67
+ if (env = ENV['CULT_PROJECT'])
68
+ Cult::CLI.set_project(env)
69
+ else
70
+ Cult.project ||= Cult::Project.from_cwd
71
+ end
72
+
73
+ if Cult.project
74
+ Cult.project.execute_cultrc
75
+ end
76
+
77
+ ERROR_CULT = Rainbow("#{File.basename($0)}:").red
78
+
79
+ begin
80
+ cult.run(ARGV)
81
+ rescue Cult::CLI::CLIError, RegexpError => e
82
+ $stderr.puts "#{ERROR_CULT} #{e.message}"
83
+ exit 1
84
+ rescue Interrupt
85
+ exit 1
86
+ end
@@ -0,0 +1,45 @@
1
+ require 'cult/transferable'
2
+ require 'cult/named_array'
3
+
4
+ module Cult
5
+ # I'd love to just call this "File", but the ambiguity with ::File would
6
+ # make it a pain.
7
+ class Artifact
8
+ include Transferable
9
+
10
+ def self.collection_name
11
+ "files"
12
+ end
13
+
14
+
15
+ def relative_path
16
+ name
17
+ end
18
+
19
+
20
+ attr_reader :path
21
+ attr_reader :role
22
+
23
+ def initialize(role, path)
24
+ @role = role
25
+ @path = path
26
+ end
27
+
28
+
29
+ def inspect
30
+ "\#<#{self.class.name} role:#{role&.name.inspect} name:#{name.inspect}>"
31
+ end
32
+ alias_method :to_s, :inspect
33
+
34
+
35
+ def self.all_for_role(project, role)
36
+ Dir.glob(File.join(role.path, "files", "**/*")).map do |filename|
37
+ next if File.directory?(filename)
38
+ new(role, filename).tap do |new_file|
39
+ yield new_file if block_given?
40
+ end
41
+ end.compact.to_named_array
42
+ end
43
+
44
+ end
45
+ end