gene_system 0.3.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,127 @@
1
+ require 'jsonnet'
2
+ require 'hashie'
3
+
4
+ module GeneSystem
5
+ # Manifest is an in memory representation of a manifest file
6
+ class Manifest
7
+ DEFAULT_QUERY = ->(_step) { return true }
8
+
9
+ class <<self
10
+ ##
11
+ # Creates a [GeneSystem::Manifest] from a manifest json so long as the
12
+ # manifest is compatible with this version of GeneSystem.
13
+ #
14
+ # @param [String] file_path
15
+ #
16
+ def new_from_file(file_path)
17
+ manifest = Jsonnet.evaluate(
18
+ File.read(file_path)
19
+ )
20
+
21
+ if incompatible?(manifest)
22
+ raise 'provided manifest is invalid or incompatible with '\
23
+ 'this version of gene_system'
24
+ end
25
+
26
+ new(
27
+ file_path,
28
+ manifest
29
+ )
30
+ end
31
+
32
+ ##
33
+ # Incompatible returns true if the current manifest is not compatible
34
+ # with this version of GeneSystem.
35
+ #
36
+ # A manifest is not compatible if it was created with a version greater
37
+ # than this the installed version.
38
+ #
39
+ # @param [Hash] manifest
40
+ #
41
+ # @return [Boolean]
42
+ #
43
+ def incompatible?(manifest)
44
+ manifest_version = manifest['metadata']['gene_system']['version']
45
+ manifest_version > GeneSystem::VERSION
46
+ rescue NoMethodError
47
+ true
48
+ end
49
+ end
50
+
51
+ # list of supported platforms
52
+ SUPPORTED_PLATFORMS = %w[macos debian].freeze
53
+
54
+ def initialize(path, data)
55
+ @path = path
56
+ @data = Hashie::Mash.new(data)
57
+ @steps = GeneSystem::Step.load_steps(@data.steps)
58
+ end
59
+
60
+ ##
61
+ # Manifest name getter
62
+ #
63
+ # @return [String]
64
+ #
65
+ def name
66
+ @data.name
67
+ end
68
+
69
+ ##
70
+ # Manifest version getter
71
+ #
72
+ # @return [String]
73
+ #
74
+ def version
75
+ @data.version
76
+ end
77
+
78
+ ##
79
+ # Manifest metadata getter
80
+ #
81
+ # @return[String]
82
+ #
83
+ def metadata
84
+ @data.metadata
85
+ end
86
+
87
+ ##
88
+ # Platform metadata getter
89
+ #
90
+ # Prints a warning when the platform is not recognized
91
+ #
92
+ # @return[String]
93
+ #
94
+ def platform
95
+ platform = @data.platform
96
+
97
+ unless SUPPORTED_PLATFORMS.include?(platform)
98
+ CLI.print_warning("WARN: unrecognized platform: #{@data.platform}")
99
+ end
100
+
101
+ platform
102
+ end
103
+
104
+ ##
105
+ # Steps executes a query function in a select call against each step to
106
+ # return a list of steps relevant to an operation.
107
+ #
108
+ # The given query function should evaluate to true when the desired step
109
+ # should be in the return set.
110
+ #
111
+ # By default a all steps will be returned.
112
+ #
113
+ # @example
114
+ # query = ->(step) { step.tags.include?("foo") }
115
+ # manifest.steps(query)
116
+ #
117
+ # @param [Lambda] query
118
+ #
119
+ # @return [Array]
120
+ #
121
+ def steps(query = DEFAULT_QUERY)
122
+ @steps.select do |step|
123
+ query.call(step)
124
+ end
125
+ end
126
+ end
127
+ end
@@ -0,0 +1,55 @@
1
+ require 'os'
2
+ require 'ruby-handlebars'
3
+
4
+ module GeneSystem
5
+ # Platform is a class to handle command execution on host system
6
+ class Platform
7
+ ##
8
+ # Platform constructor. Raises unsupported platform RuntimeError
9
+ # when the host system is not a posix system.
10
+ #
11
+ def initialize
12
+ raise 'unsupported platform' unless posix?
13
+ end
14
+
15
+ ##
16
+ # Takes an array of commands for the host and executes each one
17
+ #
18
+ # If command returns non zero status code, then a command failed
19
+ # RuntimeError will be raised
20
+ #
21
+ # @param[Array] cmds
22
+ #
23
+ def execute_commands(cmds = [], vars = {})
24
+ cmds.each do |cmd|
25
+ status = execute_command(cmd, vars)
26
+ raise "command `#{cmd}` failed - returned #{status}" unless status.zero?
27
+ end
28
+ end
29
+
30
+ ##
31
+ # Executes a command on a host system and returns command exit code
32
+ #
33
+ # @param [String] cmd
34
+ #
35
+ # @return [Integer]
36
+ def execute_command(cmd, vars = {})
37
+ hbs = Handlebars::Handlebars.new
38
+
39
+ pid = Process.spawn(hbs.compile(cmd).call(vars))
40
+ _, status = Process.waitpid2(pid)
41
+
42
+ status.exitstatus
43
+ end
44
+
45
+ ##
46
+ # Posix platform check. Returns true if host is a posix system
47
+ # i.e. Mac/Linux etc.
48
+ #
49
+ # @return [Boolean]
50
+ #
51
+ def posix?
52
+ OS.posix?
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,74 @@
1
+ require 'hashie'
2
+
3
+ module GeneSystem
4
+ # Step is an in memory representation of a manifest step
5
+ class Step
6
+ class <<self
7
+ ##
8
+ # Loads steps from an array of steps
9
+ #
10
+ # @param [Array] steps
11
+ #
12
+ # @return [Array]
13
+ #
14
+ def load_steps(steps)
15
+ steps.map do |data|
16
+ new(data)
17
+ end
18
+ end
19
+ end
20
+
21
+ attr_reader :tags
22
+
23
+ def initialize(data)
24
+ @data = Hashie::Mash.new(data)
25
+ @tags = []
26
+ @tags = @data.tags.split("\s") if @data.tags
27
+ end
28
+
29
+ ##
30
+ # Step name getter
31
+ #
32
+ # @return [String]
33
+ #
34
+ def name
35
+ @data.name
36
+ end
37
+
38
+ ##
39
+ # Step prompt getter
40
+ #
41
+ # @return [Array]
42
+ #
43
+ def prompts
44
+ @data.prompts
45
+ end
46
+
47
+ ##
48
+ # Step execution instructions getter
49
+ #
50
+ # @return [Hash]
51
+ #
52
+ def exe
53
+ @data.exe
54
+ end
55
+
56
+ ##
57
+ # Installation instructions getter
58
+ #
59
+ # @return [Array]
60
+ #
61
+ def install
62
+ exe.install
63
+ end
64
+
65
+ ##
66
+ # Removal instructions getter
67
+ #
68
+ # @return [Array]
69
+ #
70
+ def remove
71
+ exe.remove
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,3 @@
1
+ module GeneSystem
2
+ VERSION = '0.3.2'.freeze
3
+ end
@@ -0,0 +1,230 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe GeneSystem::CLI::Commands do
4
+ describe '.new' do
5
+ let(:pwd) { 'path/to/current/dir' }
6
+ let(:name) { 'manifest name' }
7
+ let(:args) { [name] }
8
+
9
+ before do
10
+ allow(Dir).to receive(:pwd).and_return(pwd)
11
+ allow(GeneSystem::Generators).to receive(:render_empty_manifest)
12
+ end
13
+
14
+ it 'renders empty manifest at current path with given name' do
15
+ described_class.new(args)
16
+
17
+ expect(GeneSystem::Generators).to have_received(:render_empty_manifest)
18
+ .with(name, pwd)
19
+ end
20
+
21
+ context 'when name is not given' do
22
+ it 'raises no manifest name provided runtime error' do
23
+ expect { described_class.new([]) }.to raise_error(
24
+ RuntimeError,
25
+ 'no manifest name provided'
26
+ )
27
+ end
28
+ end
29
+ end
30
+
31
+ describe '.install' do
32
+ let(:manifest_name) { 'manifest.json' }
33
+ let(:pwd) { 'path/to/current/dir' }
34
+
35
+ let(:args) { [manifest_name] }
36
+ let(:file_exist) { true }
37
+
38
+ let(:skip_cmd) { 'check something' }
39
+ let(:skip_result) { 1 }
40
+
41
+ let(:cmd) { 'do something' }
42
+ let(:cmds) { [cmd, cmd] }
43
+
44
+ let(:data) do
45
+ {
46
+ 'name' => 'test_manifest',
47
+ 'version' => '0.1.0',
48
+ 'steps' => [
49
+ {
50
+ 'name' => 'install',
51
+ 'exe' => {
52
+ 'install' => {
53
+ 'skip' => skip_cmd,
54
+ 'cmd' => cmds
55
+ }
56
+ }
57
+ }
58
+ ]
59
+ }
60
+ end
61
+
62
+ let(:manifest) do
63
+ GeneSystem::Manifest.new(
64
+ File.join(pwd, manifest_name),
65
+ data
66
+ )
67
+ end
68
+ let(:platform) { double(GeneSystem::Platform) }
69
+
70
+ before do
71
+ allow(Dir).to receive(:pwd).and_return(pwd)
72
+ allow(File).to receive(:exist?).and_return(file_exist)
73
+
74
+ allow(GeneSystem::Manifest).to receive(:new_from_file)
75
+ .and_return(manifest)
76
+
77
+ allow(GeneSystem::Platform).to receive(:new)
78
+ .and_return(platform)
79
+
80
+ allow(platform).to receive(:execute_commands)
81
+ allow(platform).to receive(:execute_command)
82
+ .with(skip_cmd)
83
+ .and_return(skip_result)
84
+
85
+ allow(GeneSystem::CLI).to receive(:print_message)
86
+ end
87
+
88
+ describe 'installing manifest' do
89
+ before { described_class.install(args) }
90
+
91
+ it 'loads manifest' do
92
+ expect(GeneSystem::Manifest)
93
+ .to have_received(:new_from_file)
94
+ .with(File.join(pwd, manifest_name))
95
+ end
96
+
97
+ it 'executes steps on platform' do
98
+ expect(platform)
99
+ .to have_received(:execute_commands)
100
+ .with(cmds, {})
101
+ end
102
+
103
+ it 'prints success message' do
104
+ expect(GeneSystem::CLI).to have_received(:print_message)
105
+ .with("\nmanifest successfully installed")
106
+ end
107
+
108
+ context 'when skipping step' do
109
+ let(:skip_result) { 0 }
110
+
111
+ it 'does not execute steps' do
112
+ expect(platform)
113
+ .not_to have_received(:execute_commands)
114
+ end
115
+ end
116
+ end
117
+
118
+ context 'when path is not provided' do
119
+ it 'raises no manifest path provided runtime error' do
120
+ expect { described_class.install }.to raise_error(
121
+ RuntimeError,
122
+ 'no manifest path provided'
123
+ )
124
+ end
125
+ end
126
+
127
+ context 'when manifest cannot be created at specified path' do
128
+ let(:file_exist) { false }
129
+
130
+ it 'raises cannot find manifest RuntimeError' do
131
+ expect { described_class.install(args) }.to raise_error(
132
+ RuntimeError,
133
+ 'cannot find manifest at path/to/current/dir/manifest.json'
134
+ )
135
+ end
136
+ end
137
+ end
138
+
139
+ describe '.remove' do
140
+ let(:manifest_name) { 'manifest.json' }
141
+ let(:pwd) { 'path/to/current/dir' }
142
+
143
+ let(:args) { [manifest_name] }
144
+ let(:file_exist) { true }
145
+
146
+ let(:cmd) { 'do something' }
147
+ let(:cmds) { [cmd, cmd] }
148
+
149
+ let(:data) do
150
+ {
151
+ 'name' => 'test_manifest',
152
+ 'version' => '0.1.0',
153
+ 'steps' => [
154
+ {
155
+ 'name' => 'remove',
156
+ 'exe' => {
157
+ 'remove' => {
158
+ 'cmd' => cmds
159
+ }
160
+ }
161
+ }
162
+ ]
163
+ }
164
+ end
165
+
166
+ let(:manifest) do
167
+ GeneSystem::Manifest.new(
168
+ File.join(pwd, manifest_name),
169
+ data
170
+ )
171
+ end
172
+ let(:platform) { double(GeneSystem::Platform) }
173
+
174
+ before do
175
+ allow(Dir).to receive(:pwd).and_return(pwd)
176
+ allow(File).to receive(:exist?).and_return(file_exist)
177
+
178
+ allow(GeneSystem::Manifest).to receive(:new_from_file)
179
+ .and_return(manifest)
180
+
181
+ allow(GeneSystem::Platform).to receive(:new)
182
+ .and_return(platform)
183
+
184
+ allow(platform).to receive(:execute_commands)
185
+
186
+ allow(GeneSystem::CLI).to receive(:print_message)
187
+ end
188
+
189
+ describe 'remooving manifest' do
190
+ before { described_class.remove(args) }
191
+
192
+ it 'loads manifest' do
193
+ expect(GeneSystem::Manifest)
194
+ .to have_received(:new_from_file)
195
+ .with(File.join(pwd, manifest_name))
196
+ end
197
+
198
+ it 'executes remove on platform' do
199
+ expect(platform)
200
+ .to have_received(:execute_commands)
201
+ .with(cmds)
202
+ end
203
+
204
+ it 'prints success message' do
205
+ expect(GeneSystem::CLI).to have_received(:print_message)
206
+ .with("\nmanifest successfully removed")
207
+ end
208
+ end
209
+
210
+ context 'when path is not provided' do
211
+ it 'raises no manifest path provided runtime error' do
212
+ expect { described_class.install }.to raise_error(
213
+ RuntimeError,
214
+ 'no manifest path provided'
215
+ )
216
+ end
217
+ end
218
+
219
+ context 'when manifest cannot be created at specified path' do
220
+ let(:file_exist) { false }
221
+
222
+ it 'raises cannot find manifest RuntimeError' do
223
+ expect { described_class.install(args) }.to raise_error(
224
+ RuntimeError,
225
+ 'cannot find manifest at path/to/current/dir/manifest.json'
226
+ )
227
+ end
228
+ end
229
+ end
230
+ end