terraspace 0.1.2 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +21 -0
- data/lib/templates/base/git_hook/hook.sh +5 -0
- data/lib/templates/base/project/.gitignore +0 -1
- data/lib/templates/base/shim/terraspace +7 -0
- data/lib/templates/hcl/project/config/terraform/backend.tf.tt +3 -3
- data/lib/templates/plugin/lib/templates/hcl/project/config/terraform/backend.tf.tt +2 -2
- data/lib/terraspace/app.rb +6 -0
- data/lib/terraspace/builder.rb +2 -46
- data/lib/terraspace/cli.rb +34 -18
- data/lib/terraspace/cli/build/placeholder.rb +40 -0
- data/lib/terraspace/cli/cloud.rb +24 -0
- data/lib/terraspace/cli/commander.rb +8 -1
- data/lib/terraspace/cli/init.rb +67 -0
- data/lib/terraspace/cli/list.rb +13 -0
- data/lib/terraspace/cli/new.rb +8 -0
- data/lib/terraspace/cli/new/git_hook.rb +33 -0
- data/lib/terraspace/cli/new/shim.rb +58 -0
- data/lib/terraspace/cli/summary.rb +9 -12
- data/lib/terraspace/compiler/backend.rb +9 -37
- data/lib/terraspace/compiler/backend/parser.rb +42 -0
- data/lib/terraspace/compiler/builder.rb +6 -2
- data/lib/terraspace/compiler/cleaner.rb +19 -2
- data/lib/terraspace/compiler/cleaner/backend_change.rb +1 -1
- data/lib/terraspace/compiler/dsl/syntax/mod.rb +1 -0
- data/lib/terraspace/compiler/dsl/syntax/mod/backend.rb +16 -3
- data/lib/terraspace/compiler/expander.rb +28 -1
- data/lib/terraspace/compiler/writer.rb +1 -1
- data/lib/terraspace/core.rb +7 -1
- data/lib/terraspace/mod.rb +37 -12
- data/lib/terraspace/mod/remote.rb +1 -1
- data/lib/terraspace/plugin/expander/interface.rb +48 -5
- data/lib/terraspace/plugin/infer_provider.rb +15 -0
- data/lib/terraspace/plugin/layer/interface.rb +5 -0
- data/lib/terraspace/seeder.rb +4 -4
- data/lib/terraspace/terraform/api.rb +53 -0
- data/lib/terraspace/terraform/api/client.rb +10 -0
- data/lib/terraspace/terraform/api/http.rb +106 -0
- data/lib/terraspace/terraform/api/var.rb +72 -0
- data/lib/terraspace/terraform/api/vars.rb +38 -0
- data/lib/terraspace/terraform/api/vars/base.rb +7 -0
- data/lib/terraspace/terraform/api/vars/json.rb +14 -0
- data/lib/terraspace/terraform/api/vars/rb.rb +21 -0
- data/lib/terraspace/terraform/args/custom.rb +1 -1
- data/lib/terraspace/terraform/args/default.rb +16 -2
- data/lib/terraspace/terraform/cloud.rb +25 -0
- data/lib/terraspace/terraform/cloud/workspace.rb +95 -0
- data/lib/terraspace/terraform/runner.rb +1 -1
- data/lib/terraspace/util/sh.rb +1 -1
- data/lib/terraspace/version.rb +1 -1
- data/spec/fixtures/{cache_build_dir → cache_dir}/variables.tf +0 -0
- data/spec/fixtures/projects/hcl/aws/config/backend.tf +1 -1
- data/spec/fixtures/projects/hcl/google/config/backend.tf +1 -1
- data/spec/terraspace/seeder_spec.rb +1 -1
- data/spec/terraspace/terraform/hooks/builder_spec.rb +1 -1
- data/terraspace.gemspec +3 -3
- metadata +37 -17
- data/lib/terraspace/cli/build.rb +0 -7
data/lib/terraspace/cli/new.rb
CHANGED
@@ -1,5 +1,13 @@
|
|
1
1
|
class Terraspace::CLI
|
2
2
|
class New < Terraspace::Command
|
3
|
+
long_desc Help.text(:git_hook)
|
4
|
+
GitHook.cli_options.each { |args| option(*args) }
|
5
|
+
register(GitHook, "git_hook", "git_hook", "Generates new git hook")
|
6
|
+
|
7
|
+
long_desc Help.text(:shim)
|
8
|
+
Shim.cli_options.each { |args| option(*args) }
|
9
|
+
register(Shim, "shim", "shim", "Generates terraspace shim")
|
10
|
+
|
3
11
|
long_desc Help.text(:module)
|
4
12
|
Module.base_options.each { |args| option(*args) }
|
5
13
|
Module.component_options.each { |args| option(*args) }
|
@@ -0,0 +1,33 @@
|
|
1
|
+
class Terraspace::CLI::New
|
2
|
+
class GitHook < Thor::Group
|
3
|
+
include Thor::Actions
|
4
|
+
|
5
|
+
def self.cli_options
|
6
|
+
[
|
7
|
+
[:envs, type: :array, default: %w[dev prod], desc: "envs to build"],
|
8
|
+
[:type, aliases: %w[t], default: "pre-push", desc: "git hook type"],
|
9
|
+
]
|
10
|
+
end
|
11
|
+
cli_options.each { |args| class_option(*args) }
|
12
|
+
|
13
|
+
def self.source_root
|
14
|
+
File.expand_path("../../../templates/base/git_hook", __dir__)
|
15
|
+
end
|
16
|
+
|
17
|
+
def create
|
18
|
+
return unless File.exist?(".git")
|
19
|
+
dest = ".git/hooks/#{options[:type]}"
|
20
|
+
template "hook.sh", dest
|
21
|
+
chmod dest, 0755
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
def terraspace_build_commands
|
26
|
+
code = []
|
27
|
+
@options[:envs].each do |env|
|
28
|
+
code << %Q|TS_ENV=#{env} terraspace build placeholder|
|
29
|
+
end
|
30
|
+
code.join("\n")
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
class Terraspace::CLI::New
|
2
|
+
class Shim < Thor::Group
|
3
|
+
include Thor::Actions
|
4
|
+
|
5
|
+
def self.cli_options
|
6
|
+
[
|
7
|
+
[:path, aliases: %w[p], default: "/usr/local/bin/terraspace", desc: "path to save the shim script"],
|
8
|
+
]
|
9
|
+
end
|
10
|
+
cli_options.each { |args| class_option(*args) }
|
11
|
+
|
12
|
+
def self.source_root
|
13
|
+
File.expand_path("../../../templates/base/shim", __dir__)
|
14
|
+
end
|
15
|
+
|
16
|
+
def set_vars
|
17
|
+
@path = @options[:path]
|
18
|
+
end
|
19
|
+
|
20
|
+
def create
|
21
|
+
return unless File.exist?(".git")
|
22
|
+
dest = @path
|
23
|
+
template "terraspace", dest
|
24
|
+
chmod dest, 0755
|
25
|
+
end
|
26
|
+
|
27
|
+
def message
|
28
|
+
dir = File.dirname(@path)
|
29
|
+
puts <<~EOL
|
30
|
+
A terraspace shim as been generated at #{@path}
|
31
|
+
Please make sure that it is found in the $PATH.
|
32
|
+
|
33
|
+
You can double check with:
|
34
|
+
|
35
|
+
which terraspace
|
36
|
+
|
37
|
+
You should see
|
38
|
+
|
39
|
+
$ which terraspace
|
40
|
+
#{@path}
|
41
|
+
|
42
|
+
If you do not, please add #{dir} to your PATH.
|
43
|
+
You can usually do so by adding this line to ~/.bash_profile and opening a new terminal to check.
|
44
|
+
|
45
|
+
export PATH=#{dir}:/$PATH
|
46
|
+
|
47
|
+
EOL
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
def switch_ruby_version_line
|
52
|
+
rbenv_installed = system("type rbenv 2>&1 > /dev/null")
|
53
|
+
if rbenv_installed
|
54
|
+
'eval "$(rbenv init -)"'
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -11,26 +11,19 @@ class Terraspace::CLI
|
|
11
11
|
end
|
12
12
|
|
13
13
|
def run
|
14
|
+
Terraspace.check_project!
|
14
15
|
build
|
15
16
|
puts "Summary of resources based on backend storage statefiles"
|
16
17
|
backend_expr = '.terraspace-cache/**/backend.*'
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
end
|
18
|
+
# Currently summary assumes backend are within the same bucket and key prefix
|
19
|
+
backend = Dir.glob(backend_expr).first
|
20
|
+
process(backend) if backend
|
21
21
|
end
|
22
22
|
|
23
23
|
# Grab the last module and build that.
|
24
24
|
# Assume the backend key has the same prefix
|
25
25
|
def build
|
26
|
-
|
27
|
-
|
28
|
-
mod = @options[:mod]
|
29
|
-
unless mod
|
30
|
-
mod_path = Dir.glob("{app,vendor}/{modules,stacks}/*").last
|
31
|
-
mod = File.basename(mod_path)
|
32
|
-
end
|
33
|
-
Build.new(@options.merge(mod: mod)).run # generate and init
|
26
|
+
Build::Placeholder.new(@options).build
|
34
27
|
end
|
35
28
|
|
36
29
|
def process(path)
|
@@ -43,6 +36,10 @@ class Terraspace::CLI
|
|
43
36
|
|
44
37
|
info = backend.values.first # structure within the s3 or gcs key
|
45
38
|
klass = summary_class(name)
|
39
|
+
unless klass
|
40
|
+
logger.info "Summary is unavailable for this backend: #{name}"
|
41
|
+
exit
|
42
|
+
end
|
46
43
|
summary = klass.new(info, @options)
|
47
44
|
summary.call
|
48
45
|
end
|
@@ -1,5 +1,3 @@
|
|
1
|
-
require "hcl_parser"
|
2
|
-
|
3
1
|
module Terraspace::Compiler
|
4
2
|
class Backend
|
5
3
|
extend Memoist
|
@@ -9,53 +7,27 @@ module Terraspace::Compiler
|
|
9
7
|
end
|
10
8
|
|
11
9
|
def create
|
12
|
-
klass =
|
10
|
+
klass = backend_interface(backend_name)
|
13
11
|
return unless klass # in case auto-creation is not supported for specific backend
|
14
12
|
|
15
|
-
|
16
|
-
|
13
|
+
interface = klass.new(backend_info)
|
14
|
+
interface.call
|
17
15
|
end
|
18
16
|
|
19
17
|
def backend_name
|
20
|
-
|
18
|
+
backend.keys.first # IE: s3, gcs, etc
|
21
19
|
end
|
22
20
|
|
23
21
|
def backend_info
|
24
|
-
|
25
|
-
end
|
26
|
-
|
27
|
-
def backend_raw
|
28
|
-
return {} unless exist?(backend_path)
|
29
|
-
if backend_path.include?('.json')
|
30
|
-
json_backend
|
31
|
-
else
|
32
|
-
hcl_backend
|
33
|
-
end
|
34
|
-
end
|
35
|
-
|
36
|
-
def json_backend
|
37
|
-
data = JSON.load(IO.read(backend_path))
|
38
|
-
data.dig("terraform", "backend") || {}
|
39
|
-
end
|
40
|
-
|
41
|
-
def hcl_backend
|
42
|
-
return {} unless File.exist?(backend_path)
|
43
|
-
backend_raw = HclParser.load(IO.read(backend_path))
|
44
|
-
return {} unless backend_raw
|
45
|
-
backend_raw.dig("terraform", "backend") || {}
|
46
|
-
end
|
47
|
-
|
48
|
-
def exist?(path)
|
49
|
-
path && File.exist?(path)
|
22
|
+
backend.values.first # structure within the s3 or gcs key
|
50
23
|
end
|
51
24
|
|
52
|
-
def
|
53
|
-
|
54
|
-
Dir.glob(expr).first
|
25
|
+
def backend
|
26
|
+
Parser.new(@mod).result
|
55
27
|
end
|
56
|
-
memoize :
|
28
|
+
memoize :backend
|
57
29
|
|
58
|
-
def
|
30
|
+
def backend_interface(name)
|
59
31
|
return unless name
|
60
32
|
# IE: TerraspacePluginAws::Interfaces::Backend
|
61
33
|
klass_name = Terraspace::Plugin.klass("Backend", backend: name)
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require "hcl_parser"
|
2
|
+
|
3
|
+
class Terraspace::Compiler::Backend
|
4
|
+
class Parser
|
5
|
+
extend Memoist
|
6
|
+
|
7
|
+
def initialize(mod)
|
8
|
+
@mod = mod
|
9
|
+
end
|
10
|
+
|
11
|
+
def result
|
12
|
+
return {} unless exist?(backend_path)
|
13
|
+
if backend_path.include?('.json')
|
14
|
+
json_backend
|
15
|
+
else
|
16
|
+
hcl_backend
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def json_backend
|
21
|
+
data = JSON.load(IO.read(backend_path))
|
22
|
+
data.dig("terraform", "backend") || {}
|
23
|
+
end
|
24
|
+
|
25
|
+
def hcl_backend
|
26
|
+
return {} unless File.exist?(backend_path)
|
27
|
+
backend_raw = HclParser.load(IO.read(backend_path))
|
28
|
+
return {} unless backend_raw
|
29
|
+
backend_raw.dig("terraform", "backend") || {}
|
30
|
+
end
|
31
|
+
|
32
|
+
def exist?(path)
|
33
|
+
path && File.exist?(path)
|
34
|
+
end
|
35
|
+
|
36
|
+
def backend_path
|
37
|
+
expr = "#{@mod.cache_dir}/backend.tf*"
|
38
|
+
Dir.glob(expr).first
|
39
|
+
end
|
40
|
+
memoize :backend_path
|
41
|
+
end
|
42
|
+
end
|
@@ -14,7 +14,7 @@ module Terraspace::Compiler
|
|
14
14
|
|
15
15
|
# build common config files: provider and backend for the root module
|
16
16
|
def build_config
|
17
|
-
return unless
|
17
|
+
return unless build?
|
18
18
|
build_config_templates
|
19
19
|
end
|
20
20
|
|
@@ -25,11 +25,15 @@ module Terraspace::Compiler
|
|
25
25
|
end
|
26
26
|
|
27
27
|
def build_tfvars
|
28
|
-
return unless
|
28
|
+
return unless build?
|
29
29
|
Strategy::Tfvar.new(@mod).run # writer within Strategy to control file ordering
|
30
30
|
end
|
31
31
|
|
32
32
|
private
|
33
|
+
def build?
|
34
|
+
@mod.type == "stack" || @mod.root_module?
|
35
|
+
end
|
36
|
+
|
33
37
|
def build_config_templates
|
34
38
|
expr = "#{Terraspace.root}/config/terraform/**/*"
|
35
39
|
Dir.glob(expr).each do |path|
|
@@ -19,6 +19,7 @@ module Terraspace::Compiler
|
|
19
19
|
# only remove .tf* files. leaving cache .terraform and terraform.state files
|
20
20
|
def remove_materialized_artifacts
|
21
21
|
Dir.glob("#{Terraspace.cache_root}/**/*").each do |path|
|
22
|
+
next unless within_env?(path)
|
22
23
|
next if path.include?(".tfstate")
|
23
24
|
FileUtils.rm_f(path) if File.file?(path)
|
24
25
|
end
|
@@ -31,7 +32,7 @@ module Terraspace::Compiler
|
|
31
32
|
# the file again. With verbose logging, it shows it twice so that's a little bit confusing though.
|
32
33
|
#
|
33
34
|
# def remove_materialized_artifacts_dot_terraform
|
34
|
-
# expr = "#{@mod.
|
35
|
+
# expr = "#{@mod.cache_dir}/.terraform/**/*"
|
35
36
|
#
|
36
37
|
# Dir.glob(expr).each do |path|
|
37
38
|
# logger.info "path #{path}"
|
@@ -40,7 +41,23 @@ module Terraspace::Compiler
|
|
40
41
|
|
41
42
|
def remove_empty_directories
|
42
43
|
return unless File.exist?(Terraspace.cache_root)
|
43
|
-
Dir["#{Terraspace.cache_root}/**/"].reverse_each
|
44
|
+
Dir["#{Terraspace.cache_root}/**/"].reverse_each do |d|
|
45
|
+
next unless within_env?(d)
|
46
|
+
Dir.rmdir(d) if Dir.entries(d).size == 2
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# Only remove files within an env for the TFC VCS-Workflow.
|
51
|
+
# We dont want to run:
|
52
|
+
#
|
53
|
+
# TS_ENV=prod terraspace up demo
|
54
|
+
#
|
55
|
+
# And that to delete the .terraspace-cache/us-west-2/dev files
|
56
|
+
#
|
57
|
+
# May need to allow further customization to this if user project has a stack named the same as the env.
|
58
|
+
#
|
59
|
+
def within_env?(path)
|
60
|
+
path.include?("/#{Terraspace.env}/")
|
44
61
|
end
|
45
62
|
end
|
46
63
|
end
|
@@ -27,7 +27,7 @@ class Terraspace::Compiler::Cleaner
|
|
27
27
|
end
|
28
28
|
|
29
29
|
def current_backend
|
30
|
-
materialized_path = find_src_path("#{@mod.
|
30
|
+
materialized_path = find_src_path("#{@mod.cache_dir}/backend*")
|
31
31
|
IO.read(materialized_path) if materialized_path
|
32
32
|
end
|
33
33
|
|
@@ -3,16 +3,29 @@ module Terraspace::Compiler::Dsl::Syntax::Mod
|
|
3
3
|
def backend(name, props={})
|
4
4
|
terraform = @structure[:terraform] ||= {}
|
5
5
|
backend = terraform[:backend] ||= {}
|
6
|
-
|
6
|
+
expansion_all!(name, props)
|
7
7
|
backend[name] = props
|
8
8
|
end
|
9
9
|
|
10
|
-
def
|
10
|
+
def expansion_all!(backend_name, props={})
|
11
11
|
Terraspace::Compiler::Expander.new(@mod, backend_name).expand(props)
|
12
12
|
end
|
13
13
|
|
14
|
+
# Can set opts to explicitly use an specfic backend. Example:
|
15
|
+
#
|
16
|
+
# opts = {backend: s3}
|
17
|
+
#
|
18
|
+
# Else Terraspace autodetects the backend installed.
|
19
|
+
#
|
20
|
+
def expansion(string, opts={})
|
21
|
+
expander = Terraspace::Compiler::Expander.autodetect(@mod, opts)
|
22
|
+
expander.expansion(string)
|
23
|
+
end
|
24
|
+
|
25
|
+
# DEPRECATED: Will be removed in future release
|
14
26
|
def backend_expand(backend_name, string)
|
15
|
-
|
27
|
+
logger.info "DEPRECATED backend_expand: instead use expansion(string)"
|
28
|
+
Terraspace::Compiler::Expander.new(@mod, backend_name).expansion(string)
|
16
29
|
end
|
17
30
|
end
|
18
31
|
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
module Terraspace::Compiler
|
2
2
|
class Expander
|
3
|
-
delegate :expand, :
|
3
|
+
delegate :expand, :expansion, to: :expander
|
4
4
|
|
5
5
|
attr_reader :expander
|
6
6
|
def initialize(mod, name)
|
@@ -15,5 +15,32 @@ module Terraspace::Compiler
|
|
15
15
|
rescue NameError
|
16
16
|
Terraspace::Plugin::Expander::Generic
|
17
17
|
end
|
18
|
+
|
19
|
+
class << self
|
20
|
+
extend Memoist
|
21
|
+
|
22
|
+
def autodetect(mod, opts={})
|
23
|
+
backend = opts[:backend]
|
24
|
+
unless backend
|
25
|
+
plugin = find_plugin
|
26
|
+
backend = plugin[:backend]
|
27
|
+
end
|
28
|
+
new(mod, backend)
|
29
|
+
end
|
30
|
+
memoize :autodetect
|
31
|
+
|
32
|
+
def find_plugin
|
33
|
+
plugins = Terraspace::Plugin.meta
|
34
|
+
if plugins.size == 1
|
35
|
+
plugins.first[1]
|
36
|
+
else
|
37
|
+
precedence = %w[aws azurerm google]
|
38
|
+
plugin = precedence.find do |provider|
|
39
|
+
plugins[provider]
|
40
|
+
end
|
41
|
+
plugins[plugin]
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
18
45
|
end
|
19
46
|
end
|