gene_system 0.3.2

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,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