terrafile 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,3 @@
1
+ module Terrafile
2
+ class Error < RuntimeError; end
3
+ end
@@ -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,3 @@
1
+ module Terrafile
2
+ VERSION = '0.1.1'.freeze
3
+ 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
@@ -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,5 @@
1
+ RSpec.describe Terrafile do
2
+ it 'has a version number' do
3
+ expect(Terrafile::VERSION).not_to be nil
4
+ end
5
+ 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