pangea 0.0.1
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 +56 -0
- data/.rubocop.yml +54 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/Dockerfile +6 -0
- data/Gemfile +29 -0
- data/Gemfile.lock +127 -0
- data/LICENSE +201 -0
- data/README.md +59 -0
- data/Rakefile +33 -0
- data/bin/pangea +3 -0
- data/bin/stitches +0 -0
- data/example/config/README.md +3 -0
- data/example/config/sample.rb +20 -0
- data/flake.lock +60 -0
- data/flake.nix +17 -0
- data/gemset.nix +500 -0
- data/lib/pangea/cli/config.rb +69 -0
- data/lib/pangea/cli/constants.rb +36 -0
- data/lib/pangea/cli/subcommands/config.rb +14 -0
- data/lib/pangea/cli/subcommands/infra.rb +206 -0
- data/lib/pangea/cli/subcommands/main.rb +60 -0
- data/lib/pangea/cli/subcommands/stitches.rb +18 -0
- data/lib/pangea/cli.rb +1 -0
- data/lib/pangea/docker.rb +91 -0
- data/lib/pangea/errors/incorrect_subcommand_error.rb +2 -0
- data/lib/pangea/errors/namespace_not_found_error.rb +2 -0
- data/lib/pangea/errors/no_infra_target_error.rb +2 -0
- data/lib/pangea/errors/project_not_found_error.rb +2 -0
- data/lib/pangea/errors/site_not_found_error.rb +2 -0
- data/lib/pangea/extractors/README.md +3 -0
- data/lib/pangea/log/init.rb +2 -0
- data/lib/pangea/say/init.rb +27 -0
- data/lib/pangea/shell/README.md +1 -0
- data/lib/pangea/shell/terraform.rb +21 -0
- data/lib/pangea/structures/README.md +3 -0
- data/lib/pangea/structures/abstract.rb +2 -0
- data/lib/pangea/structures/namespace.rb +4 -0
- data/lib/pangea/structures/project.rb +4 -0
- data/lib/pangea/structures/site.rb +4 -0
- data/lib/pangea/synthesizer/config.rb +39 -0
- data/lib/pangea/version.rb +3 -0
- data/pangea.gemspec +39 -0
- data/spec/cli/config_spec.rb +16 -0
- data/spec/synthesizer/config_spec.rb +42 -0
- metadata +231 -0
@@ -0,0 +1,206 @@
|
|
1
|
+
require_relative %(./pangea)
|
2
|
+
require_relative %(../../cli/constants)
|
3
|
+
require_relative %(../../cli/config)
|
4
|
+
require_relative %(../../errors/namespace_not_found_error)
|
5
|
+
require_relative %(../../errors/site_not_found_error)
|
6
|
+
require_relative %(../../errors/project_not_found_error)
|
7
|
+
require_relative %(../../errors/no_infra_target_error)
|
8
|
+
require_relative %(../../errors/incorrect_subcommand_error)
|
9
|
+
require_relative %(../../say/init)
|
10
|
+
|
11
|
+
require %(pangea/synthesizer/terraform)
|
12
|
+
require %(json)
|
13
|
+
|
14
|
+
class InfraCommand < StitchesCommand
|
15
|
+
include Constants
|
16
|
+
NAME = :infra
|
17
|
+
|
18
|
+
usage do
|
19
|
+
desc %(manage infrastructure)
|
20
|
+
program %(pangea)
|
21
|
+
command %(infra)
|
22
|
+
end
|
23
|
+
|
24
|
+
argument :subcommand do
|
25
|
+
desc %(the subcommand)
|
26
|
+
end
|
27
|
+
|
28
|
+
argument :target do
|
29
|
+
desc %(target like ${namespace}.${site}.${project})
|
30
|
+
end
|
31
|
+
|
32
|
+
def run(argv)
|
33
|
+
parse(argv)
|
34
|
+
|
35
|
+
# grab a config synth
|
36
|
+
cfg_synth = Config.resolve_configurations
|
37
|
+
|
38
|
+
# reject empty configurations
|
39
|
+
if cfg_synth.empty?
|
40
|
+
Say.terminal %(configuration empty, exiting...)
|
41
|
+
exit
|
42
|
+
end
|
43
|
+
|
44
|
+
# preflight checks for the command execution
|
45
|
+
check_run
|
46
|
+
check_target(params[:target], cfg_synth)
|
47
|
+
|
48
|
+
targets = params[:target].split('.').map(&:to_sym)
|
49
|
+
process_target(targets, cfg_synth)
|
50
|
+
|
51
|
+
# provide some kind of default exit of the command execution
|
52
|
+
exit
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
# process stage for target
|
58
|
+
def process_target(targets, cfg_synth)
|
59
|
+
namespaces = cfg_synth[:namespace].keys.map(&:to_sym)
|
60
|
+
environments = []
|
61
|
+
|
62
|
+
namespaces.each do |ns_name|
|
63
|
+
environments.concat(cfg_synth[:namespace][ns_name].keys.map(&:to_sym))
|
64
|
+
end
|
65
|
+
|
66
|
+
case targets.length.to_i
|
67
|
+
# only provided namespace
|
68
|
+
when 1
|
69
|
+
nil
|
70
|
+
# only provided namespace.site
|
71
|
+
when 2
|
72
|
+
nil
|
73
|
+
# only provided namespace.site.project
|
74
|
+
when 3
|
75
|
+
announce_preflight_info(targets, cfg_synth)
|
76
|
+
environments.each do |e_name|
|
77
|
+
projects = cfg_synth[:namespace][targets[0]][e_name][:projects]
|
78
|
+
projects.each do |project|
|
79
|
+
announce_project(project)
|
80
|
+
|
81
|
+
synth = TerraformSynthesizer.new
|
82
|
+
case project[:src][:type].to_sym
|
83
|
+
when :local
|
84
|
+
system %(mkdir -p #{CACHE_DIR}) unless Dir.exist?(CACHE_DIR)
|
85
|
+
PROJECT_SRC_DIRS.each do |src_dir|
|
86
|
+
if File.exist?(File.join(
|
87
|
+
project[:src][:location].to_s,
|
88
|
+
%(src),
|
89
|
+
src_dir.to_s
|
90
|
+
))
|
91
|
+
stitch_files = Dir.glob("#{File.join(
|
92
|
+
project[:src][:location].to_s,
|
93
|
+
%(src),
|
94
|
+
src_dir.to_s
|
95
|
+
)}/**/*.rb")
|
96
|
+
stitch_files.each do |stitch_file|
|
97
|
+
synth.synthesize(File.read(stitch_file))
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
Say.terminal JSON.pretty_generate(synth.synthesis)
|
103
|
+
|
104
|
+
project_cache_dir = File.join(CACHE_DIR, project[:name].to_s)
|
105
|
+
|
106
|
+
system %(mkdir -p #{project_cache_dir}) unless Dir.exist?(project_cache_dir)
|
107
|
+
File.write(File.join(project_cache_dir, ARTIFACT_FILE), synth.synthesis.to_json)
|
108
|
+
system %(cd #{project_cache_dir} && terraform init)
|
109
|
+
system %(cd #{project_cache_dir} && terraform plan)
|
110
|
+
|
111
|
+
when :git
|
112
|
+
nil
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
# fetch project data
|
118
|
+
# projet_data = cfg_synth[:namespace][targets[0]]
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
def announce_project(project)
|
123
|
+
msg = []
|
124
|
+
msg << %(project: #{project[:name]})
|
125
|
+
msg << %(src: #{project[:src]})
|
126
|
+
Say.terminal msg.map(&:strip).join(%(\n))
|
127
|
+
end
|
128
|
+
|
129
|
+
def announce_preflight_info(targets, cfg_synth)
|
130
|
+
namespaces = cfg_synth[:namespace].keys.map(&:to_sym)
|
131
|
+
environments = []
|
132
|
+
|
133
|
+
namespaces.each do |ns_name|
|
134
|
+
environments.concat(cfg_synth[:namespace][ns_name].keys.map(&:to_sym))
|
135
|
+
end
|
136
|
+
|
137
|
+
preflight_info = []
|
138
|
+
preflight_info << %(environments: #{environments})
|
139
|
+
preflight_info << %(\n)
|
140
|
+
preflight_info << %(namespace: #{targets[0]})
|
141
|
+
preflight_info << %(site: #{targets[1]})
|
142
|
+
preflight_info << %(project: #{targets[2]})
|
143
|
+
|
144
|
+
Say.terminal preflight_info.map(&:strip).join(%(\n))
|
145
|
+
end
|
146
|
+
|
147
|
+
# targets can be...
|
148
|
+
# ${namespace}
|
149
|
+
# ${namespace}.${site}
|
150
|
+
# ${namespace}.${site}.${project}
|
151
|
+
def check_target(target, config)
|
152
|
+
raise NamespaceNotFoundError if target.nil?
|
153
|
+
|
154
|
+
targets = target.split('.').map(&:to_sym)
|
155
|
+
namespaces = config[:namespace].keys.map(&:to_sym)
|
156
|
+
runtype = nil
|
157
|
+
environments = []
|
158
|
+
|
159
|
+
namespaces.each do |ns_name|
|
160
|
+
environments.concat(config[:namespace][ns_name].keys.map(&:to_sym))
|
161
|
+
end
|
162
|
+
|
163
|
+
raise NamespaceNotFoundError unless namespaces.include?(targets[0])
|
164
|
+
|
165
|
+
namespaces.each do |ns_name|
|
166
|
+
environments.each do |e_name|
|
167
|
+
sites = config[:namespace][ns_name][e_name][:sites] || []
|
168
|
+
|
169
|
+
next if sites.empty?
|
170
|
+
|
171
|
+
site_names = []
|
172
|
+
sites.each do |site|
|
173
|
+
site_names << site[:name]
|
174
|
+
end
|
175
|
+
|
176
|
+
raise SiteNotFoundError unless site_names.include?(targets[1].to_sym)
|
177
|
+
|
178
|
+
projects = config[:namespace][ns_name][e_name][:projects] || []
|
179
|
+
|
180
|
+
next if projects.empty?
|
181
|
+
|
182
|
+
project_names = []
|
183
|
+
projects.each do |project|
|
184
|
+
project_names << project[:name]
|
185
|
+
end
|
186
|
+
|
187
|
+
raise ProjectNotFoundError \
|
188
|
+
unless project_names
|
189
|
+
.include?(
|
190
|
+
targets[2].to_sym
|
191
|
+
)
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
def check_run
|
197
|
+
raise IncorrectSubcommandError unless correct_subcommand?(
|
198
|
+
params[:subcommand]
|
199
|
+
)
|
200
|
+
raise NoInfraTargetError unless params[:target]
|
201
|
+
end
|
202
|
+
|
203
|
+
def correct_subcommand?(sbcmd)
|
204
|
+
NAME.to_s.eql?(sbcmd.to_s)
|
205
|
+
end
|
206
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require_relative %(./pangea)
|
2
|
+
require_relative %(./infra)
|
3
|
+
require_relative %(./config)
|
4
|
+
require_relative %(../version)
|
5
|
+
|
6
|
+
###############################################################################
|
7
|
+
# cli entrypoint
|
8
|
+
###############################################################################
|
9
|
+
|
10
|
+
class Command < PangeaCommand
|
11
|
+
usage do
|
12
|
+
desc %(manage crud apis declaratively)
|
13
|
+
program %(pangea)
|
14
|
+
end
|
15
|
+
|
16
|
+
argument :subcommand do
|
17
|
+
desc %(subcommand for pangea)
|
18
|
+
required
|
19
|
+
end
|
20
|
+
|
21
|
+
def help
|
22
|
+
<<~HELP
|
23
|
+
Usage: pangea command [OPTIONS] SUBCOMMAND
|
24
|
+
|
25
|
+
stitch together infrastructure
|
26
|
+
|
27
|
+
Arguments:
|
28
|
+
SUBCOMMAND subcommand for pangea
|
29
|
+
|
30
|
+
Options:
|
31
|
+
-h, --help Print usage
|
32
|
+
-v, --version Print version
|
33
|
+
|
34
|
+
Subcommands:
|
35
|
+
infra manage infrastructure
|
36
|
+
config manage configuration
|
37
|
+
HELP
|
38
|
+
end
|
39
|
+
|
40
|
+
def run
|
41
|
+
argv = ARGV
|
42
|
+
parse(argv)
|
43
|
+
|
44
|
+
case params[:subcommand].to_s
|
45
|
+
when %(infra)
|
46
|
+
InfraCommand.new.run(argv)
|
47
|
+
when %(config)
|
48
|
+
ConfigCommand.new.run(argv)
|
49
|
+
else
|
50
|
+
if params[:version]
|
51
|
+
puts Pangea::Cli::VERSION
|
52
|
+
else
|
53
|
+
puts help
|
54
|
+
end
|
55
|
+
exit
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
# end cli entrypoint
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require %(tty-option)
|
2
|
+
|
3
|
+
# shared command behavior for all commands in Stitches
|
4
|
+
class StitchesCommand
|
5
|
+
include TTY::Option
|
6
|
+
|
7
|
+
flag :help do
|
8
|
+
short %(-h)
|
9
|
+
long %(--help)
|
10
|
+
desc %(Print usage)
|
11
|
+
end
|
12
|
+
|
13
|
+
flag :version do
|
14
|
+
short %(-v)
|
15
|
+
long %(--version)
|
16
|
+
desc %(Print version)
|
17
|
+
end
|
18
|
+
end
|
data/lib/pangea/cli.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require_relative %(./cli/subcommands/main)
|
@@ -0,0 +1,91 @@
|
|
1
|
+
module Shell
|
2
|
+
class << self
|
3
|
+
def run(bin, cmd)
|
4
|
+
final = []
|
5
|
+
final << bin
|
6
|
+
final.concat(cmd)
|
7
|
+
system final.join(%( ))
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
module Compose
|
13
|
+
BIN = ENV[%(COMPOSE_BIN)] || %(docker-compose).freeze
|
14
|
+
|
15
|
+
class << self
|
16
|
+
def run(cmd)
|
17
|
+
Shell.run(BIN, cmd)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
module Docker
|
23
|
+
BIN = ENV[%(DOCKER_BIN)] || %(docker).freeze
|
24
|
+
|
25
|
+
class << self
|
26
|
+
def run(cmd)
|
27
|
+
Shell.run(BIN, cmd)
|
28
|
+
end
|
29
|
+
|
30
|
+
def build(image)
|
31
|
+
cmd = []
|
32
|
+
cmd << %(build)
|
33
|
+
cmd << %(-t)
|
34
|
+
cmd << %(#{image}:latest)
|
35
|
+
cmd << %(.)
|
36
|
+
run cmd
|
37
|
+
end
|
38
|
+
|
39
|
+
def tag(image)
|
40
|
+
sha = `git rev-parse --short HEAD`
|
41
|
+
cmd = []
|
42
|
+
cmd << %(tag)
|
43
|
+
cmd << %(#{image}:latest)
|
44
|
+
cmd << %(#{image}:#{sha})
|
45
|
+
run cmd
|
46
|
+
end
|
47
|
+
|
48
|
+
def create(name, image)
|
49
|
+
cmd = []
|
50
|
+
cmd << %(create)
|
51
|
+
cmd << %(-it)
|
52
|
+
cmd << %(--name #{name})
|
53
|
+
cmd << image
|
54
|
+
cmd << %(/bin/bash)
|
55
|
+
run cmd
|
56
|
+
end
|
57
|
+
|
58
|
+
def start(name)
|
59
|
+
cmd = []
|
60
|
+
cmd << %(start)
|
61
|
+
cmd << %(-i -a)
|
62
|
+
cmd << name
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
class Image
|
68
|
+
def initialize(name)
|
69
|
+
@name = name
|
70
|
+
end
|
71
|
+
|
72
|
+
def build
|
73
|
+
Docker.build(@name)
|
74
|
+
Docker.tag(@name)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
class Container
|
79
|
+
def initialize(name, image)
|
80
|
+
@name = name
|
81
|
+
@image = image
|
82
|
+
end
|
83
|
+
|
84
|
+
def create
|
85
|
+
Docker.create(@name, @image)
|
86
|
+
end
|
87
|
+
|
88
|
+
def start
|
89
|
+
Docker.start(@name)
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require %(tty-color)
|
2
|
+
require %(tty-box)
|
3
|
+
|
4
|
+
module Say
|
5
|
+
class << self
|
6
|
+
def terminal(msg)
|
7
|
+
spec = {
|
8
|
+
width: 80,
|
9
|
+
style: {
|
10
|
+
fg: :yellow,
|
11
|
+
bg: :blue,
|
12
|
+
border: { fg: :green, bg: :black }
|
13
|
+
},
|
14
|
+
align: :right,
|
15
|
+
border: :thick
|
16
|
+
# padding: 0,
|
17
|
+
# height: 1
|
18
|
+
}
|
19
|
+
|
20
|
+
box = TTY::Box.frame(**spec)
|
21
|
+
|
22
|
+
puts box + "\n"
|
23
|
+
puts msg.strip
|
24
|
+
puts "\n" + box
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
# localize calling out to the shell
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Shell
|
2
|
+
module Terraform
|
3
|
+
BIN = ENV[%(TERRAFORM_BIN)] || %(terraform).freeze
|
4
|
+
|
5
|
+
class << self
|
6
|
+
def run(terraform_cmd)
|
7
|
+
cmd = []
|
8
|
+
cmd << %(cd)
|
9
|
+
cmd << Dir.pwd
|
10
|
+
cmd << %(&&)
|
11
|
+
cmd << BIN
|
12
|
+
cmd << terraform_cmd
|
13
|
+
system cmd.join(%( ))
|
14
|
+
end
|
15
|
+
|
16
|
+
def plan
|
17
|
+
run(%(plan))
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require_relative %(../cli/constants)
|
2
|
+
require %(pangea/synthesizer/abstract)
|
3
|
+
require %(toml-rb)
|
4
|
+
require %(json)
|
5
|
+
require %(yaml)
|
6
|
+
|
7
|
+
###############################################################################
|
8
|
+
# read files merge config data and provide a single configuation structure
|
9
|
+
###############################################################################
|
10
|
+
|
11
|
+
class ConfigSynthesizer < AbstractSynthesizer
|
12
|
+
include Constants
|
13
|
+
|
14
|
+
def synthesize(content, ext)
|
15
|
+
case ext.to_s
|
16
|
+
when %(yaml), %(yml)
|
17
|
+
translation[:template] = YAML.safe_load(content)
|
18
|
+
when %(toml)
|
19
|
+
translation[:template] = TomlRB.parse(content)
|
20
|
+
when %(json)
|
21
|
+
translation[:template] = JSON.parse(content)
|
22
|
+
when %(rb)
|
23
|
+
if block_given?
|
24
|
+
yield
|
25
|
+
else
|
26
|
+
instance_eval(content)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def method_missing(method_name, *args, &block)
|
32
|
+
abstract_method_missing(
|
33
|
+
method_name,
|
34
|
+
%i[namespace],
|
35
|
+
*args,
|
36
|
+
&block
|
37
|
+
)
|
38
|
+
end
|
39
|
+
end
|
data/pangea.gemspec
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
lib = File.expand_path(%(lib), __dir__)
|
4
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
5
|
+
require_relative %(./lib/pangea/version)
|
6
|
+
|
7
|
+
Gem::Specification.new do |spec|
|
8
|
+
spec.name = %(pangea)
|
9
|
+
spec.version = Pangea::VERSION
|
10
|
+
spec.authors = [%(drzthslnt@gmail.com)]
|
11
|
+
spec.email = [%(drzthslnt@gmail.com)]
|
12
|
+
spec.description = %(control rest apis declaratively with ruby)
|
13
|
+
spec.summary = %(control rest apis declaratively with ruby)
|
14
|
+
spec.homepage = %(https://github.com/drzln/#{spec.name})
|
15
|
+
spec.license = %(MIT)
|
16
|
+
spec.files = `git ls-files`.split($OUTPUT_RECORD_SEPARATOR)
|
17
|
+
spec.require_paths = [%(lib)]
|
18
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
19
|
+
spec.required_ruby_version = %(>= #{`cat .ruby-version`})
|
20
|
+
|
21
|
+
%i[
|
22
|
+
rubocop-rspec
|
23
|
+
rubocop-rake
|
24
|
+
solargraph
|
25
|
+
keycutter
|
26
|
+
rubocop
|
27
|
+
rspec
|
28
|
+
rake
|
29
|
+
yard
|
30
|
+
].each do |gem|
|
31
|
+
spec.add_development_dependency(gem)
|
32
|
+
end
|
33
|
+
|
34
|
+
%i[terraform-synthesizer tty-option].each do |gem|
|
35
|
+
spec.add_runtime_dependency(gem)
|
36
|
+
end
|
37
|
+
|
38
|
+
spec.metadata[%(rubygems_mfa_required)] = %(true)
|
39
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require %(pangea/cli/config)
|
4
|
+
|
5
|
+
describe Config do
|
6
|
+
context %(#resolve_configuration) do
|
7
|
+
defaults = { ignore_default_paths: true }
|
8
|
+
it %(empty when paths ignored) do
|
9
|
+
expect(
|
10
|
+
Config.resolve_configurations(**defaults.merge(
|
11
|
+
{ extra_paths: [] }
|
12
|
+
))
|
13
|
+
).to be_empty
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require %(pangea/synthesizer/config)
|
4
|
+
|
5
|
+
# describe ConfigSynthesizer do
|
6
|
+
# context %(main) do
|
7
|
+
# let(:synth) do
|
8
|
+
# described_class.new
|
9
|
+
# end
|
10
|
+
#
|
11
|
+
# it %(should compile small declaration and be hash) do
|
12
|
+
# synth.synthesize do
|
13
|
+
# namespace :nietztche, :thus_spoke_zarathustra do
|
14
|
+
# superman(%(came from krypton))
|
15
|
+
# overman(%(came from austria))
|
16
|
+
# end
|
17
|
+
# end
|
18
|
+
# expect(synth.synthesis).to be_kind_of(Hash)
|
19
|
+
# end
|
20
|
+
#
|
21
|
+
# it %(should raise an argument error with more than one arg per field) do
|
22
|
+
# expect do
|
23
|
+
# synth.synthesize do
|
24
|
+
# namespace :socrates, :the_republic do
|
25
|
+
# things(%(may), %(not), %(be), %(what), %(you), %(think))
|
26
|
+
# end
|
27
|
+
# end
|
28
|
+
# end.to raise_error(TooManyFieldValuesError)
|
29
|
+
# end
|
30
|
+
#
|
31
|
+
# it %(should have expected keys) do
|
32
|
+
# synth.synthesize do
|
33
|
+
# namespace :kafka, :metamorphasis do
|
34
|
+
# change :inevitable
|
35
|
+
# end
|
36
|
+
# end
|
37
|
+
# expect(
|
38
|
+
# synth.synthesis[:namespace][:kafka][:metamorphasis][:change]
|
39
|
+
# ).to be(:inevitable)
|
40
|
+
# end
|
41
|
+
# end
|
42
|
+
# end
|