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.
@@ -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