cult 0.1.1.pre

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 (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