pangea 0.0.41 → 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.
@@ -1,15 +1,12 @@
1
1
  module Constants
2
- # useful names
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
- ENV.fetch(%(HOME), nil),
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
- if bucket_exist?(bucket_name)
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(ENV.fetch(%(HOME), nil), %(.pangea))
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
- process_modules
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
- namespace = cfg_synth[:namespace][namespace_name]
75
- context_names = namespace.keys.map(&:to_sym)
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 = namespace[cn]
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)
@@ -10,7 +10,6 @@ require %(pangea/version)
10
10
 
11
11
  class Command < PangeaCommand
12
12
  usage do
13
- desc %(manage crud apis declaratively)
14
13
  program %(pangea)
15
14
  end
16
15
 
@@ -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 %(pangea/cli/subcommands/main)
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,10 @@
1
+ require 'pangea/shell'
2
+ # an executor is anything that follows opentofu api
3
+
4
+ module Pangea
5
+ module Executor
6
+ def run(command)
7
+ Pangea::Shell.run(command)
8
+ end
9
+ end
10
+ 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
@@ -113,10 +113,10 @@ module PangeaModule
113
113
 
114
114
  # entrypoint for module processing
115
115
  def process(mod)
116
- Say.terminal mod
117
116
  mod = symbolize(mod)
118
117
 
119
118
  name = mod.fetch(:name)
119
+ data = mod.fetch(:data, {})
120
120
 
121
121
  raise ArgumentError, %(name cannot be nil) if name.nil?
122
122
 
@@ -126,8 +126,7 @@ module PangeaModule
126
126
  require_name = %(#{context}-#{name})
127
127
 
128
128
  require require_name
129
- puts render
130
- puts %(end)
129
+ render(data)
131
130
  end
132
131
  end
133
132
  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