pangea 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +56 -0
  3. data/.rubocop.yml +54 -0
  4. data/.ruby-gemset +1 -0
  5. data/.ruby-version +1 -0
  6. data/Dockerfile +6 -0
  7. data/Gemfile +29 -0
  8. data/Gemfile.lock +127 -0
  9. data/LICENSE +201 -0
  10. data/README.md +59 -0
  11. data/Rakefile +33 -0
  12. data/bin/pangea +3 -0
  13. data/bin/stitches +0 -0
  14. data/example/config/README.md +3 -0
  15. data/example/config/sample.rb +20 -0
  16. data/flake.lock +60 -0
  17. data/flake.nix +17 -0
  18. data/gemset.nix +500 -0
  19. data/lib/pangea/cli/config.rb +69 -0
  20. data/lib/pangea/cli/constants.rb +36 -0
  21. data/lib/pangea/cli/subcommands/config.rb +14 -0
  22. data/lib/pangea/cli/subcommands/infra.rb +206 -0
  23. data/lib/pangea/cli/subcommands/main.rb +60 -0
  24. data/lib/pangea/cli/subcommands/stitches.rb +18 -0
  25. data/lib/pangea/cli.rb +1 -0
  26. data/lib/pangea/docker.rb +91 -0
  27. data/lib/pangea/errors/incorrect_subcommand_error.rb +2 -0
  28. data/lib/pangea/errors/namespace_not_found_error.rb +2 -0
  29. data/lib/pangea/errors/no_infra_target_error.rb +2 -0
  30. data/lib/pangea/errors/project_not_found_error.rb +2 -0
  31. data/lib/pangea/errors/site_not_found_error.rb +2 -0
  32. data/lib/pangea/extractors/README.md +3 -0
  33. data/lib/pangea/log/init.rb +2 -0
  34. data/lib/pangea/say/init.rb +27 -0
  35. data/lib/pangea/shell/README.md +1 -0
  36. data/lib/pangea/shell/terraform.rb +21 -0
  37. data/lib/pangea/structures/README.md +3 -0
  38. data/lib/pangea/structures/abstract.rb +2 -0
  39. data/lib/pangea/structures/namespace.rb +4 -0
  40. data/lib/pangea/structures/project.rb +4 -0
  41. data/lib/pangea/structures/site.rb +4 -0
  42. data/lib/pangea/synthesizer/config.rb +39 -0
  43. data/lib/pangea/version.rb +3 -0
  44. data/pangea.gemspec +39 -0
  45. data/spec/cli/config_spec.rb +16 -0
  46. data/spec/synthesizer/config_spec.rb +42 -0
  47. 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,2 @@
1
+ class IncorrectSubcommandError < StandardError
2
+ end
@@ -0,0 +1,2 @@
1
+ class NamespaceNotFoundError < StandardError
2
+ end
@@ -0,0 +1,2 @@
1
+ class NoInfraTargetError < StandardError
2
+ end
@@ -0,0 +1,2 @@
1
+ class ProjectNotFoundError < StandardError
2
+ end
@@ -0,0 +1,2 @@
1
+ class SiteNotFoundError < StandardError
2
+ end
@@ -0,0 +1,3 @@
1
+ # extractors
2
+
3
+ take a synthesis and return appropriate hash of namespaces
@@ -0,0 +1,2 @@
1
+ module Log
2
+ 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,3 @@
1
+ # structures
2
+
3
+ The principle structures driving the pangea cli
@@ -0,0 +1,2 @@
1
+ class AbstractStitchesStructure
2
+ end
@@ -0,0 +1,4 @@
1
+ require_relative './abstract'
2
+
3
+ class Namespace < AbstractStitchesStructure
4
+ end
@@ -0,0 +1,4 @@
1
+ require_relative './abstract'
2
+
3
+ class Project < AbstractStitchesStructure
4
+ end
@@ -0,0 +1,4 @@
1
+ require_relative './abstract'
2
+
3
+ class Site < AbstractStitchesStructure
4
+ 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
@@ -0,0 +1,3 @@
1
+ module Pangea
2
+ VERSION = %(0.0.1).freeze
3
+ 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