molder 0.1.4
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
[![Build Status](https://travis-ci.org/kigster/molder.svg?branch=master)](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
|
+
![output](docs/molder.png)
|
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
|
+
![cli](docs/molder-cli.png)
|
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: []
|