pangea 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|