molder 0.1.4
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 +7 -0
- data/.gitignore +18 -0
- data/.rspec +2 -0
- data/.rspec_status +4 -0
- data/.travis.yml +26 -0
- data/Gemfile +6 -0
- data/LICENSE.txt +21 -0
- data/README.md +209 -0
- data/Rakefile +35 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/docs/molder-cli.png +0 -0
- data/docs/molder.png +0 -0
- data/exe/molder +6 -0
- data/lib/molder.rb +9 -0
- data/lib/molder/app.rb +81 -0
- data/lib/molder/cli.rb +188 -0
- data/lib/molder/command.rb +16 -0
- data/lib/molder/configuration.rb +27 -0
- data/lib/molder/errors.rb +7 -0
- data/lib/molder/renderer.rb +109 -0
- data/lib/molder/template.rb +35 -0
- data/lib/molder/version.rb +7 -0
- data/molder.gemspec +35 -0
- metadata +240 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: d2480986a1941cf8389155f51644915554997ce09b92437f3eb24c1b4bd6fbe9
|
4
|
+
data.tar.gz: 3001d82af80718bc6f4db8ade5bef766e27d199f16fac8136d7a30a5924a036e
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: f94f91528b905aea08c623e7a50fa575304f6dfbd9ef8d8c8c821252ee1e3c7919237eff400c3398f0d78d868a7034524a6b63ec55d8eafc04cd487656ef3cb5
|
7
|
+
data.tar.gz: 13f261c86a9ea2892d26790ef2b5bf17d76b4235d05e22e909df334f599821cfa777fad1079a78fc71ab6f3f868552c90399fbe807db13a353d33349b930fa4f
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.rspec_status
ADDED
data/.travis.yml
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
language: ruby
|
2
|
+
env:
|
3
|
+
global:
|
4
|
+
- CODECLIMATE_REPO_TOKEN=49ca8c0e2d56ab70e7eabc237692e21cc8fe43ef13ed5d411fa20bf1e8997a44
|
5
|
+
rvm:
|
6
|
+
- 2.2.9
|
7
|
+
- 2.3.6
|
8
|
+
- 2.4.3
|
9
|
+
- 2.5.0
|
10
|
+
cache:
|
11
|
+
- bundler
|
12
|
+
before_script:
|
13
|
+
- curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter
|
14
|
+
- chmod +x ./cc-test-reporter
|
15
|
+
- ./cc-test-reporter before-build
|
16
|
+
after_script:
|
17
|
+
- ./cc-test-reporter after-build --exit-code $TRAVIS_TEST_RESULT
|
18
|
+
script: bundle exec rspec
|
19
|
+
notifications:
|
20
|
+
slack:
|
21
|
+
rooms:
|
22
|
+
email:
|
23
|
+
recipients:
|
24
|
+
- kigster@gmail.com
|
25
|
+
on_success: change
|
26
|
+
on_failure: always
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2018 Konstantin Gredeskoul
|
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,209 @@
|
|
1
|
+
[](https://travis-ci.org/kigster/molder)
|
2
|
+
|
3
|
+
# Molder
|
4
|
+
|
5
|
+
Molder is a command line tool for generating and running (in parallel) a set of related but similar commands. A key
|
6
|
+
use-case is auto-generation of the host provisioning commands for an arbitrary cloud environment. The gem is not constrained to any particular cloud tool or even a command, and can be used to generate a consistent set of commands based on several customizable dimensions.
|
7
|
+
|
8
|
+
For example, you could generate 600 provisioning commands for hosts in EC2, numbered from 1 to 100, constrained to the dimensions "zone-id" (values: ["a", "b", "c"]) and the data center "dc" (values: ['us-west2', 'us-east1' ]).
|
9
|
+
|
10
|
+
## Usage
|
11
|
+
|
12
|
+
Molder works in the following way:
|
13
|
+
|
14
|
+
* It reads the configuration YAML file, described further below
|
15
|
+
|
16
|
+
* It parses command line arguments, and extracts the **command name** and **template name(s)**
|
17
|
+
|
18
|
+
* It matches the command name with one of the commands specified in the YAML file
|
19
|
+
|
20
|
+
* It matches the template name(s) with those in the YAML file
|
21
|
+
|
22
|
+
* It then uses [Liquid template language](https://shopify.github.io/liquid/) to substitute tokens in the command template, possibly enumerating over provided numbers.
|
23
|
+
|
24
|
+
* Tokens are taken from the template definition, and two more tokens are added: `formatted_number` and `number`.
|
25
|
+
|
26
|
+
### YAML Configuration File
|
27
|
+
|
28
|
+
Here is the semi-minimal YAML file that demonstrates all features.
|
29
|
+
|
30
|
+
First, at the top, we define the global section. The only arguments there are `log_dir` and `index_format`, the latter is the `sprintf` pattern applied to the numeric index.
|
31
|
+
|
32
|
+
```yaml
|
33
|
+
global:
|
34
|
+
log_dir: ./log
|
35
|
+
index_format: '%03.3d'
|
36
|
+
```
|
37
|
+
|
38
|
+
Second, we have a `configuration` section that is not really used by the library, but is used within this YAML file to define the templates with the minimum repetition. Note, how we are using `? role[base]` to indicate a hash key with a nil value. These can be merged down below, and `molder` converts all hashes with nil values into an array of keys.
|
39
|
+
|
40
|
+
Note how in the name of the instances we are using Liquid markup to reference `formatted_number` and `zone_id`. The former is provided by the gem, and the latter is provided by the template itself.
|
41
|
+
|
42
|
+
```yaml
|
43
|
+
configuration:
|
44
|
+
ec2-base: &ec2-base
|
45
|
+
image: ami-f9u98f
|
46
|
+
flavor: c5.4xlarge
|
47
|
+
security_group_id: ssg-f8987987
|
48
|
+
ssh_user: ubuntu
|
49
|
+
ssh_key: ubuntu_key
|
50
|
+
identity_file: '~/.ssh/ec2.pem'
|
51
|
+
run_list: &run_list_base
|
52
|
+
? role[base]
|
53
|
+
|
54
|
+
web: &ec2-web
|
55
|
+
<<: *ec2-base
|
56
|
+
name: web{{ formatted_number }}-{{ zone_id }}
|
57
|
+
run_list: &run_list_web
|
58
|
+
? role[web]
|
59
|
+
|
60
|
+
job: &ec2-job
|
61
|
+
<<: *ec2-base
|
62
|
+
flavor: c5.2xlarge
|
63
|
+
name: job{{ formatted_number }}-{{ zone_id }}
|
64
|
+
run_list: &run_list_job
|
65
|
+
? role[job]
|
66
|
+
|
67
|
+
us-east1-a: &us-east1-a
|
68
|
+
subnet: subnet-ff09898
|
69
|
+
zone: us-east1-a
|
70
|
+
zone_id: a
|
71
|
+
run_list: &run_list_zone_a
|
72
|
+
? role[zone-a]
|
73
|
+
|
74
|
+
us-east1-b: &us-east1-b
|
75
|
+
subnet: subnet-f909809
|
76
|
+
zone: us-east1-b
|
77
|
+
zone_id: b
|
78
|
+
run_list: &run_list_zone_b
|
79
|
+
? role[zone-b]
|
80
|
+
```
|
81
|
+
|
82
|
+
Next, we define the actual templates. These are composed of several YAML entries defined above.
|
83
|
+
|
84
|
+
Note that we define both zone-specific instances of two types (web and job), as well as an array of web instances (comprised of both zones a and b), and job instances.
|
85
|
+
|
86
|
+
|
87
|
+
```yaml
|
88
|
+
templates:
|
89
|
+
web-a: &web-a
|
90
|
+
<<: *us-east1-a
|
91
|
+
<<: *ec2-web
|
92
|
+
run_list:
|
93
|
+
<<: *run_list_base
|
94
|
+
<<: *run_list_web
|
95
|
+
<<: *run_list_zone_a
|
96
|
+
|
97
|
+
web-b: &web-b
|
98
|
+
<<: *ec2-web
|
99
|
+
<<: *us-east1-b
|
100
|
+
run_list:
|
101
|
+
<<: *run_list_base
|
102
|
+
<<: *run_list_web
|
103
|
+
<<: *run_list_zone_b
|
104
|
+
|
105
|
+
job-a: &job-a
|
106
|
+
<<: *ec2-job
|
107
|
+
<<: *us-east1-a
|
108
|
+
run_list:
|
109
|
+
<<: *run_list_base
|
110
|
+
<<: *run_list_job
|
111
|
+
<<: *run_list_zone_a
|
112
|
+
|
113
|
+
job-b: &job-b
|
114
|
+
<<: *ec2-job
|
115
|
+
<<: *us-east1-b
|
116
|
+
run_list:
|
117
|
+
<<: *run_list_base
|
118
|
+
<<: *run_list_job
|
119
|
+
<<: *run_list_zone_b
|
120
|
+
|
121
|
+
web:
|
122
|
+
- <<: *web-a
|
123
|
+
- <<: *web-b
|
124
|
+
|
125
|
+
job:
|
126
|
+
- <<: *job-a
|
127
|
+
- <<: *job-b
|
128
|
+
```
|
129
|
+
|
130
|
+
The final section defines commands that we can generate using this YAML file. We are only including one command here, called `provision`, which comes with a description and args. Args include extensive set of liquid tokens that are pulled from the template attributes applied to this command.
|
131
|
+
|
132
|
+
```yaml
|
133
|
+
commands:
|
134
|
+
provision:
|
135
|
+
desc: Provision hosts on AWS EC2 using knife ec2 plugin.
|
136
|
+
args: |
|
137
|
+
echo knife ec2 server create
|
138
|
+
-N {{ name }}
|
139
|
+
-I {{ image }}
|
140
|
+
-Z {{ zone }}
|
141
|
+
-f {{ flavor }}
|
142
|
+
--environment {{ environment }}
|
143
|
+
--subnet {{ subnet }}
|
144
|
+
-g {{ security_group_id }}
|
145
|
+
-r {{ run_list }}
|
146
|
+
-S {{ ssh_key }}
|
147
|
+
-i {{ identity_file }}
|
148
|
+
--ssh-user {{ ssh_user }}; sleep 2
|
149
|
+
```
|
150
|
+
|
151
|
+
So how would we use molder to generate commands that provision a bunch of ec2 hosts for us?
|
152
|
+
|
153
|
+
```bash
|
154
|
+
$ molder provision web[1..5]/job[1,4,6] -a environment=production -c config/molder.yml
|
155
|
+
```
|
156
|
+
|
157
|
+
This is the output we would see:
|
158
|
+
|
159
|
+

|
160
|
+
|
161
|
+
Let's understand this command:
|
162
|
+
|
163
|
+
* first argument is `provision` — it has to match one of the commands in the YAML file.
|
164
|
+
|
165
|
+
* second argument consists of template names, followed by the numbers in square brackets. Either comma-separated numbers are supported, or a range (not that a range must also be included in square brackets)
|
166
|
+
|
167
|
+
* multiple template names can be separated by a slash, as seen here.
|
168
|
+
|
169
|
+
* next we pass `-a environment=production` — notice that our provision command defined in the template uses `{{ environment }}` token, even though no such attribute is defined in any of the templates. if we do not supply this argument, the value of environment in the command line would be blank.
|
170
|
+
|
171
|
+
* Note that you can pass multiple attributes, separated by a slash, like so: `-a environment=production/flavor=c5.4xlarge`
|
172
|
+
|
173
|
+
* The final argument is the template file. The default location is `config/molder.yml` — so if you place the file in that folder you don't need to pass `-c` argument.
|
174
|
+
|
175
|
+
### Complete Options List
|
176
|
+
|
177
|
+
If you run `molder -h` you would see the following:
|
178
|
+
|
179
|
+

|
180
|
+
|
181
|
+
## Installation
|
182
|
+
|
183
|
+
Add this line to your application's Gemfile:
|
184
|
+
|
185
|
+
```ruby
|
186
|
+
gem 'molder'
|
187
|
+
```
|
188
|
+
|
189
|
+
And then execute:
|
190
|
+
|
191
|
+
$ bundle
|
192
|
+
|
193
|
+
Or install it yourself as:
|
194
|
+
|
195
|
+
$ gem install molder
|
196
|
+
|
197
|
+
## Development
|
198
|
+
|
199
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
200
|
+
|
201
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
202
|
+
|
203
|
+
## Contributing
|
204
|
+
|
205
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/kigster/molder.
|
206
|
+
|
207
|
+
## License
|
208
|
+
|
209
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'bundler/gem_tasks'
|
2
|
+
require 'rspec/core/rake_task'
|
3
|
+
require 'yard'
|
4
|
+
|
5
|
+
def shell(*args)
|
6
|
+
puts "running: #{args.join(' ')}"
|
7
|
+
system(args.join(' '))
|
8
|
+
end
|
9
|
+
|
10
|
+
task :clean do
|
11
|
+
shell 'rm -rf log/ pkg/ tmp/ coverage/ doc/'
|
12
|
+
end
|
13
|
+
|
14
|
+
task :gem => [:build] do
|
15
|
+
shell('gem install pkg/*')
|
16
|
+
end
|
17
|
+
|
18
|
+
task :permissions => [ :clean ] do
|
19
|
+
shell("chmod -v o+r,g+r * */* */*/* */*/*/* */*/*/*/* */*/*/*/*/*")
|
20
|
+
shell("find . -type d -exec chmod o+x,g+x {} \\;")
|
21
|
+
end
|
22
|
+
|
23
|
+
task :build => :permissions
|
24
|
+
|
25
|
+
YARD::Rake::YardocTask.new(:doc) do |t|
|
26
|
+
t.files = %w(lib/**/*.rb exe/*.rb - README.md LICENSE.txt)
|
27
|
+
t.options.unshift('--title','Molder - command generator based on templates')
|
28
|
+
t.after = ->() { exec('open doc/index.html') }
|
29
|
+
end
|
30
|
+
|
31
|
+
RSpec::Core::RakeTask.new(:spec)
|
32
|
+
|
33
|
+
task :default => :spec
|
34
|
+
|
35
|
+
|
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "molder"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
data/docs/molder-cli.png
ADDED
Binary file
|
data/docs/molder.png
ADDED
Binary file
|
data/exe/molder
ADDED
data/lib/molder.rb
ADDED
data/lib/molder/app.rb
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
require 'hashie/mash'
|
2
|
+
require 'hashie/extensions/mash/symbolize_keys'
|
3
|
+
require 'molder/errors'
|
4
|
+
require 'parallel'
|
5
|
+
require 'fileutils'
|
6
|
+
module Molder
|
7
|
+
class App
|
8
|
+
|
9
|
+
attr_accessor :config, :options, :command, :command_name, :commands, :templates, :log_dir
|
10
|
+
|
11
|
+
def initialize(config:, options:, command_name:)
|
12
|
+
self.config = config
|
13
|
+
self.options = options
|
14
|
+
self.command_name = command_name
|
15
|
+
self.commands = []
|
16
|
+
self.log_dir = options[:log_dir] || config.global.log_dir || './log'
|
17
|
+
|
18
|
+
resolve_command!
|
19
|
+
|
20
|
+
resolve_templates!
|
21
|
+
end
|
22
|
+
|
23
|
+
def execute!
|
24
|
+
colors = %i(yellow blue red green magenta cyan white)
|
25
|
+
|
26
|
+
FileUtils.mkdir_p(log_dir)
|
27
|
+
puts "Executing #{commands.size} commands using a pool of up to #{options.max_processes} processes:\n".bold.cyan.underlined
|
28
|
+
::Parallel.each((1..commands.size),
|
29
|
+
:in_processes => options.max_processes) do |i|
|
30
|
+
|
31
|
+
color = colors[(i - 1) % colors.size]
|
32
|
+
cmd = commands[i - 1]
|
33
|
+
|
34
|
+
printf('%s', "Worker: #{Parallel.worker_number}, command #{i}\n".send(color)) if options.verbose
|
35
|
+
puts "#{cmd}\n".send(color)
|
36
|
+
|
37
|
+
system %Q(( #{cmd} ) > #{log_dir}/#{command_name}.#{i}.log) unless options.dry_run
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def resolve_templates!
|
44
|
+
self.templates ||= []
|
45
|
+
options.names.each_pair do |name, indexes|
|
46
|
+
if config.templates[name]
|
47
|
+
template_array = config.templates[name].is_a?(Array) ?
|
48
|
+
config.templates[name] :
|
49
|
+
[config.templates[name]]
|
50
|
+
|
51
|
+
template_array.flatten.each do |attrs|
|
52
|
+
attributes = attrs.dup
|
53
|
+
attributes.merge!(options.override) if options.override
|
54
|
+
self.templates << ::Molder::Template.new(config: config,
|
55
|
+
name: name,
|
56
|
+
indexes: indexes,
|
57
|
+
command: command,
|
58
|
+
attributes: attributes)
|
59
|
+
end
|
60
|
+
else
|
61
|
+
raise ::Molder::InvalidTemplateName, "Template name #{name} is not valid."
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
self.templates.each do |t|
|
66
|
+
t.each_command do |cmd|
|
67
|
+
self.commands << cmd
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def resolve_command!
|
73
|
+
unless config.commands.include?(command_name)
|
74
|
+
raise(::Molder::InvalidCommandError, "Command #{command_name} is not defined in the configuration file #{options[:config]}")
|
75
|
+
end
|
76
|
+
|
77
|
+
command_hash = Hashie::Extensions::SymbolizeKeys.symbolize_keys(config.commands[command_name].to_h)
|
78
|
+
self.command = Molder::Command.new(name: command_name, config: config, **command_hash)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
data/lib/molder/cli.rb
ADDED
@@ -0,0 +1,188 @@
|
|
1
|
+
require 'hashie/mash'
|
2
|
+
require 'colored2'
|
3
|
+
require 'optionparser'
|
4
|
+
|
5
|
+
require 'etc'
|
6
|
+
|
7
|
+
module Molder
|
8
|
+
class CLI
|
9
|
+
attr_accessor :argv, :original_argv, :options, :config, :command
|
10
|
+
|
11
|
+
def initialize(argv, stdin = STDIN, stdout = STDOUT, stderr = STDERR, kernel = Kernel)
|
12
|
+
@argv, @stdin, @stdout, @stderr, @kernel = argv, stdin, stdout, stderr, kernel
|
13
|
+
|
14
|
+
self.options = Hashie::Mash.new
|
15
|
+
self.options[:max_processes] = Etc.nprocessors - 2
|
16
|
+
self.argv = argv.dup
|
17
|
+
self.original_argv = argv.dup
|
18
|
+
|
19
|
+
self.argv << '-h' if argv.empty?
|
20
|
+
|
21
|
+
parser.parse!(self.argv)
|
22
|
+
exit(0) if options.help
|
23
|
+
|
24
|
+
pre_parse!
|
25
|
+
|
26
|
+
if options.indexes
|
27
|
+
override = {}
|
28
|
+
options.names.each_pair do |name, values|
|
29
|
+
if values.nil?
|
30
|
+
override[name] = option.indexes
|
31
|
+
end
|
32
|
+
end
|
33
|
+
options.names.merge!(override)
|
34
|
+
end
|
35
|
+
|
36
|
+
self.config = if options.config
|
37
|
+
if File.exist?(options.config)
|
38
|
+
Configuration.load(options.config)
|
39
|
+
else
|
40
|
+
report_error(message: "file #{options.config} does not exist.")
|
41
|
+
end
|
42
|
+
else
|
43
|
+
Configuration.default
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def execute!
|
48
|
+
exit_code = begin
|
49
|
+
$stderr = @stderr
|
50
|
+
$stdin = @stdin
|
51
|
+
$stdout = @stdout
|
52
|
+
|
53
|
+
App.new(config: config, options: options, command_name: command).execute!
|
54
|
+
|
55
|
+
0
|
56
|
+
rescue StandardError => e
|
57
|
+
report_error(exception: e)
|
58
|
+
1
|
59
|
+
rescue SystemExit => e
|
60
|
+
e.status
|
61
|
+
ensure
|
62
|
+
$stderr = STDERR
|
63
|
+
$stdin = STDIN
|
64
|
+
$stdout = STDOUT
|
65
|
+
end
|
66
|
+
@kernel.exit(exit_code)
|
67
|
+
end
|
68
|
+
|
69
|
+
private
|
70
|
+
|
71
|
+
def pre_parse!
|
72
|
+
if argv[0] && !argv[0].start_with?('-')
|
73
|
+
self.command = argv.shift
|
74
|
+
end
|
75
|
+
|
76
|
+
if self.argv[0] && !self.argv[0].start_with?('-')
|
77
|
+
options[:names] = Hashie::Mash.new
|
78
|
+
self.argv.shift.split('/').each { |arg| parse_templates(arg) }
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def parser
|
83
|
+
OptionParser.new(nil, 35) do |opts|
|
84
|
+
opts.separator 'OPTIONS:'.bold.yellow
|
85
|
+
|
86
|
+
opts.on('-c', '--config [file]',
|
87
|
+
'Main YAML configuration file') { |config| options[:config] = config }
|
88
|
+
|
89
|
+
opts.on('-t', '--template [n1/n2/..]',
|
90
|
+
'Names of the templates to use') do |value|
|
91
|
+
options[:names] ||= Hashie::Mash.new
|
92
|
+
value.split('/').each { |arg| parse_templates(arg) }
|
93
|
+
end
|
94
|
+
|
95
|
+
opts.on('-i', '--index [range/array]',
|
96
|
+
'Numbers to use in generating commands',
|
97
|
+
'Can be a comma-separated list of values,',
|
98
|
+
'or a range, eg "1..5"') do |value|
|
99
|
+
options[:indexes] = index_expression_to_array(value)
|
100
|
+
end
|
101
|
+
|
102
|
+
opts.on('-a', '--attrs [k1=v1/k2=v2/...]',
|
103
|
+
'Provide additional attributes, or override existing ones') do |value|
|
104
|
+
h = {}
|
105
|
+
value.split('/').each do |pair|
|
106
|
+
key, value = pair.split('=')
|
107
|
+
h[key] = value
|
108
|
+
end
|
109
|
+
options[:override] = h
|
110
|
+
end
|
111
|
+
|
112
|
+
opts.on('-m', '--max-processes [number]',
|
113
|
+
'Do not start more than this many processes at once') { |value| options[:max_processes] = value.to_i }
|
114
|
+
|
115
|
+
opts.on('-l', '--log-dir [dir]',
|
116
|
+
'Directory where STDOUT of running commands is saved') { |value| options[:log_dir] = value }
|
117
|
+
|
118
|
+
opts.on('-n', '--dry-run',
|
119
|
+
'Don\'t actually run commands, just print them') { |_value| options[:dry_run] = true }
|
120
|
+
|
121
|
+
opts.on('-v', '--verbose',
|
122
|
+
'Print more output') { |_value| options[:verbose] = true }
|
123
|
+
|
124
|
+
opts.on('-b', '--backtrace',
|
125
|
+
'Show error stack trace if available') { |_value| options[:backtrace] = true }
|
126
|
+
|
127
|
+
opts.on('-h', '--help',
|
128
|
+
'Show help') do
|
129
|
+
@stdout.puts opts
|
130
|
+
options[:help] = true
|
131
|
+
end
|
132
|
+
|
133
|
+
end.tap do |p|
|
134
|
+
p.banner = <<-eof
|
135
|
+
#{'DESCRIPTION'.bold.yellow}
|
136
|
+
Molder is a template based command generator and runner for cases where you need to
|
137
|
+
generate many similar and yet somewhat different commands, defined in the
|
138
|
+
YAML template. Please read #{'https://github.com/kigster/molder'.bold.blue.underlined} for
|
139
|
+
a detailed explanation of the config file structure.
|
140
|
+
|
141
|
+
Note that the default configuration file is #{Molder::Configuration::DEFAULT_CONFIG.bold.green}.
|
142
|
+
|
143
|
+
#{'USAGE'.bold.yellow}
|
144
|
+
#{'molder [-c config.yml] command template1[n1..n2]/template2[n1,n2,..]/... [options]'.bold.blue}
|
145
|
+
#{'molder [-c config.yml] command -t template -i index [options]'.blue.bold}
|
146
|
+
|
147
|
+
#{'EXAMPLES'.bold.yellow}
|
148
|
+
#{'# The following commands assume YAML file is in the default location:'.bold.black}
|
149
|
+
#{'molder provision web[1,3,5]'.bold.blue}
|
150
|
+
|
151
|
+
#{'# -n flag means dry run — so instead of running commands, just print them:'.bold.black}
|
152
|
+
#{'molder provision web[1..4]/job[1..4] -n'.bold.blue}
|
153
|
+
|
154
|
+
eof
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
def report_error(message: nil, exception: nil)
|
159
|
+
if options[:backtrace] && exception.backtrace
|
160
|
+
@stderr.puts exception.backtrace.reverse.join("\n").yellow.italic
|
161
|
+
end
|
162
|
+
@stderr.puts "Error: #{exception.to_s.bold.red}" if exception
|
163
|
+
@stderr.puts "Error: #{message.bold.red}" if message
|
164
|
+
@kernel.exit(1)
|
165
|
+
end
|
166
|
+
|
167
|
+
def parse_templates(arg)
|
168
|
+
options[:names] ||= Hashie::Mash.new
|
169
|
+
templates = arg.split('/')
|
170
|
+
templates.each do |t|
|
171
|
+
name, indexes = parse_name(t)
|
172
|
+
options[:names][name] = indexes
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
def parse_name(t)
|
177
|
+
name, values = t.split('[')
|
178
|
+
values.gsub!(/\]/, '') if values
|
179
|
+
[name, index_expression_to_array(values)]
|
180
|
+
end
|
181
|
+
|
182
|
+
def index_expression_to_array(value = nil)
|
183
|
+
return nil if value.nil?
|
184
|
+
value.include?('..') ? eval("(#{value}).to_a") : eval("[#{value}]")
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Molder
|
2
|
+
class Command
|
3
|
+
attr_accessor :name, :config, :desc, :supervise, :concurrent, :examples, :args
|
4
|
+
|
5
|
+
def initialize(name:, config:, desc:, supervise: true, concurrent: true, examples: [], args:)
|
6
|
+
self.name = name
|
7
|
+
self.config = config
|
8
|
+
self.desc = desc
|
9
|
+
self.supervise = supervise
|
10
|
+
self.concurrent = concurrent
|
11
|
+
self.examples = examples
|
12
|
+
self.args = args
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require 'hashie/mash'
|
3
|
+
require 'hashie/extensions/parsers/yaml_erb_parser'
|
4
|
+
|
5
|
+
module Molder
|
6
|
+
class Configuration < Hashie::Mash
|
7
|
+
DEFAULT_CONFIG = 'config/molder.yml'.freeze
|
8
|
+
class << self
|
9
|
+
def default_config
|
10
|
+
DEFAULT_CONFIG
|
11
|
+
end
|
12
|
+
|
13
|
+
def default
|
14
|
+
if File.exist?(default_config)
|
15
|
+
load(default_config)
|
16
|
+
else
|
17
|
+
raise ::Molder::ConfigNotFound, "Default file #{default_config} was not found"
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
|
22
|
+
def load(file)
|
23
|
+
self.new(YAML.load(File.read(file)))
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require 'liquid'
|
3
|
+
require 'hashie'
|
4
|
+
require 'colored2'
|
5
|
+
|
6
|
+
class Symbol
|
7
|
+
def to_liquid
|
8
|
+
to_i
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
module Molder
|
13
|
+
#
|
14
|
+
# == Usage
|
15
|
+
#
|
16
|
+
# Generally you first generate a set of parameters (a nested hash) that
|
17
|
+
# represents configuration of your application. As was mentioned above, the
|
18
|
+
# parameter hash can be self-referential – it will be automatically expanded.
|
19
|
+
#
|
20
|
+
# Once you create a Renderer instance with a given parameter set, you can then
|
21
|
+
# use the +#render+ method to convert content with Renderer placeholers into a
|
22
|
+
# fully resolved string.
|
23
|
+
#
|
24
|
+
# == Example
|
25
|
+
#/
|
26
|
+
# require 'molder/renderer'
|
27
|
+
#
|
28
|
+
# params = { 'invitee' => 'Adam',
|
29
|
+
# 'extra' => 'Eve',
|
30
|
+
# 'salutation' => 'Dear {{ invitee }} & {{ extra }}',
|
31
|
+
# 'from' => 'Jesus'
|
32
|
+
# }
|
33
|
+
# @Renderer = ::Molder::Renderer.new(params)
|
34
|
+
# ⤷ #<Molder::Renderer:0x007fb90b9c32d8>
|
35
|
+
# content = '{{ salutation }}, please attend my birthday. Sincerely, {{ from }}.'
|
36
|
+
# ⤷ {{ salutation }}, please attend my birthday. Sincerely, {{ from }}.
|
37
|
+
# @Renderer.render(content)
|
38
|
+
# ⤷ "Dear Adam & Eve, please attend my birthday. Sincerely, Jesus."
|
39
|
+
#
|
40
|
+
# == Troubleshooting
|
41
|
+
#
|
42
|
+
# See errors documented under this class.
|
43
|
+
#
|
44
|
+
class Renderer
|
45
|
+
|
46
|
+
# When the parameter hash contains a circular reference, this
|
47
|
+
# error will be thrown. It is thrown after the params hash is attempted
|
48
|
+
# to be expanded MAX_RECURSIONS times.
|
49
|
+
class TooManyRecursionsError < StandardError;
|
50
|
+
end
|
51
|
+
|
52
|
+
# When a Renderer (or params) contain a reference that can not be resolved
|
53
|
+
# this error is raised.
|
54
|
+
class UnresolvedReferenceError < ArgumentError;
|
55
|
+
end
|
56
|
+
|
57
|
+
# During parameter resolution phase (constructor) this error indicates that
|
58
|
+
# internal representation of the params hash (YAML) no longer compiles after
|
59
|
+
# some parameters have been resolved. This would be an internal error that
|
60
|
+
# should be coded around and fixed as a bug if it ever to occur.
|
61
|
+
class SyntaxError < StandardError;
|
62
|
+
end
|
63
|
+
|
64
|
+
MAX_RECURSIONS = 100
|
65
|
+
|
66
|
+
attr_accessor :template
|
67
|
+
|
68
|
+
# Create Renderer object, while storing and auto-expanding params.
|
69
|
+
def initialize(template)
|
70
|
+
self.template = template
|
71
|
+
end
|
72
|
+
|
73
|
+
# Render given content using expanded params.
|
74
|
+
def render(params)
|
75
|
+
attributes = expand_arguments(Hashie.stringify_keys(params.to_h))
|
76
|
+
liquid_template = Liquid::Template.parse(template)
|
77
|
+
liquid_template.render(attributes, { strict_variables: true }).tap do
|
78
|
+
unless liquid_template.errors.empty?
|
79
|
+
raise LiquidTemplateError, "#{liquid_template.errors.map(&:message).join("\n")}"
|
80
|
+
end
|
81
|
+
end.gsub(/\n/, ' ').gsub(/\s{2,}/, ' ').strip
|
82
|
+
rescue ArgumentError => e
|
83
|
+
raise UnresolvedReferenceError.new(e)
|
84
|
+
end
|
85
|
+
|
86
|
+
private
|
87
|
+
|
88
|
+
def expand_arguments(params)
|
89
|
+
current = YAML.dump(params)
|
90
|
+
recursions = 0
|
91
|
+
|
92
|
+
while current =~ %r[{{\s*[a-z_]+\s*}}]
|
93
|
+
recursions += 1
|
94
|
+
raise TooManyRecursionsError.new if recursions > MAX_RECURSIONS
|
95
|
+
previous = current
|
96
|
+
current = ::Liquid::Template.parse(previous).render(params)
|
97
|
+
end
|
98
|
+
|
99
|
+
begin
|
100
|
+
Hashie::Mash.new(YAML.load(current))
|
101
|
+
rescue Psych::SyntaxError => e
|
102
|
+
STDERR.puts "Error parsing YAML Renderer:\n" +
|
103
|
+
e.message.red +
|
104
|
+
"\n#{current}"
|
105
|
+
raise SyntaxError.new(e)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'molder/renderer'
|
2
|
+
module Molder
|
3
|
+
class Template
|
4
|
+
attr_accessor :config, :name, :attributes, :indexes, :command
|
5
|
+
|
6
|
+
def initialize(config:, name:, indexes:, attributes: {}, command:)
|
7
|
+
self.config = config
|
8
|
+
self.name = name
|
9
|
+
self.indexes = indexes
|
10
|
+
self.command = command
|
11
|
+
self.attributes = self.class.normalize(attributes)
|
12
|
+
end
|
13
|
+
|
14
|
+
def each_command
|
15
|
+
indexes.map do |i|
|
16
|
+
self.attributes[:number] = i
|
17
|
+
self.attributes[:formatted_number] = sprintf(config.global.index_format, i)
|
18
|
+
::Molder::Renderer.new(command.args).render(attributes.dup).tap do |cmd|
|
19
|
+
yield(cmd) if block_given?
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.normalize(attrs)
|
25
|
+
override = {}
|
26
|
+
attrs.each_pair do |key, value|
|
27
|
+
if value.is_a?(Hash) && value.values.compact.empty?
|
28
|
+
override[key] = value.keys.to_a.join(',')
|
29
|
+
end
|
30
|
+
end
|
31
|
+
attrs.merge!(override)
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,7 @@
|
|
1
|
+
module Molder
|
2
|
+
VERSION = '0.1.4'.freeze
|
3
|
+
DESCRIPTION = <<-eof
|
4
|
+
Molder is a command line tool for generating and running (in parallel, across a configurable number of processes) a set of related but similar commands that are generated based on a merge of a template with a set of attributes. A key use-case is auto-generation of the host provisioning commands for an arbitrary cloud environment. The gem is not constrained to any particular cloud tool or even a command, and can be used to generate a consistent set of commands based on several customizable dimensions. For example, you could generate 600 provisioning commands for hosts in EC2, numbered from 1 to 100, constrained to the dimensions "zone-id" (values: ["a", "b", "c"]) and the data center "dc" (values: ['us-west2', 'us-east1' ]).
|
5
|
+
eof
|
6
|
+
.gsub(/\s{2,}/, ' ')
|
7
|
+
end
|
data/molder.gemspec
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
lib = File.expand_path('../lib', __FILE__)
|
2
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
3
|
+
require 'molder/version'
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = 'molder'
|
7
|
+
spec.version = ::Molder::VERSION
|
8
|
+
spec.authors = ['Konstantin Gredeskoul']
|
9
|
+
spec.email = ['kigster@gmail.com']
|
10
|
+
|
11
|
+
spec.summary = Molder::DESCRIPTION
|
12
|
+
spec.description = Molder::DESCRIPTION
|
13
|
+
spec.homepage = 'https://github.com/kigster/molder'
|
14
|
+
spec.license = 'MIT'
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
17
|
+
f.match(%r{^(test|spec|features)/})
|
18
|
+
end
|
19
|
+
spec.bindir = 'exe'
|
20
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
21
|
+
spec.require_paths = ['lib']
|
22
|
+
|
23
|
+
spec.add_dependency 'liquid'
|
24
|
+
spec.add_dependency 'hashie'
|
25
|
+
spec.add_dependency 'colored2'
|
26
|
+
spec.add_dependency 'parallel'
|
27
|
+
spec.add_dependency 'require_dir', '~> 2'
|
28
|
+
|
29
|
+
spec.add_development_dependency 'simplecov'
|
30
|
+
spec.add_development_dependency 'awesome_print'
|
31
|
+
spec.add_development_dependency 'rake'
|
32
|
+
spec.add_development_dependency 'yard'
|
33
|
+
spec.add_development_dependency 'rspec', '~> 3'
|
34
|
+
spec.add_development_dependency 'rspec-its'
|
35
|
+
end
|
metadata
ADDED
@@ -0,0 +1,240 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: molder
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.4
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Konstantin Gredeskoul
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2018-04-02 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: liquid
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: hashie
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: colored2
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: parallel
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: require_dir
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '2'
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '2'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: simplecov
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: awesome_print
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: rake
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ">="
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ">="
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: yard
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - ">="
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '0'
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - ">="
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '0'
|
139
|
+
- !ruby/object:Gem::Dependency
|
140
|
+
name: rspec
|
141
|
+
requirement: !ruby/object:Gem::Requirement
|
142
|
+
requirements:
|
143
|
+
- - "~>"
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
version: '3'
|
146
|
+
type: :development
|
147
|
+
prerelease: false
|
148
|
+
version_requirements: !ruby/object:Gem::Requirement
|
149
|
+
requirements:
|
150
|
+
- - "~>"
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
version: '3'
|
153
|
+
- !ruby/object:Gem::Dependency
|
154
|
+
name: rspec-its
|
155
|
+
requirement: !ruby/object:Gem::Requirement
|
156
|
+
requirements:
|
157
|
+
- - ">="
|
158
|
+
- !ruby/object:Gem::Version
|
159
|
+
version: '0'
|
160
|
+
type: :development
|
161
|
+
prerelease: false
|
162
|
+
version_requirements: !ruby/object:Gem::Requirement
|
163
|
+
requirements:
|
164
|
+
- - ">="
|
165
|
+
- !ruby/object:Gem::Version
|
166
|
+
version: '0'
|
167
|
+
description: 'Molder is a command line tool for generating and running (in parallel,
|
168
|
+
across a configurable number of processes) a set of related but similar commands
|
169
|
+
that are generated based on a merge of a template with a set of attributes. A key
|
170
|
+
use-case is auto-generation of the host provisioning commands for an arbitrary cloud
|
171
|
+
environment. The gem is not constrained to any particular cloud tool or even a command,
|
172
|
+
and can be used to generate a consistent set of commands based on several customizable
|
173
|
+
dimensions. For example, you could generate 600 provisioning commands for hosts
|
174
|
+
in EC2, numbered from 1 to 100, constrained to the dimensions "zone-id" (values:
|
175
|
+
["a", "b", "c"]) and the data center "dc" (values: [''us-west2'', ''us-east1'' ]).
|
176
|
+
|
177
|
+
'
|
178
|
+
email:
|
179
|
+
- kigster@gmail.com
|
180
|
+
executables:
|
181
|
+
- molder
|
182
|
+
extensions: []
|
183
|
+
extra_rdoc_files: []
|
184
|
+
files:
|
185
|
+
- ".gitignore"
|
186
|
+
- ".rspec"
|
187
|
+
- ".rspec_status"
|
188
|
+
- ".travis.yml"
|
189
|
+
- Gemfile
|
190
|
+
- LICENSE.txt
|
191
|
+
- README.md
|
192
|
+
- Rakefile
|
193
|
+
- bin/console
|
194
|
+
- bin/setup
|
195
|
+
- docs/molder-cli.png
|
196
|
+
- docs/molder.png
|
197
|
+
- exe/molder
|
198
|
+
- lib/molder.rb
|
199
|
+
- lib/molder/app.rb
|
200
|
+
- lib/molder/cli.rb
|
201
|
+
- lib/molder/command.rb
|
202
|
+
- lib/molder/configuration.rb
|
203
|
+
- lib/molder/errors.rb
|
204
|
+
- lib/molder/renderer.rb
|
205
|
+
- lib/molder/template.rb
|
206
|
+
- lib/molder/version.rb
|
207
|
+
- molder.gemspec
|
208
|
+
homepage: https://github.com/kigster/molder
|
209
|
+
licenses:
|
210
|
+
- MIT
|
211
|
+
metadata: {}
|
212
|
+
post_install_message:
|
213
|
+
rdoc_options: []
|
214
|
+
require_paths:
|
215
|
+
- lib
|
216
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
217
|
+
requirements:
|
218
|
+
- - ">="
|
219
|
+
- !ruby/object:Gem::Version
|
220
|
+
version: '0'
|
221
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
222
|
+
requirements:
|
223
|
+
- - ">="
|
224
|
+
- !ruby/object:Gem::Version
|
225
|
+
version: '0'
|
226
|
+
requirements: []
|
227
|
+
rubyforge_project:
|
228
|
+
rubygems_version: 2.7.6
|
229
|
+
signing_key:
|
230
|
+
specification_version: 4
|
231
|
+
summary: 'Molder is a command line tool for generating and running (in parallel, across
|
232
|
+
a configurable number of processes) a set of related but similar commands that are
|
233
|
+
generated based on a merge of a template with a set of attributes. A key use-case
|
234
|
+
is auto-generation of the host provisioning commands for an arbitrary cloud environment.
|
235
|
+
The gem is not constrained to any particular cloud tool or even a command, and can
|
236
|
+
be used to generate a consistent set of commands based on several customizable dimensions.
|
237
|
+
For example, you could generate 600 provisioning commands for hosts in EC2, numbered
|
238
|
+
from 1 to 100, constrained to the dimensions "zone-id" (values: ["a", "b", "c"])
|
239
|
+
and the data center "dc" (values: [''us-west2'', ''us-east1'' ]).'
|
240
|
+
test_files: []
|