terraspace-bundler 0.1.0 → 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 +13 -0
- data/README.md +72 -24
- data/lib/terraspace_bundler.rb +1 -0
- data/lib/terraspace_bundler/cli/base.rb +9 -1
- data/lib/terraspace_bundler/cli/bundle.rb +26 -12
- data/lib/terraspace_bundler/cli/help/bundle/info.md +8 -0
- data/lib/terraspace_bundler/cli/help/bundle/install.md +1 -1
- data/lib/terraspace_bundler/cli/help/bundle/list.md +9 -0
- data/lib/terraspace_bundler/cli/help/bundle/update.md +6 -0
- data/lib/terraspace_bundler/cli/{clean.rb → purge_cache.rb} +3 -3
- data/lib/terraspace_bundler/cli/{install.rb → runner.rb} +1 -1
- data/lib/terraspace_bundler/config.rb +15 -2
- data/lib/terraspace_bundler/core.rb +9 -4
- data/lib/terraspace_bundler/dsl.rb +6 -3
- data/lib/terraspace_bundler/dsl/concern.rb +7 -0
- data/lib/terraspace_bundler/dsl/syntax.rb +8 -0
- data/lib/terraspace_bundler/exporter.rb +47 -0
- data/lib/terraspace_bundler/info.rb +25 -0
- data/lib/terraspace_bundler/installer.rb +15 -33
- data/lib/terraspace_bundler/list.rb +22 -0
- data/lib/terraspace_bundler/lockfile.rb +70 -0
- data/lib/terraspace_bundler/lockfile/version_comparer.rb +43 -0
- data/lib/terraspace_bundler/lockfile/yamler.rb +37 -0
- data/lib/terraspace_bundler/logger.rb +0 -20
- data/lib/terraspace_bundler/logger/formatter.rb +7 -0
- data/lib/terraspace_bundler/mod.rb +31 -63
- data/lib/terraspace_bundler/mod/downloader.rb +55 -0
- data/lib/terraspace_bundler/mod/{tmp_paths.rb → path_concern.rb} +1 -1
- data/lib/terraspace_bundler/mod/props_builder.rb +57 -0
- data/lib/terraspace_bundler/mod/props_extension.rb +15 -0
- data/lib/terraspace_bundler/mod/registry.rb +8 -6
- data/lib/terraspace_bundler/syncer.rb +62 -4
- data/lib/terraspace_bundler/terrafile.rb +39 -0
- data/lib/terraspace_bundler/util/git.rb +31 -0
- data/lib/terraspace_bundler/{logging.rb → util/logging.rb} +1 -1
- data/lib/terraspace_bundler/version.rb +1 -1
- data/spec/fixtures/Terrafile +2 -2
- data/spec/terraform_bundler/runner_spec.rb +17 -0
- metadata +24 -17
- data/lib/terraspace_bundler/cli/update.rb +0 -7
- data/lib/terraspace_bundler/helper/git.rb +0 -21
- data/lib/terraspace_bundler/mod/export.rb +0 -23
- data/lib/terraspace_bundler/mod/locked.rb +0 -48
- data/lib/terraspace_bundler/mod/sync.rb +0 -53
- data/lib/terraspace_bundler/setup.rb +0 -20
- data/lib/terraspace_bundler/updater.rb +0 -49
- data/lib/terraspace_bundler/updater/lockfile.rb +0 -48
- data/lib/terraspace_bundler/util/registry.rb +0 -24
- data/spec/terraform_bundler/installer_spec.rb +0 -16
@@ -0,0 +1,25 @@
|
|
1
|
+
module TerraspaceBundler
|
2
|
+
class Info < TB::CLI::Base
|
3
|
+
def run
|
4
|
+
name = @options[:mod]
|
5
|
+
found = lockfile.mods.find { |m| m.name == @options[:mod] }
|
6
|
+
if found
|
7
|
+
show(found)
|
8
|
+
else
|
9
|
+
logger.info "Could not find module in #{TB.config.lockfile}: #{name}".color(:red)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def show(mod)
|
14
|
+
props = mod.props.reject { |k,v| k == :name }.stringify_keys # for sort
|
15
|
+
puts "#{mod.name}:"
|
16
|
+
props.keys.sort.each do |k|
|
17
|
+
puts " #{k}: #{props[k]}"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def lockfile
|
22
|
+
Lockfile.instance
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -1,45 +1,27 @@
|
|
1
1
|
module TerraspaceBundler
|
2
2
|
class Installer < CLI::Base
|
3
|
-
extend Memoist
|
4
|
-
|
5
|
-
def initialize(options={})
|
6
|
-
super
|
7
|
-
@download = options[:download].nil? ? true : options[:download]
|
8
|
-
end
|
9
|
-
|
10
3
|
def run
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
logger.info "Modules saved to #{TB.config.export_path}"
|
4
|
+
Syncer.new(@options).run
|
5
|
+
Exporter.new(@options).run
|
6
|
+
finish_message
|
15
7
|
end
|
16
8
|
|
17
|
-
def
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
9
|
+
def finish_message
|
10
|
+
no_modules_found = true
|
11
|
+
export_paths.each do |export_path|
|
12
|
+
found = Dir.exist?(export_path) && !Dir.empty?(export_path)
|
13
|
+
next unless found
|
14
|
+
logger.info "Modules saved to #{export_path}"
|
15
|
+
no_modules_found = false
|
24
16
|
end
|
25
17
|
|
26
|
-
|
18
|
+
logger.info("No modules were found.") if no_modules_found
|
27
19
|
end
|
28
20
|
|
29
|
-
def
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
def mods
|
34
|
-
locked_mods.map(&:to_mod)
|
35
|
-
end
|
36
|
-
memoize :mods
|
37
|
-
|
38
|
-
def locked_mods
|
39
|
-
data = YAML.load_file(TB.config.lockfile).deep_symbolize_keys
|
40
|
-
data.map do |name, info|
|
41
|
-
Mod::Locked.new(name, info)
|
42
|
-
end
|
21
|
+
def export_paths
|
22
|
+
export_paths = Terrafile.instance.mods.map(&:export_to).compact.uniq
|
23
|
+
export_paths << TB.config.export_path
|
24
|
+
export_paths
|
43
25
|
end
|
44
26
|
end
|
45
27
|
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module TerraspaceBundler
|
2
|
+
class List < TB::CLI::Base
|
3
|
+
def run
|
4
|
+
file = TB.config.lockfile
|
5
|
+
unless File.exist?(file)
|
6
|
+
logger.info "No #{file} found".color(:red)
|
7
|
+
logger.info "Maybe run: terraspace bundle"
|
8
|
+
return
|
9
|
+
end
|
10
|
+
|
11
|
+
logger.info "Modules included by #{file}\n\n"
|
12
|
+
lockfile.mods.each do |mod|
|
13
|
+
logger.info " #{mod.name}"
|
14
|
+
end
|
15
|
+
logger.info "\nUse `terraspace bundle info` to print more detailed information about a module"
|
16
|
+
end
|
17
|
+
|
18
|
+
def lockfile
|
19
|
+
Lockfile.instance
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
require "yaml"
|
2
|
+
|
3
|
+
module TerraspaceBundler
|
4
|
+
class Lockfile
|
5
|
+
include Singleton
|
6
|
+
include TB::Util::Logging
|
7
|
+
|
8
|
+
# {"vpc"=>
|
9
|
+
# {"sha"=>"52328b2b5197f9b95e3005cfcfb99595040ee45b",
|
10
|
+
# "source"=>"org/terraform-aws-vpc",
|
11
|
+
# "url"=>"git@github.com:org/terraform-aws-vpc"},
|
12
|
+
# "instance"=>
|
13
|
+
# {"sha"=>"570cca3ea7b25e3af1961dc57b27ca2c129b934a",
|
14
|
+
# "source"=>"org/terraform-aws-instance",
|
15
|
+
# "url"=>"git@github.com:org/terraform-aws-instance"}}
|
16
|
+
@@mods = nil
|
17
|
+
def mods
|
18
|
+
return @@mods if @@mods
|
19
|
+
lockfile = TB.config.lockfile
|
20
|
+
mods = File.exist?(lockfile) ? YAML.load_file(lockfile) : []
|
21
|
+
@@mods = mods.map do |name, props|
|
22
|
+
new_mod(name, props)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def new_mod(name, props)
|
27
|
+
Mod.new(props.merge(name: name))
|
28
|
+
end
|
29
|
+
|
30
|
+
# update (if version mismatch) or create (if missing)
|
31
|
+
def sync(mod)
|
32
|
+
changed = changed?(mod)
|
33
|
+
logger.debug "Detecting change for mod #{mod.name} changed #{changed.inspect}"
|
34
|
+
replace!(mod) if changed
|
35
|
+
end
|
36
|
+
|
37
|
+
# mod built from Terrafile
|
38
|
+
def changed?(mod)
|
39
|
+
# missing module case
|
40
|
+
found = mods.find { |locked_mod| locked_mod.name == mod.name }
|
41
|
+
unless found
|
42
|
+
logger.debug "Replacing mod: #{mod.name}. Not found in Terrafile.lock"
|
43
|
+
return true
|
44
|
+
end
|
45
|
+
|
46
|
+
comparer = VersionComparer.new(found, mod)
|
47
|
+
comparer.run
|
48
|
+
logger.debug(comparer.reason) if comparer.reason
|
49
|
+
comparer.changed?
|
50
|
+
end
|
51
|
+
|
52
|
+
def replace!(mod)
|
53
|
+
# mods are immediately fresh from writing to @@mods directly
|
54
|
+
@@mods.delete_if { |m| m.name == mod.name }
|
55
|
+
@@mods << mod
|
56
|
+
@@mods.sort_by!(&:name)
|
57
|
+
end
|
58
|
+
|
59
|
+
def prune(removed_mods)
|
60
|
+
removals = removed_mods.map(&:name)
|
61
|
+
@@mods.delete_if { |m| removals.include?(m.name) }
|
62
|
+
end
|
63
|
+
|
64
|
+
class << self
|
65
|
+
def write
|
66
|
+
Yamler.write(@@mods) if @@mods
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
class TerraspaceBundler::Lockfile
|
2
|
+
class VersionComparer
|
3
|
+
attr_reader :reason, :changed
|
4
|
+
def initialize(locked, current)
|
5
|
+
@locked, @current = locked, current
|
6
|
+
@changed = false
|
7
|
+
end
|
8
|
+
|
9
|
+
def changed?
|
10
|
+
@changed
|
11
|
+
end
|
12
|
+
|
13
|
+
def run
|
14
|
+
@changed = false
|
15
|
+
strict_versions = %w[subfolder ref tag export_to]
|
16
|
+
strict_versions.each do |version|
|
17
|
+
@changed = @locked.send(version) != @current.send(version)
|
18
|
+
if @changed
|
19
|
+
@reason = reason_message(version)
|
20
|
+
return @changed
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# Loose version checks work a little different. If not set it explicitly, they will not be checked.
|
25
|
+
# Will use locked version in Terrafile.lock in this case.
|
26
|
+
# Note: Also, check the sha last since it triggers a git fetch.
|
27
|
+
loose_versions = %w[branch sha]
|
28
|
+
loose_versions.each do |version|
|
29
|
+
@changed = @current.send(version) && @current.send(version) != @locked.send(version)
|
30
|
+
if @changed
|
31
|
+
@reason = reason_message(version)
|
32
|
+
return @changed
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
@changed
|
37
|
+
end
|
38
|
+
|
39
|
+
def reason_message(version)
|
40
|
+
"Replacing mod: #{@current.name}. #{version} is different in Terrafile and Terrafile.lock"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require "yaml"
|
2
|
+
|
3
|
+
class TerraspaceBundler::Lockfile
|
4
|
+
class Yamler
|
5
|
+
def initialize(mods)
|
6
|
+
@mods = mods.sort_by(&:name)
|
7
|
+
end
|
8
|
+
|
9
|
+
def dump
|
10
|
+
YAML.dump(data.deep_stringify_keys)
|
11
|
+
end
|
12
|
+
|
13
|
+
def data
|
14
|
+
@mods.inject({}) do |acc, mod|
|
15
|
+
acc.merge(item(mod))
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def item(mod)
|
20
|
+
props = mod.props.dup # passthrough: name, url, version, ref, tag, branch etc
|
21
|
+
props.delete(:name) # different structure in Terrafile.lock YAML
|
22
|
+
props[:sha] = mod.sha
|
23
|
+
props.delete_if { |k,v| v.nil? }
|
24
|
+
{ mod.name => props }
|
25
|
+
end
|
26
|
+
|
27
|
+
def write
|
28
|
+
IO.write(TB.config.lockfile, dump)
|
29
|
+
end
|
30
|
+
|
31
|
+
class << self
|
32
|
+
def write(mods)
|
33
|
+
new(mods).write
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -2,25 +2,5 @@ require 'logger'
|
|
2
2
|
|
3
3
|
module TerraspaceBundler
|
4
4
|
class Logger < ::Logger
|
5
|
-
# Only need to override the add method as the other calls all lead to it.
|
6
|
-
def add(severity, message = nil, progname = nil)
|
7
|
-
# Taken from Logger#add source
|
8
|
-
# https://ruby-doc.org/stdlib-2.5.1/libdoc/logger/rdoc/Logger.html#method-i-add
|
9
|
-
if message.nil?
|
10
|
-
if block_given?
|
11
|
-
message = yield
|
12
|
-
else
|
13
|
-
message = progname
|
14
|
-
progname = @progname
|
15
|
-
end
|
16
|
-
end
|
17
|
-
|
18
|
-
super # original logic
|
19
|
-
end
|
20
|
-
|
21
|
-
# plain formatting
|
22
|
-
def format_message(severity, timestamp, progname, msg)
|
23
|
-
"#{msg}\n"
|
24
|
-
end
|
25
5
|
end
|
26
6
|
end
|
@@ -1,85 +1,53 @@
|
|
1
1
|
module TerraspaceBundler
|
2
2
|
class Mod
|
3
|
-
extend
|
4
|
-
|
3
|
+
extend PropsExtension
|
4
|
+
props :name, :source, :url, :subfolder, :type, :export_to
|
5
5
|
|
6
|
-
attr_reader :
|
7
|
-
def initialize(
|
8
|
-
@
|
9
|
-
|
10
|
-
@
|
11
|
-
|
12
|
-
# support variety of options, prefer version
|
13
|
-
@version = @options[:version]
|
14
|
-
@branch = @options[:branch]
|
15
|
-
@ref = @options[:ref]
|
16
|
-
@tag = @options[:tag]
|
17
|
-
end
|
18
|
-
|
19
|
-
def source
|
20
|
-
if @options[:source].split('/').size == 1
|
21
|
-
"#{org}/#{@options[:source]}"
|
22
|
-
else
|
23
|
-
@options[:source]
|
24
|
-
end
|
6
|
+
attr_reader :props, :version, :ref, :tag, :branch
|
7
|
+
def initialize(props={})
|
8
|
+
@props = props.symbolize_keys
|
9
|
+
# These props are used for version comparing by VersionComparer
|
10
|
+
@version, @ref, @tag, @branch = @props[:version], @props[:ref], @props[:tag], @props[:branch]
|
25
11
|
end
|
26
12
|
|
27
|
-
def
|
28
|
-
|
13
|
+
def sha
|
14
|
+
# sha will already be set if coming from Terrafile.lock
|
15
|
+
# sha will be lazily fetch set if coming from Terrafile
|
16
|
+
@props[:sha] ||= fetch_sha
|
29
17
|
end
|
30
18
|
|
31
|
-
def
|
32
|
-
|
19
|
+
def checkout_version
|
20
|
+
v = detected_version
|
21
|
+
v = "v#{v}" if type == "registry" && @version && !v.starts_with?("v")
|
22
|
+
v
|
33
23
|
end
|
34
24
|
|
35
|
-
|
36
|
-
|
25
|
+
# use url instead of source because for registry modules, the repo name is different
|
26
|
+
def repo
|
27
|
+
url_words[-1]
|
37
28
|
end
|
38
29
|
|
39
|
-
def
|
40
|
-
|
41
|
-
normalize_git_url(source)
|
30
|
+
def org
|
31
|
+
url_words[-2] # second to last word
|
42
32
|
end
|
43
33
|
|
44
|
-
def
|
45
|
-
|
34
|
+
def full_repo
|
35
|
+
"#{org}/#{repo}"
|
46
36
|
end
|
47
37
|
|
48
|
-
|
49
|
-
|
38
|
+
private
|
39
|
+
def fetch_sha
|
40
|
+
downloader = Downloader.new(self)
|
41
|
+
downloader.run
|
42
|
+
downloader.sha
|
50
43
|
end
|
51
|
-
|
52
|
-
def
|
44
|
+
# support variety of options, prefer version
|
45
|
+
def detected_version
|
53
46
|
@version || @ref || @tag || @branch
|
54
47
|
end
|
55
48
|
|
56
|
-
def
|
57
|
-
|
58
|
-
v = "v#{v}" if registry?(source) && @version && !v.starts_with?("v")
|
59
|
-
v
|
60
|
-
end
|
61
|
-
|
62
|
-
def sync
|
63
|
-
sync = Sync.new(self, url)
|
64
|
-
sync.run
|
65
|
-
@sha = sync.sha
|
66
|
-
end
|
67
|
-
memoize :sync
|
68
|
-
|
69
|
-
def normalize_git_url(s)
|
70
|
-
if git_url?(s)
|
71
|
-
s
|
72
|
-
else
|
73
|
-
"#{base_url}#{s}"
|
74
|
-
end
|
75
|
-
end
|
76
|
-
|
77
|
-
def base_url
|
78
|
-
@global[:base_url] || "git@github.com:"
|
79
|
-
end
|
80
|
-
|
81
|
-
def git_url?(s)
|
82
|
-
s.include?("http") || s.include?("git@")
|
49
|
+
def url_words
|
50
|
+
url.split('/')
|
83
51
|
end
|
84
52
|
end
|
85
53
|
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
class TerraspaceBundler::Mod
|
2
|
+
class Downloader
|
3
|
+
extend Memoist
|
4
|
+
include TB::Util::Git
|
5
|
+
include TB::Util::Logging
|
6
|
+
include TB::Mod::PathConcern
|
7
|
+
|
8
|
+
attr_reader :sha
|
9
|
+
def initialize(mod)
|
10
|
+
@mod = mod
|
11
|
+
end
|
12
|
+
|
13
|
+
def run
|
14
|
+
setup_tmp
|
15
|
+
org_path = "#{cache_root}/#{@mod.org}"
|
16
|
+
FileUtils.mkdir_p(org_path)
|
17
|
+
|
18
|
+
Dir.chdir(org_path) do
|
19
|
+
unless File.exist?(@mod.repo)
|
20
|
+
sh "git clone #{@mod.url}"
|
21
|
+
end
|
22
|
+
|
23
|
+
Dir.chdir(@mod.repo) do
|
24
|
+
git "pull"
|
25
|
+
git "submodule update --init"
|
26
|
+
stage
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def stage
|
32
|
+
copy_to_stage
|
33
|
+
# TODO: if there's no master, need to checkout if its the default branch
|
34
|
+
checkout = @mod.checkout_version || "master"
|
35
|
+
switch_version(checkout)
|
36
|
+
end
|
37
|
+
|
38
|
+
def switch_version(version)
|
39
|
+
stage_path = stage_path("#{@mod.org}/#{@mod.repo}")
|
40
|
+
logger.debug "Within: #{stage_path}"
|
41
|
+
Dir.chdir(stage_path) do
|
42
|
+
git "checkout #{version}"
|
43
|
+
@sha = git("rev-parse HEAD").strip
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def copy_to_stage
|
48
|
+
cache_path = cache_path("#{@mod.org}/#{@mod.repo}")
|
49
|
+
stage_path = stage_path("#{@mod.org}/#{@mod.repo}")
|
50
|
+
FileUtils.rm_rf(stage_path)
|
51
|
+
FileUtils.mkdir_p(File.dirname(stage_path))
|
52
|
+
FileUtils.cp_r(cache_path, stage_path)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|