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.
- checksums.yaml +7 -0
- data/.doxie.json +25 -0
- data/.github/workflows/build.yml +25 -0
- data/.gitignore +20 -0
- data/.rubocop.yml +20 -0
- data/.ruby-version +1 -0
- data/CONTRIBUTING.md +51 -0
- data/Gemfile +10 -0
- data/Gemfile.lock +150 -0
- data/LICENSE +21 -0
- data/README.md +255 -0
- data/Rakefile +19 -0
- data/bin/genesystem +52 -0
- data/data/manifest.jsonnet +87 -0
- data/gene_system.gemspec +40 -0
- data/lib/gene_system.rb +9 -0
- data/lib/gene_system/cli.rb +76 -0
- data/lib/gene_system/cli/commands.rb +109 -0
- data/lib/gene_system/generators.rb +58 -0
- data/lib/gene_system/manifest.rb +127 -0
- data/lib/gene_system/platform.rb +55 -0
- data/lib/gene_system/step.rb +74 -0
- data/lib/gene_system/version.rb +3 -0
- data/spec/gene_system/cli/commands_spec.rb +230 -0
- data/spec/gene_system/cli_spec.rb +83 -0
- data/spec/gene_system/generators_spec.rb +65 -0
- data/spec/gene_system/manifest_spec.rb +239 -0
- data/spec/gene_system/platform_spec.rb +94 -0
- data/spec/gene_system/step_spec.rb +108 -0
- data/spec/spec_helper.rb +27 -0
- metadata +344 -0
@@ -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,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
|