blufin-lib 1.5.0 → 1.5.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/blufin-lib.rb +4 -1
- data/lib/core/base.rb +17 -0
- data/lib/core/config.rb +110 -0
- data/lib/core/files.rb +17 -1
- data/lib/core/projects.rb +331 -0
- data/lib/core/strings.rb +11 -0
- data/lib/core/terminal.rb +85 -46
- data/lib/core/versioning.rb +29 -0
- data/lib/version.rb +1 -1
- data/opt/awx/project-schema.yml +130 -0
- metadata +21 -3
- data/lib/core/ssh.rb +0 -145
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 270bafc81b6eadf414cfadf2468ad3b85bf569b8
|
4
|
+
data.tar.gz: ef34b1af83e74ec125af126924d5772d8cadf6ca
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d95f777b6152f9b00ad9bd5ee0ac5894abd2a12f129927519656fd295f5af64d1681ff3e39d2c4d1f4c68bc9561def591c8fa198f7aae8688a4e31b5d482718d
|
7
|
+
data.tar.gz: 41b89f6e3318cc01fee5dca3b1d657fa199c39cb34f32e23446f89e812e7c55d7028874adf9d1ba446b5d0fd37d465c75e160b60db94735a8e559fb0c2449f03
|
data/lib/blufin-lib.rb
CHANGED
@@ -1,18 +1,21 @@
|
|
1
1
|
module Blufin
|
2
2
|
|
3
3
|
autoload :Arrays, 'core/arrays'
|
4
|
+
autoload :Base, 'core/base'
|
4
5
|
autoload :Browser, 'core/browser'
|
6
|
+
autoload :Config, 'core/config'
|
5
7
|
autoload :DateTimeUtils, 'core/datetime_utils'
|
6
8
|
autoload :Encryptor, 'core/encryptor'
|
7
9
|
autoload :Files, 'core/files'
|
8
10
|
autoload :Image, 'core/image'
|
9
11
|
autoload :Network, 'core/network'
|
10
12
|
autoload :Numbers, 'core/numbers'
|
13
|
+
autoload :Projects, 'core/projects'
|
11
14
|
autoload :Routes, 'core/routes'
|
12
|
-
autoload :SSH, 'core/ssh'
|
13
15
|
autoload :Strings, 'core/strings'
|
14
16
|
autoload :Terminal, 'core/terminal'
|
15
17
|
autoload :Tools, 'core/tools'
|
16
18
|
autoload :Validate, 'core/validate'
|
19
|
+
autoload :Versioning, 'core/versioning'
|
17
20
|
|
18
21
|
end
|
data/lib/core/base.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
module Blufin
|
2
|
+
|
3
|
+
class Base
|
4
|
+
|
5
|
+
OPT_PATH = '/opt'
|
6
|
+
|
7
|
+
# Get PATH to opt files.
|
8
|
+
# @return String
|
9
|
+
def self.get_base_path
|
10
|
+
base_path = File.dirname(File.expand_path(__FILE__))
|
11
|
+
base_path = base_path.gsub(/\/\w+\/\w+\z/i, '')
|
12
|
+
base_path
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
data/lib/core/config.rb
ADDED
@@ -0,0 +1,110 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require 'kwalify'
|
3
|
+
|
4
|
+
module Blufin
|
5
|
+
|
6
|
+
class Config
|
7
|
+
|
8
|
+
@@data = {}
|
9
|
+
|
10
|
+
# Validate and initialize the configuration file.
|
11
|
+
# @return void
|
12
|
+
def self.init(schema_file, template_file, config_file, gem_name, setup_command = 'x')
|
13
|
+
schema_file = File.expand_path(schema_file)
|
14
|
+
template_file = File.expand_path(template_file)
|
15
|
+
config_file = File.expand_path(config_file)
|
16
|
+
raise RuntimeError, "File not found: #{schema_file}" unless Blufin::Files::file_exists(schema_file)
|
17
|
+
raise RuntimeError, "File not found: #{template_file}" unless Blufin::Files::file_exists(template_file)
|
18
|
+
if Blufin::Files::file_exists(config_file)
|
19
|
+
# Validate the user created config file.
|
20
|
+
load_config(config_file, schema_file, gem_name)
|
21
|
+
else
|
22
|
+
system('clear')
|
23
|
+
if Blufin::Terminal::prompt_yes_no(" Missing Configuration File: #{Blufin::Terminal::format_directory(config_file)}", 'This script can automatically create it.', 'Create configuration file')
|
24
|
+
make_config(template_file, config_file)
|
25
|
+
edit_config(config_file)
|
26
|
+
Blufin::Terminal::custom('File Created', 56, "Now try running: #{Blufin::Terminal::format_command(gem_name)}", nil, false)
|
27
|
+
end
|
28
|
+
exit
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# Straight-up returns the data object so you can get keys using string literals.
|
33
|
+
# Hard-coding keys is OK because configuration rarely changes and it's validated, so you can be assured the data is there.
|
34
|
+
# @return Hash
|
35
|
+
def self.get
|
36
|
+
@@data
|
37
|
+
end
|
38
|
+
|
39
|
+
# Use for path. Basically wraps them in File.expand_path.
|
40
|
+
# @return String
|
41
|
+
def self.get_path(*args)
|
42
|
+
val = get
|
43
|
+
args.each { |arg| val = val[arg] }
|
44
|
+
File.expand_path(val)
|
45
|
+
end
|
46
|
+
|
47
|
+
# Opens the config file in VIM.
|
48
|
+
# @return void
|
49
|
+
def self.edit_config(config_file)
|
50
|
+
system("vim #{config_file}")
|
51
|
+
end
|
52
|
+
|
53
|
+
# This validates the configuration file against the defined schema.
|
54
|
+
# @return Array
|
55
|
+
def self.validate_file(config_file, schema_file)
|
56
|
+
begin
|
57
|
+
schema_file_parsed = YAML.load_file(schema_file)
|
58
|
+
rescue => e
|
59
|
+
Blufin::Terminal::error("Failed to parse config file: #{Blufin::Terminal::format_directory(config_file)}", e.message)
|
60
|
+
end
|
61
|
+
validator = Kwalify::Validator.new(schema_file_parsed)
|
62
|
+
begin
|
63
|
+
document = YAML.load_file(config_file)
|
64
|
+
# This is here because the IDE is incorrectly resolving to Convoy::validate().
|
65
|
+
# noinspection RubyArgCount
|
66
|
+
errors = validator.validate(document)
|
67
|
+
rescue => e
|
68
|
+
Blufin::Terminal::error("Failed to parse config file: #{Blufin::Terminal::format_directory(config_file)}", e.message)
|
69
|
+
end
|
70
|
+
return document, errors
|
71
|
+
end
|
72
|
+
|
73
|
+
# This shows the schema validation errors in a consistent manner across all the gems.
|
74
|
+
# @return void
|
75
|
+
def self.display_errors(errors, gem_name, setup_command = 'x')
|
76
|
+
errors_output = []
|
77
|
+
errors.each { |e|
|
78
|
+
errors_output << "[#{e.path}] #{e.message}"
|
79
|
+
}
|
80
|
+
invalid_configuration(gem_name, errors, setup_command)
|
81
|
+
end
|
82
|
+
|
83
|
+
# Standardized way of outputting invalid configuration.
|
84
|
+
# @return void
|
85
|
+
def self.invalid_configuration(gem_name, errors = nil, setup_command = 'x')
|
86
|
+
Blufin::Terminal::error("Your configuration file is invalid. To fix it run: #{Blufin::Terminal::format_command("#{gem_name} #{setup_command}")}", errors, true)
|
87
|
+
end
|
88
|
+
|
89
|
+
private
|
90
|
+
|
91
|
+
# Loads the config file into global Hash.
|
92
|
+
# @return void
|
93
|
+
def self.load_config(config_file, schema_file, gem_name, setup_command = 'x')
|
94
|
+
document, errors = validate_file(config_file, schema_file)
|
95
|
+
if errors && !errors.empty?
|
96
|
+
display_errors(errors, gem_name, setup_command)
|
97
|
+
else
|
98
|
+
@@data = document
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
# Creates a the config file from the given template.
|
103
|
+
# @return
|
104
|
+
def self.make_config(template_file, config_file)
|
105
|
+
`cp #{template_file} #{config_file}`
|
106
|
+
end
|
107
|
+
|
108
|
+
end
|
109
|
+
|
110
|
+
end
|
data/lib/core/files.rb
CHANGED
@@ -162,6 +162,12 @@ module Blufin
|
|
162
162
|
def self.write_file(path_and_file, array_of_lines)
|
163
163
|
raise RuntimeError, "Expected String, instead got: #{path_and_file.class}" unless path_and_file.is_a?(String)
|
164
164
|
path_and_file = File.expand_path(path_and_file)
|
165
|
+
# If this comes in as a string, convert it to an Array of lines.
|
166
|
+
if array_of_lines.is_a?(String)
|
167
|
+
array_of_lines_new = []
|
168
|
+
array_of_lines.split("\n").each { |line| array_of_lines_new << line.gsub("\n", '') }
|
169
|
+
array_of_lines = array_of_lines_new
|
170
|
+
end
|
165
171
|
raise RuntimeError, "Expected an array of lines to write to file, instead got: #{array_of_lines.class}" unless array_of_lines.is_a? Array
|
166
172
|
prepare_for_file_write(path_and_file)
|
167
173
|
begin
|
@@ -327,7 +333,7 @@ module Blufin
|
|
327
333
|
files.uniq.sort
|
328
334
|
end
|
329
335
|
|
330
|
-
|
336
|
+
# Get and array of directories in a directory.
|
331
337
|
# @return Array
|
332
338
|
def self.get_dirs_in_dir(path, recursive = false)
|
333
339
|
raise RuntimeError, "Expected String, instead got: #{path.class}" unless path.is_a?(String)
|
@@ -381,6 +387,16 @@ module Blufin
|
|
381
387
|
return File.basename(File.expand_path(path_and_file), '*') unless include_extension
|
382
388
|
end
|
383
389
|
|
390
|
+
# Returns TRUE if file is empty. Ignores spaces and new-line characters.
|
391
|
+
# @return bool
|
392
|
+
def self.is_empty(path_and_file)
|
393
|
+
Blufin::Files::read_file(path_and_file).each do |line|
|
394
|
+
line.strip!
|
395
|
+
return false if line != ''
|
396
|
+
end
|
397
|
+
true
|
398
|
+
end
|
399
|
+
|
384
400
|
private
|
385
401
|
|
386
402
|
# Creates path (if not exists) and deletes file (if exists).
|
@@ -0,0 +1,331 @@
|
|
1
|
+
module Blufin
|
2
|
+
|
3
|
+
class Projects
|
4
|
+
|
5
|
+
SCHEMA_FILE = "#{Blufin::Base::get_base_path}#{Blufin::Base::OPT_PATH}/awx/project-schema.yml"
|
6
|
+
SCRIPT_RUN = 'run'
|
7
|
+
SCRIPT_TEST = 'test'
|
8
|
+
SCRIPT_BUILD = 'build'
|
9
|
+
SCRIPT_DEPLOY = 'deploy'
|
10
|
+
RUN_SCRIPTS = 'RunScripts'
|
11
|
+
TEST_SCRIPTS = 'TestScripts'
|
12
|
+
BUILD_SCRIPTS = 'BuildScripts'
|
13
|
+
DEPLOY_SCRIPTS = 'DeployScripts'
|
14
|
+
DEPLOYMENT_ID = 'Id'
|
15
|
+
TYPE = 'Type'
|
16
|
+
PROJECT = 'Project'
|
17
|
+
REPO_ROOT = 'RepoRoot'
|
18
|
+
REPO_PATH = 'RepoPath'
|
19
|
+
PORT_RANGE = 'PortRange'
|
20
|
+
RUN = 'Run'
|
21
|
+
TEST = 'Test'
|
22
|
+
BUILD = 'Build'
|
23
|
+
DEPLOY = 'Deploy'
|
24
|
+
SERVERS = 'Servers'
|
25
|
+
REGION = 'Region'
|
26
|
+
STACK = 'Stack'
|
27
|
+
ENVIRONMENTS = 'Environments'
|
28
|
+
TARGETS = 'Targets'
|
29
|
+
COMMANDS = 'Commands'
|
30
|
+
API = 'API'
|
31
|
+
TITLE = 'Title'
|
32
|
+
ALIAS = 'Alias'
|
33
|
+
DOMAIN = 'Domain'
|
34
|
+
PROJECT_NAME_PASCAL_CASE = 'ProjectNamePascalCase'
|
35
|
+
PROJECT_NAME = 'ProjectName'
|
36
|
+
TYPE_ALEXA = 'alexa'
|
37
|
+
TYPE_API = 'api'
|
38
|
+
TYPE_LAMBDA = 'lambda'
|
39
|
+
TYPE_UI = 'ui'
|
40
|
+
VALID_TYPES = [
|
41
|
+
TYPE_ALEXA,
|
42
|
+
TYPE_API,
|
43
|
+
TYPE_LAMBDA,
|
44
|
+
TYPE_UI,
|
45
|
+
]
|
46
|
+
|
47
|
+
@@projects = nil
|
48
|
+
@@project_names = []
|
49
|
+
@@scripts = nil
|
50
|
+
@@deployments = nil
|
51
|
+
@@apis = nil
|
52
|
+
@@environments = []
|
53
|
+
|
54
|
+
# Takes a Hash that should have the APIs, Lambdas and UIs keys in the root.
|
55
|
+
# This can come from both .awx.yml['Profiles'] or .blufin.yml (root).
|
56
|
+
# @return void
|
57
|
+
def self.init(hash, file)
|
58
|
+
raise RuntimeError, 'Cannot run Blufin::Projects::init() more than once.' unless @@projects.nil? && @@scripts.nil?
|
59
|
+
@@projects = {}
|
60
|
+
@@scripts = {}
|
61
|
+
if hash.has_key?('Projects')
|
62
|
+
projects = hash['Projects']
|
63
|
+
# Throw error if we have multiple sources defined, can only have 1.
|
64
|
+
Blufin::Terminal::error("Found multiple sources for #{Blufin::Terminal::format_highlight(UIs)} property in: #{Blufin::Terminal::format_directory(file)}. Can #{Blufin::Terminal::format_highlight('only have one')} of the following, not both:", [Blufin::Terminal::format_invalid('Local'), Blufin::Terminal::format_invalid('S3Bucket')], true) if projects.has_key?('Local') && projects.has_key?('S3Bucket')
|
65
|
+
if projects.has_key?('Local')
|
66
|
+
source_file = File.expand_path(projects['Local']['File'])
|
67
|
+
# Throw error if source file doesn't exist.
|
68
|
+
Blufin::Terminal::error("Cannot find source file: #{Blufin::Terminal::format_directory(source_file)}") unless Blufin::Files::file_exists(source_file)
|
69
|
+
# Validate the source file against the expected schema.
|
70
|
+
process_source_file(source_file)
|
71
|
+
elsif projects.has_key?('S3Bucket')
|
72
|
+
|
73
|
+
# TODO - Finish this once we start running this on an EC2 instance (build/deploy server).
|
74
|
+
# TODO - Whatever file we validate should be available on disk locally.
|
75
|
+
# TODO - If the source is an S3 bucket, pull it down into a /tmp folder (on EC2 instance) and validate from there.
|
76
|
+
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
# Gets Project(s).
|
82
|
+
# @return Hash
|
83
|
+
def self.get_projects
|
84
|
+
@@projects
|
85
|
+
end
|
86
|
+
|
87
|
+
# Gets Project Name(s).
|
88
|
+
# @return Array
|
89
|
+
def self.get_project_names
|
90
|
+
@@project_names
|
91
|
+
end
|
92
|
+
|
93
|
+
# Gets Script(s).
|
94
|
+
# @return Hash
|
95
|
+
def self.get_scripts
|
96
|
+
@@scripts
|
97
|
+
end
|
98
|
+
|
99
|
+
# Gets API(s).
|
100
|
+
# @return Hash
|
101
|
+
def self.get_apis
|
102
|
+
@@apis
|
103
|
+
end
|
104
|
+
|
105
|
+
# Gets Environments(s).
|
106
|
+
# @return Array
|
107
|
+
def self.get_environments
|
108
|
+
@@environments
|
109
|
+
end
|
110
|
+
|
111
|
+
# Get matching deployments for parameters.
|
112
|
+
# @return Hash
|
113
|
+
def self.get_deployments(project, stack, region, environment)
|
114
|
+
if @@deployments.nil?
|
115
|
+
@@deployments = {}
|
116
|
+
if @@projects.is_a?(Hash) && @@projects.has_key?(project)
|
117
|
+
project = @@projects[project]
|
118
|
+
project.each do |deployment_id, data|
|
119
|
+
if data.is_a?(Hash) && data.has_key?(SERVERS)
|
120
|
+
servers = data[SERVERS]
|
121
|
+
if servers.is_a?(Array)
|
122
|
+
servers.each do |server|
|
123
|
+
if server.has_key?(REGION) && server[REGION] == region
|
124
|
+
if server.has_key?(STACK) && server[STACK] == stack
|
125
|
+
if server.has_key?(ENVIRONMENTS) && server[ENVIRONMENTS].include?(environment)
|
126
|
+
@@deployments[deployment_id] = data
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
@@deployments
|
137
|
+
end
|
138
|
+
|
139
|
+
# Maps root-level property to enum.
|
140
|
+
# @return string
|
141
|
+
def self.script_key_mapper(script_type)
|
142
|
+
if [RUN_SCRIPTS, RUN].include?(script_type)
|
143
|
+
return SCRIPT_RUN
|
144
|
+
elsif [TEST_SCRIPTS, TEST].include?(script_type)
|
145
|
+
return TEST_SCRIPTS
|
146
|
+
elsif [BUILD_SCRIPTS, BUILD].include?(script_type)
|
147
|
+
return BUILD_SCRIPTS
|
148
|
+
elsif [DEPLOY_SCRIPTS, DEPLOY].include?(script_type)
|
149
|
+
return DEPLOY_SCRIPTS
|
150
|
+
else
|
151
|
+
raise RuntimeError, "Unhandled script type: #{script_type}"
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
private
|
156
|
+
|
157
|
+
# Validate the Project YML.
|
158
|
+
# @return void
|
159
|
+
def self.process_source_file(source_file)
|
160
|
+
# Skip empty file.
|
161
|
+
return if Blufin::Files::is_empty(source_file)
|
162
|
+
# Otherwise, validate file.
|
163
|
+
document, errors = Blufin::Config::validate_file(source_file, SCHEMA_FILE)
|
164
|
+
if errors && !errors.empty?
|
165
|
+
errors_output = []
|
166
|
+
errors.each { |e|
|
167
|
+
errors_output << "[#{e.path}] #{e.message}"
|
168
|
+
}
|
169
|
+
Blufin::Terminal::error("Your configuration file is invalid. Please fix: #{Blufin::Terminal::format_directory(source_file)}", errors)
|
170
|
+
else
|
171
|
+
begin
|
172
|
+
file_parsed = YAML.load_file(source_file)
|
173
|
+
rescue => e
|
174
|
+
Blufin::Terminal::error("Failed to parse config file: #{Blufin::Terminal::format_directory(source_file)}", e.message)
|
175
|
+
end
|
176
|
+
|
177
|
+
# Buffer script(s).
|
178
|
+
[RUN_SCRIPTS, TEST_SCRIPTS, BUILD_SCRIPTS, DEPLOY_SCRIPTS].each do |script_type|
|
179
|
+
if file_parsed.has_key?(script_type)
|
180
|
+
if file_parsed[script_type].is_a?(Array)
|
181
|
+
script_key = script_key_mapper(script_type)
|
182
|
+
@@scripts[script_key] = {} unless @@scripts.has_key?(script_key)
|
183
|
+
file_parsed[script_type].each do |script|
|
184
|
+
script_id = script[DEPLOYMENT_ID]
|
185
|
+
# Throw error if duplicate script is found for type.
|
186
|
+
Blufin::Terminal::error("#{Blufin::Terminal::format_highlight("#{script_type} - #{script_id}")} \xe2\x80\x94 Duplicate script found: #{Blufin::Terminal::format_invalid(script_id)}") if @@scripts[script_key].has_key?(script_id)
|
187
|
+
@@scripts[script_key][script_id] = script
|
188
|
+
# Check that if script is AWS, it doesn't have --region or --profile flag.
|
189
|
+
if script.has_key?(COMMANDS)
|
190
|
+
script[COMMANDS].each do |command|
|
191
|
+
if command.strip =~ /^aws/i
|
192
|
+
Blufin::Terminal::error("#{Blufin::Terminal::format_highlight("#{script_type} - #{script_id}")} \xe2\x80\x94 AWS scripts cannot have: #{Blufin::Terminal::format_invalid('--region')} flag.", command) if command =~ /\s--region\s/
|
193
|
+
Blufin::Terminal::error("#{Blufin::Terminal::format_highlight("#{script_type} - #{script_id}")} \xe2\x80\x94 AWS scripts cannot have: #{Blufin::Terminal::format_invalid('--profile')} flag.", command) if command =~ /\s--profile\s/
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
used_ports = {}
|
203
|
+
|
204
|
+
# Loop (and validate) projects.
|
205
|
+
file_parsed['Projects'].each do |project|
|
206
|
+
# Validate keys are in specific order.
|
207
|
+
expected = {
|
208
|
+
DEPLOYMENT_ID => true,
|
209
|
+
TYPE => true,
|
210
|
+
PROJECT => true,
|
211
|
+
REPO_ROOT => true,
|
212
|
+
REPO_PATH => true,
|
213
|
+
PORT_RANGE => false,
|
214
|
+
RUN => false,
|
215
|
+
TEST => false,
|
216
|
+
BUILD => false,
|
217
|
+
DEPLOY => false,
|
218
|
+
SERVERS => false,
|
219
|
+
API => false
|
220
|
+
}
|
221
|
+
Blufin::Validate::assert_valid_keys(expected, project.keys, source_file)
|
222
|
+
project_id = project[DEPLOYMENT_ID]
|
223
|
+
project_name = project[PROJECT]
|
224
|
+
project_type = project[TYPE]
|
225
|
+
|
226
|
+
@@project_names << project_name
|
227
|
+
|
228
|
+
# Validate Type.
|
229
|
+
Blufin::Terminal::error("#{project_id} \xe2\x80\x94 Invalid Project Type: #{Blufin::Terminal::format_invalid(project_type)}. Valid types are:", VALID_TYPES, true) unless VALID_TYPES.include?(project_type)
|
230
|
+
# Validate Script(s).
|
231
|
+
[RUN, TEST, BUILD, DEPLOY].each do |script_type|
|
232
|
+
if project.has_key?(script_type)
|
233
|
+
# Validate the LAMBDA functions don't need build scripts.
|
234
|
+
Blufin::Terminal::error("#{project_id} \xe2\x80\x94 Project type: #{Blufin::Terminal::format_highlight(TYPE_LAMBDA)} cannot have #{Blufin::Terminal::format_invalid(script_type)} property.", 'This type of project does not support this.', true) if [BUILD].include?(script_type) && project_type == TYPE_LAMBDA
|
235
|
+
if project[script_type].is_a?(Array)
|
236
|
+
script_key = script_key_mapper(script_type)
|
237
|
+
valid_scripts = []
|
238
|
+
valid_scripts = @@scripts[script_key].keys if @@scripts.has_key?(script_key)
|
239
|
+
project[script_type].each do |script|
|
240
|
+
script_name = script['Script']
|
241
|
+
unless valid_scripts.include?(script_name)
|
242
|
+
error = valid_scripts.any? ? 'Valid values are:' : "There currently are no #{script_key} script(s) defined."
|
243
|
+
Blufin::Terminal::error("#{project_id} \xe2\x80\x94 #{Blufin::Terminal::format_highlight(script_type)} \xe2\x80\x94 Invalid script reference: #{Blufin::Terminal::format_invalid(script_name)}. #{error}", valid_scripts)
|
244
|
+
end
|
245
|
+
end
|
246
|
+
end
|
247
|
+
end
|
248
|
+
end
|
249
|
+
# Validate Server(s) and create new 'Targets' Hash from it.
|
250
|
+
if project.has_key?(SERVERS)
|
251
|
+
targets = {}
|
252
|
+
project[SERVERS].each do |server|
|
253
|
+
region = server['Region']
|
254
|
+
stack = server['Stack']
|
255
|
+
environments = server['Environments']
|
256
|
+
environments.each do |environment|
|
257
|
+
targets[environment] = {} unless targets.has_key?(environment)
|
258
|
+
targets[environment] = {
|
259
|
+
:region => region,
|
260
|
+
:stack => stack,
|
261
|
+
}
|
262
|
+
@@environments << environment
|
263
|
+
end
|
264
|
+
end
|
265
|
+
project[TARGETS] = targets
|
266
|
+
end
|
267
|
+
# Validate APIs and build @@api Hash.
|
268
|
+
if project_type == TYPE_API
|
269
|
+
# Make sure we have the API & PortRange properties.
|
270
|
+
Blufin::Terminal::error("#{project_id} \xe2\x80\x94 Missing property: #{Blufin::Terminal::format_highlight(API)}", "This property is required for project(s) with type: #{API}", true) unless project.has_key?(API)
|
271
|
+
Blufin::Terminal::error("#{project_id} \xe2\x80\x94 Missing property: #{Blufin::Terminal::format_highlight(PORT_RANGE)}", "This property is required for project(s) with type: #{API}", true) unless project.has_key?(PORT_RANGE)
|
272
|
+
# Validate keys are in specific order.
|
273
|
+
expected = {
|
274
|
+
TITLE => true,
|
275
|
+
ALIAS => true,
|
276
|
+
DOMAIN => true,
|
277
|
+
PROJECT_NAME => true,
|
278
|
+
PROJECT_NAME_PASCAL_CASE => true
|
279
|
+
}
|
280
|
+
Blufin::Validate::assert_valid_keys(expected, project[API].keys, source_file)
|
281
|
+
@@apis = {} if @@apis.nil?
|
282
|
+
@@apis[project[DEPLOYMENT_ID]] = {
|
283
|
+
REPO_ROOT => project[REPO_ROOT],
|
284
|
+
TITLE => project[API][TITLE],
|
285
|
+
ALIAS => project[API][ALIAS],
|
286
|
+
PROJECT_NAME => project[API][PROJECT_NAME],
|
287
|
+
PROJECT_NAME_PASCAL_CASE => project[API][PROJECT_NAME_PASCAL_CASE],
|
288
|
+
DOMAIN => project[API][DOMAIN],
|
289
|
+
PORT_RANGE => project[PORT_RANGE]
|
290
|
+
}
|
291
|
+
else
|
292
|
+
# Make sure we DON'T have the API key.
|
293
|
+
Blufin::Terminal::error("#{project_id} \xe2\x80\x94 Property not supported: #{Blufin::Terminal::format_invalid(API)}", "This property is only allowed for project(s) with type: #{API}", true) if project.has_key?(API)
|
294
|
+
end
|
295
|
+
|
296
|
+
# Validate the ports range.
|
297
|
+
if project.has_key?(PORT_RANGE)
|
298
|
+
port_range = project[PORT_RANGE].strip
|
299
|
+
# Make sure Port Range is in format: XXXX-XXXX
|
300
|
+
port_range_regex = /^\d{4}-\d{4}$/
|
301
|
+
Blufin::Terminal::error("#{project_id} \xe2\x80\x94 Port Range does not match regex: #{Blufin::Terminal::format_invalid(port_range)}", "Must match regex: #{port_range_regex}", true) unless port_range =~ port_range_regex
|
302
|
+
prs = port_range.split('-')
|
303
|
+
raise RuntimeError, "Expected port range to have exactly one hyphen (-), instead got: #{port_range}" unless prs.length == 2
|
304
|
+
# Make sure port range spans exactly 20 ports (by having a difference of 19).
|
305
|
+
Blufin::Terminal::error("#{project_id} \xe2\x80\x94 Port Range either doesn't span 20 ports or is not in correct, numerical order: #{Blufin::Terminal::format_invalid(port_range)}") unless prs[1].to_i - prs[0].to_i == 19
|
306
|
+
# Make sure none of the ports conflict with other projects.
|
307
|
+
(prs[0].to_i..prs[1].to_i).each do |i|
|
308
|
+
if used_ports.has_key?(i)
|
309
|
+
Blufin::Terminal::error("#{project_id} \xe2\x80\x94 Port overlap detected for range: #{Blufin::Terminal::format_invalid(port_range)}", ["The conflicting project is: #{used_ports[i]}"])
|
310
|
+
end
|
311
|
+
end
|
312
|
+
# Add ports to used_ports.
|
313
|
+
(prs[0].to_i..prs[1].to_i).each { |i| used_ports[i] = "#{Blufin::Terminal::format_highlight(project_id)} \xe2\x80\x94 #{port_range}" }
|
314
|
+
end
|
315
|
+
|
316
|
+
@@projects[project_name] = {} unless @@projects.has_key?(project_name)
|
317
|
+
Blufin::Terminal::error("Duplicate project ID: #{Blufin::Terminal::format_invalid(project_id)}") if @@projects[project_name].has_key?(project_id)
|
318
|
+
@@projects[project_name][project_id] = project
|
319
|
+
|
320
|
+
end
|
321
|
+
|
322
|
+
@@project_names.uniq!.sort!
|
323
|
+
@@environments.uniq!.sort!
|
324
|
+
|
325
|
+
end
|
326
|
+
|
327
|
+
end
|
328
|
+
|
329
|
+
end
|
330
|
+
|
331
|
+
end
|
data/lib/core/strings.rb
CHANGED
@@ -1,7 +1,11 @@
|
|
1
|
+
require 'securerandom'
|
2
|
+
|
1
3
|
module Blufin
|
2
4
|
|
3
5
|
class Strings
|
4
6
|
|
7
|
+
RETURN_CHARACTER = '↵'
|
8
|
+
|
5
9
|
# Convert 'snake_case' or 'SnAKE_cAse' to 'SnakeCase'.
|
6
10
|
# @param String
|
7
11
|
# @return String
|
@@ -82,6 +86,13 @@ module Blufin
|
|
82
86
|
return ((discrepancies.to_f / total_letters.to_f) * 100).round
|
83
87
|
end
|
84
88
|
|
89
|
+
# Generate Random string.
|
90
|
+
# Currently returns something like: 1ec6c763
|
91
|
+
# @return String
|
92
|
+
def self.random_string
|
93
|
+
"#{SecureRandom.uuid.split('-')[0].downcase}#{SecureRandom.uuid.split('-')[0].downcase}"
|
94
|
+
end
|
95
|
+
|
85
96
|
private
|
86
97
|
|
87
98
|
# Internal string validation.
|
data/lib/core/terminal.rb
CHANGED
@@ -11,20 +11,20 @@ module Blufin
|
|
11
11
|
|
12
12
|
extend Columnist
|
13
13
|
|
14
|
-
MSG_INFO
|
15
|
-
MSG_WARNING
|
16
|
-
MSG_ERROR
|
17
|
-
MSG_TODO
|
18
|
-
MSG_AUTOMATIC
|
19
|
-
MSG_GENERATED
|
20
|
-
MSG_PROCESSED
|
21
|
-
MSG_PROGRESS
|
22
|
-
MSG_CUSTOM
|
14
|
+
MSG_INFO = 'info'
|
15
|
+
MSG_WARNING = 'warning'
|
16
|
+
MSG_ERROR = 'error'
|
17
|
+
MSG_TODO = 'todo'
|
18
|
+
MSG_AUTOMATIC = 'automatic'
|
19
|
+
MSG_GENERATED = 'generated'
|
20
|
+
MSG_PROCESSED = 'processed'
|
21
|
+
MSG_PROGRESS = 'progress'
|
22
|
+
MSG_CUSTOM = 'custom'
|
23
23
|
MSG_CUSTOM_AUTO_PAD = 'custom_auto_pad'
|
24
24
|
|
25
25
|
# Runs a series of commands in the terminal.
|
26
26
|
# @return void
|
27
|
-
def self.command(commands, location = nil, verbose = true, verbose_cd = true, capture_real_output = false)
|
27
|
+
def self.command(commands, location = nil, verbose = true, verbose_cd = true, capture_real_output = false, pbl: false, tbl: false)
|
28
28
|
unless commands.is_a?(Array)
|
29
29
|
commands = [commands]
|
30
30
|
end
|
@@ -35,13 +35,15 @@ module Blufin
|
|
35
35
|
end
|
36
36
|
output = []
|
37
37
|
if verbose_cd && verbose && commands.any? && !location.nil?
|
38
|
-
puts "\x1B[38;5;231m\x1B[
|
38
|
+
puts "\x1B[38;5;231m\x1B[48;5;22m Executing \x1B[0m \x1B[0m\xe2\x86\x92\x1B[0m #{Blufin::Terminal::format_command("cd #{location}")}\n"
|
39
39
|
end
|
40
40
|
if commands.any?
|
41
41
|
commands.each do |command|
|
42
42
|
if verbose
|
43
|
-
puts "\x1B[38;5;231m\x1B[
|
43
|
+
puts "\x1B[38;5;231m\x1B[48;5;22m Executing \x1B[0m \x1B[0m\xe2\x86\x92\x1B[0m #{Blufin::Terminal::format_command("#{command}")}\n"
|
44
|
+
puts if pbl
|
44
45
|
end
|
46
|
+
|
45
47
|
if location.nil?
|
46
48
|
if capture_real_output
|
47
49
|
output << `#{command}`
|
@@ -60,6 +62,7 @@ module Blufin
|
|
60
62
|
if capture_real_output
|
61
63
|
output.map! { |v| v.gsub('\n', "\n") }
|
62
64
|
end
|
65
|
+
puts if tbl
|
63
66
|
output
|
64
67
|
end
|
65
68
|
|
@@ -69,25 +72,29 @@ module Blufin
|
|
69
72
|
command(commands, location, verbose, verbose_cd, true)
|
70
73
|
end
|
71
74
|
|
72
|
-
# Executes a command and shows that something is happening (via a cli-spinner)
|
75
|
+
# Executes a command and shows that something is happening (via a cli-spinner).
|
76
|
+
# If capture: is false, returns TRUE:FALSE whether command was successful or not.
|
77
|
+
# If capture: is true, returns raw output of command.
|
73
78
|
# See: https://github.com/piotrmurach/tty-spinner/blob/master/lib/tty/spinner/formats.rb (for spinner options).
|
74
79
|
# @return void
|
75
|
-
def self.execute(command, path = nil, capture
|
76
|
-
|
77
|
-
|
78
|
-
spinner.
|
80
|
+
def self.execute(command, path = nil, capture: false, verbose: true, text: nil)
|
81
|
+
text = text.is_a?(String) ? text : command
|
82
|
+
t1 = Time.now
|
83
|
+
spinner = TTY::Spinner.new("[:spinner] \x1B[38;5;208m#{text}#{!path.nil? ? " \x1B[38;5;246m\xe2\x86\x92 \x1B[38;5;240m#{path}" : nil}\x1B[0m", format: :dots) if verbose
|
84
|
+
spinner.auto_spin if verbose
|
79
85
|
path = File.expand_path('~/') if path.nil?
|
80
86
|
if capture
|
81
|
-
res =
|
87
|
+
res = `cd #{path} && #{command}`
|
82
88
|
else
|
83
89
|
res = system("cd #{path} && #{command} > /tmp/execute-output")
|
84
90
|
end
|
85
|
-
t2
|
91
|
+
t2 = Time.now
|
86
92
|
delta = "#{'%.3f' % (t2 - t1).abs}s"
|
87
93
|
if res || capture
|
88
|
-
spinner.success("\x1B[38;5;246m\xe2\x86\x92 \x1B[38;5;46mComplete \x1B[38;5;240m(#{delta})\x1B[0m\x1B[0m")
|
94
|
+
spinner.success("\x1B[38;5;246m\xe2\x86\x92 \x1B[38;5;46mComplete \x1B[38;5;240m(#{delta})\x1B[0m\x1B[0m") if verbose
|
89
95
|
else
|
90
|
-
spinner.error("\x1B[38;5;246m\xe2\x86\x92 \x1B[38;5;196mFailed (#{delta})\x1B[0m")
|
96
|
+
spinner.error("\x1B[38;5;246m\xe2\x86\x92 \x1B[38;5;196mFailed (#{delta})\x1B[0m") if verbose
|
97
|
+
# If there is an error, output it (even if verbose == false).
|
91
98
|
puts "\x1B[38;5;240m"
|
92
99
|
system('cat /tmp/execute-output')
|
93
100
|
puts "\x1B[0m"
|
@@ -100,12 +107,12 @@ module Blufin
|
|
100
107
|
def self.execute_proc(title, proc, verbose: true)
|
101
108
|
raise RuntimeError, "Expected String, instead got:#{title.class}" unless title.is_a?(String)
|
102
109
|
raise RuntimeError, "Expected proc to be an instance of Proc, instead got: #{proc.class}" unless proc.is_a?(Proc)
|
103
|
-
t1
|
110
|
+
t1 = Time.now
|
104
111
|
spinner = nil
|
105
112
|
spinner = TTY::Spinner.new("[:spinner] \x1B[38;5;208m#{title}\x1B[0m", format: :dots) if verbose
|
106
113
|
spinner.auto_spin if verbose
|
107
|
-
res
|
108
|
-
t2
|
114
|
+
res = proc.call
|
115
|
+
t2 = Time.now
|
109
116
|
delta = "#{'%.3f' % (t2 - t1).abs}s"
|
110
117
|
spinner.success("\x1B[38;5;246m\xe2\x86\x92 \x1B[38;5;46mComplete \x1B[38;5;240m(#{delta})\x1B[0m\x1B[0m") if verbose && res
|
111
118
|
spinner.error("\x1B[38;5;246m\xe2\x86\x92 \x1B[38;5;196mFailed (#{delta})\x1B[0m") if verbose && !res
|
@@ -151,7 +158,7 @@ module Blufin
|
|
151
158
|
# Displays error and exits script by default.
|
152
159
|
# @return void
|
153
160
|
def self.abort(title = nil, message = nil, exit_script = true, preceding_blank_line = false)
|
154
|
-
title
|
161
|
+
title = 'Abandon ship!' if title.nil?
|
155
162
|
message = "You have chosen to \x1B[38;5;196mABORT\x1B[38;5;240m the script." if message.nil?
|
156
163
|
Blufin::Terminal::error(title, message, exit_script, preceding_blank_line, 'ABORT')
|
157
164
|
end
|
@@ -193,7 +200,7 @@ module Blufin
|
|
193
200
|
# @return void
|
194
201
|
def self.warning(title = nil, message = nil, preceding_blank_line = true)
|
195
202
|
puts if preceding_blank_line
|
196
|
-
puts " \x1B[38;5;231m\x1B[48;5;
|
203
|
+
puts " \x1B[38;5;231m\x1B[48;5;130m Warning \x1B[0m #{title.nil? ? '' : "\xe2\x86\x92 "}#{title}\n"
|
197
204
|
parse_messages(message)
|
198
205
|
end
|
199
206
|
|
@@ -210,6 +217,7 @@ module Blufin
|
|
210
217
|
# Displays custom message (ideally, keyword should be 7 characters long to match the rest of the output).
|
211
218
|
# @return void
|
212
219
|
def self.custom(keyword = 'N/A', color = 1, title = nil, message = nil, preceding_blank_line = true)
|
220
|
+
color = 55 if color.nil?
|
213
221
|
puts if preceding_blank_line
|
214
222
|
puts " \x1B[38;5;231m\x1B[48;5;#{color}m #{keyword} \x1B[0m #{title.nil? ? '' : "\xe2\x86\x92 "}#{title}\n"
|
215
223
|
parse_messages(message)
|
@@ -272,11 +280,25 @@ module Blufin
|
|
272
280
|
|
273
281
|
# Returns action message in consistent, uniform manner.
|
274
282
|
# @return String
|
275
|
-
def self.format_highlight(highlighted_text, capitalize = false)
|
283
|
+
def self.format_highlight(highlighted_text, capitalize = false, target: nil)
|
284
|
+
case target
|
285
|
+
when 'cf' # CloudFormation
|
286
|
+
color = 82
|
287
|
+
color_end = "\x1B[38;5;246m"
|
288
|
+
when 'cf-ctt' # CloudFormation (Creation Time Title)
|
289
|
+
color = 196
|
290
|
+
color_end = "\x1B[38;5;246m"
|
291
|
+
when 'cf-ct' # CloudFormation (Creation Time)
|
292
|
+
color = 82
|
293
|
+
color_end = "\x1B[38;5;246m"
|
294
|
+
else
|
295
|
+
color = 117
|
296
|
+
color_end = "\x1B[0m"
|
297
|
+
end
|
276
298
|
if capitalize
|
277
|
-
return "\x1B[38;5
|
299
|
+
return "\x1B[38;5;#{color}m#{highlighted_text.upcase}#{color_end}"
|
278
300
|
else
|
279
|
-
return "\x1B[38;5
|
301
|
+
return "\x1B[38;5;#{color}m#{highlighted_text}#{color_end}"
|
280
302
|
end
|
281
303
|
end
|
282
304
|
|
@@ -303,10 +325,10 @@ module Blufin
|
|
303
325
|
# Gives a prompt where 'y/Y' will return TRUE, 'n/N' will return false, and ANY other key will do nothing.
|
304
326
|
# @return void
|
305
327
|
def self.prompt_yes_no(title = nil, message = nil, confirmation_message = nil, preceding_blank_line = true)
|
306
|
-
title
|
328
|
+
title = 'Please confirm YES or NO.' if title.nil?
|
307
329
|
confirmation_message = 'Would you like to continue?' if confirmation_message.nil?
|
308
330
|
puts if preceding_blank_line
|
309
|
-
puts " \x1B[38;5;231m\x1B[48;5;
|
331
|
+
puts " \x1B[38;5;231m\x1B[48;5;55m Confirm \x1B[0m #{title.nil? ? '' : "\xe2\x86\x92 "}#{title}\n"
|
310
332
|
parse_messages(message)
|
311
333
|
response = ''
|
312
334
|
until %w[y Y n N x X a A].include? response
|
@@ -320,9 +342,9 @@ module Blufin
|
|
320
342
|
puts "\n"
|
321
343
|
return false
|
322
344
|
when 'a'
|
323
|
-
Blufin::Terminal::error('Abandon ship!', ["You have chosen to \x1B[38;5;
|
345
|
+
Blufin::Terminal::error('Abandon ship!', ["You have chosen to \x1B[38;5;196mABORT\x1B[38;5;240m the script.", nil, 'Please note that whenever you do this, any scripted tasks which were running', 'will have been interrupted mid-script. This may (or may not) cause problems.'], true)
|
324
346
|
when 'x'
|
325
|
-
Blufin::Terminal::error('Abandon ship!', ["You have chosen to \x1B[38;5;
|
347
|
+
Blufin::Terminal::error('Abandon ship!', ["You have chosen to \x1B[38;5;196mABORT\x1B[38;5;240m the script.", nil, 'Please note that whenever you do this, any scripted tasks which were running', 'will have been interrupted mid-script. This may (or may not) cause problems.'], true)
|
326
348
|
else
|
327
349
|
raise RuntimeError, "Un-handled response: #{response.downcase}"
|
328
350
|
end
|
@@ -423,10 +445,26 @@ module Blufin
|
|
423
445
|
# ⬡ bourbon
|
424
446
|
# @options = %w(vodka beer wine whisky bourbon)
|
425
447
|
# @return Array
|
426
|
-
def self.prompt_multi_select(question, options)
|
448
|
+
def self.prompt_multi_select(question, options, help: nil, per_page: 20, cycle: true)
|
427
449
|
raise RuntimeError, "Expected Array, instead got #{options.class}" unless options.is_a?(Array)
|
450
|
+
puts display_prompt_help(help) unless help.nil?
|
428
451
|
prompt = TTY::Prompt.new
|
429
|
-
|
452
|
+
if options[0].is_a?(String)
|
453
|
+
prompt.multi_select(display_prompt_text(question), options, per_page: per_page, cycle: cycle)
|
454
|
+
elsif options[0].is_a?(Hash)
|
455
|
+
prompt.multi_select(display_prompt_text(question), options, per_page: per_page, cycle: cycle) do |menu|
|
456
|
+
options.each do |option|
|
457
|
+
raise RuntimeError, "Expected option to be Hash, instead got: (#{option.class}) #{option.inspect}" unless option.is_a?(Hash)
|
458
|
+
raise RuntimeError, 'Option is missing key => :text' unless option.has_key?(:text)
|
459
|
+
raise RuntimeError, 'Option is missing key => :value' unless option.has_key?(:value)
|
460
|
+
raise RuntimeError, "Expected :disabled option to be String, instead got: #{option[:disabled].class}" if option.has_key?(:disabled) && !option[:disabled].is_a?(String) && !option[:disabled].nil?
|
461
|
+
menu.choice option[:text], option[:value] unless option.has_key?(:disabled)
|
462
|
+
menu.choice option[:text], option[:value], disabled: "\x1B[38;5;196m#{option[:disabled]}\x1B[0m" if option.has_key?(:disabled)
|
463
|
+
end
|
464
|
+
end
|
465
|
+
else
|
466
|
+
raise RuntimeError, "Expected options Array to consist of either Strings or Hashes, instead got: #{options.inspect}"
|
467
|
+
end
|
430
468
|
end
|
431
469
|
|
432
470
|
# Select an editor?
|
@@ -454,8 +492,8 @@ module Blufin
|
|
454
492
|
def self.prompt_expand(question, options)
|
455
493
|
raise RuntimeError, "Expected Array, instead got #{options.class}" unless options.is_a?(Array)
|
456
494
|
options.each do |option|
|
457
|
-
found_key
|
458
|
-
found_name
|
495
|
+
found_key = false
|
496
|
+
found_name = false
|
459
497
|
found_value = false
|
460
498
|
option.each do |k, v|
|
461
499
|
case k
|
@@ -521,11 +559,11 @@ module Blufin
|
|
521
559
|
# @return void
|
522
560
|
def self.print_files_written(array_of_paths_or_files, message = nil, limit = 10, preceding_blank_line = true)
|
523
561
|
raise RuntimeError, "Expected Array of files, instead got: #{array_of_paths_or_files.class}" unless array_of_paths_or_files.is_a?(Array)
|
524
|
-
message
|
525
|
-
limit
|
562
|
+
message = "Wrote the following #{Blufin::Terminal::format_highlight('files/directories')}" if message.nil?
|
563
|
+
limit = limit.nil? ? 99999999999 : limit.to_i
|
526
564
|
file_output = []
|
527
|
-
file_count
|
528
|
-
file_more
|
565
|
+
file_count = 0
|
566
|
+
file_more = 0
|
529
567
|
array_of_paths_or_files.compact!
|
530
568
|
array_of_paths_or_files.sort!
|
531
569
|
array_of_paths_or_files.each do |path_or_file|
|
@@ -555,13 +593,13 @@ module Blufin
|
|
555
593
|
# @return void
|
556
594
|
def self.code_highlight(string, type, indent = 5)
|
557
595
|
raise RuntimeError, "Expected String, instead got:#{string.class}" unless string.is_a?(String)
|
558
|
-
type
|
596
|
+
type = type.downcase
|
559
597
|
types = {
|
560
|
-
'yml'
|
598
|
+
'yml' => Rouge::Lexers::YAML.new,
|
561
599
|
'json' => Rouge::Lexers::JSON.new
|
562
600
|
}
|
563
601
|
raise RuntimeError, "Lexer not defined for type: #{type}" unless types.has_key?(type)
|
564
|
-
repeat
|
602
|
+
repeat = ' ' * indent
|
565
603
|
formatter = Rouge::Formatters::Terminal256.new
|
566
604
|
formatter.format(types[type].lex(string)).split("\n").each do |line|
|
567
605
|
puts "#{repeat}#{line}"
|
@@ -601,8 +639,9 @@ module Blufin
|
|
601
639
|
|
602
640
|
# Standardized way of displaying prompts text.
|
603
641
|
# @return string
|
604
|
-
def self.display_prompt_text(question)
|
605
|
-
"\x1B[38;5;208m#{question}\x1B[38;5;240m =>\x1B[0m"
|
642
|
+
def self.display_prompt_text(question, answer = nil)
|
643
|
+
return "\x1B[38;5;208m#{question}\x1B[38;5;240m =>\x1B[0m" if answer.nil?
|
644
|
+
return "\x1B[38;5;208m#{question}\x1B[38;5;240m => \x1B[38;5;46m#{answer}\x1B[0m" unless answer.nil?
|
606
645
|
end
|
607
646
|
|
608
647
|
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Blufin
|
2
|
+
|
3
|
+
class Versioning
|
4
|
+
|
5
|
+
VERSION_REGEX = /\d{1,3}\.\d{1,3}\.\d{0,3}/
|
6
|
+
|
7
|
+
# Shows prompt to let you select a new major, minor or patch version.
|
8
|
+
# @return string
|
9
|
+
def self.get_new_version_from_prompt(title, current_version, capitalize_tile: true)
|
10
|
+
Blufin::Terminal::error("Current version does not match regex: #{current_version}", "Expected something like: #{Blufin::Terminal::format_highlight('12.1.0')}", true) unless current_version =~ VERSION_REGEX
|
11
|
+
vp = current_version.split('.')
|
12
|
+
color1 = 246
|
13
|
+
color2 = 40
|
14
|
+
patch = "\x1B[38;5;#{color1}mPatch \x1B[38;5;242m\xe2\x80\x94 \x1B[38;5;#{color2}m#{vp[0]}.#{vp[1]}.#{vp[2].to_i + 1} \x1B[38;5;242m\xe2\x80\x94\x1B[38;5;240m New version is interchangeable, users can upgrade/downgrade freely."
|
15
|
+
minor = "\x1B[38;5;#{color1}mMinor \x1B[38;5;242m\xe2\x80\x94 \x1B[38;5;#{color2}m#{vp[0]}.#{vp[1].to_i + 1}.0 \x1B[38;5;242m\xe2\x80\x94\x1B[38;5;240m New version is backwards compatible, users can upgrade freely."
|
16
|
+
major = "\x1B[38;5;#{color1}mMajor \x1B[38;5;242m\xe2\x80\x94 \x1B[38;5;#{color2}m#{vp[0].to_i + 1}.0.0 \x1B[38;5;242m\xe2\x80\x94\x1B[38;5;240m New version has breaking API changes, users #{Blufin::Terminal::format_invalid('CANNOT')}\x1B[38;5;240m upgrade without making code changes."
|
17
|
+
options = [
|
18
|
+
{:text => patch, :value => "#{vp[0]}.#{vp[1]}.#{vp[2].to_i + 1}"},
|
19
|
+
{:text => minor, :value => "#{vp[0]}.#{vp[1].to_i + 1}.0"},
|
20
|
+
{:text => major, :value => "#{vp[0].to_i + 1}.0.0"}
|
21
|
+
]
|
22
|
+
title = title.upcase if capitalize_tile
|
23
|
+
Blufin::Terminal::custom(title, nil, "Current version is: #{Blufin::Terminal::format_highlight(current_version)}")
|
24
|
+
Blufin::Terminal::prompt_select('Choose new semantic version:', options)
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
data/lib/version.rb
CHANGED
@@ -1 +1 @@
|
|
1
|
-
BLUFIN_LIB_VERSION = '1.5.
|
1
|
+
BLUFIN_LIB_VERSION = '1.5.2'
|
@@ -0,0 +1,130 @@
|
|
1
|
+
type: map
|
2
|
+
mapping:
|
3
|
+
RunScripts:
|
4
|
+
type: seq
|
5
|
+
sequence:
|
6
|
+
- type: map
|
7
|
+
mapping:
|
8
|
+
Id:
|
9
|
+
required: yes
|
10
|
+
Commands:
|
11
|
+
type: seq
|
12
|
+
required: yes
|
13
|
+
sequence:
|
14
|
+
- type: str
|
15
|
+
TestScripts:
|
16
|
+
type: seq
|
17
|
+
sequence:
|
18
|
+
- type: map
|
19
|
+
mapping:
|
20
|
+
Id:
|
21
|
+
required: yes
|
22
|
+
Commands:
|
23
|
+
type: seq
|
24
|
+
required: yes
|
25
|
+
sequence:
|
26
|
+
- type: str
|
27
|
+
BuildScripts:
|
28
|
+
type: seq
|
29
|
+
sequence:
|
30
|
+
- type: map
|
31
|
+
mapping:
|
32
|
+
Id:
|
33
|
+
required: yes
|
34
|
+
Commands:
|
35
|
+
type: seq
|
36
|
+
required: yes
|
37
|
+
sequence:
|
38
|
+
- type: str
|
39
|
+
DeployScripts:
|
40
|
+
type: seq
|
41
|
+
sequence:
|
42
|
+
- type: map
|
43
|
+
mapping:
|
44
|
+
Id:
|
45
|
+
required: yes
|
46
|
+
Commands:
|
47
|
+
type: seq
|
48
|
+
required: yes
|
49
|
+
sequence:
|
50
|
+
- type: str
|
51
|
+
Projects:
|
52
|
+
type: seq
|
53
|
+
required: yes
|
54
|
+
sequence:
|
55
|
+
- type: map
|
56
|
+
mapping:
|
57
|
+
Id:
|
58
|
+
required: yes
|
59
|
+
Type:
|
60
|
+
required: yes
|
61
|
+
Project:
|
62
|
+
required: yes
|
63
|
+
RepoRoot:
|
64
|
+
required: yes
|
65
|
+
RepoPath:
|
66
|
+
required: yes
|
67
|
+
PortRange:
|
68
|
+
Run:
|
69
|
+
type: seq
|
70
|
+
sequence:
|
71
|
+
- type: map
|
72
|
+
mapping:
|
73
|
+
Path:
|
74
|
+
required: yes
|
75
|
+
Script:
|
76
|
+
required: yes
|
77
|
+
Test:
|
78
|
+
type: seq
|
79
|
+
sequence:
|
80
|
+
- type: map
|
81
|
+
mapping:
|
82
|
+
Path:
|
83
|
+
required: yes
|
84
|
+
Script:
|
85
|
+
required: yes
|
86
|
+
Build:
|
87
|
+
type: seq
|
88
|
+
sequence:
|
89
|
+
- type: map
|
90
|
+
mapping:
|
91
|
+
Path:
|
92
|
+
required: yes
|
93
|
+
Script:
|
94
|
+
required: yes
|
95
|
+
Deploy:
|
96
|
+
type: seq
|
97
|
+
sequence:
|
98
|
+
- type: map
|
99
|
+
mapping:
|
100
|
+
Path:
|
101
|
+
required: yes
|
102
|
+
Script:
|
103
|
+
required: yes
|
104
|
+
Servers:
|
105
|
+
type: seq
|
106
|
+
sequence:
|
107
|
+
- type: map
|
108
|
+
mapping:
|
109
|
+
Region:
|
110
|
+
required: yes
|
111
|
+
Stack:
|
112
|
+
required: yes
|
113
|
+
Environments:
|
114
|
+
type: seq
|
115
|
+
required: yes
|
116
|
+
sequence:
|
117
|
+
- type: str
|
118
|
+
API:
|
119
|
+
type: map
|
120
|
+
mapping:
|
121
|
+
Title:
|
122
|
+
required: yes
|
123
|
+
Alias:
|
124
|
+
required: yes
|
125
|
+
Domain:
|
126
|
+
required: yes
|
127
|
+
ProjectName:
|
128
|
+
required: yes
|
129
|
+
ProjectNamePascalCase:
|
130
|
+
required: yes
|
metadata
CHANGED
@@ -1,15 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: blufin-lib
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.5.
|
4
|
+
version: 1.5.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Albert Rannetsperger
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-
|
11
|
+
date: 2019-08-08 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: kwalify
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - '='
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 0.7.2
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - '='
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 0.7.2
|
13
27
|
- !ruby/object:Gem::Dependency
|
14
28
|
name: json
|
15
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -74,20 +88,24 @@ extra_rdoc_files: []
|
|
74
88
|
files:
|
75
89
|
- lib/blufin-lib.rb
|
76
90
|
- lib/core/arrays.rb
|
91
|
+
- lib/core/base.rb
|
77
92
|
- lib/core/browser.rb
|
93
|
+
- lib/core/config.rb
|
78
94
|
- lib/core/datetime_utils.rb
|
79
95
|
- lib/core/encryptor.rb
|
80
96
|
- lib/core/files.rb
|
81
97
|
- lib/core/image.rb
|
82
98
|
- lib/core/network.rb
|
83
99
|
- lib/core/numbers.rb
|
100
|
+
- lib/core/projects.rb
|
84
101
|
- lib/core/routes.rb
|
85
|
-
- lib/core/ssh.rb
|
86
102
|
- lib/core/strings.rb
|
87
103
|
- lib/core/terminal.rb
|
88
104
|
- lib/core/tools.rb
|
89
105
|
- lib/core/validate.rb
|
106
|
+
- lib/core/versioning.rb
|
90
107
|
- lib/version.rb
|
108
|
+
- opt/awx/project-schema.yml
|
91
109
|
homepage: https://github.com/alb3rtuk
|
92
110
|
licenses:
|
93
111
|
- MIT
|
data/lib/core/ssh.rb
DELETED
@@ -1,145 +0,0 @@
|
|
1
|
-
module Blufin
|
2
|
-
|
3
|
-
class SSH
|
4
|
-
|
5
|
-
SSH_PREFIX = 'ssh_'
|
6
|
-
|
7
|
-
# SSH into remote host.
|
8
|
-
# @return void
|
9
|
-
def self.ssh_into_remote(config_key)
|
10
|
-
if config_key.nil?
|
11
|
-
show_available_ssh_hosts
|
12
|
-
else
|
13
|
-
ssh_user, ssh_host, ssh_cert = get_ssh_credentials(config_key)
|
14
|
-
ssh_into_remote_credentials(ssh_user, ssh_host, ssh_cert)
|
15
|
-
end
|
16
|
-
end
|
17
|
-
|
18
|
-
# SSH into remote with pre-supplied credentials.
|
19
|
-
# @return void
|
20
|
-
def self.ssh_into_remote_credentials(ssh_user, ssh_host, ssh_cert = nil)
|
21
|
-
if ssh_cert.nil?
|
22
|
-
Blufin::Terminal::command("ssh #{ssh_user}@#{ssh_host}", nil, false, false)
|
23
|
-
else
|
24
|
-
Blufin::Terminal::command("ssh -i #{ssh_cert} #{ssh_user}@#{ssh_host}", nil, false, false)
|
25
|
-
end
|
26
|
-
end
|
27
|
-
|
28
|
-
# Source can be a file or directory but whatever it is, target must be the same (a file or directory).
|
29
|
-
# @return void
|
30
|
-
def self.scp_to_remote(config_key, source, target)
|
31
|
-
if config_key.nil?
|
32
|
-
show_available_ssh_hosts
|
33
|
-
else
|
34
|
-
ssh_user, ssh_host, ssh_cert = get_ssh_credentials(config_key)
|
35
|
-
unless Blufin::Files::file_exists(source)
|
36
|
-
Blufin::Terminal::error("The file doesn't exist: #{Blufin::Terminal::format_directory(source)}", nil, true)
|
37
|
-
return
|
38
|
-
end
|
39
|
-
Blufin::Terminal::command("scp -i #{ssh_cert} #{source} #{ssh_user}@#{ssh_host}:#{target}", nil)
|
40
|
-
end
|
41
|
-
end
|
42
|
-
|
43
|
-
private
|
44
|
-
|
45
|
-
# Get SSH credentials from the config file.
|
46
|
-
# @return Array
|
47
|
-
def self.get_ssh_credentials(config_key)
|
48
|
-
ssh_parts = Blufin::Config.get_custom_key(SSH_PREFIX, config_key).split('|')
|
49
|
-
ssh_user = ssh_parts[0]
|
50
|
-
ssh_host = ssh_parts[1]
|
51
|
-
ssh_cert = ssh_parts[2].to_s.strip.length == 0 ? nil : ssh_parts[2]
|
52
|
-
help_message = [
|
53
|
-
"The file: #{Blufin::Terminal::format_directory(ConfigUnique::CONFIG_FILE)} must contain a line similar to the following:",
|
54
|
-
nil,
|
55
|
-
'ssh_ec2=ec2-user|ec2-XX-XX-XX-XX.eu-west-1.compute.amazonaws.com|~/.ssh/pem-key.pem',
|
56
|
-
'ssh_ec2=ec2-user|ec2-XX-XX-XX-XX.eu-west-1.compute.amazonaws.com',
|
57
|
-
]
|
58
|
-
Blufin::Terminal::error("SSH #{Blufin::Terminal::format_highlight('user')} required", help_message, true) if ssh_user.nil? || ssh_user == ''
|
59
|
-
Blufin::Terminal::error("SSH #{Blufin::Terminal::format_highlight('host')} required", help_message, true) if ssh_host.nil? || ssh_host == ''
|
60
|
-
unless ssh_cert.nil?
|
61
|
-
unless Blufin::Files::file_exists(File.expand_path(ssh_cert))
|
62
|
-
Blufin::Terminal::error("PEM key not found: #{Blufin::Terminal::format_directory(ssh_cert)}")
|
63
|
-
end
|
64
|
-
end
|
65
|
-
return ssh_user, ssh_host, ssh_cert
|
66
|
-
end
|
67
|
-
|
68
|
-
# Check that SSHPASS is installed.
|
69
|
-
# @return void
|
70
|
-
def self.check_sshpass_is_installed
|
71
|
-
Blufin::UtilsTools::check_sshpass_is_installed
|
72
|
-
end
|
73
|
-
|
74
|
-
# Runs a series of commands on the VM.
|
75
|
-
# @return void
|
76
|
-
def self.command(commands, ssh_user, ssh_host, ssh_cert, verbose = true)
|
77
|
-
commands = [commands] unless commands.is_a?(Array)
|
78
|
-
if commands.any?
|
79
|
-
commands.each do |command|
|
80
|
-
puts "\x1B[48;5;136m Executing \x1B[0m \xe2\x86\x92\x1B[0m #{Blufin::Terminal::format_command("#{command} \x1B[0m\xe2\x86\x92 \x1B[38;5;202m[\x1B[38;5;168m#{ssh_user}@#{ssh_host}\x1B[38;5;202m]\x1B[0m")}" if verbose
|
81
|
-
if ssh_cert.nil?
|
82
|
-
Blufin::Terminal::command("ssh #{ssh_user}@#{ssh_host} '#{command}'", nil, false, false)
|
83
|
-
else
|
84
|
-
Blufin::Terminal::command("ssh -i #{ssh_cert} #{ssh_user}@#{ssh_host} '#{command}'", nil, false, false)
|
85
|
-
end
|
86
|
-
end
|
87
|
-
end
|
88
|
-
end
|
89
|
-
|
90
|
-
# Runs a series of commands on the VM and capture output.
|
91
|
-
# Currently only works for single-line output (IE: Won't capture multi-line output properly).
|
92
|
-
# @return void
|
93
|
-
def self.command_capture(commands, ssh_user, ssh_host, ssh_cert, verbose = true)
|
94
|
-
commands = [commands] unless commands.is_a?(Array)
|
95
|
-
results = []
|
96
|
-
if commands.any?
|
97
|
-
commands.each do |command|
|
98
|
-
puts "\x1B[48;5;136m Executing \x1B[0m \xe2\x86\x92\x1B[0m #{Blufin::Terminal::format_command("#{command} \x1B[0m\xe2\x86\x92 \x1B[38;5;202m[\x1B[38;5;168m#{ssh_user}@#{ssh_host}\x1B[38;5;202m]\x1B[0m")}" if verbose
|
99
|
-
if ssh_cert.nil?
|
100
|
-
results << Blufin::Terminal::command_capture("ssh #{ssh_user}@#{ssh_host} '#{command}'", nil, false, false)[0].gsub(/\n$/, '')
|
101
|
-
else
|
102
|
-
results << Blufin::Terminal::command_capture("ssh -i #{ssh_cert} #{ssh_user}@#{ssh_host} '#{command}'", nil, false, false)[0].gsub(/\n$/, '')
|
103
|
-
end
|
104
|
-
end
|
105
|
-
end
|
106
|
-
results
|
107
|
-
end
|
108
|
-
|
109
|
-
# Show a list of available SSH hosts.
|
110
|
-
# @return void
|
111
|
-
def self.show_available_ssh_hosts
|
112
|
-
all_keys = Blufin::Config::params
|
113
|
-
ssh_hosts = []
|
114
|
-
ssh_hosts_lengths = []
|
115
|
-
all_keys.keys.each do |key|
|
116
|
-
if key =~ /\A#{SSH_PREFIX}.+/i
|
117
|
-
ssh_hosts_lengths << key.length
|
118
|
-
end
|
119
|
-
end
|
120
|
-
if ssh_hosts_lengths.any?
|
121
|
-
ssh_hosts_lengths.uniq!
|
122
|
-
max_key_length = ssh_hosts_lengths.max - SSH_PREFIX.length
|
123
|
-
all_keys.keys.sort!.each do |key|
|
124
|
-
if key =~ /\A#{SSH_PREFIX}.+/i
|
125
|
-
ssh_user, ssh_host, ssh_cert = get_ssh_credentials(key.gsub(SSH_PREFIX, ''))
|
126
|
-
ssh_hosts << "\x1B[38;5;154m#{key.gsub(SSH_PREFIX, '').rjust(max_key_length, ' ')}\x1B[0m\x1B[38;5;240m \xe2\x80\x94 #{ssh_host} \x1B[38;5;245m(#{ssh_user})#{ssh_cert.nil? ? '' : "\x1B[38;5;204m #{ssh_cert}"}"
|
127
|
-
end
|
128
|
-
end
|
129
|
-
Blufin::Terminal::info('Pre-configured hosts to SSH into:', ssh_hosts)
|
130
|
-
else
|
131
|
-
help_message = [
|
132
|
-
"To define a remote host \xe2\x80\x94 run #{Blufin::Terminal::format_command("#{ConfigUnique::GEM_NAME} x")} and add a line similar to:",
|
133
|
-
nil,
|
134
|
-
"\x1B[38;5;154mssh_ec2=ec2-user|ec2-XX-XX-XX-XX.eu-west-1.compute.amazonaws.com|~/.ssh/pem-key.pem",
|
135
|
-
"\x1B[38;5;154mssh_ec2=ec2-user|ec2-XX-XX-XX-XX.eu-west-1.compute.amazonaws.com",
|
136
|
-
nil,
|
137
|
-
"IMPORTANT: Configuration key must start with prefix: #{Blufin::Terminal::format_highlight(SSH_PREFIX)}"
|
138
|
-
]
|
139
|
-
Blufin::Terminal::info('No remote host(s) defined in configuration file', help_message)
|
140
|
-
end
|
141
|
-
end
|
142
|
-
|
143
|
-
end
|
144
|
-
|
145
|
-
end
|