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