terrafile 0.1.1
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 +7 -0
- data/.gitignore +11 -0
- data/.rspec +3 -0
- data/.rubocop.yml +51 -0
- data/.ruby-version +1 -0
- data/.travis.yml +7 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +105 -0
- data/LICENSE.txt +21 -0
- data/README.md +60 -0
- data/Rakefile +12 -0
- data/bin/setup +8 -0
- data/bin/terrafile +3 -0
- data/lib/terrafile.rb +12 -0
- data/lib/terrafile/dependency.rb +35 -0
- data/lib/terrafile/errors.rb +3 -0
- data/lib/terrafile/helper.rb +50 -0
- data/lib/terrafile/installer.rb +47 -0
- data/lib/terrafile/version.rb +3 -0
- data/spec/integration/runs_terrafile_executable_spec.rb +47 -0
- data/spec/spec_helper.rb +20 -0
- data/spec/terrafile_spec.rb +5 -0
- data/spec/unit/dependency_spec.rb +152 -0
- data/spec/unit/helper_spec.rb +182 -0
- data/spec/unit/installer_spec.rb +157 -0
- data/terrafile.gemspec +48 -0
- metadata +176 -0
@@ -0,0 +1,50 @@
|
|
1
|
+
module Terrafile
|
2
|
+
module Helper
|
3
|
+
def self.run!(command)
|
4
|
+
begin
|
5
|
+
_stdout, stderr, status = Open3.capture3(command)
|
6
|
+
rescue Errno::ENOENT => error
|
7
|
+
raise Error, "'#{command}' failed (#{error})"
|
8
|
+
end
|
9
|
+
|
10
|
+
unless status.success?
|
11
|
+
raise Error, "'#{command}' failed with exit code #{status.exitstatus} " \
|
12
|
+
"(#{stderr.chomp})"
|
13
|
+
end
|
14
|
+
|
15
|
+
true
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.run_with_output(command)
|
19
|
+
begin
|
20
|
+
stdout, _stderr, _status = Open3.capture3(command)
|
21
|
+
rescue Errno::ENOENT => error
|
22
|
+
raise Error, "'#{command}' failed (#{error})"
|
23
|
+
end
|
24
|
+
|
25
|
+
stdout.chomp
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.clone(source, destination)
|
29
|
+
run!("git clone --depth 1 #{source} #{destination} &> /dev/null")
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.dir_exists?(path)
|
33
|
+
Dir.exist?(path)
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.file_exists?(path)
|
37
|
+
File.exist?(path)
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.repo_up_to_date?(version)
|
41
|
+
current_tag = run_with_output('git describe --tags').tr("\n", '')
|
42
|
+
current_hash = run_with_output('git rev-parse HEAD').tr("\n", '')
|
43
|
+
[current_tag, current_hash].include? version
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.pull_repo
|
47
|
+
run!('git pull &> /dev/null')
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module Terrafile
|
2
|
+
class Installer
|
3
|
+
MODULES_PATH = 'vendor/terraform_modules'.freeze
|
4
|
+
|
5
|
+
def initialize
|
6
|
+
@dependencies = read_terrafile
|
7
|
+
end
|
8
|
+
|
9
|
+
def call
|
10
|
+
create_modules_directory_if_needed
|
11
|
+
checkout_modules
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
attr_reader :dependencies
|
17
|
+
|
18
|
+
def read_terrafile
|
19
|
+
return built_dependencies if Helper.file_exists?(TERRAFILE_PATH)
|
20
|
+
|
21
|
+
Kernel.puts '[*] Terrafile does not exist'
|
22
|
+
Kernel.exit 1
|
23
|
+
end
|
24
|
+
|
25
|
+
def built_dependencies
|
26
|
+
Dependency.build_from_terrafile
|
27
|
+
end
|
28
|
+
|
29
|
+
def checkout_modules
|
30
|
+
Dir.chdir(Installer::MODULES_PATH) do
|
31
|
+
dependencies.each do |dependency|
|
32
|
+
msg = "Checking out #{dependency.version} from #{dependency.source}"
|
33
|
+
Kernel.puts msg
|
34
|
+
dependency.fetch
|
35
|
+
dependency.checkout
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def create_modules_directory_if_needed
|
41
|
+
return if Helper.dir_exists?(MODULES_PATH)
|
42
|
+
|
43
|
+
Kernel.puts "[*] Creating Terraform modules directory at '#{MODULES_PATH}'"
|
44
|
+
FileUtils.makedirs MODULES_PATH
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require_relative '../spec_helper'
|
2
|
+
|
3
|
+
module Terrafile
|
4
|
+
RSpec.describe 'CLI runs terrafile executable', type: :aruba do
|
5
|
+
context 'when no Terrafile is found' do
|
6
|
+
let(:file) { 'Bananafile' }
|
7
|
+
let(:content) { 'Something else entirely' }
|
8
|
+
let(:output) { 'Terrafile does not exist' }
|
9
|
+
|
10
|
+
before(:each) { write_file file, content }
|
11
|
+
before(:each) { run('terrafile') }
|
12
|
+
|
13
|
+
it 'prints out an error message' do
|
14
|
+
expect(last_command_started).to have_output(/#{output}/)
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'exits non-zero' do
|
18
|
+
expect(last_command_started).not_to have_exit_status(0)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
context 'when a Terrafile IS found' do
|
23
|
+
let(:file) { 'Terrafile' }
|
24
|
+
let(:content) do
|
25
|
+
<<~YML
|
26
|
+
terraform-aws-module1:
|
27
|
+
source: "git@github.com:org1/repo1.git"
|
28
|
+
version: "0.8.0"
|
29
|
+
|
30
|
+
terraform-aws-module2:
|
31
|
+
source: "git@github.com:org2/repo2.git"
|
32
|
+
version: "004e5791c2ef816578420fb6695e4009a838c863"
|
33
|
+
YML
|
34
|
+
end
|
35
|
+
let(:notice) { 'Creating Terraform modules directory' }
|
36
|
+
|
37
|
+
before(:each) { write_file file, content }
|
38
|
+
before(:each) { run('terrafile') }
|
39
|
+
|
40
|
+
context 'and the modules directory does not yet exist' do
|
41
|
+
it 'prints out its intention to create that directory' do
|
42
|
+
expect(last_command_started).to have_output(/#{notice}/)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'simplecov'
|
2
|
+
SimpleCov.start do
|
3
|
+
minimum_coverage 100
|
4
|
+
end
|
5
|
+
require 'bundler/setup'
|
6
|
+
require 'aruba/rspec'
|
7
|
+
require 'terrafile'
|
8
|
+
# require 'pry'
|
9
|
+
|
10
|
+
RSpec.configure do |config|
|
11
|
+
# Enable flags like --only-failures and --next-failure
|
12
|
+
config.example_status_persistence_file_path = '.rspec_status'
|
13
|
+
|
14
|
+
# Disable RSpec exposing methods globally on `Module` and `main`
|
15
|
+
config.disable_monkey_patching!
|
16
|
+
|
17
|
+
config.expect_with :rspec do |c|
|
18
|
+
c.syntax = :expect
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,152 @@
|
|
1
|
+
require_relative '../spec_helper'
|
2
|
+
|
3
|
+
module Terrafile
|
4
|
+
RSpec.describe Dependency do
|
5
|
+
describe 'class methods' do
|
6
|
+
describe '::build_from_terrafile' do
|
7
|
+
before do
|
8
|
+
modules = {
|
9
|
+
'terraform-aws-name1' => {
|
10
|
+
'source' => 'git@github.com:org1/repo1',
|
11
|
+
'version' => '0.8.0',
|
12
|
+
},
|
13
|
+
'terraform-aws-name2' => {
|
14
|
+
'source' => 'git@github.com:org2/repo2',
|
15
|
+
'version' => '004e5791',
|
16
|
+
},
|
17
|
+
}
|
18
|
+
allow(File).to receive(:read).and_return(double)
|
19
|
+
allow(YAML).to receive(:safe_load).and_return(modules)
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'instantiates a dependency for each module listed in the Terrafile' do
|
23
|
+
allow(Dependency).to receive(:new)
|
24
|
+
|
25
|
+
Dependency.build_from_terrafile
|
26
|
+
|
27
|
+
expect(Dependency).to have_received(:new).with(
|
28
|
+
name: 'terraform-aws-name1',
|
29
|
+
source: 'git@github.com:org1/repo1',
|
30
|
+
version: '0.8.0'
|
31
|
+
)
|
32
|
+
|
33
|
+
expect(Dependency).to have_received(:new).with(
|
34
|
+
name: 'terraform-aws-name2',
|
35
|
+
source: 'git@github.com:org2/repo2',
|
36
|
+
version: '004e5791'
|
37
|
+
)
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'returns a list of dependencies' do
|
41
|
+
expect(Dependency.build_from_terrafile.size).to eq(2)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
describe 'instance methods' do
|
47
|
+
describe '#fetch' do
|
48
|
+
let(:dependency) do
|
49
|
+
Dependency.new(
|
50
|
+
name: 'terraform-of-mine',
|
51
|
+
version: '1.2.3',
|
52
|
+
source: 'git@github.com:org1/repo1'
|
53
|
+
)
|
54
|
+
end
|
55
|
+
|
56
|
+
before do
|
57
|
+
allow(Dir).to receive(:chdir).and_yield
|
58
|
+
allow(Helper).to receive(:clone)
|
59
|
+
allow(Helper).to receive(:repo_up_to_date?)
|
60
|
+
allow(Helper).to receive(:pull_repo)
|
61
|
+
end
|
62
|
+
|
63
|
+
context 'if a sub-directory named for the dependency already exists' do
|
64
|
+
before do
|
65
|
+
allow(Helper).to receive(:dir_exists?)
|
66
|
+
.with('terraform-of-mine').and_return(true)
|
67
|
+
end
|
68
|
+
|
69
|
+
it 'does not re-clone the module' do
|
70
|
+
dependency.fetch
|
71
|
+
|
72
|
+
expect(Helper).not_to have_received(:clone)
|
73
|
+
.with('git@github.com:org1/repo1', 'terraform-aws-name1')
|
74
|
+
end
|
75
|
+
|
76
|
+
it 'changes into the sub-directory for that dependency' do
|
77
|
+
dependency.fetch
|
78
|
+
|
79
|
+
expect(Dir).to have_received(:chdir).with('terraform-of-mine')
|
80
|
+
end
|
81
|
+
|
82
|
+
it 'tests whether the repo is out of date' do
|
83
|
+
dependency.fetch
|
84
|
+
|
85
|
+
expect(Helper).to have_received(:repo_up_to_date?).with('1.2.3')
|
86
|
+
end
|
87
|
+
|
88
|
+
context 'if the module needs to be updated' do
|
89
|
+
before do
|
90
|
+
allow(Helper).to receive(:repo_up_to_date?).and_return(false)
|
91
|
+
end
|
92
|
+
|
93
|
+
it 'pulls the latest revision down' do
|
94
|
+
dependency.fetch
|
95
|
+
|
96
|
+
expect(Helper).to have_received(:pull_repo)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
context 'if the module is up-to-date' do
|
101
|
+
before do
|
102
|
+
allow(Helper).to receive(:repo_up_to_date?).and_return(true)
|
103
|
+
end
|
104
|
+
|
105
|
+
it 'does not attempt to pull fresh revisions' do
|
106
|
+
dependency.fetch
|
107
|
+
|
108
|
+
expect(Helper).not_to have_received(:pull_repo)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
context 'if a sub-directory named for the dependency DOES NOT already exists' do
|
114
|
+
before do
|
115
|
+
allow(Helper).to receive(:dir_exists?)
|
116
|
+
.with('terraform-of-mine').and_return(false)
|
117
|
+
end
|
118
|
+
|
119
|
+
it 'does clone the module' do
|
120
|
+
dependency.fetch
|
121
|
+
|
122
|
+
expect(Helper).to have_received(:clone)
|
123
|
+
.with('git@github.com:org1/repo1', 'terraform-of-mine')
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
describe '#checkout' do
|
129
|
+
let(:dependency) do
|
130
|
+
Dependency.new(name: 'terraform-of-mine', version: '1.2.3', source: double)
|
131
|
+
end
|
132
|
+
|
133
|
+
before do
|
134
|
+
allow(Dir).to receive(:chdir).and_yield
|
135
|
+
allow(Helper).to receive(:run!)
|
136
|
+
end
|
137
|
+
|
138
|
+
it 'changes to the given dependency\'s directory' do
|
139
|
+
dependency.checkout
|
140
|
+
|
141
|
+
expect(Dir).to have_received(:chdir).with('terraform-of-mine')
|
142
|
+
end
|
143
|
+
|
144
|
+
it 'checks out the right version of that dependency, suppressing output' do
|
145
|
+
dependency.checkout
|
146
|
+
|
147
|
+
expect(Helper).to have_received(:run!).with('git checkout 1.2.3 &> /dev/null')
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
@@ -0,0 +1,182 @@
|
|
1
|
+
require_relative '../spec_helper'
|
2
|
+
|
3
|
+
module Terrafile
|
4
|
+
RSpec.describe Helper do
|
5
|
+
describe '::run!' do
|
6
|
+
it 'passes given cmd to Open3.capture3' do
|
7
|
+
stdout = double
|
8
|
+
stderr = double
|
9
|
+
status = double(success?: true)
|
10
|
+
allow(Open3).to receive(:capture3).and_return([stdout, stderr, status])
|
11
|
+
|
12
|
+
Helper.run!('git clone foo')
|
13
|
+
|
14
|
+
expect(Open3).to have_received(:capture3).with('git clone foo')
|
15
|
+
end
|
16
|
+
|
17
|
+
context 'when the system call return a zero exit status' do
|
18
|
+
let(:zero_result) do
|
19
|
+
stdout = "45bca5861c0c7e4defe0702e8f4d3e1f255fe96b\n"
|
20
|
+
stderr = ''
|
21
|
+
status = double(exitstatus: 1, success?: true)
|
22
|
+
[stdout, stderr, status]
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'returns true' do
|
26
|
+
allow(Open3).to receive(:capture3).and_return(zero_result)
|
27
|
+
|
28
|
+
expect(Helper.run!('git rev-parse HEAD')).to be true
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
context 'when the system call returns a non-zero exit status' do
|
33
|
+
let(:non_zero_result) do
|
34
|
+
stdout = ''
|
35
|
+
stderr = "fatal: repository 'bar' does not exist\n"
|
36
|
+
status = double(exitstatus: 128, success?: false)
|
37
|
+
[stdout, stderr, status]
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'raises a helpful Terrafile::Error' do
|
41
|
+
allow(Open3).to receive(:capture3).and_return(non_zero_result)
|
42
|
+
msg = "'git clone bar' failed with exit code 128 " \
|
43
|
+
"(fatal: repository 'bar' does not exist)"
|
44
|
+
|
45
|
+
expect { Helper.run!('git clone bar') }
|
46
|
+
.to raise_error(Terrafile::Error, msg)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
context 'when the system call raises an ENOENT error' do
|
51
|
+
it 'catches this and raises a helpful Terrafile::Error' do
|
52
|
+
allow(Open3).to receive(:capture3).and_raise(Errno::ENOENT, 'rubbish')
|
53
|
+
|
54
|
+
msg = "'rubbish' failed (No such file or directory - rubbish)"
|
55
|
+
|
56
|
+
expect { Helper.run!('rubbish') }.to raise_error(Terrafile::Error, msg)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
describe '::run_with_output(cmd)' do
|
62
|
+
it 'passes given cmd to Open3.capture3' do
|
63
|
+
stdout = double(chomp: '')
|
64
|
+
stderr = double
|
65
|
+
status = double(success?: true)
|
66
|
+
allow(Open3).to receive(:capture3).and_return([stdout, stderr, status])
|
67
|
+
|
68
|
+
Helper.run_with_output('git describe --tags')
|
69
|
+
|
70
|
+
expect(Open3).to have_received(:capture3).with('git describe --tags')
|
71
|
+
end
|
72
|
+
|
73
|
+
context 'when the system call returns a zero exit status' do
|
74
|
+
let(:zero_result) do
|
75
|
+
stdout = "1.1.2\n"
|
76
|
+
stderr = ''
|
77
|
+
status = double(exitstatus: 1, success?: true)
|
78
|
+
[stdout, stderr, status]
|
79
|
+
end
|
80
|
+
|
81
|
+
it 'returns the systems output to STDOUT' do
|
82
|
+
allow(Open3).to receive(:capture3).and_return(zero_result)
|
83
|
+
|
84
|
+
expect(Helper.run_with_output('git describe --tags')).to eq('1.1.2')
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
context 'when the system call returns a non-zero exit status' do
|
89
|
+
let(:non_zero_result) do
|
90
|
+
stdout = ''
|
91
|
+
stderr = "fatal: No names found, cannot describe anything.\n"
|
92
|
+
status = double(exitstatus: 128, success?: false)
|
93
|
+
[stdout, stderr, status]
|
94
|
+
end
|
95
|
+
|
96
|
+
it 'also returns STDOUT ignoring the exit code and STDERR' do
|
97
|
+
allow(Open3).to receive(:capture3).and_return(non_zero_result)
|
98
|
+
|
99
|
+
expect(Helper.run_with_output('git describe --tags')).to eq('')
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
context 'when the system call raises an ENOENT error' do
|
104
|
+
it 'catches this and raises a helpful Terrafile::Error' do
|
105
|
+
allow(Open3).to receive(:capture3).and_raise(Errno::ENOENT, 'rubbish')
|
106
|
+
|
107
|
+
msg = "'rubbish' failed (No such file or directory - rubbish)"
|
108
|
+
|
109
|
+
expect { Helper.run_with_output('rubbish') }.to raise_error(Terrafile::Error, msg)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
describe '::clone(source, destination)' do
|
115
|
+
it 'checks out the given revision using run!' do
|
116
|
+
allow(Helper).to receive(:run!)
|
117
|
+
source = 'git@github.com:org1/repo1'
|
118
|
+
destination = 'terraform-aws-name1'
|
119
|
+
|
120
|
+
Helper.clone(source, destination)
|
121
|
+
|
122
|
+
expect(Helper).to have_received(:run!)
|
123
|
+
.with("git clone --depth 1 #{source} #{destination} &> /dev/null")
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
describe '::repo_up_to_date?(version)' do
|
128
|
+
before { allow(Helper).to receive(:run_with_output).and_return('') }
|
129
|
+
|
130
|
+
it 'gets a list of any tags at the current revision' do
|
131
|
+
Helper.repo_up_to_date?(double)
|
132
|
+
|
133
|
+
expect(Helper).to have_received(:run_with_output)
|
134
|
+
.with('git describe --tags')
|
135
|
+
end
|
136
|
+
|
137
|
+
it 'gets the SHA of the current revision' do
|
138
|
+
Helper.repo_up_to_date?(double)
|
139
|
+
|
140
|
+
expect(Helper).to have_received(:run_with_output)
|
141
|
+
.with('git rev-parse HEAD')
|
142
|
+
end
|
143
|
+
|
144
|
+
describe 'determining if the repo is up to date' do
|
145
|
+
it 'returns true if the given version matches a tag' do
|
146
|
+
allow(Helper).to receive(:run_with_output)
|
147
|
+
.with('git describe --tags').and_return('0.1.0')
|
148
|
+
|
149
|
+
expect(Helper.repo_up_to_date?('0.1.0')).to be true
|
150
|
+
expect(Helper.repo_up_to_date?('0.1.1')).to be false
|
151
|
+
end
|
152
|
+
|
153
|
+
it 'returns true if the given version matches the SHA' do
|
154
|
+
allow(Helper).to receive(:run_with_output)
|
155
|
+
.with('git rev-parse HEAD').and_return('abc123')
|
156
|
+
|
157
|
+
expect(Helper.repo_up_to_date?('abc123')).to be true
|
158
|
+
expect(Helper.repo_up_to_date?('bcd321')).to be false
|
159
|
+
end
|
160
|
+
|
161
|
+
it 'returns false if the given version matches neither a tag nor the SHA' do
|
162
|
+
allow(Helper).to receive(:run_with_output)
|
163
|
+
.with('git describe --tags').and_return('0.1.0')
|
164
|
+
allow(Helper).to receive(:run_with_output)
|
165
|
+
.with('git rev-parse HEAD').and_return('abc123')
|
166
|
+
|
167
|
+
expect(Helper.repo_up_to_date?('bananas')).to be false
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
describe '::pull_repo' do
|
173
|
+
it 'pulls the latest code without generating any output' do
|
174
|
+
allow(Helper).to receive(:run!)
|
175
|
+
|
176
|
+
Helper.pull_repo
|
177
|
+
|
178
|
+
expect(Helper).to have_received(:run!).with('git pull &> /dev/null')
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|