genesis_framework 0.5.2

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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: ab603bd2b1113f7ec2bdc86187156625e40a333d
4
+ data.tar.gz: 75d4060aa531be4bef61a680cb7ee39ef51363c2
5
+ SHA512:
6
+ metadata.gz: 2f3723dff9601c50c2ac4bc8a4d3b48e9aa09dc7691d69776a781f287385490665ff03194ab2d9ddfd6b57a1a040096e3d820a99297b5595b3902b418b80bad1
7
+ data.tar.gz: ca2ff9803849bba62cbafe426f663fe803c1a53b34bd4a78fa8629a21429cac58948e004016e70e50f3282951f802dabd172ff0d2332f3f6fdd1d98f746c57bf
@@ -0,0 +1,19 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ begin
4
+ require 'rake'
5
+ rescue LoadError
6
+ require 'rubygems'
7
+ require 'rake'
8
+ end
9
+
10
+ ENV['GENESIS_ROOT'] ||= '/var/run/genesis'
11
+
12
+ # rake does chdir also, so don't put this in a block to avoid the warning
13
+ Dir::chdir( File.join(ENV['GENESIS_ROOT'], '/tasks') )
14
+
15
+ # run the normal rake application, but change its name to genesis
16
+ rake = Rake.application
17
+ rake.init('genesis')
18
+ rake.load_rakefile
19
+ rake.top_level
@@ -0,0 +1,6 @@
1
+ # put the facter path in the LOAD_PATH so facter looks for new facts there
2
+ # this path needs to have a 'facter' dir as an immediate subdir
3
+ $LOAD_PATH << File.join(File.expand_path(File.dirname(__FILE__)),'genesisframework')
4
+ require 'genesisframework/task'
5
+ require 'genesisframework/tasks'
6
+ require 'genesisframework/utils'
@@ -0,0 +1,5 @@
1
+ Facter.add(:test_fact) do
2
+ setcode do
3
+ "gabes_test_fact"
4
+ end
5
+ end
@@ -0,0 +1,175 @@
1
+ require 'collins_client'
2
+ require 'syslog'
3
+ require 'retryingfetcher'
4
+ require 'promptcli'
5
+ require 'facter'
6
+ require 'open3'
7
+
8
+ module Genesis
9
+ module Framework
10
+ module Task
11
+ def self.included base
12
+ base.extend TaskDslMethods
13
+ end
14
+
15
+ module TaskDslMethods
16
+ attr_accessor :blocks, :options
17
+
18
+ def description desc
19
+ set_option :description, desc
20
+ end
21
+
22
+ def precondition description, &block
23
+ add_block :precondition, description, block
24
+ end
25
+
26
+ def init &block
27
+ set_block :init, block
28
+ end
29
+
30
+ def condition description, &block
31
+ add_block :condition, description, block
32
+ end
33
+
34
+ def run &block
35
+ set_block :run, block
36
+ end
37
+
38
+ def rollback &block
39
+ set_block :rollback, block
40
+ end
41
+
42
+ def success &block
43
+ set_block :success, block
44
+ end
45
+
46
+ def timeout secs
47
+ set_option :timeout, secs
48
+ end
49
+
50
+ def retries count
51
+ if count.is_a? Enumerator then
52
+ set_option :retries, count.to_a
53
+ else
54
+ set_option :retries, count.times.to_a
55
+ end
56
+ end
57
+
58
+ def collins
59
+ Genesis::Framework::Utils.collins
60
+ end
61
+
62
+ def facter
63
+ # lets cache the facts on first use
64
+ # TODO symbolize these keys?
65
+ # TODO implement method_missing? on this hash for easy access
66
+ Genesis::Framework::Utils.facter
67
+ end
68
+
69
+ def run_cmd *cmd, stdin_data: '', return_both_streams: false, return_merged_streams: false
70
+ if return_both_streams && return_merged_streams
71
+ raise "Invalid run_cmd invocation, can's specify both and merged together"
72
+ end
73
+
74
+ if return_merged_streams
75
+ output, status = Open3.capture2e(*cmd, stdin_data: stdin_data)
76
+ else
77
+ stdout, stderr, status = Open3.capture3(*cmd, stdin_data: stdin_data)
78
+ if return_both_streams
79
+ output = [stdout, stderr]
80
+ else
81
+ output = stdout
82
+ end
83
+ end
84
+
85
+ if status.exitstatus != 0
86
+ log("Run Command failed for '%s' with exit status '%d' and output: %s" % [cmd.to_s, status.exitstatus, output.to_s])
87
+ raise 'run_cmd exited with status: ' + status.exitstatus.to_s
88
+ end
89
+
90
+ return output
91
+ end
92
+
93
+ def config
94
+ # We are intentionally causing a deep copy here so one task
95
+ # can't pollute another task's config setup
96
+ # TODO: consider possibly patching hash to not allow setting members?
97
+ @config ||= Marshal.load(Marshal.dump(Genesis::Framework::Utils.config_cache))
98
+ end
99
+
100
+ def log message
101
+ Genesis::Framework::Utils.log(self.class.name, message)
102
+ end
103
+
104
+ def prompt message, seconds=15, default=false
105
+ Genesis::PromptCLI.ask message, seconds, default
106
+ end
107
+
108
+ def install provider, *what
109
+ if provider == :rpm
110
+ Kernel.system("yum", "install", "-y", *what)
111
+ if $?.exitstatus != 0
112
+ raise 'yum install exited with status: ' + $?.exitstatus.to_s
113
+ end
114
+ elsif provider == :gem
115
+ # we give a decent try at detecting if the gem is installed before trying to reinstall again
116
+ # if it contains a - (aka you are specifying a specific version or a / (aka you are specifying a path to find it)
117
+ # then we punt on trying to determine if the gem is already installed and just pass it to install anyway
118
+ gems = what.select { |item| item.include?("-") || item.include?("/") || Gem::Dependency.new(item).matching_specs.count == 0 }
119
+ Kernel.system("gem", "install", "--no-ri", "--no-rdoc", *gems)
120
+ if $?.exitstatus != 0
121
+ raise 'gem install exited with status: ' + $?.exitstatus.to_s
122
+ end
123
+
124
+ # now need to clear out the Gem cache so we can load it
125
+ Gem.clear_paths
126
+
127
+ # Now we require all the gems you asked to be installed
128
+ what.all? { |gem| require gem }
129
+ else
130
+ raise "Unknown install provider: " + provider.to_s
131
+ end
132
+ end
133
+
134
+ def fetch what, filename, base_url: ENV['GENESIS_URL']
135
+ filepath = tmp_path(filename)
136
+ Genesis::RetryingFetcher.get(what, base_url) {|data| File.open(filepath, "w", 0755) { |file| file.write data }}
137
+ end
138
+
139
+ def tmp_path filename
140
+ Genesis::Framework::Utils.tmp_path(filename, self.class.name)
141
+ end
142
+
143
+ #############################################################
144
+ # These methods are private and not part of the exposed DSL.
145
+ # Use at your own risk.
146
+ #############################################################
147
+
148
+ def set_block sym, block
149
+ self.init_defaults
150
+ self.blocks[sym] = block
151
+ end
152
+
153
+ def add_block sym, description, block
154
+ self.init_defaults
155
+ if self.blocks[sym].has_key?(description)
156
+ raise "Task defines multiple conditions with the same description"
157
+ end
158
+ self.blocks[sym][description] = block
159
+ end
160
+
161
+ def set_option sym, option
162
+ self.init_defaults
163
+ self.options[sym] = option
164
+ end
165
+
166
+ def init_defaults
167
+ self.blocks ||= { :precondition => {}, :init => nil, :condition => {}, :run => nil, :rollback => nil, :success => nil }
168
+ self.options ||= { :retries => 3.times.to_a, :timeout => 0, :description => nil }
169
+ end
170
+
171
+ end
172
+ end
173
+ end
174
+ end
175
+
@@ -0,0 +1,136 @@
1
+ require 'timeout'
2
+ require 'promptcli'
3
+ require 'yaml'
4
+
5
+ module Genesis
6
+ module Framework
7
+ module Tasks
8
+ def self.load_config file
9
+ begin
10
+ data = File.read(file)
11
+
12
+ ## TODO: consider tokenizing the keys of the hash? needed???
13
+ Genesis::Framework::Utils.config_cache = YAML::load(data)
14
+ rescue => e
15
+ raise "Unable to parse config %s: %s" % [file, e.message]
16
+ end
17
+ end
18
+
19
+ def self.call_block blocks, sym, msg = nil
20
+ if blocks.has_key?(sym) && blocks[sym].respond_to?(:call)
21
+ puts msg if msg
22
+ blocks[sym].call
23
+ else
24
+ true
25
+ end
26
+ end
27
+
28
+ def self.load_tasks dir
29
+ # expand the LOAD_PATH to include modules, so facts are available
30
+ $:.unshift File.join(File.expand_path(dir),'modules')
31
+ puts "\nParsing tasks from directory: %s" % [dir]
32
+
33
+ Dir.glob(File.join(dir,'*.rb')) do |f|
34
+ begin
35
+ Genesis::Framework::Tasks.class_eval File.read(f)
36
+ rescue => e
37
+ raise "Error parsing task %s: %s" % [f, e.message]
38
+ end
39
+ end
40
+
41
+ @tasks = Genesis::Framework::Tasks.constants.select do |c|
42
+ Genesis::Framework::Tasks.const_get(c).include?( Genesis::Framework::Task )
43
+ end
44
+
45
+ @tasks.sort!
46
+ end
47
+
48
+ def self.execute task_name
49
+ puts "\n#{task_name}\n================================================="
50
+
51
+ return unless Genesis::PromptCLI.ask("Would you like to run this task?", 10, true) == true
52
+
53
+ task = Genesis::Framework::Tasks.const_get(task_name)
54
+
55
+ if task.blocks.nil?
56
+ puts "task is empty with nothing to do, skipping..."
57
+ return true
58
+ end
59
+
60
+ begin
61
+ puts "task is now testing if it needs to be initialized..."
62
+ if task.blocks.has_key?(:precondition)
63
+ task.blocks[:precondition].each do |description, block|
64
+ puts "Testing: %s" % description
65
+ unless self.call_block(task.blocks[:precondition], description)
66
+ puts "task is being skipped..."
67
+ return true
68
+ end
69
+ end
70
+ end
71
+ rescue => e
72
+ puts "%s task had error on testing if it needs initialization: %s" % [task_name, e.message]
73
+ return false
74
+ end
75
+
76
+ begin
77
+ puts "task is now initializing..."
78
+ self.call_block(task.blocks, :init);
79
+ puts "task is now initialized..."
80
+ rescue => e
81
+ puts "%s task threw error on initialization: %s" % [task_name, e.message]
82
+ return false
83
+ end
84
+
85
+ begin
86
+ puts "task is now testing if it can run..."
87
+ if task.blocks.has_key?(:condition)
88
+ task.blocks[:condition].each do |description, block|
89
+ puts "Checking: %s" % description
90
+ unless self.call_block(task.blocks[:condition], description)
91
+ puts "Conditional failed. Task is being skipped."
92
+ return true
93
+ end
94
+ end
95
+ end
96
+ rescue => e
97
+ puts "%s task had error on testing if it needs running: %s" % [task_name, e.message]
98
+ return false
99
+ end
100
+
101
+ success = nil
102
+ task.options[:retries].each_with_index do |sleep_interval, index|
103
+ attempt = index + 1
104
+ begin
105
+ puts "task is attempting run #%d..." % [attempt]
106
+ Timeout::timeout(task.options[:timeout]) do
107
+ success = self.call_block(task.blocks, :run)
108
+ end
109
+ # a run block should raise an error or be false for a failure
110
+ success = true if success.nil?
111
+ rescue => e
112
+ puts "%s task [run #%d] caused error: %s" % [task_name, attempt, e.message]
113
+ success = nil # cause a retry
114
+ end
115
+ break unless success.nil? # if we got an answer, we're done
116
+ puts "task is sleeping for %d seconds..." % [sleep_interval]
117
+ Kernel.sleep(sleep_interval)
118
+ end
119
+ success = false if success.nil? # must have used all the retries, fail
120
+
121
+ if success
122
+ success = self.call_block(task.blocks, :success)
123
+ puts "task is successful!"
124
+ else
125
+ puts 'task failed!!!'
126
+ success = self.call_block(task.blocks, :rollback, "rolling back!")
127
+ end
128
+
129
+ puts "\n\n"
130
+ return success
131
+ end
132
+
133
+ end
134
+ end
135
+ end
136
+
@@ -0,0 +1,55 @@
1
+ require 'syslog'
2
+ require 'collins_client'
3
+ require 'facter'
4
+
5
+ module Genesis
6
+ module Framework
7
+ module Utils
8
+ def self.tmp_path filename, sandbox = ""
9
+ location = File.join(ENV['GENESIS_ROOT'], "tmp", sandbox)
10
+ Dir.mkdir(location, 0755) unless File.directory? location
11
+ File.join(location, filename)
12
+ end
13
+
14
+ @@config_cache = Hash.new
15
+ @@collins_conn = nil
16
+ @@facter = nil
17
+
18
+ # mimicking rail's cattr_accessor
19
+ def self.config_cache
20
+ @@config_cache
21
+ end
22
+
23
+ def self.config_cache= (obj)
24
+ @@config_cache = obj
25
+ end
26
+
27
+ def self.collins
28
+ if @@collins_conn.nil?
29
+ cfg = { :host => self.config_cache['collins']['host'], :username => self.config_cache['collins']['username'], :password => self.config_cache['collins']['password'] }
30
+ @@collins_conn = ::Collins::Client.new(cfg)
31
+ end
32
+
33
+ @@collins_conn
34
+ end
35
+
36
+ def self.facter
37
+ if @@facter.nil?
38
+ @@facter = Facter.to_hash
39
+ end
40
+
41
+ @@facter
42
+ end
43
+
44
+ def self.log subsystem, message
45
+ logline = subsystem.to_s + " :: " + message
46
+ puts logline
47
+ Syslog.open("genesis", Syslog::LOG_PID, Syslog::LOG_USER) unless Syslog.opened?
48
+ Syslog.log(Syslog::LOG_INFO, logline)
49
+ if self.facter['asset_tag']
50
+ self.collins.log! self.facter['asset_tag'], message
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
metadata ADDED
@@ -0,0 +1,133 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: genesis_framework
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.5.2
5
+ platform: ruby
6
+ authors:
7
+ - Jeremy Johnstone
8
+ - Roy Marantz
9
+ - Gabe Conradi
10
+ autorequire:
11
+ bindir: bin
12
+ cert_chain: []
13
+ date: 2014-12-29 00:00:00.000000000 Z
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: genesis_promptcli
17
+ requirement: !ruby/object:Gem::Requirement
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: '0.2'
22
+ - - '>='
23
+ - !ruby/object:Gem::Version
24
+ version: 0.2.0
25
+ type: :runtime
26
+ prerelease: false
27
+ version_requirements: !ruby/object:Gem::Requirement
28
+ requirements:
29
+ - - ~>
30
+ - !ruby/object:Gem::Version
31
+ version: '0.2'
32
+ - - '>='
33
+ - !ruby/object:Gem::Version
34
+ version: 0.2.0
35
+ - !ruby/object:Gem::Dependency
36
+ name: genesis_retryingfetcher
37
+ requirement: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - ~>
40
+ - !ruby/object:Gem::Version
41
+ version: '0.4'
42
+ - - '>='
43
+ - !ruby/object:Gem::Version
44
+ version: 0.4.0
45
+ type: :runtime
46
+ prerelease: false
47
+ version_requirements: !ruby/object:Gem::Requirement
48
+ requirements:
49
+ - - ~>
50
+ - !ruby/object:Gem::Version
51
+ version: '0.4'
52
+ - - '>='
53
+ - !ruby/object:Gem::Version
54
+ version: 0.4.0
55
+ - !ruby/object:Gem::Dependency
56
+ name: collins_client
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: '0.2'
62
+ - - '>='
63
+ - !ruby/object:Gem::Version
64
+ version: 0.2.0
65
+ type: :runtime
66
+ prerelease: false
67
+ version_requirements: !ruby/object:Gem::Requirement
68
+ requirements:
69
+ - - ~>
70
+ - !ruby/object:Gem::Version
71
+ version: '0.2'
72
+ - - '>='
73
+ - !ruby/object:Gem::Version
74
+ version: 0.2.0
75
+ - !ruby/object:Gem::Dependency
76
+ name: facter
77
+ requirement: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ~>
80
+ - !ruby/object:Gem::Version
81
+ version: '2.0'
82
+ - - '>='
83
+ - !ruby/object:Gem::Version
84
+ version: 2.0.0
85
+ type: :runtime
86
+ prerelease: false
87
+ version_requirements: !ruby/object:Gem::Requirement
88
+ requirements:
89
+ - - ~>
90
+ - !ruby/object:Gem::Version
91
+ version: '2.0'
92
+ - - '>='
93
+ - !ruby/object:Gem::Version
94
+ version: 2.0.0
95
+ description: Genesis is a project used to manage provisioning of hardware. This is
96
+ the framework which runs the specified tasks.
97
+ email: opensourcesoftware@tumblr.com
98
+ executables:
99
+ - genesis
100
+ extensions: []
101
+ extra_rdoc_files: []
102
+ files:
103
+ - bin/genesis
104
+ - lib/genesisframework/facter/test_fact.rb
105
+ - lib/genesisframework/task.rb
106
+ - lib/genesisframework/tasks.rb
107
+ - lib/genesisframework/utils.rb
108
+ - lib/genesisframework.rb
109
+ homepage: https://github.ewr01.tumblr.net/Tumblr/genesis
110
+ licenses:
111
+ - Apache License, 2.0
112
+ metadata: {}
113
+ post_install_message:
114
+ rdoc_options: []
115
+ require_paths:
116
+ - lib
117
+ required_ruby_version: !ruby/object:Gem::Requirement
118
+ requirements:
119
+ - - '>='
120
+ - !ruby/object:Gem::Version
121
+ version: '0'
122
+ required_rubygems_version: !ruby/object:Gem::Requirement
123
+ requirements:
124
+ - - '>='
125
+ - !ruby/object:Gem::Version
126
+ version: '0'
127
+ requirements: []
128
+ rubyforge_project:
129
+ rubygems_version: 2.0.14
130
+ signing_key:
131
+ specification_version: 4
132
+ summary: Generic server onboarding framework
133
+ test_files: []