rzo 0.3.0 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +9 -0
- data/.travis.yml +1 -0
- data/CHANGELOG.md +18 -1
- data/Guardfile +33 -18
- data/README.md +20 -4
- data/lib/rzo/app.rb +14 -2
- data/lib/rzo/app/config_validation.rb +306 -0
- data/lib/rzo/app/roles.rb +31 -0
- data/lib/rzo/app/subcommand.rb +8 -14
- data/lib/rzo/option_parsing.rb +15 -5
- data/lib/rzo/version.rb +1 -1
- data/rzo.gemspec +1 -0
- data/scripts/functional_gem_behavior.sh +343 -0
- metadata +19 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 57197ea4ec41e6e0b5ae935a754b415ee3ef7ebc
|
4
|
+
data.tar.gz: ae8140e57a89faf648963cb66f96f45f1fdfa8e5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 627ff40899c739564bd9cd0a5a8bdb0d5995ab76a650e11293a51cf322c456e7310709940b016d539e66c8a23984c17b2ccd67eaef52aadc4a2a8777affde89b
|
7
|
+
data.tar.gz: eaecfbd7e222a971b4c1737b0451b30e24a8dbd0a402ff46ffca2ba0c11895a98914e3618f4b49a21dd6205a166d14d68c9f3e0741eeba900e07c743e0f63eb6
|
data/.rubocop.yml
CHANGED
@@ -7,8 +7,11 @@ AllCops:
|
|
7
7
|
- pkg/**/*
|
8
8
|
- spec/fixtures/**/*
|
9
9
|
- lib/rzo/trollop.rb
|
10
|
+
- Vagrantfile
|
10
11
|
|
11
12
|
# Cop's to ignore
|
13
|
+
Style/RedundantReturn:
|
14
|
+
Enabled: false
|
12
15
|
|
13
16
|
Lint/UnneededDisable:
|
14
17
|
Enabled: false
|
@@ -16,7 +19,13 @@ Lint/UnneededDisable:
|
|
16
19
|
Layout/MultilineOperationIndentation:
|
17
20
|
Enabled: false
|
18
21
|
|
22
|
+
# if / else / end is more clear than conditional assignment
|
23
|
+
Style/ConditionalAssignment:
|
24
|
+
Enabled: false
|
25
|
+
|
19
26
|
# With this enabled it suggests a change that will break the Gemfile
|
27
|
+
# Also, if name = hsh['name'] is really useful to avoid Unknown method called on
|
28
|
+
# nil object errors.
|
20
29
|
Lint/AssignmentInCondition:
|
21
30
|
Enabled: false
|
22
31
|
|
data/.travis.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,7 +1,24 @@
|
|
1
1
|
# Change Log
|
2
2
|
|
3
|
-
## [v0.
|
3
|
+
## [v0.4.0](https://github.com/ghoneycutt/rizzo/tree/v0.4.0)
|
4
4
|
|
5
|
+
[Full Changelog](https://github.com/ghoneycutt/rizzo/compare/v0.3.0...v0.4.0)
|
6
|
+
|
7
|
+
**Closed issues:**
|
8
|
+
|
9
|
+
- Add Safety Checking [\#16](https://github.com/ghoneycutt/rizzo/issues/16)
|
10
|
+
- Fix uninitialized constant Rzo::App::Subcommand::Pathname \(NameError\) [\#14](https://github.com/ghoneycutt/rizzo/issues/14)
|
11
|
+
- Add `rzo roles' command to list the roles [\#10](https://github.com/ghoneycutt/rizzo/issues/10)
|
12
|
+
- Convert into a ruby gem [\#5](https://github.com/ghoneycutt/rizzo/issues/5)
|
13
|
+
|
14
|
+
**Merged pull requests:**
|
15
|
+
|
16
|
+
- \(\#16\) Add Directory Safety Checks [\#20](https://github.com/ghoneycutt/rizzo/pull/20) ([jeffmccune](https://github.com/jeffmccune))
|
17
|
+
- \(\#14\) Fix uninitialized constant Pathname \(NameError\) [\#15](https://github.com/ghoneycutt/rizzo/pull/15) ([jeffmccune](https://github.com/jeffmccune))
|
18
|
+
- Add functional testing [\#13](https://github.com/ghoneycutt/rizzo/pull/13) ([jeffmccune](https://github.com/jeffmccune))
|
19
|
+
- \(\#10\) Add roles subcommand [\#12](https://github.com/ghoneycutt/rizzo/pull/12) ([jeffmccune](https://github.com/jeffmccune))
|
20
|
+
|
21
|
+
## [v0.3.0](https://github.com/ghoneycutt/rizzo/tree/v0.3.0) (2017-08-24)
|
5
22
|
[Full Changelog](https://github.com/ghoneycutt/rizzo/compare/v0.2.0...v0.3.0)
|
6
23
|
|
7
24
|
**Merged pull requests:**
|
data/Guardfile
CHANGED
@@ -1,18 +1,14 @@
|
|
1
|
-
#
|
2
|
-
|
3
|
-
|
4
|
-
# * bundler binstubs: 'bin/rspec'
|
5
|
-
# * spring: 'bin/rspec' (This will use spring if running and you have
|
6
|
-
# installed the spring binstubs per the docs)
|
7
|
-
# * zeus: 'zeus rspec' (requires the server to be started separately)
|
8
|
-
# * 'just' rspec: 'rspec'
|
9
|
-
rspec_results = File.expand_path('.rspec_status')
|
10
|
-
guard 'yard', server: false do
|
11
|
-
watch(%r{app\/.+\.rb})
|
12
|
-
watch(%r{lib\/.+\.rb})
|
13
|
-
watch(%r{ext\/.+\.c})
|
1
|
+
# Convert a lib path to a spec path
|
2
|
+
def to_spec(path)
|
3
|
+
path.sub('lib/', 'spec/').sub(/\.rb$/, '_spec.rb')
|
14
4
|
end
|
15
5
|
|
6
|
+
# guard 'yard', server: false do
|
7
|
+
# watch(%r{app\/.+\.rb})
|
8
|
+
# watch(%r{lib\/.+\.rb})
|
9
|
+
# watch(%r{ext\/.+\.c})
|
10
|
+
# end
|
11
|
+
|
16
12
|
guard :rubocop do
|
17
13
|
watch(/.+\.rb$/)
|
18
14
|
watch('Gemfile')
|
@@ -22,20 +18,39 @@ guard :rubocop do
|
|
22
18
|
watch(%r{(?:.+/)?\.rubocop(?:_todo)?\.yml$}) { |m| File.dirname(m[0]) }
|
23
19
|
end
|
24
20
|
|
25
|
-
guard :
|
21
|
+
guard :shell do
|
26
22
|
require 'guard/rspec/dsl'
|
23
|
+
require 'pathname'
|
27
24
|
dsl = Guard::RSpec::Dsl.new(self)
|
28
25
|
|
26
|
+
runner = proc do |p|
|
27
|
+
if system("rspec -fd #{p}")
|
28
|
+
n 'Spec tests pass', 'rspec', :success
|
29
|
+
else
|
30
|
+
n 'Spec tests fail', 'rspec', :failed
|
31
|
+
end
|
32
|
+
nil
|
33
|
+
end
|
34
|
+
|
29
35
|
# Feel free to open issues for suggestions and improvements
|
30
36
|
|
31
37
|
# RSpec files
|
32
38
|
rspec = dsl.rspec
|
33
|
-
watch(rspec.spec_helper)
|
34
|
-
|
35
|
-
|
39
|
+
watch(rspec.spec_helper) do |m|
|
40
|
+
runner.call(m[0])
|
41
|
+
end
|
42
|
+
watch(rspec.spec_support) do
|
43
|
+
runner.call(m[0])
|
44
|
+
end
|
45
|
+
watch rspec.spec_files do |m|
|
46
|
+
runner.call(m[0])
|
47
|
+
end
|
36
48
|
|
37
49
|
# Ruby files
|
38
50
|
ruby = dsl.ruby
|
39
|
-
|
51
|
+
watch(ruby.lib_files) do |m|
|
52
|
+
spec = Pathname.new(to_spec(m[0]))
|
53
|
+
runner.call(spec.to_s) if spec.readable?
|
54
|
+
end
|
40
55
|
end
|
41
56
|
# vim:ft=ruby
|
data/README.md
CHANGED
@@ -4,6 +4,12 @@ Rizzo is a heavily customized Vagrant configuration and work flow with a
|
|
4
4
|
role based focus. It is meant to make working with Vagrant easier and
|
5
5
|
purpose built for layered Puppet control repositories.
|
6
6
|
|
7
|
+
Rizzo loads a personal configuration file, `~/.rizzo.json` by default, which
|
8
|
+
lists one or more control repositories. Rizzo then looks for a loads a
|
9
|
+
`.rizzo.json` configuration file located at the root of the top level control
|
10
|
+
repository. The top level control repository is the first listed in
|
11
|
+
the array of control repositories in the personal configuration file.
|
12
|
+
|
7
13
|
There should be at least one node for every role that is managed by a
|
8
14
|
control repo. This information is stored in `.rizzo.json` under the
|
9
15
|
control repo. This makes it apparent what roles are available and aids
|
@@ -90,7 +96,11 @@ repo.
|
|
90
96
|
|
91
97
|
## `~/.rizzo.json`
|
92
98
|
|
93
|
-
|
99
|
+
The personal configuration file is loaded first from `~/.rizzo.json` by default.
|
100
|
+
The global `--config` option allows the end user to specific a different path to
|
101
|
+
the personal configuration file.
|
102
|
+
|
103
|
+
Using this example, change the paths to your git repos:
|
94
104
|
|
95
105
|
```json
|
96
106
|
{
|
@@ -98,6 +108,7 @@ Change the paths to your git repos
|
|
98
108
|
"bootstrap_repo_path": "/Users/gh/git/bootstrap"
|
99
109
|
},
|
100
110
|
"control_repos": [
|
111
|
+
"/Users/gh/git/puppet-control-myteam",
|
101
112
|
"/Users/gh/git/puppetdata",
|
102
113
|
"/Users/gh/git/puppet-modules"
|
103
114
|
],
|
@@ -124,11 +135,16 @@ Change the paths to your git repos
|
|
124
135
|
}
|
125
136
|
```
|
126
137
|
|
127
|
-
Once you have `~/.rizzo.json`, change to
|
138
|
+
Once you have a personal config file, `~/.rizzo.json`, change directories to
|
139
|
+
your top level control repository and generate your `Vagrantfile`:
|
128
140
|
|
129
141
|
```shell
|
142
|
+
cd ~/git/puppet-control-myteam
|
130
143
|
bundle exec rizzo generate
|
131
144
|
```
|
145
|
+
|
146
|
+
Expected output:
|
147
|
+
|
132
148
|
```
|
133
149
|
Wrote vagrant config to Vagrantfile
|
134
150
|
```
|
@@ -155,8 +171,8 @@ VM, run `vagrant status NAME`.
|
|
155
171
|
### defaults
|
156
172
|
|
157
173
|
The defaults hash is merged with each node entries hash. Put user
|
158
|
-
specific entries in `~/.rizzo.json` and
|
159
|
-
`${PATH_TO_CONTROL_REPO}/.rizzo.json`.
|
174
|
+
specific entries in the personal configuration file at `~/.rizzo.json` and
|
175
|
+
control repo specific entries in `${PATH_TO_CONTROL_REPO}/.rizzo.json`.
|
160
176
|
|
161
177
|
### control_repos
|
162
178
|
|
data/lib/rzo/app.rb
CHANGED
@@ -3,6 +3,7 @@ require 'rzo/logging'
|
|
3
3
|
require 'rzo/option_parsing'
|
4
4
|
require 'rzo/app/config'
|
5
5
|
require 'rzo/app/generate'
|
6
|
+
require 'rzo/app/roles'
|
6
7
|
require 'json'
|
7
8
|
|
8
9
|
module Rzo
|
@@ -24,9 +25,11 @@ module Rzo
|
|
24
25
|
# method in the app controller
|
25
26
|
class ErrorAndExit < StandardError
|
26
27
|
attr_accessor :exit_status
|
28
|
+
attr_accessor :log_fatal
|
27
29
|
def initialize(message = nil, exit_status = 1)
|
28
30
|
super(message)
|
29
31
|
self.exit_status = exit_status
|
32
|
+
self.log_fatal = []
|
30
33
|
end
|
31
34
|
end
|
32
35
|
|
@@ -59,6 +62,12 @@ module Rzo
|
|
59
62
|
@generate ||= Generate.new(opts, @stdout, @stderr)
|
60
63
|
end
|
61
64
|
|
65
|
+
##
|
66
|
+
# Accessor to Subcommand::Config
|
67
|
+
def config
|
68
|
+
@config ||= Config.new(opts, @stdout, @stderr)
|
69
|
+
end
|
70
|
+
|
62
71
|
##
|
63
72
|
# Override this later to allow trollop to write to an intercepted file
|
64
73
|
# descriptor for testing. This will avoid trollop's behavior of calling
|
@@ -76,14 +85,17 @@ module Rzo
|
|
76
85
|
def run
|
77
86
|
case opts[:subcommand]
|
78
87
|
when 'config'
|
79
|
-
|
88
|
+
config.run
|
80
89
|
when 'generate'
|
81
90
|
generate.run
|
91
|
+
when 'roles'
|
92
|
+
Roles.new(opts, @stdout, @stderr).run
|
82
93
|
else
|
83
94
|
educate
|
84
95
|
end
|
85
96
|
rescue ErrorAndExit => e
|
86
|
-
log.
|
97
|
+
log.fatal e.message
|
98
|
+
e.log_fatal.each { |m| log.fatal(m) }
|
87
99
|
e.backtrace.each { |l| log.debug(l) }
|
88
100
|
e.exit_status
|
89
101
|
end
|
@@ -0,0 +1,306 @@
|
|
1
|
+
require 'rzo/app'
|
2
|
+
require 'pathname'
|
3
|
+
require 'json-schema'
|
4
|
+
# rubocop:disable Style/GuardClause
|
5
|
+
module Rzo
|
6
|
+
class App
|
7
|
+
##
|
8
|
+
# Mix-in module providing configuration validation methods and safety
|
9
|
+
# checking. The goal is to provide useful feedback to the end user in the
|
10
|
+
# situation where ~/.rizzo.json is configured to point at directories which
|
11
|
+
# do not exist, have missing keys, etc...
|
12
|
+
# rubocop:disable Metrics/ModuleLength
|
13
|
+
module ConfigValidation
|
14
|
+
## Rizzo configuration schema for the personal configuration file at
|
15
|
+
# ~/.rizzo.json. Minimum necessary to load the complete configuration from
|
16
|
+
# all control repositories.
|
17
|
+
RZO_PERSONAL_CONFIG_SCHEMA = {
|
18
|
+
'$schema' => 'http://json-schema.org/draft-06/schema#',
|
19
|
+
title: 'Personal Configuration',
|
20
|
+
description: 'Rizzo personal configuration file',
|
21
|
+
type: 'object',
|
22
|
+
properties: {
|
23
|
+
defaults: {
|
24
|
+
type: 'object',
|
25
|
+
},
|
26
|
+
control_repos: {
|
27
|
+
type: 'array',
|
28
|
+
items: { type: 'string' },
|
29
|
+
uniqueItems: true,
|
30
|
+
},
|
31
|
+
},
|
32
|
+
required: ['control_repos'],
|
33
|
+
}.freeze
|
34
|
+
## Rizzo complete configuration schema. This should move to a JSON file outside
|
35
|
+
# the code.
|
36
|
+
RZO_REPO_CONFIG_SCHEMA = {
|
37
|
+
type: 'object',
|
38
|
+
required: %w[defaults control_repos puppetmaster],
|
39
|
+
properties: {
|
40
|
+
defaults: {
|
41
|
+
type: 'object',
|
42
|
+
required: ['bootstrap_repo_path'],
|
43
|
+
properties: {
|
44
|
+
bootstrap_repo_path: {
|
45
|
+
type: 'string',
|
46
|
+
pattern: '^([a-zA-Z]:){0,1}(/[^/]+)+$',
|
47
|
+
},
|
48
|
+
},
|
49
|
+
},
|
50
|
+
puppetmaster: {
|
51
|
+
type: 'object',
|
52
|
+
required: %w[name modulepath synced_folders],
|
53
|
+
properties: {
|
54
|
+
name: {
|
55
|
+
type: 'array',
|
56
|
+
items: { type: 'string' },
|
57
|
+
},
|
58
|
+
modulepath: {
|
59
|
+
type: 'array',
|
60
|
+
items: { type: 'string' },
|
61
|
+
},
|
62
|
+
synced_folders: {
|
63
|
+
'$schema' => 'http://json-schema.org/draft-06/schema#',
|
64
|
+
type: 'object',
|
65
|
+
properties: {
|
66
|
+
'/' => {},
|
67
|
+
patternProperties: {
|
68
|
+
'^(/[^/]+)+$' => {},
|
69
|
+
},
|
70
|
+
additionalProperties: false,
|
71
|
+
required: ['/'],
|
72
|
+
}
|
73
|
+
},
|
74
|
+
},
|
75
|
+
},
|
76
|
+
control_repos: {
|
77
|
+
type: 'array',
|
78
|
+
items: { type: 'string' },
|
79
|
+
uniqueItems: true,
|
80
|
+
},
|
81
|
+
nodes: {
|
82
|
+
type: 'array',
|
83
|
+
items: {
|
84
|
+
type: 'object',
|
85
|
+
required: %w[name hostname ip],
|
86
|
+
properties: {
|
87
|
+
name: { type: 'string' },
|
88
|
+
hostname: { type: 'string' },
|
89
|
+
ip: { type: 'string' },
|
90
|
+
memory: {
|
91
|
+
type: 'string',
|
92
|
+
pattern: '^[0-9]+$'
|
93
|
+
},
|
94
|
+
forwarded_ports: {
|
95
|
+
type: 'array',
|
96
|
+
items: {
|
97
|
+
type: 'object',
|
98
|
+
required: %w[guest host],
|
99
|
+
properties: {
|
100
|
+
guest: {
|
101
|
+
type: 'string',
|
102
|
+
pattern: '^[0-9]+$',
|
103
|
+
},
|
104
|
+
host: {
|
105
|
+
type: 'string',
|
106
|
+
pattern: '^[0-9]+$',
|
107
|
+
},
|
108
|
+
},
|
109
|
+
},
|
110
|
+
},
|
111
|
+
},
|
112
|
+
},
|
113
|
+
uniqueItems: true,
|
114
|
+
}
|
115
|
+
},
|
116
|
+
}.freeze
|
117
|
+
# The checks to execute, in order. Each method must return nil if there
|
118
|
+
# are no issues found. Otherwise, the check should return either one, or
|
119
|
+
# an array of Issue instances.
|
120
|
+
CHECKS_PERSONAL_CONFIG = %i[validate_personal_schema validate_control_repos].freeze
|
121
|
+
CHECKS_REPO_CONFIG = %i[validate_schema validate_defaults_key validate_control_repos].freeze
|
122
|
+
|
123
|
+
##
|
124
|
+
# Class to model an issue found during validation
|
125
|
+
class Issue
|
126
|
+
attr_accessor :message
|
127
|
+
|
128
|
+
def initialize(msg)
|
129
|
+
self.message = msg
|
130
|
+
end
|
131
|
+
|
132
|
+
def to_s
|
133
|
+
message
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
##
|
138
|
+
# Compute Issues given a config map (base or complete), and an Array of
|
139
|
+
# methods to execute.
|
140
|
+
#
|
141
|
+
# @param [Array<Symbol>] checks the method identifiers to execute, passing
|
142
|
+
# config. These methods must return nil (no issue found), an Issue
|
143
|
+
# instance, or Array<Instance> for multiple issues found.
|
144
|
+
#
|
145
|
+
# @param [Hash] config the config hash, either a base configuration or a
|
146
|
+
# fully merged configuration.
|
147
|
+
#
|
148
|
+
# @return [Array<Issue>] Array of issue instances, or an empty array if no
|
149
|
+
# issues found with the config.
|
150
|
+
def compute_issues(checks, config)
|
151
|
+
ctx = self
|
152
|
+
checks.each_with_object([]) do |mth, ary|
|
153
|
+
debug "Checking config for #{mth} issues"
|
154
|
+
if issue = ctx.send(mth, config)
|
155
|
+
# May get back an Array<Issue> or one Issue
|
156
|
+
ary.concat([*issue])
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
##
|
162
|
+
# Validate a personal configuration, typically originating from
|
163
|
+
# ~/.rizzo.json. This configuration is necessary to build a complete
|
164
|
+
# control repo configuration using the top level control repo. This
|
165
|
+
# validation focuses on the minimum necessary configuration to bootstrap
|
166
|
+
# the complete configuration, primarily the repo locations and existence.
|
167
|
+
def validate_personal_config!(config)
|
168
|
+
issues = compute_issues(CHECKS_PERSONAL_CONFIG, config)
|
169
|
+
if issues.empty?
|
170
|
+
debug 'No issues detected with the personal configuration.'
|
171
|
+
else
|
172
|
+
validate_inform!(issues)
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
##
|
177
|
+
# Validate a complete loaded configuration. This is distinct from a base
|
178
|
+
# configuration in that the JSON files in each control repository have
|
179
|
+
# already been merged, in order, on top of the base configuration
|
180
|
+
# originating at ~/.rizzo.json. This implements safety checking. These
|
181
|
+
# methods are expected to execute within the context of a
|
182
|
+
# Rzo::App::Subcommand instance, therefore log methods and the parsed
|
183
|
+
# configuration are assumed to be available.
|
184
|
+
#
|
185
|
+
# The approach is to collect an Array of Issue instances. If issues are
|
186
|
+
# found, control is handed off to validate_inform! to inform the user of
|
187
|
+
# the issues and potentially abort the program.
|
188
|
+
#
|
189
|
+
# @param [Hash] config the config hash, fully merged by load_config!
|
190
|
+
def validate_complete_config!(config)
|
191
|
+
issues = compute_issues(CHECKS_REPO_CONFIG, config)
|
192
|
+
if issues.empty?
|
193
|
+
debug 'No issues detected with the complete, merged configuration.'
|
194
|
+
else
|
195
|
+
validate_inform!(issues)
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
##
|
200
|
+
# Validate using
|
201
|
+
# [json-schema](https://github.com/ruby-json-schema/json-schema)
|
202
|
+
#
|
203
|
+
# @return [Issue,nil] Issue found, or nil if no issues found.
|
204
|
+
def validate_schema(config)
|
205
|
+
if JSON::Validator.validate(RZO_REPO_CONFIG_SCHEMA, config)
|
206
|
+
debug 'No schema violations found in loaded config.'
|
207
|
+
return nil
|
208
|
+
else
|
209
|
+
err_msgs = JSON::Validator.fully_validate(RZO_REPO_CONFIG_SCHEMA, config)
|
210
|
+
return err_msgs.map { |msg| Issue.new("Schema violation: #{msg}") }
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
##
|
215
|
+
# Validate the configuration has a top level key named "defaults" and the
|
216
|
+
# value is a Hash map.
|
217
|
+
# rubocop:disable Metrics/MethodLength
|
218
|
+
#
|
219
|
+
# @return [Issue,nil] Issue found, or nil if no issues found.
|
220
|
+
def validate_defaults_key(config)
|
221
|
+
if defaults = config['defaults']
|
222
|
+
return Issue.new('Top level key "defaults" must have a Hash value') unless defaults.is_a? Hash
|
223
|
+
else
|
224
|
+
return Issue.new('Configuration does not contain top level "defaults" key')
|
225
|
+
end
|
226
|
+
if pth = defaults['bootstrap_repo_path']
|
227
|
+
return Issue.new('#/defaults/bootstrap_repo_path is not a String') unless pth.is_a? String
|
228
|
+
else
|
229
|
+
return Issue.new 'Configuration "defaults" value does not contain a '\
|
230
|
+
'"bootstrap_repo_path" key. For example, '\
|
231
|
+
'{"defaults":{"bootstrap_repo_path":"/tmp/foo"}}'
|
232
|
+
end
|
233
|
+
validate_existence(pth, '#/defaults/bootstrap_repo_path value of ')
|
234
|
+
end
|
235
|
+
# rubocop:enable Metrics/MethodLength
|
236
|
+
|
237
|
+
##
|
238
|
+
# Validate the top level "control_repos" key, which should have a value of
|
239
|
+
# Array<String> where each string value is a fully qualified path.
|
240
|
+
#
|
241
|
+
# @return [Issue,nil] Issue found, or nil if no issues found.
|
242
|
+
def validate_control_repos(config)
|
243
|
+
if repos = config['control_repos']
|
244
|
+
return Issue.new('Top level key "control_repos" must have an Array value') unless repos.is_a? Array
|
245
|
+
else
|
246
|
+
return Issue.new('Top level key "control_repos" is not specified. It must be an Array of paths to your control repos.')
|
247
|
+
end
|
248
|
+
repos.each_with_object([]) do |pth, ary|
|
249
|
+
if issue = validate_existence(pth, '#/control_repos')
|
250
|
+
ary << issue
|
251
|
+
end
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|
255
|
+
##
|
256
|
+
# Given a string, validate it's a fully qualified path, readable, and a
|
257
|
+
# git directory.
|
258
|
+
#
|
259
|
+
# @return [Issue,Array<Issue>,nil] nil if no issues found, or one or more
|
260
|
+
# Issue instances.
|
261
|
+
def validate_existence(path, prefix = '')
|
262
|
+
pn = Pathname.new(path)
|
263
|
+
git = pn + '.git'
|
264
|
+
return Issue.new("#{prefix}#{pn} is not an absolute path. It must be fully qualified, not relative") unless pn.absolute?
|
265
|
+
return Issue.new("#{prefix}#{pn} is not a directory. Has it been cloned?") unless pn.directory?
|
266
|
+
return Issue.new("#{prefix}#{pn} is not readable. Are permissions correct?") unless pn.readable?
|
267
|
+
return Issue.new("#{prefix}#{git} does not exist. Has #{git.dirname} been cloned properly?") unless git.directory?
|
268
|
+
end
|
269
|
+
|
270
|
+
##
|
271
|
+
# Validate the personal configuration, focus on ensuring the rest of the
|
272
|
+
# configuration can load properly.
|
273
|
+
#
|
274
|
+
# @return [Issue,Array<Issue>,nil] nil if no issues found, or one or more
|
275
|
+
# Issue instances.
|
276
|
+
def validate_personal_schema(config)
|
277
|
+
if JSON::Validator.validate(RZO_PERSONAL_CONFIG_SCHEMA, config)
|
278
|
+
debug 'No schema violations found in personal configuration file.'
|
279
|
+
return nil
|
280
|
+
else
|
281
|
+
err_msgs = JSON::Validator.fully_validate(RZO_PERSONAL_CONFIG_SCHEMA, config)
|
282
|
+
return err_msgs.map { |msg| Issue.new("Personal config problem: #{msg}") }
|
283
|
+
end
|
284
|
+
end
|
285
|
+
|
286
|
+
# Inform the user about issues found and exit the program. The top level
|
287
|
+
# exception handler is not expected to display much information on
|
288
|
+
# validation errors. This method is expected to provide the helpful
|
289
|
+
# guidance.
|
290
|
+
#
|
291
|
+
# @param [Array<Issue>] issues Array of issues. Each hash must have at
|
292
|
+
# least a key named `:message`
|
293
|
+
def validate_inform!(issues)
|
294
|
+
if opts[:validate]
|
295
|
+
msg = "Validation issues found with #{opts[:config]}"
|
296
|
+
exc = ErrorAndExit.new(msg, 2)
|
297
|
+
exc.log_fatal = issues.each_with_object([]) { |i, a| a << i.to_s }
|
298
|
+
raise exc
|
299
|
+
else
|
300
|
+
issues.each { |i| log.warn(i.to_s) }
|
301
|
+
end
|
302
|
+
end
|
303
|
+
end
|
304
|
+
# rubocop:enable Metrics/ModuleLength
|
305
|
+
end
|
306
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'rzo/app/subcommand'
|
2
|
+
module Rzo
|
3
|
+
class App
|
4
|
+
##
|
5
|
+
# Load all rizzo config files and print the roles
|
6
|
+
class Roles < Subcommand
|
7
|
+
attr_reader :config
|
8
|
+
|
9
|
+
##
|
10
|
+
# Map the combined config to a list of roles. No effort is made to sort
|
11
|
+
# them.
|
12
|
+
#
|
13
|
+
# @return [Array<String>] array of strings identifying each Puppet role
|
14
|
+
# name. This is the same as the name of the VM.
|
15
|
+
def roles
|
16
|
+
return [] unless nodes = config['nodes']
|
17
|
+
nodes.each_with_object([]) do |node, a|
|
18
|
+
next unless node['name']
|
19
|
+
a << node['name']
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def run
|
24
|
+
exit_status = 0
|
25
|
+
load_config!
|
26
|
+
write_file(opts[:output]) { |fd| fd.puts(roles) }
|
27
|
+
exit_status
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
data/lib/rzo/app/subcommand.rb
CHANGED
@@ -1,9 +1,12 @@
|
|
1
|
+
require 'pathname'
|
1
2
|
require 'rzo/logging'
|
2
3
|
require 'deep_merge'
|
4
|
+
require 'rzo/app/config_validation'
|
3
5
|
module Rzo
|
4
6
|
class App
|
5
7
|
# The base class for subcommands
|
6
8
|
class Subcommand
|
9
|
+
include ConfigValidation
|
7
10
|
include Logging
|
8
11
|
extend Logging
|
9
12
|
# The options hash injected from the application controller via the
|
@@ -55,12 +58,14 @@ module Rzo
|
|
55
58
|
# at first match and merge on top of local, defaults (~/.rizzo.json)
|
56
59
|
def load_config!
|
57
60
|
config = load_rizzo_config(opts[:config])
|
58
|
-
|
61
|
+
validate_personal_config!(config)
|
59
62
|
repos = config['control_repos']
|
60
63
|
@config = load_repo_configs(config, repos)
|
61
64
|
debug "Merged configuration: \n#{JSON.pretty_generate(@config)}"
|
62
|
-
|
63
|
-
|
65
|
+
# TODO: Move these validations to an instance method?
|
66
|
+
validate_complete_config!(@config)
|
67
|
+
# validate_forwarded_ports(@config)
|
68
|
+
# validate_ip_addresses(@config)
|
64
69
|
@config
|
65
70
|
end
|
66
71
|
|
@@ -87,17 +92,6 @@ module Rzo
|
|
87
92
|
end
|
88
93
|
end
|
89
94
|
|
90
|
-
##
|
91
|
-
# Basic validation of the configuration file content.
|
92
|
-
#
|
93
|
-
# @param [Hash] config the configuration map
|
94
|
-
def validate_config(config)
|
95
|
-
errors = []
|
96
|
-
errors.push 'control_repos key is not an Array' unless config['control_repos'].is_a?(Array)
|
97
|
-
errors.each { |l| log.error l }
|
98
|
-
raise ErrorAndExit, 'Errors found in config file. Cannot proceed.' unless errors.empty?
|
99
|
-
end
|
100
|
-
|
101
95
|
##
|
102
96
|
# Check for duplicate forwarded host ports across all hosts and exit
|
103
97
|
# non-zero with an error message if found.
|
data/lib/rzo/option_parsing.rb
CHANGED
@@ -47,7 +47,7 @@ module Rzo
|
|
47
47
|
# name.
|
48
48
|
#
|
49
49
|
# @return [Hash<Symbol, String>] Global options
|
50
|
-
# rubocop:disable Metrics/MethodLength
|
50
|
+
# rubocop:disable Metrics/MethodLength, Metrics/AbcSize
|
51
51
|
def parse_global_options!(argv, env)
|
52
52
|
semver = Rzo::VERSION
|
53
53
|
prog_name = NAME
|
@@ -58,9 +58,13 @@ module Rzo
|
|
58
58
|
log_msg = 'Log file to write to or keywords '\
|
59
59
|
'STDOUT, STDERR {RZO_LOGTO}'
|
60
60
|
opt :logto, log_msg, default: env['RZO_LOGTO'] || 'STDERR'
|
61
|
+
opt :validate, 'Check the configuration for common issues {RZO_VALIDATE="false"}',
|
62
|
+
default: env['RZO_VALIDATE'] == 'false' ? false : true
|
61
63
|
opt :syslog, 'Log to syslog', default: false, conflicts: :logto
|
62
|
-
opt :verbose, 'Set log level to INFO'
|
63
|
-
|
64
|
+
opt :verbose, 'Set log level to INFO {RZO_VERBOSE="true"}',
|
65
|
+
default: env['RZO_VERBOSE'] == 'true'
|
66
|
+
opt :debug, 'Set log level to DEBUG {RZO_DEBUG="true"}',
|
67
|
+
default: env['RZO_DEBUG'] == 'true'
|
64
68
|
opt :config, 'Rizzo config file {RZO_CONFIG}',
|
65
69
|
default: env['RZO_CONFIG'] || '~/.rizzo.json'
|
66
70
|
end
|
@@ -86,7 +90,7 @@ module Rzo
|
|
86
90
|
# parsed.
|
87
91
|
#
|
88
92
|
# @return [Hash<Symbol, String>] Subcommand specific options hash
|
89
|
-
# rubocop:disable Metrics/MethodLength
|
93
|
+
# rubocop:disable Metrics/MethodLength, Metrics/AbcSize, Metrics/CyclomaticComplexity
|
90
94
|
def parse_subcommand_options!(subcommand, argv, env)
|
91
95
|
prog_name = NAME
|
92
96
|
case subcommand
|
@@ -100,11 +104,16 @@ module Rzo
|
|
100
104
|
banner "#{prog_name} #{subcommand} options:"
|
101
105
|
opt :vagrantfile, 'Output Vagrantfile', short: 'o', default: env['RZO_VAGRANTFILE'] || 'Vagrantfile'
|
102
106
|
end
|
107
|
+
when 'roles'
|
108
|
+
Rzo::Trollop.options(argv) do
|
109
|
+
banner "#{prog_name} #{subcommand} options:"
|
110
|
+
opt :output, 'Roles output', short: 'o', default: env['RZO_OUTPUT'] || 'STDOUT'
|
111
|
+
end
|
103
112
|
else
|
104
113
|
Rzo::Trollop.die "Unknown subcommand: #{subcommand.inspect}"
|
105
114
|
end
|
106
115
|
end
|
107
|
-
# rubocop:enable Metrics/MethodLength, Metrics/AbcSize
|
116
|
+
# rubocop:enable Metrics/MethodLength, Metrics/AbcSize, Metrics/CyclomaticComplexity
|
108
117
|
|
109
118
|
# The name of the executable, could be `rizzo` or `rzo`
|
110
119
|
NAME = File.basename($PROGRAM_NAME).freeze
|
@@ -116,6 +125,7 @@ Sub Commands:
|
|
116
125
|
|
117
126
|
config Print out the combined rizzo json config
|
118
127
|
generate Initialize Vagrantfile in top control repo
|
128
|
+
roles Output all roles defined in the combined config
|
119
129
|
|
120
130
|
Global options: (Note, command line arguments supersede ENV vars in {}'s)
|
121
131
|
EOBANNER
|
data/lib/rzo/version.rb
CHANGED
@@ -5,7 +5,7 @@ module Rzo
|
|
5
5
|
# The authoritative location of the rzo version. It should be possible to
|
6
6
|
# `require 'rizzo/version'` and access `Rizzo::VERSION` from third party
|
7
7
|
# libraries and the gemspec. The version is defined as a Semantic Version.
|
8
|
-
VERSION = '0.
|
8
|
+
VERSION = '0.4.0'.freeze
|
9
9
|
|
10
10
|
##
|
11
11
|
# Return the SemVer string, e.g. `"0.1.0"`
|
data/rzo.gemspec
CHANGED
@@ -42,5 +42,6 @@ Gem::Specification.new do |spec|
|
|
42
42
|
spec.add_development_dependency 'guard-shell', '~> 0.7'
|
43
43
|
spec.add_development_dependency 'simplecov', '~> 0.14'
|
44
44
|
spec.add_dependency 'json', '~> 2.1'
|
45
|
+
spec.add_dependency 'json-schema', '~> 2.8'
|
45
46
|
spec.add_dependency 'deep_merge', '~> 1.1'
|
46
47
|
end
|
@@ -0,0 +1,343 @@
|
|
1
|
+
#! /bin/bash
|
2
|
+
#
|
3
|
+
# Test to ensure the gem actually builds. Pre-requisite step to verify the gem
|
4
|
+
# installs. Run this script from the repository root.
|
5
|
+
# can
|
6
|
+
|
7
|
+
# Turn off validation for testing purposes. We'll turn it back on for explicit
|
8
|
+
# testing of the validation behavior
|
9
|
+
export RZO_VALIDATE='false'
|
10
|
+
|
11
|
+
set -eu
|
12
|
+
|
13
|
+
STAMP=$(date +%s)
|
14
|
+
|
15
|
+
if [[ -z "${NO_COLOR:-}" ]]; then
|
16
|
+
NC='\033[0m' # No Color
|
17
|
+
RED='\033[0;31m'
|
18
|
+
GREEN='\033[0;32m'
|
19
|
+
YELLOW='\033[0;33m'
|
20
|
+
BLUE='\033[0;34m'
|
21
|
+
else
|
22
|
+
NC=''
|
23
|
+
RED=''
|
24
|
+
GREEN=''
|
25
|
+
YELLOW=''
|
26
|
+
BLUE=''
|
27
|
+
fi
|
28
|
+
|
29
|
+
# Describe the test with a nice heading
|
30
|
+
desc() {
|
31
|
+
msg="$1"
|
32
|
+
echo
|
33
|
+
echo -e "${GREEN}${msg}${NC}"
|
34
|
+
echo "${msg//?/=}"
|
35
|
+
return 0
|
36
|
+
}
|
37
|
+
|
38
|
+
testcase() {
|
39
|
+
msg="$1"
|
40
|
+
echo -en " * ${NC}${msg}:${NC} "
|
41
|
+
return 0
|
42
|
+
}
|
43
|
+
|
44
|
+
# Look for a string in some output, e.g. stderr or stdout.
|
45
|
+
match() {
|
46
|
+
msg="$1" # descriptive message
|
47
|
+
expected="$2" # a file
|
48
|
+
actual="$3" # extended regexp
|
49
|
+
testcase "$msg"
|
50
|
+
if grep -qE "$expected" "$actual"; then
|
51
|
+
pass "It does."
|
52
|
+
return 0
|
53
|
+
else
|
54
|
+
echo "Expected $actual to contain '${expected}', but it did not." >&2
|
55
|
+
echo "Expected:"
|
56
|
+
cat "$expected"
|
57
|
+
echo
|
58
|
+
echo "Actual:"
|
59
|
+
cat "$actual"
|
60
|
+
fail "Did not find '$expected' in $actual"
|
61
|
+
return 1
|
62
|
+
fi
|
63
|
+
}
|
64
|
+
|
65
|
+
pass() {
|
66
|
+
msg="$1"
|
67
|
+
echo -e "${GREEN}PASS:${NC} ${msg}" >&2
|
68
|
+
return 0
|
69
|
+
}
|
70
|
+
|
71
|
+
fail() {
|
72
|
+
msg="$1"
|
73
|
+
echo -e "${RED}FAIL:${NC} $msg" >&2
|
74
|
+
return 1
|
75
|
+
}
|
76
|
+
|
77
|
+
warn() {
|
78
|
+
msg="$1"
|
79
|
+
echo -e "${YELLOW}Warning:${NC} $msg" >&2
|
80
|
+
}
|
81
|
+
|
82
|
+
err() {
|
83
|
+
msg="$1"
|
84
|
+
echo -e "${RED}Error:${NC} $msg" >&2
|
85
|
+
}
|
86
|
+
|
87
|
+
debug() {
|
88
|
+
[[ -z "${DEBUG:-}" ]] && return 0
|
89
|
+
msg="$1"
|
90
|
+
echo -e "${BLUE}Debug:${NC} $msg" >&2
|
91
|
+
}
|
92
|
+
|
93
|
+
# Move ~/.rizzo.json out of the way if it exists
|
94
|
+
if [[ -e ~/.rizzo.json ]]; then
|
95
|
+
debug "Moving ~/.rizzo.json to ~/.rizzo.json.$STAMP"
|
96
|
+
mv -f ~/.rizzo.json ~/.rizzo.json.$STAMP
|
97
|
+
fi
|
98
|
+
|
99
|
+
# Clean up our temp directory
|
100
|
+
scratch=$(mktemp -d)
|
101
|
+
export TMPDIR="$scratch"
|
102
|
+
finish() {
|
103
|
+
if [[ -e ~/.rizzo.json.$STAMP ]]; then
|
104
|
+
mv -f ~/.rizzo.json.$STAMP ~/.rizzo.json
|
105
|
+
debug "Moved ~/.rizzo.json.$STAMP to ~/.rizzo.json"
|
106
|
+
fi
|
107
|
+
if [[ -d "$scratch" ]]; then
|
108
|
+
rm -rf "$scratch"
|
109
|
+
debug "Removed $scratch"
|
110
|
+
fi
|
111
|
+
}
|
112
|
+
trap finish EXIT
|
113
|
+
|
114
|
+
[ -d pkg ] && rm -rf pkg
|
115
|
+
mkdir pkg
|
116
|
+
bundle exec rake build
|
117
|
+
|
118
|
+
# Load RVM into a shell session *as a function*. This is necessary to switch
|
119
|
+
# gemsets. This script should still operate without rvm, but it would pollute
|
120
|
+
# GEM_HOME, so must be explicitly enabled by the user using RZO_GEM_INSTALL
|
121
|
+
for i in "$HOME/.rvm/scripts/rvm" "/usr/local/rvm/scripts/rvm"; do
|
122
|
+
if [[ -s "$i" ]]; then
|
123
|
+
set +u # RVM uses unbound variables, which makes me very sad. :(
|
124
|
+
source "$i"
|
125
|
+
RZO_GEM_INSTALL=yes
|
126
|
+
break
|
127
|
+
fi
|
128
|
+
done
|
129
|
+
|
130
|
+
# We use a custom gemset to ensure the build dependency bundle is isolated from
|
131
|
+
# the functional testing phase.
|
132
|
+
if [[ -z ${RZO_GEM_INSTALL:-} ]]; then
|
133
|
+
warn "rvm not found, GEM_HOME will be tainted by the tests."
|
134
|
+
warn "To proceed anyway, run: INSTALL_TO_GEM_HOME=true $0"
|
135
|
+
[[ -z ${INSTALL_TO_GEM_HOME:-} ]] && exit 1
|
136
|
+
else
|
137
|
+
rvm gemset create cleanroom
|
138
|
+
rvm gemset use cleanroom
|
139
|
+
fi
|
140
|
+
|
141
|
+
desc "The gem environment used for functional testing"
|
142
|
+
gem env
|
143
|
+
|
144
|
+
desc "There should be minimal gems installed initially"
|
145
|
+
gem list
|
146
|
+
|
147
|
+
desc "Install the gem"
|
148
|
+
gem install pkg/*.gem
|
149
|
+
|
150
|
+
desc "The gem and dependencies should be installed"
|
151
|
+
gem list
|
152
|
+
|
153
|
+
desc "The executable should be in the path"
|
154
|
+
which rzo
|
155
|
+
|
156
|
+
desc "rzo --help should contain usage"
|
157
|
+
stdout=$(mktemp -t XXXXXX.stdout)
|
158
|
+
rzo --help | tee $stdout
|
159
|
+
expected='usage: .*GLOBAL OPTIONS.*SUBCOMMAND.*ARGS'
|
160
|
+
if ! grep -qE "$expected" $stdout; then
|
161
|
+
fail "rzo --help STDOUT does not contain '$expected'"
|
162
|
+
fi
|
163
|
+
|
164
|
+
desc "rzo --version should output a semantic version string"
|
165
|
+
stdout=$(mktemp -t XXXXXX.stdout)
|
166
|
+
rzo --version | tee $stdout
|
167
|
+
grep -qE '[0-9]+\.[0-9]+\.[0-9]' $stdout
|
168
|
+
|
169
|
+
desc "rzo bare (no arguments) should match --help"
|
170
|
+
bare_output=$(mktemp -t XXXXXX.rzo_bare)
|
171
|
+
help_output=$(mktemp -t XXXXXX.rzo_help)
|
172
|
+
rzo > $bare_output
|
173
|
+
rzo --help > $help_output
|
174
|
+
if diff -U2 $help_output $bare_output; then
|
175
|
+
pass "It does."
|
176
|
+
else
|
177
|
+
fail "rzo is not the same as rzo --help"
|
178
|
+
fi
|
179
|
+
|
180
|
+
desc "rzo config with no config should be helpful"
|
181
|
+
stdout=$(mktemp -t XXXXXX.stdout)
|
182
|
+
stderr=$(mktemp -t XXXXXX.stderr)
|
183
|
+
rzo config 2> $stderr | tee $stdout
|
184
|
+
expected="Cannot read config file"
|
185
|
+
if ! grep -E "$expected" $stderr; then
|
186
|
+
echo "STDERR:"
|
187
|
+
cat $stderr >&2
|
188
|
+
fail "rzo config STDERR does not contain '$expected'"
|
189
|
+
else
|
190
|
+
pass "Looks good."
|
191
|
+
fi
|
192
|
+
|
193
|
+
# Puppet data repositories used by rizzo
|
194
|
+
PUPPETDATA="${TMPDIR}/git/puppetdata"
|
195
|
+
|
196
|
+
desc "With a valid ~/.rizzo.json file looking like:"
|
197
|
+
cat > ~/.rizzo.json <<EOCONFIG
|
198
|
+
{
|
199
|
+
"defaults": { "bootstrap_repo_path": "${HOME}/git/bootstrap" },
|
200
|
+
"control_repos": [ "${PUPPETDATA}", "${HOME}/git/ghoneycutt-modules" ],
|
201
|
+
"puppetmaster": {
|
202
|
+
"name": [ "puppetca", "puppet" ],
|
203
|
+
"modulepath": [ "./modules", "./puppetdata/modules", "./ghoneycutt/modules" ],
|
204
|
+
"synced_folders": {
|
205
|
+
"/repos/puppetdata": { "local": "${PUPPETDATA}", "owner": "root", "group": "root" },
|
206
|
+
"/repos/ghoneycutt": { "local": "${HOME}/git/ghoneycutt-modules", "owner": "root", "group": "root" }
|
207
|
+
}
|
208
|
+
}
|
209
|
+
}
|
210
|
+
EOCONFIG
|
211
|
+
# Print out the config
|
212
|
+
expected=$(mktemp -t XXXXXX.rizzo.json)
|
213
|
+
ruby -rjson -e 'puts JSON.pretty_generate(JSON.parse(ARGF.read))' ~/.rizzo.json | tee $expected
|
214
|
+
|
215
|
+
desc "rzo config is expected to pretty generate the JSON config"
|
216
|
+
stdout=$(mktemp -t XXXXXX.stdout)
|
217
|
+
stderr=$(mktemp -t XXXXXX.stderr)
|
218
|
+
rzo config > $stdout 2> $stderr
|
219
|
+
if diff -U2 $expected $stdout; then
|
220
|
+
pass "It does."
|
221
|
+
else
|
222
|
+
fail "rzo config STDOUT differs from expected pretty generated config"
|
223
|
+
fi
|
224
|
+
|
225
|
+
desc "rzo generate produces a minimal Vagrantfile"
|
226
|
+
|
227
|
+
expected=$(mktemp -t XXXXXXX.vagrantfile)
|
228
|
+
actual=$(mktemp -t XXXXXXX.vagrantfile)
|
229
|
+
# NOTE: The first line is omitted because it contains a timestamp
|
230
|
+
cat > $expected <<VAGRANTFILE
|
231
|
+
# https://github.com/ghoneycutt/rizzo
|
232
|
+
Vagrant.configure(2) do |config|
|
233
|
+
# use 'vagrant plugin install vagrant-proxyconf' to install
|
234
|
+
if Vagrant.has_plugin?('vagrant-proxyconf')
|
235
|
+
config.proxy.http = ENV['HTTP_PROXY'] if ENV['HTTP_PROXY']
|
236
|
+
config.proxy.https = ENV['HTTPS_PROXY'] if ENV['HTTPS_PROXY']
|
237
|
+
end
|
238
|
+
end
|
239
|
+
# -*- mode: ruby -*-
|
240
|
+
# vim:ft=ruby
|
241
|
+
VAGRANTFILE
|
242
|
+
|
243
|
+
stdout=$(mktemp -t XXXXXX.stdout)
|
244
|
+
stderr=$(mktemp -t XXXXXX.stderr)
|
245
|
+
rzo generate 2> $stderr
|
246
|
+
tail -n+2 Vagrantfile > $actual
|
247
|
+
if diff -U2 $expected $actual; then
|
248
|
+
pass "It does."
|
249
|
+
else
|
250
|
+
fail "rzo generate produced a Vagrantfile different than expected"
|
251
|
+
fi
|
252
|
+
|
253
|
+
expected="Wrote vagrant config to Vagrantfile"
|
254
|
+
desc "rzo generate STDERR is expected to match '$expected'"
|
255
|
+
if ! grep -qE "$expected" $stderr; then
|
256
|
+
echo "Expected:"
|
257
|
+
echo "$expected"
|
258
|
+
echo
|
259
|
+
echo "Actual:"
|
260
|
+
cat $stderr
|
261
|
+
echo
|
262
|
+
fail "Expected STDOUT of rzo generate does not match actual output"
|
263
|
+
else
|
264
|
+
pass "It does."
|
265
|
+
fi
|
266
|
+
|
267
|
+
desc "rzo roles with no personal .rizzo.json is expected to warn"
|
268
|
+
stdout=$(mktemp -t XXXXXX.stdout)
|
269
|
+
stderr=$(mktemp -t XXXXXX.stderr)
|
270
|
+
rzo roles 2> $stderr > $stdout
|
271
|
+
|
272
|
+
match "warns about puppetdata not being a directory" 'WARN .*puppetdata is not a directory' $stderr
|
273
|
+
match "warns about ghoneycutt-modules not being a directory" 'WARN .*ghoneycutt-modules is not a directory' $stderr
|
274
|
+
match "warns about bootstrap not being a directory" 'WARN .*bootstrap is not a directory' $stderr
|
275
|
+
|
276
|
+
desc "with a single puppetca role, rzo roles outputs the role name"
|
277
|
+
if ! [[ -d "$PUPPETDATA" ]]; then
|
278
|
+
mkdir -p "$PUPPETDATA"
|
279
|
+
debug "Created $PUPPETDATA"
|
280
|
+
fi
|
281
|
+
echo '{"nodes":[{"name":"puppetca"}]}' > ${PUPPETDATA}/.rizzo.json
|
282
|
+
|
283
|
+
stdout=$(mktemp -t XXXXXX.stdout)
|
284
|
+
stderr=$(mktemp -t XXXXXX.stderr)
|
285
|
+
rzo roles 2> $stderr > $stdout
|
286
|
+
expected='puppetca'
|
287
|
+
if grep -qxE "$expected" $stdout; then
|
288
|
+
pass "rzo roles STDOUT, expected: '$expected' got: '$(cat $stdout)'"
|
289
|
+
else
|
290
|
+
fail "rzo roles STDOUT, expected: '$expected' got: '$(cat $stdout)'"
|
291
|
+
fi
|
292
|
+
|
293
|
+
desc "rzo generate is expected to produce a Vagrantfile with one VM defined"
|
294
|
+
expected=$(mktemp -t XXXXXX.vagrantfile.expected)
|
295
|
+
stdout=$(mktemp -t XXXXXX.stdout)
|
296
|
+
stderr=$(mktemp -t XXXXXX.stderr)
|
297
|
+
cat <<VAGRANTFILE > $expected
|
298
|
+
# https://github.com/ghoneycutt/rizzo
|
299
|
+
Vagrant.configure(2) do |config|
|
300
|
+
# use 'vagrant plugin install vagrant-proxyconf' to install
|
301
|
+
if Vagrant.has_plugin?('vagrant-proxyconf')
|
302
|
+
config.proxy.http = ENV['HTTP_PROXY'] if ENV['HTTP_PROXY']
|
303
|
+
config.proxy.https = ENV['HTTPS_PROXY'] if ENV['HTTPS_PROXY']
|
304
|
+
end
|
305
|
+
|
306
|
+
config.vm.define "puppetca", autostart: false do |cfg|
|
307
|
+
cfg.vm.box = nil
|
308
|
+
cfg.vm.box_url = nil
|
309
|
+
cfg.vm.box_download_checksum = nil
|
310
|
+
cfg.vm.box_download_checksum_type = nil
|
311
|
+
cfg.vm.provider :virtualbox do |vb|
|
312
|
+
vb.customize ['modifyvm', :id, '--memory', nil]
|
313
|
+
end
|
314
|
+
cfg.vm.hostname = nil
|
315
|
+
cfg.vm.network 'private_network',
|
316
|
+
ip: nil,
|
317
|
+
netmask: nil
|
318
|
+
cfg.vm.synced_folder "${PUPPETDATA}", "/repos/puppetdata",
|
319
|
+
owner: "root", group: "root"
|
320
|
+
cfg.vm.synced_folder "${HOME}/git/ghoneycutt-modules", "/repos/ghoneycutt",
|
321
|
+
owner: "root", group: "root"
|
322
|
+
config.vm.synced_folder "${HOME}/git/bootstrap",
|
323
|
+
nil,
|
324
|
+
owner: 'vagrant', group: 'root'
|
325
|
+
config.vm.provision 'shell', inline: "echo 'modulepath = ./modules:./puppetdata/modules:./ghoneycutt/modules' > /environment.conf"
|
326
|
+
config.vm.provision 'shell', inline: "/bin/bash / "
|
327
|
+
end
|
328
|
+
end
|
329
|
+
# -*- mode: ruby -*-
|
330
|
+
# vim:ft=ruby
|
331
|
+
VAGRANTFILE
|
332
|
+
rzo generate 2> $stderr > $stdout
|
333
|
+
actual=$(mktemp -t XXXXXX.vagrantfile.actual)
|
334
|
+
tail -n+2 Vagrantfile > $actual
|
335
|
+
if diff -U2 $expected $actual; then
|
336
|
+
cat Vagrantfile
|
337
|
+
pass "It does."
|
338
|
+
else
|
339
|
+
fail "actual Vagrantfile does not match expected file"
|
340
|
+
fi
|
341
|
+
|
342
|
+
desc "END of functional testing"
|
343
|
+
pass "Functional testing completed successfully."
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rzo
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Garrett Honeycutt
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: exe
|
11
11
|
cert_chain: []
|
12
|
-
date: 2017-
|
12
|
+
date: 2017-09-13 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rake
|
@@ -193,6 +193,20 @@ dependencies:
|
|
193
193
|
- - "~>"
|
194
194
|
- !ruby/object:Gem::Version
|
195
195
|
version: '2.1'
|
196
|
+
- !ruby/object:Gem::Dependency
|
197
|
+
name: json-schema
|
198
|
+
requirement: !ruby/object:Gem::Requirement
|
199
|
+
requirements:
|
200
|
+
- - "~>"
|
201
|
+
- !ruby/object:Gem::Version
|
202
|
+
version: '2.8'
|
203
|
+
type: :runtime
|
204
|
+
prerelease: false
|
205
|
+
version_requirements: !ruby/object:Gem::Requirement
|
206
|
+
requirements:
|
207
|
+
- - "~>"
|
208
|
+
- !ruby/object:Gem::Version
|
209
|
+
version: '2.8'
|
196
210
|
- !ruby/object:Gem::Dependency
|
197
211
|
name: deep_merge
|
198
212
|
requirement: !ruby/object:Gem::Requirement
|
@@ -237,7 +251,9 @@ files:
|
|
237
251
|
- lib/rzo.rb
|
238
252
|
- lib/rzo/app.rb
|
239
253
|
- lib/rzo/app/config.rb
|
254
|
+
- lib/rzo/app/config_validation.rb
|
240
255
|
- lib/rzo/app/generate.rb
|
256
|
+
- lib/rzo/app/roles.rb
|
241
257
|
- lib/rzo/app/subcommand.rb
|
242
258
|
- lib/rzo/app/templates/Vagrantfile.erb
|
243
259
|
- lib/rzo/logging.rb
|
@@ -245,6 +261,7 @@ files:
|
|
245
261
|
- lib/rzo/trollop.rb
|
246
262
|
- lib/rzo/version.rb
|
247
263
|
- rzo.gemspec
|
264
|
+
- scripts/functional_gem_behavior.sh
|
248
265
|
homepage: https://github.com/ghoneycutt/rizzo
|
249
266
|
licenses:
|
250
267
|
- Apache-2.0
|