pangea 0.0.42 → 0.0.46
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 +4 -4
- data/.gitignore +7 -55
- data/Gemfile +1 -30
- data/Gemfile.lock +121 -70
- data/Rakefile +1 -2
- data/bin/pangea +2 -0
- data/flake.lock +49 -16
- data/flake.nix +33 -13
- data/gemset.nix +285 -106
- data/lib/pangea/cli/config.rb +1 -1
- data/lib/pangea/cli/constants.rb +4 -7
- data/lib/pangea/cli/subcommands/config.rb +3 -9
- data/lib/pangea/cli/subcommands/infra.rb +35 -8
- data/lib/pangea/cli/subcommands/main.rb +0 -1
- data/lib/pangea/cli/subcommands/state.rb +30 -0
- data/lib/pangea/cli.rb +63 -1
- data/lib/pangea/config.rb +35 -0
- data/lib/pangea/executor.rb +10 -0
- data/lib/pangea/modcache.rb +79 -0
- data/lib/pangea/module.rb +17 -0
- data/lib/pangea/processor.rb +101 -0
- data/lib/pangea/renderer.rb +241 -0
- data/lib/pangea/shell/terraform.rb +3 -3
- data/lib/pangea/shell.rb +27 -0
- data/lib/pangea/stack.rb +11 -0
- data/lib/pangea/state.rb +96 -0
- data/lib/pangea/structures/abstract.rb +2 -2
- data/lib/pangea/synthesizer/config.rb +2 -3
- data/lib/pangea/utils.rb +32 -0
- data/lib/pangea/version.rb +1 -1
- data/lib/pangea.rb +18 -6
- data/pangea.gemspec +28 -26
- metadata +74 -21
data/lib/pangea/cli/constants.rb
CHANGED
@@ -1,15 +1,12 @@
|
|
1
1
|
module Constants
|
2
|
-
|
3
|
-
ARTIFACT_FILE = %(artifact.tf.json)
|
2
|
+
ARTIFACT_FILE = %(artifact.tf.json).freeze
|
4
3
|
|
5
|
-
# cli related constants
|
6
4
|
CACHE_DIR = File.join(
|
7
|
-
|
5
|
+
Dir.home,
|
8
6
|
%(.cache),
|
9
7
|
%(pangea)
|
10
8
|
).freeze
|
11
9
|
|
12
|
-
# project related constants
|
13
10
|
PROJECT_VERSION = %i[version.rb].freeze
|
14
11
|
|
15
12
|
# order of elements matters here
|
@@ -18,10 +15,10 @@ module Constants
|
|
18
15
|
# this order. changing this can significantly
|
19
16
|
# impact how a project is processed.
|
20
17
|
PROJECT_SRC_DIRS = %i[
|
21
|
-
lib
|
22
|
-
pre
|
23
18
|
resources
|
24
19
|
post
|
20
|
+
lib
|
21
|
+
pre
|
25
22
|
].freeze
|
26
23
|
|
27
24
|
# configuration extensions
|
@@ -101,9 +101,7 @@ class ConfigCommand < PangeaCommand
|
|
101
101
|
###################################################################
|
102
102
|
# setup modules
|
103
103
|
###################################################################
|
104
|
-
|
105
|
-
module_dirs = %w[lib src test]
|
106
|
-
modules = ctx[:modules]
|
104
|
+
modules = ctx[:modules]
|
107
105
|
|
108
106
|
modules.each_key do |mod_name|
|
109
107
|
this_mod = modules[mod_name]
|
@@ -187,11 +185,7 @@ class ConfigCommand < PangeaCommand
|
|
187
185
|
|
188
186
|
bucket_name =
|
189
187
|
ctx[:state_config][:terraform][:s3][:bucket]
|
190
|
-
|
191
|
-
nil
|
192
|
-
else
|
193
|
-
s3.create_bucket(bucket: bucket_name)
|
194
|
-
end
|
188
|
+
s3.create_bucket(bucket: bucket_name) unless bucket_exist?(bucket_name)
|
195
189
|
|
196
190
|
# end s3 bucket setup
|
197
191
|
|
@@ -199,7 +193,7 @@ class ConfigCommand < PangeaCommand
|
|
199
193
|
# setup directories
|
200
194
|
###################################################################
|
201
195
|
|
202
|
-
base_dir = File.join(
|
196
|
+
base_dir = File.join(Dir.home, %(.pangea))
|
203
197
|
context_dir = File.join(base_dir, ctx_name)
|
204
198
|
synth_dir = File.join(base_dir, synth_dir)
|
205
199
|
|
@@ -6,10 +6,9 @@ require %(pangea/errors/site_not_found_error)
|
|
6
6
|
require %(pangea/errors/project_not_found_error)
|
7
7
|
require %(pangea/errors/no_infra_target_error)
|
8
8
|
require %(pangea/errors/incorrect_subcommand_error)
|
9
|
+
require %(pangea/shell/terraform)
|
9
10
|
require %(pangea/say/init)
|
10
11
|
require %(pangea/modules)
|
11
|
-
# require %(terraform-synthesizer)
|
12
|
-
# require %(json)
|
13
12
|
|
14
13
|
class InfraCommand < PangeaCommand
|
15
14
|
include Constants
|
@@ -50,6 +49,14 @@ class InfraCommand < PangeaCommand
|
|
50
49
|
HELP
|
51
50
|
end
|
52
51
|
|
52
|
+
def pangea_home
|
53
|
+
File.join(Dir.home, %(.pangea))
|
54
|
+
end
|
55
|
+
|
56
|
+
def terraform_files_home
|
57
|
+
File.join(pangea_home, %(terraform))
|
58
|
+
end
|
59
|
+
|
53
60
|
def cfg_synth
|
54
61
|
@cfg_synth ||= Config.resolve_configurations
|
55
62
|
end
|
@@ -57,7 +64,23 @@ class InfraCommand < PangeaCommand
|
|
57
64
|
def plan(argv)
|
58
65
|
Say.terminal %(planning!)
|
59
66
|
parse(argv)
|
60
|
-
|
67
|
+
namespaces = cfg_synth[:namespace].keys.map(&:to_sym)
|
68
|
+
namespaces.each do |namespace|
|
69
|
+
system(%(mkdir -p #{File.join(terraform_files_home.to_s, namespace.to_s)})) unless Dir.exist?(
|
70
|
+
File.join(
|
71
|
+
terraform_files_home.to_s, namespace.to_s
|
72
|
+
)
|
73
|
+
)
|
74
|
+
processed = process_modules(ns)
|
75
|
+
template = processed[:template]
|
76
|
+
ns_home = File.join(terraform_files_home, ns.to_s)
|
77
|
+
File.write(
|
78
|
+
File.join(ns_home, %(template.tf.json)),
|
79
|
+
JSON[template]
|
80
|
+
)
|
81
|
+
system(%(cd #{ns_home} && terraform init))
|
82
|
+
system(%(cd #{ns_home} && terraform plan))
|
83
|
+
end
|
61
84
|
end
|
62
85
|
|
63
86
|
def config
|
@@ -68,22 +91,26 @@ class InfraCommand < PangeaCommand
|
|
68
91
|
reject_empty_configurations
|
69
92
|
end
|
70
93
|
|
71
|
-
def process_modules
|
94
|
+
def process_modules(namespace)
|
95
|
+
template = {}
|
72
96
|
namespaces = cfg_synth[:namespace].keys.map(&:to_sym)
|
73
97
|
namespaces.each do |namespace_name|
|
74
|
-
|
75
|
-
context_names =
|
98
|
+
ns = cfg_synth[:namespace][namespace_name]
|
99
|
+
context_names = ns.keys.map(&:to_sym)
|
76
100
|
|
77
101
|
context_names.each do |cn|
|
78
|
-
context =
|
102
|
+
context = ns[cn]
|
79
103
|
modules = context[:modules]
|
80
104
|
|
105
|
+
next unless namespace_name.to_s.eql?(namespace.to_s)
|
106
|
+
|
81
107
|
modules.each_key do |mod_key|
|
82
108
|
mod = modules[mod_key]
|
83
|
-
PangeaModule.process(mod)
|
109
|
+
template.merge!(PangeaModule.process(mod))
|
84
110
|
end
|
85
111
|
end
|
86
112
|
end
|
113
|
+
{ template: template, namespaces: cfg_synth[:namespace].keys.map(&:to_sym) }
|
87
114
|
end
|
88
115
|
|
89
116
|
def run(argv)
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# require %(pangea/cli/subcommands/pangea)
|
2
|
+
# require %(pangea/cli/constants)
|
3
|
+
# require %(pangea/cli/config)
|
4
|
+
# require %(pangea/errors/namespace_not_found_error)
|
5
|
+
# require %(pangea/errors/site_not_found_error)
|
6
|
+
# require %(pangea/errors/project_not_found_error)
|
7
|
+
# require %(pangea/errors/no_infra_target_error)
|
8
|
+
# require %(pangea/errors/incorrect_subcommand_error)
|
9
|
+
# require %(pangea/shell/terraform)
|
10
|
+
# require %(pangea/say/init)
|
11
|
+
# require %(pangea/modules)
|
12
|
+
|
13
|
+
class StateCommand < PangeaCommand
|
14
|
+
include Constants
|
15
|
+
NAME = :state
|
16
|
+
|
17
|
+
usage do
|
18
|
+
desc %(manage infrastructure state)
|
19
|
+
program %(pangea)
|
20
|
+
command %(state)
|
21
|
+
end
|
22
|
+
|
23
|
+
argument :subcommand do
|
24
|
+
desc %(manage infrastructure state)
|
25
|
+
end
|
26
|
+
|
27
|
+
def run
|
28
|
+
nil
|
29
|
+
end
|
30
|
+
end
|
data/lib/pangea/cli.rb
CHANGED
@@ -1 +1,63 @@
|
|
1
|
-
require
|
1
|
+
require 'terraform-synthesizer'
|
2
|
+
require 'pangea/processor'
|
3
|
+
require 'pangea/renderer'
|
4
|
+
require 'pangea/config'
|
5
|
+
require 'pangea/state'
|
6
|
+
require 'pangea/utils'
|
7
|
+
require 'thor'
|
8
|
+
|
9
|
+
module TheseUtils
|
10
|
+
class << self
|
11
|
+
def cfg
|
12
|
+
@cfg ||= Pangea::Utils.symbolize(Pangea::Config.config)
|
13
|
+
end
|
14
|
+
|
15
|
+
def state_init
|
16
|
+
state = Pangea::S3State.new
|
17
|
+
cfg[:namespaces].each_key do |nk|
|
18
|
+
ns_config = cfg[:namespaces][nk]
|
19
|
+
case ns_config[:state][:type].to_sym
|
20
|
+
when :s3
|
21
|
+
bucket_name = ns_config[:state][:config][:bucket]
|
22
|
+
region = ns_config[:state][:config][:region]
|
23
|
+
lock_table_name = ns_config[:state][:config][:lock]
|
24
|
+
state.create_bucket(name: bucket_name, region: region)
|
25
|
+
state.create_dynamodb_table_for_lock(name: lock_table_name, region: region)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
module Pangea
|
33
|
+
class Cli < Thor
|
34
|
+
desc 'apply FILE', 'apply a FILE of pangea code'
|
35
|
+
def apply(file)
|
36
|
+
Pangea::Processor.register_action('plan')
|
37
|
+
Pangea::Processor.process(File.read(file))
|
38
|
+
end
|
39
|
+
|
40
|
+
desc 'show FILE', 'transpile a FILE of pangea code to json'
|
41
|
+
def show(file)
|
42
|
+
Pangea::Processor.register_action('show')
|
43
|
+
Pangea::Processor.process(File.read(file))
|
44
|
+
end
|
45
|
+
|
46
|
+
desc 'plan FILE', 'plan a FILE of pangea code'
|
47
|
+
def plan(file)
|
48
|
+
Pangea::Processor.register_action('plan')
|
49
|
+
Pangea::Processor.process(File.read(file))
|
50
|
+
end
|
51
|
+
|
52
|
+
desc 'destroy FILE', 'destroy a FILE of pangea code'
|
53
|
+
def destroy(file)
|
54
|
+
Pangea::Processor.register_action('destroy')
|
55
|
+
Pangea::Processor.process(File.read(file))
|
56
|
+
end
|
57
|
+
|
58
|
+
desc 'init', 'initialize an s3 state configuation according to pangea.yml'
|
59
|
+
def init
|
60
|
+
TheseUtils.state_init
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'pangea/utils'
|
2
|
+
require 'yaml'
|
3
|
+
|
4
|
+
module Pangea
|
5
|
+
module Config
|
6
|
+
class << self
|
7
|
+
# Public: Returns a memoized configuration Hash.
|
8
|
+
# The configuration is lazily loaded and deep-merged so that:
|
9
|
+
# - Keys in pangea.yml override keys in ~/.config/pangea/config.yml
|
10
|
+
# - Keys in ~/.config/pangea/config.yml override keys in /etc/pangea/config.yml
|
11
|
+
def config
|
12
|
+
@config ||= load_config
|
13
|
+
end
|
14
|
+
|
15
|
+
# File paths in increasing order of priority.
|
16
|
+
CONFIG_PATHS = [
|
17
|
+
'/etc/pangea/config.yml',
|
18
|
+
File.expand_path('~/.config/pangea/config.yml'),
|
19
|
+
'pangea.yml'
|
20
|
+
].freeze
|
21
|
+
|
22
|
+
# Loads and deep-merges available YAML configuration files.
|
23
|
+
def load_config
|
24
|
+
merged = {}
|
25
|
+
CONFIG_PATHS.each do |file_path|
|
26
|
+
if File.exist?(file_path)
|
27
|
+
data = YAML.load_file(file_path)
|
28
|
+
merged = Pangea::Utils.deep_merge(merged, data) if data.is_a?(Hash)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
merged
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
# unfortunately, in order to call an opentofu mod/resource combination a file must be created
|
2
|
+
# represent the resource module and then another file must represent the caller.
|
3
|
+
# this means temporarily having a folder created for each as we move through mod/resources
|
4
|
+
|
5
|
+
module Pangea
|
6
|
+
class ModCache
|
7
|
+
attr_reader :name,
|
8
|
+
:mod,
|
9
|
+
:resource,
|
10
|
+
:virtual,
|
11
|
+
:basedir,
|
12
|
+
:modcachedir,
|
13
|
+
:address
|
14
|
+
|
15
|
+
def initialize(name, mod, resource, virtual)
|
16
|
+
@name = name
|
17
|
+
@mod = mod
|
18
|
+
@resource = resource
|
19
|
+
@virtual = virtual
|
20
|
+
@basedir = File.join(Dir.home, '.pangea')
|
21
|
+
@modcachedir = File.join(basedir, 'modcache')
|
22
|
+
@address = "#{name}/#{mod}/#{resource}/#{virtual}/module"
|
23
|
+
@internal_module_file_name = 'internal.tf.json'
|
24
|
+
end
|
25
|
+
|
26
|
+
def preflight
|
27
|
+
[basedir, modcachedir, File.join(modcachedir, address)].each do |d|
|
28
|
+
`mkdir -p #{modcachedir}` unless Dir.exist?(d)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def place_internal_module(template)
|
33
|
+
preflight
|
34
|
+
internal_module_path = File.join(
|
35
|
+
modcachedir,
|
36
|
+
@internal_module_file_name
|
37
|
+
)
|
38
|
+
File.write(internal_module_path, template)
|
39
|
+
end
|
40
|
+
|
41
|
+
def place_caller_template
|
42
|
+
internal_module_path = File.join(
|
43
|
+
modcachedir,
|
44
|
+
@internal_module_file_name
|
45
|
+
)
|
46
|
+
caller_template = {
|
47
|
+
provider: {
|
48
|
+
aws: {
|
49
|
+
region: 'us-east-1'
|
50
|
+
}
|
51
|
+
},
|
52
|
+
module: {
|
53
|
+
"#{name}": {
|
54
|
+
source: internal_module_path
|
55
|
+
}
|
56
|
+
}
|
57
|
+
}
|
58
|
+
|
59
|
+
internal_caller_template_path = File.join(
|
60
|
+
modcachedir,
|
61
|
+
'caller.tf.json'
|
62
|
+
)
|
63
|
+
File.write(internal_caller_template_path, JSON[caller_template])
|
64
|
+
end
|
65
|
+
|
66
|
+
# class << self
|
67
|
+
# def register_module(mod)
|
68
|
+
# @mod = mod
|
69
|
+
# end
|
70
|
+
#
|
71
|
+
# def register_name(name)
|
72
|
+
# @name = name
|
73
|
+
# end
|
74
|
+
#
|
75
|
+
#
|
76
|
+
# def create_directories(dirs); end
|
77
|
+
# end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'pangea/renderer'
|
2
|
+
require 'pangea/config'
|
3
|
+
|
4
|
+
module Pangea
|
5
|
+
# Pangea::Module is a group of delcared render_component calls
|
6
|
+
class Module
|
7
|
+
attr_reader :name
|
8
|
+
|
9
|
+
def initialize(name)
|
10
|
+
@name = name
|
11
|
+
end
|
12
|
+
|
13
|
+
# def render(namespace, &block)
|
14
|
+
# Pangea::S3Renderer.new(namespace).render_component(name, &block)
|
15
|
+
# end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
require 'terraform-synthesizer'
|
2
|
+
require 'pangea/renderer'
|
3
|
+
require 'pangea/config'
|
4
|
+
require 'pangea/utils'
|
5
|
+
require 'aws-sdk-s3'
|
6
|
+
|
7
|
+
module Pangea
|
8
|
+
# ingests files and builds a context
|
9
|
+
# for rendering Pangea programming
|
10
|
+
module Processor
|
11
|
+
class << self
|
12
|
+
def register_action(action)
|
13
|
+
permitted_actions = %i[plan apply show]
|
14
|
+
@action = action if permitted_actions.map(&:to_s).include?(action.to_s)
|
15
|
+
@action = 'plan' if @action.nil?
|
16
|
+
end
|
17
|
+
|
18
|
+
def process(content)
|
19
|
+
instance_eval(content)
|
20
|
+
end
|
21
|
+
|
22
|
+
def synthesizer
|
23
|
+
@synthesizer ||= TerraformSynthesizer.new
|
24
|
+
end
|
25
|
+
|
26
|
+
def config
|
27
|
+
@config ||= Pangea::Utils.symbolize(
|
28
|
+
Pangea::Config.config
|
29
|
+
)
|
30
|
+
end
|
31
|
+
|
32
|
+
def namespace
|
33
|
+
@namespace ||= ENV.fetch('PANGEA_NAMESPACE')
|
34
|
+
end
|
35
|
+
|
36
|
+
def s3
|
37
|
+
@s3 = Aws::S3::Client.new
|
38
|
+
end
|
39
|
+
|
40
|
+
def bin
|
41
|
+
'tofu'
|
42
|
+
end
|
43
|
+
|
44
|
+
def namespace_config
|
45
|
+
sns = ''
|
46
|
+
config[:namespaces].each_key do |ns|
|
47
|
+
sns = config[:namespaces][ns] if ns.to_s.eql?(namespace.to_s)
|
48
|
+
end
|
49
|
+
@namespace_config ||= sns
|
50
|
+
end
|
51
|
+
|
52
|
+
def template(name, &block)
|
53
|
+
prefix = "#{namespace}/#{name}"
|
54
|
+
pangea_home = %(#{Dir.home}/.pangea/#{namespace})
|
55
|
+
local_cache = File.join(pangea_home, prefix)
|
56
|
+
`mkdir -p #{local_cache}` unless Dir.exist?(local_cache)
|
57
|
+
synthesizer.synthesize(&block)
|
58
|
+
sns = namespace_config
|
59
|
+
unless synthesizer.synthesis[:terraform]
|
60
|
+
synthesizer.synthesize do
|
61
|
+
terraform do
|
62
|
+
backend(
|
63
|
+
s3: {
|
64
|
+
key: prefix,
|
65
|
+
dynamodb_table: sns[:state][:config][:lock].to_s,
|
66
|
+
bucket: sns[:state][:config][:bucket].to_s,
|
67
|
+
region: sns[:state][:config][:region].to_s,
|
68
|
+
encrypt: true
|
69
|
+
}
|
70
|
+
)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
File.write(
|
75
|
+
File.join(
|
76
|
+
local_cache, 'main.tf.json'
|
77
|
+
), JSON[synthesizer.synthesis]
|
78
|
+
)
|
79
|
+
|
80
|
+
system("cd #{local_cache} && #{bin} init -input=false") unless File.exist?(
|
81
|
+
File.join(
|
82
|
+
local_cache,
|
83
|
+
'.terraform.lock.hcl'
|
84
|
+
)
|
85
|
+
)
|
86
|
+
|
87
|
+
system("cd #{local_cache} && #{bin} apply -auto-approve") if @action.to_s.eql?('apply')
|
88
|
+
system("cd #{local_cache} && #{bin} plan") if @action.to_s.eql?('plan')
|
89
|
+
system("cd #{local_cache} && #{bin} destroy -auto-approve") if @action.to_s.eql?('destroy')
|
90
|
+
|
91
|
+
template = Pangea::Utils.symbolize(
|
92
|
+
JSON[File.read(
|
93
|
+
File.join(local_cache, 'main.tf.json')
|
94
|
+
)]
|
95
|
+
)
|
96
|
+
puts JSON.pretty_generate(tempalte) if @action.to_s.eql?('show')
|
97
|
+
{ template: template }
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|