doable 0.0.2 → 0.0.3
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.
- checksums.yaml +4 -4
- data/LICENSE +1 -1
- data/README.md +88 -0
- data/lib/doable/exceptions/{framework_exceptions.rb → framework.rb} +1 -1
- data/lib/doable/exceptions/os.rb +23 -0
- data/lib/doable/helpers/{framework_helpers.rb → framework.rb} +1 -1
- data/lib/doable/helpers/{logging_helpers.rb → logging.rb} +4 -5
- data/lib/doable/helpers/os.rb +129 -0
- data/lib/doable/helpers/{password_helpers.rb → password.rb} +1 -1
- data/lib/doable/job.rb +7 -7
- data/lib/doable.rb +6 -4
- metadata +8 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8049e12988f84644e8ad77af4aa81c69586cd584
|
4
|
+
data.tar.gz: cdde8ea4ff303b1cdd1407ce2a45f4155fad67f4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8e5e57273ae3eb3a16aafa7992a2513bc7eefada445a1feac54d53e55a4633001eeed83b7a7967a3dbced96d7868a7fb041db469c5ed968763c4023a2a2f8af4
|
7
|
+
data.tar.gz: e13885ee5299ae731b16a9d1c83ec4868994a66cd2ed57e5bd0a8372b7758a3645e12c95e5ad4d0eab946e84d42c71af7da200b6d41caef7ccf20bebc9f56074
|
data/LICENSE
CHANGED
data/README.md
ADDED
@@ -0,0 +1,88 @@
|
|
1
|
+
Ruby Doable Framework
|
2
|
+
=====================
|
3
|
+
|
4
|
+
About
|
5
|
+
-----
|
6
|
+
This is a gem largely targeted and making the lives of DevOps / SysAdmin types easier. Whether it is installing complicated sets of software, writing maintenance scripts, or automating administrative tasks, `Doable` aims to simplify these common tasks, as well as make them more reliable and robust.
|
7
|
+
|
8
|
+
The core set of values for this framework are:
|
9
|
+
|
10
|
+
* Extensibility
|
11
|
+
* Modularity
|
12
|
+
* Simplicity
|
13
|
+
* Concurrency
|
14
|
+
* Flexibility
|
15
|
+
* Reliability
|
16
|
+
* Remaining lightweight
|
17
|
+
|
18
|
+
These goals are largely achieved through a small set of core features, with nearly all functionality broken out into plugins that can be enabled as required.
|
19
|
+
|
20
|
+
Building
|
21
|
+
-----
|
22
|
+
|
23
|
+
The eventual goal is for this gem to simply be available via a normal rubygems search, but until then, you must build and install the gem yourself. This can be done like so:
|
24
|
+
|
25
|
+
#!bash
|
26
|
+
# need Mercurial to clone the repo... or download it from https://bitbucket.org/jgnagy/doable/get/tip.zip
|
27
|
+
hg clone https://bitbucket.org/jgnagy/doable
|
28
|
+
cd doable
|
29
|
+
# highly recommend using RVM here, and Ruby 2.x or above
|
30
|
+
gem build doable.gemspec
|
31
|
+
# install what you just built
|
32
|
+
gem install ./doable-*.gem
|
33
|
+
|
34
|
+
Sometimes you might get lucky and there's a semi-recent version of the pre-built gem available on bitbucket [here](https://bitbucket.org/jgnagy/doable/downloads).
|
35
|
+
|
36
|
+
Usage
|
37
|
+
-----
|
38
|
+
|
39
|
+
Require the right gem(s):
|
40
|
+
|
41
|
+
#!ruby
|
42
|
+
require 'doable'
|
43
|
+
|
44
|
+
To create a simple job, define a plan:
|
45
|
+
|
46
|
+
#!ruby
|
47
|
+
job = Doable::Job.plan do |j|
|
48
|
+
j.before { log "Starting my awesome job" }
|
49
|
+
j.step { # do some stuff here }
|
50
|
+
j.attempt { # try to do some other stuff here }
|
51
|
+
j.after { log "Looks like we're all set" }
|
52
|
+
end
|
53
|
+
|
54
|
+
job.run # executes our job
|
55
|
+
|
56
|
+
This job is obviously pretty boring, but it shows some of the basics. Now let's continue and include some additional helpers in a new, more complex job:
|
57
|
+
|
58
|
+
#!ruby
|
59
|
+
require 'doable/helpers/password'
|
60
|
+
|
61
|
+
class JobWithPasswords < Doable::Job
|
62
|
+
include Doable::Helpers::Password
|
63
|
+
end
|
64
|
+
|
65
|
+
job2 = JobWithPasswords.plan do |j|
|
66
|
+
j.before { log "Here's a password for you..." }
|
67
|
+
j.step { puts "Password: " + generate_password(16) }
|
68
|
+
end
|
69
|
+
|
70
|
+
job2.run
|
71
|
+
|
72
|
+
After this job runs, you should see output that looks something like:
|
73
|
+
|
74
|
+
[2015/01/29 00:10:36] Here's a password for you...
|
75
|
+
Password: ZwyIVH8LjHGYYzDX
|
76
|
+
[2015/01/29 00:10:36] All Job steps completed successfully!
|
77
|
+
|
78
|
+
Hopefully this demonstrates how to get access to additional helper methods.
|
79
|
+
|
80
|
+
License
|
81
|
+
-------
|
82
|
+
|
83
|
+
Doable is distributed under the MIT License.
|
84
|
+
|
85
|
+
Contributing
|
86
|
+
------------
|
87
|
+
|
88
|
+
I welcome pull-requests. I may or may not use your code, but I encourage the growth of others too. If this project inspires you to contribute, feel free to fork my code and submit a pull request. In most cases, I'll probably recommend you package additions in an extension gem rather than including new files, etc, directly into the core codebase.
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Doable
|
2
|
+
module Exceptions
|
3
|
+
module OS
|
4
|
+
class OSException < GenericFrameworkError
|
5
|
+
end
|
6
|
+
|
7
|
+
class WrongUser < OSException
|
8
|
+
end
|
9
|
+
|
10
|
+
class MissingDirectory < OSException
|
11
|
+
end
|
12
|
+
|
13
|
+
class DirectoryExists < OSException
|
14
|
+
end
|
15
|
+
|
16
|
+
class InvalidHostname < OSException
|
17
|
+
end
|
18
|
+
|
19
|
+
class MissingFile < OSException
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -1,10 +1,9 @@
|
|
1
1
|
module Doable
|
2
|
+
# Create a mutex to manage logging to STDOUT
|
3
|
+
LOGGING_MUTEX = Mutex.new
|
4
|
+
LOGGING_MUTEX.freeze
|
2
5
|
module Helpers
|
3
|
-
module
|
4
|
-
# Create a mutex to manage logging to STDOUT
|
5
|
-
LOGGING_MUTEX = Mutex.new
|
6
|
-
LOGGING_MUTEX.freeze
|
7
|
-
|
6
|
+
module Logging
|
8
7
|
# Applies a color (with optional addition settings) to some text
|
9
8
|
# @return [String]
|
10
9
|
# @param text [String] The String to be colorized
|
@@ -0,0 +1,129 @@
|
|
1
|
+
require 'doable/exceptions/os'
|
2
|
+
|
3
|
+
module Doable
|
4
|
+
module Helpers
|
5
|
+
module OS
|
6
|
+
include Doable::Exceptions::OS
|
7
|
+
# Confirms the current script is running as a certain user
|
8
|
+
# @raise [WrongUser]
|
9
|
+
# @param user [String] User to confirm we're using
|
10
|
+
def must_run_as(user)
|
11
|
+
raise WrongUser, user unless user == Etc.getpwuid.name
|
12
|
+
log "Running as correct user"
|
13
|
+
end
|
14
|
+
|
15
|
+
# Ensure a directory exists
|
16
|
+
# @param directory [String] Directory to create or verify
|
17
|
+
# @return [String]
|
18
|
+
def find_or_create_directory(directory)
|
19
|
+
full_path = File.expand_path(directory)
|
20
|
+
FileUtils.mkdir_p(full_path) unless File.directory?(full_path)
|
21
|
+
return full_path
|
22
|
+
end
|
23
|
+
|
24
|
+
# Die if a directory doesn't exist
|
25
|
+
# @raise [MissingDirectory]
|
26
|
+
# @param directory [String] Directory to verify
|
27
|
+
# @return [String]
|
28
|
+
def find_directory(directory)
|
29
|
+
full_path = File.expand_path(directory)
|
30
|
+
raise MissingDirectory, directory unless File.directory?(full_path)
|
31
|
+
return full_path
|
32
|
+
end
|
33
|
+
|
34
|
+
# Die if a directory __does__ exist.
|
35
|
+
# Useful for ensuring software wasn't previously installed, etc.
|
36
|
+
# @raise [DirectoryExists]
|
37
|
+
# @param directory [String] Directory to verify does not exist
|
38
|
+
# @return [Boolean]
|
39
|
+
def die_if_dir_exists(directory)
|
40
|
+
full_path = File.expand_path(directory)
|
41
|
+
if File.directory?(full_path)
|
42
|
+
raise DirectoryExists, directory
|
43
|
+
else
|
44
|
+
return true
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# Remove a file or directory
|
49
|
+
# @param file_or_directory [String] File or directory path to remove
|
50
|
+
def remove(file_or_directory)
|
51
|
+
FileUtils.rm_rf(File.expand_path(file_or_directory))
|
52
|
+
end
|
53
|
+
|
54
|
+
# Check that the OS can resolve a hostname
|
55
|
+
# @raise [InvalidHostname]
|
56
|
+
# @param hostname [String] Hostname to validate is resolveable
|
57
|
+
def validate_host(hostname)
|
58
|
+
raise InvalidHostname, hostname unless Resolv.getaddress(hostname)
|
59
|
+
end
|
60
|
+
|
61
|
+
# Ruby in-memory equivalent of `tee`
|
62
|
+
# Note that this command is dangerous for very long command output. Need to consider setting a buffer max size for this.
|
63
|
+
# @param command [String] Command to run
|
64
|
+
# @return [Fixnum, String] Returns the exit code of the command, along with any output
|
65
|
+
def tee(command)
|
66
|
+
command_output = ""
|
67
|
+
IO.popen(command) do |io|
|
68
|
+
while line = io.gets
|
69
|
+
command_output << line
|
70
|
+
LOGGING_MUTEX.synchronize { puts line }
|
71
|
+
end
|
72
|
+
end
|
73
|
+
return [$?.exitstatus, command_output]
|
74
|
+
end
|
75
|
+
|
76
|
+
# Used like Unix touch
|
77
|
+
# @param file_list [Array<String>,String] List of files to create or verify
|
78
|
+
def touch(file_list, options = {})
|
79
|
+
FileUtils.touch(file_list, options)
|
80
|
+
end
|
81
|
+
|
82
|
+
# Used for backwards compatibility (just a wrapper for touch)
|
83
|
+
# @param file [String] File to create or verify
|
84
|
+
def find_or_create_file(file)
|
85
|
+
unless File.exists?(File.expand_path(file))
|
86
|
+
log "File '#{file}' does not seem to exist... creating it...", :warn
|
87
|
+
touch File.expand_path(file)
|
88
|
+
end
|
89
|
+
return File.expand_path(file)
|
90
|
+
end
|
91
|
+
|
92
|
+
# Throws an exception if a file doesn't exist
|
93
|
+
# @raise [MissingFile]
|
94
|
+
# @param file [String] File or directory to verify
|
95
|
+
# @param message [String] Option message to log if file does not exist
|
96
|
+
# @return [String]
|
97
|
+
def find_file(file, message = "")
|
98
|
+
unless File.exists?(File.expand_path(file))
|
99
|
+
message.empty? ? log("Filename #{file} is invalid", :error) : log(message, :error)
|
100
|
+
raise MissingFile, file
|
101
|
+
end
|
102
|
+
|
103
|
+
return File.expand_path(file)
|
104
|
+
end
|
105
|
+
|
106
|
+
# A simple replacement for `sed`. This is far from a complete implementation and should be used sparingly. A much
|
107
|
+
# better approach is to use ERB files.
|
108
|
+
# @raise [InvalidInput]
|
109
|
+
# @param substitutions [Hash] Hash of Strings indexing Strings. Keys are replaced with their value.
|
110
|
+
# @param input_file [IO,String] IO object or String to use as the source
|
111
|
+
# @param output_file [IO,String] IO object or String to use as the destination
|
112
|
+
def sed(substitutions, input_file, output_file)
|
113
|
+
input_file = input_file.kind_of?(IO) ? input_file : File.open(input_file, "r")
|
114
|
+
output_file = output_file.kind_of?(IO) ? output_file : File.open(output_file, "w")
|
115
|
+
raise InvalidInput unless substitutions.kind_of?(Hash)
|
116
|
+
|
117
|
+
# Very fancy way to replace things in a file and write to a new file
|
118
|
+
output_file.puts(
|
119
|
+
input_file.read.gsub(Regexp.union(substitutions.keys)) do |match|
|
120
|
+
# Hideous way to work with regular expressions *and* string substitutions in a single pass over a large file
|
121
|
+
substitutions[substitutions.keys.keep_if {|s| substitutions[s].to_s if match == s or s.match(match) }.first].to_s
|
122
|
+
end
|
123
|
+
)
|
124
|
+
output_file.close
|
125
|
+
end
|
126
|
+
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
data/lib/doable/job.rb
CHANGED
@@ -4,8 +4,8 @@ module Doable
|
|
4
4
|
# can also describe how to recover when things break and provides hooks and triggers to make more flexible
|
5
5
|
# scripts for varying environments.
|
6
6
|
class Job
|
7
|
-
include Helpers::
|
8
|
-
include Helpers::
|
7
|
+
include Helpers::Framework
|
8
|
+
include Helpers::Logging
|
9
9
|
attr_reader :steps, :hooks, :handlers, :threads
|
10
10
|
|
11
11
|
def self.plan(&block)
|
@@ -141,12 +141,12 @@ module Doable
|
|
141
141
|
|
142
142
|
unless step.successful?
|
143
143
|
message = "\n\nUnhandled Exception in #{colorize("hooks##{hook}[#{index}]", :yellow)}: #{colorize("#{e.class}: (#{e.message})", :red)}\n\n"
|
144
|
-
if @config.auto_rollback
|
145
|
-
|
146
|
-
|
147
|
-
else
|
144
|
+
#if @config.auto_rollback
|
145
|
+
# log message
|
146
|
+
# rollback!
|
147
|
+
#else
|
148
148
|
raise message
|
149
|
-
end
|
149
|
+
#end
|
150
150
|
end # unless
|
151
151
|
end
|
152
152
|
end # begin()
|
data/lib/doable.rb
CHANGED
@@ -8,10 +8,12 @@ require 'erb'
|
|
8
8
|
require 'thread'
|
9
9
|
require 'pathname'
|
10
10
|
require 'yaml'
|
11
|
+
require 'etc'
|
12
|
+
require 'resolv'
|
11
13
|
# Framework requirements
|
12
|
-
require 'doable/exceptions/
|
13
|
-
include Doable::Exceptions::
|
14
|
-
require 'doable/helpers/
|
15
|
-
require 'doable/helpers/
|
14
|
+
require 'doable/exceptions/framework'
|
15
|
+
include Doable::Exceptions::Framework
|
16
|
+
require 'doable/helpers/logging'
|
17
|
+
require 'doable/helpers/framework'
|
16
18
|
require 'doable/step'
|
17
19
|
require 'doable/job'
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: doable
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jonathan Gnagy
|
@@ -17,11 +17,14 @@ extensions: []
|
|
17
17
|
extra_rdoc_files: []
|
18
18
|
files:
|
19
19
|
- LICENSE
|
20
|
+
- README.md
|
20
21
|
- lib/doable.rb
|
21
|
-
- lib/doable/exceptions/
|
22
|
-
- lib/doable/
|
23
|
-
- lib/doable/helpers/
|
24
|
-
- lib/doable/helpers/
|
22
|
+
- lib/doable/exceptions/framework.rb
|
23
|
+
- lib/doable/exceptions/os.rb
|
24
|
+
- lib/doable/helpers/framework.rb
|
25
|
+
- lib/doable/helpers/logging.rb
|
26
|
+
- lib/doable/helpers/os.rb
|
27
|
+
- lib/doable/helpers/password.rb
|
25
28
|
- lib/doable/job.rb
|
26
29
|
- lib/doable/step.rb
|
27
30
|
homepage: https://rubygems.org/gems/doable
|