blufin-lib 1.7.6 → 1.8.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 8895eedbca233a947357cfc7ee7ca2fcea304563
4
- data.tar.gz: 95f7c83226ab47eadea54c08af12a892287b9acc
3
+ metadata.gz: 951694bcd607959a657e046002654767755b74d6
4
+ data.tar.gz: def507a9855268d197362bad03e915720ef2c26f
5
5
  SHA512:
6
- metadata.gz: 49103398cdf8a6469fa599302dcc0c00568ca6014a6f7a7a15c13b9cd8d7768a149ae84626d6399ba51a596294d3077f75ed358cc3476c86059570ac135de255
7
- data.tar.gz: 302868314496ef6b238cae5da06b0516d5d3189629b8ce34071261597b05716f0b06374a3f57b39a4d252a78412d810c40e8ed9c8b4bb8aca809d78e129217ac
6
+ metadata.gz: 0bedc142caf1e81f3aa0dcff437217451f8db5560f6d4ef4f58e3fa6540148ad913e781f28873a20a647e21877c21b0d0397a5877576a1f489d554a82ba6ca54
7
+ data.tar.gz: 6326f21a88d05ce9abf5296e14d5ccd1cf55a9cd57efd9a43628afd82b1afe095fb4ce9562f2f14618ca3562ff4df1f8989036b178fc9d1a2d95a746494cdf6e
@@ -6,6 +6,9 @@ module Blufin
6
6
  autoload :Config, 'core/config'
7
7
  autoload :DateTimeUtils, 'core/datetime_utils'
8
8
  autoload :Files, 'core/files'
9
+ autoload :GenerateBase, 'generate/generate_base'
10
+ autoload :GenerateUiRoutes, 'generate/generate_ui_routes'
11
+ autoload :Git, 'core/git'
9
12
  autoload :Image, 'core/image'
10
13
  autoload :Network, 'core/network'
11
14
  autoload :Numbers, 'core/numbers'
@@ -14,7 +17,9 @@ module Blufin
14
17
  autoload :Strings, 'core/strings'
15
18
  autoload :Terminal, 'core/terminal'
16
19
  autoload :Tools, 'core/tools'
20
+ autoload :Update, 'core/update'
17
21
  autoload :Validate, 'core/validate'
18
22
  autoload :Versioning, 'core/versioning'
23
+ autoload :Yml, 'core/yml'
19
24
 
20
25
  end
@@ -13,6 +13,27 @@ module Blufin
13
13
  !(array.uniq.length == array.length)
14
14
  end
15
15
 
16
+ # Converts an Array of lines to a string (for easier gsub replacement).
17
+ # @return String
18
+ def self.convert_line_array_to_string(array_of_lines)
19
+ raise RuntimeError, "Expected Array of lines, instead got: #{array_of_lines.class}" unless array_of_lines.is_a?(Array)
20
+ string = ''
21
+ array_of_lines.each_with_index do |line, idx|
22
+ newline_or_not = (idx == (array_of_lines.length - 1)) ? '' : "\n"
23
+ string += "#{line}#{newline_or_not}"
24
+ end
25
+ string
26
+ end
27
+
28
+ # Converts a string to an Array of lines to a string.
29
+ # @return String
30
+ def self.convert_string_to_line_array(string)
31
+ raise RuntimeError, "Expected String, instead got: #{string.class}" unless string.is_a?(String)
32
+ array_of_lines = []
33
+ string.split("\n").each { |line| array_of_lines << line.gsub("\n", '') }
34
+ array_of_lines
35
+ end
36
+
16
37
  end
17
38
 
18
39
  end
@@ -4,6 +4,15 @@ module Blufin
4
4
 
5
5
  S3_EXCLUDE = "--exclude '.DS_Store' --exclude '*/.DS_Store' --exclude '*.icloud' --exclude '*/*.icloud'"
6
6
 
7
+ # Checks if script is running on an EC2 instance.
8
+ # Not sure if this is 100% reliable, but it's about as good as it gets I think.
9
+ # @return bool
10
+ def self.is_ec2
11
+ res = `#{Blufin::Base::get_base_path}#{Blufin::Base::OPT_PATH}/shell/ec2-check`.to_s
12
+ res = Blufin::Strings::strip_newline(res)
13
+ res.downcase.strip == 'yes'
14
+ end
15
+
7
16
  # Uploads a file (or path) to S3. If path, will upload recursively (which is mandatory with s3 sync command).
8
17
  # @return string (S3 URL).
9
18
  def self.upload_s3_data(bucket_name, bucket_path, file_or_path, profile: nil, region: nil, is_file: false, dryrun: false)
@@ -25,7 +34,7 @@ module Blufin
25
34
  Blufin::Terminal::info('Performing Dry Run:', Blufin::Terminal::format_command(cmd))
26
35
  system("#{cmd} #{S3_EXCLUDE} --exclude '*.blank*'")
27
36
  else
28
- raise RuntimeError unless Blufin::Terminal::execute("#{cmd} #{S3_EXCLUDE} --exclude '*.blank*'", verbose: true, text: cmd)
37
+ raise RuntimeError unless Blufin::Terminal::execute("#{cmd} #{S3_EXCLUDE} --exclude '*.blank*'", verbose: true, text: cmd)[0]
29
38
  end
30
39
  s3_url
31
40
  rescue
@@ -49,7 +58,7 @@ module Blufin
49
58
  tmp_location = "/tmp/awx-s3-cache-#{bucket_name}#{bucket_path_tmp}"
50
59
  # If path/file exists and we're using cached values, simply return.
51
60
  if file.nil?
52
- return tmp_location if Blufin::Files::path_exists(tmp_location) && use_cache
61
+ return tmp_location if Blufin::Files::path_exists(tmp_location) && use_cache && Blufin::Files::get_files_in_dir(tmp_location).any?
53
62
  else
54
63
  return tmp_location if Blufin::Files::file_exists(tmp_location) && use_cache
55
64
  end
@@ -60,7 +69,7 @@ module Blufin
60
69
  system("rm #{tmp_location}") unless file.nil?
61
70
  Blufin::Files::create_directory(tmp_location)
62
71
  }, verbose: true)
63
- raise RuntimeError unless Blufin::Terminal::execute("aws s3 cp s3://#{bucket_name}#{bucket_path_s3} #{tmp_location} --recursive --region #{region}#{App::AWS::get_profile_for_cli}", verbose: true)
72
+ raise RuntimeError unless Blufin::Terminal::execute("aws s3 cp s3://#{bucket_name}#{bucket_path_s3} #{tmp_location} --recursive --region #{region}#{App::AWS::get_profile_for_cli}", verbose: true)[0]
64
73
  tmp_location
65
74
  rescue
66
75
  system("rm -rf #{tmp_location}") if file.nil?
@@ -56,7 +56,7 @@ module Blufin
56
56
  begin
57
57
  schema_file_parsed = YAML.load_file(schema_file)
58
58
  rescue => e
59
- Blufin::Terminal::error("Failed to parse config file: #{Blufin::Terminal::format_directory(config_file)}", e.message)
59
+ Blufin::Terminal::error("Failed to schema config file: #{Blufin::Terminal::format_directory(schema_file)}", e.message)
60
60
  end
61
61
  validator = Kwalify::Validator.new(schema_file_parsed)
62
62
  begin
@@ -0,0 +1,274 @@
1
+ module Blufin
2
+
3
+ class Git
4
+
5
+ HISTORY_TYPES = %w(branch tag commit)
6
+ DETACHED_HEAD_REGEX = /\(HEAD\s*detached\s*at\s*/
7
+
8
+ @@branch_current_cache = {}
9
+ @@btc_exists_cache = {}
10
+ @@commit_cache = {}
11
+ @@uncommitted_files_cache = {}
12
+
13
+ # Tag a branch:
14
+ # $ git tag -a "v0.1.0-beta" -m "Version: v0.1.0-beta"
15
+
16
+ # Show all commits between 2 tags...
17
+ # $ git log v0.1.0-beta..v0.1.1-beta | grep "^commit [a-z0-9]\{40\}$" | awk '{print $2}'
18
+
19
+ # Get commit message for commit hash... (must .strip string)
20
+ # $ git show d29f2870c59ffab4abb565c3e3c06430cd62515c | grep "^commit [a-z0-9]\{40\}$" -A4 | tail -n 1
21
+
22
+ # Easy, clean way to squash all commits on a branch into 1.
23
+ # $ git reset --soft dev/master
24
+ # $ git add .
25
+ # $ git commit -m 'All changes from my branch squashed'
26
+
27
+ # Merge that single commit into dev (WITHOUT A MERGE COMMIT!).
28
+ # [feature] $ git rebase dev
29
+ # [feature] $ git checkout dev
30
+ # [dev] $ git merge --ff-only feature
31
+
32
+ # Checks out a project and returns the path to it.
33
+ # branch_tag_or_commit can be a branch, commit or tag.
34
+ # Returns current branch/tag/commit (if local) -- in case you want to revert after.
35
+ # @return string |
36
+ def self.checkout(project_id, branch: nil, tag: nil, commit: nil, is_ec2: false)
37
+ begin
38
+ project = Blufin::Projects::get_project_by_id(project_id, true)
39
+ repo_path = Blufin::Projects::get_project_path(project_id, is_ec2: is_ec2)
40
+ repo_data = project[Blufin::Projects::REPOSITORY]
41
+ type, btc = resolve_type_btc(branch, tag, commit)
42
+ errors = nil
43
+ current_head = nil
44
+ if repo_data.has_key?(Blufin::Projects::LOCAL) && !is_ec2
45
+ # Make sure branch/tag/commit exists.
46
+ raise "#{Blufin::Terminal::format_highlight(type.capitalize)} not found: #{Blufin::Terminal::format_invalid(btc)}" unless Blufin::Git::exists(repo_path, btc, type, false)
47
+ # Get current branch (need to reset to this locally after script has run).
48
+ current_head = Blufin::Git::get_current_branch(repo_path)
49
+ else
50
+ # If path already exists, do some checks...
51
+ if Blufin::Files::path_exists(repo_path)
52
+ res = Blufin::Terminal::execute("git remote -v | tail -n 1 | awk '{print $2}'", repo_path, capture: true, verbose: false, display_error: false)
53
+ wipe = false
54
+ res = res[0]
55
+ if res.nil? || Blufin::Strings::strip_newline(res) == ''
56
+ wipe = true
57
+ else
58
+ repo_expected = Blufin::Projects::get_project_repo_name(project_id)
59
+ repo_actual = extract_repo_name(res)
60
+ wipe = true if repo_expected != repo_actual
61
+ end
62
+ # Wipe /tmp folder ONLY if something weird is going on... I put the /tmp regex just in case :)
63
+ `rm -rf #{repo_path}/` if wipe && repo_path =~ /^\/tmp\/[A-Za-z0-9\.]+/
64
+ end
65
+ # Checkout repo (if not exists).
66
+ unless Blufin::Files::path_exists(repo_path)
67
+ clone_cmd = "git clone #{project[Blufin::Projects::REPOSITORY][Blufin::Projects::REMOTE]} #{repo_path}"
68
+ unless Blufin::Terminal::execute_proc(clone_cmd, Proc.new {
69
+ res = Blufin::Terminal::execute("#{clone_cmd} &>/dev/null", '/tmp', capture: true, verbose: false, display_error: false)
70
+ errors = res[1].split("\n")
71
+ res[1] =~ /^Cloning\s*into\s*('|").+/
72
+ })
73
+ raise RuntimeError, "Failed to checkout #{type}: #{Blufin::Terminal::format_invalid(btc)}"
74
+ end
75
+ end
76
+ # At this point we should have the repo checked out. Throw an error if we don't.
77
+ raise RuntimeError, "Path not found: #{repo_path}" unless Blufin::Files::path_exists(repo_path)
78
+ end
79
+ # Checkout branch/tag/commit.
80
+ unless Blufin::Terminal::execute_proc("git checkout #{btc}", Proc.new {
81
+ res = Blufin::Terminal::execute("git checkout #{btc} &>/dev/null", repo_path, capture: true, verbose: false, display_error: false)
82
+ errors = res[1].split("\n")
83
+ last_line = errors[errors.length - 1].strip
84
+ last_line =~ /^HEAD\s*is\s*now\s*at\s*.+/i || last_line =~ /^Already\s*on\s*('|")#{btc}('|")$/ || last_line =~ /^Switched\s*to(\s*a\s*new)?\s*branch\s*('|")#{btc}('|")$/
85
+ })
86
+ raise RuntimeError, "Failed to checkout #{type}: #{Blufin::Terminal::format_invalid(btc)}"
87
+ end
88
+ current_head
89
+ rescue => e
90
+ if is_ec2
91
+ err_output = nil
92
+ err_output = " - #{errors.is_a?(Array) ? errors.join(', ') : errors.to_s}" unless errors.nil? || errors.strip == ''
93
+ raise RuntimeError, Blufin::Strings::strip_ansi_colors("#{e.message}#{err_output}")
94
+ else
95
+ Blufin::Terminal::error(e.message, errors)
96
+ end
97
+ end
98
+ end
99
+
100
+ # Gets current branch for a repository.
101
+ # @return String -> (IE: "master")
102
+ def self.get_current_branch(path = nil, verbose: false)
103
+ raise RuntimeError, "Path not found: #{path}" unless path.nil? || Blufin::Files::path_exists(path)
104
+ path = Blufin::Strings::strip_newline(`pwd`) if path.nil?
105
+ key = File.expand_path(path)
106
+ return @@branch_current_cache[key] if @@branch_current_cache.has_key?(key)
107
+ res = run("git branch | grep \\*", path, text: 'Getting Branch', verbose: verbose)
108
+ branch = Blufin::Strings::strip_newline(res).gsub(/^\*\s?/, '')
109
+ branch = branch.strip.gsub(DETACHED_HEAD_REGEX, '').gsub(/\)$/, '') if branch =~ DETACHED_HEAD_REGEX
110
+ @@branch_current_cache[key] = branch
111
+ @@branch_current_cache[key]
112
+ end
113
+
114
+ # Gets latest commit hash for a repository.
115
+ # @return String -> (IE: "5b7559e5952eacb5251a9baf81dd964fe1ef57f5")
116
+ def self.get_latest_commit_hash(path, verbose: false)
117
+ raise RuntimeError, "Path not found: #{path}" unless Blufin::Files::path_exists(path)
118
+ key = File.expand_path(path)
119
+ return @@commit_cache[key] if @@commit_cache.has_key?(key)
120
+ res = run('git rev-parse HEAD', path, text: 'Getting HEAD Commit', verbose: verbose)
121
+ @@commit_cache[key] = Blufin::Strings::strip_newline(res)
122
+ @@commit_cache[key]
123
+ end
124
+
125
+ # Gets a list of uncommitted files for a repository.
126
+ # Returns an empty Array if branch is clean.
127
+ # @return Array -> (IE: ["file-1.txt","file-2.txt"] or [])
128
+ def self.get_uncommitted_files(path, run_git_add: true, verbose: false, formatted: false, spacer: nil)
129
+ raise RuntimeError, "Path not found: #{path}" unless Blufin::Files::path_exists(path)
130
+ raise RuntimeError, "Expected String, instead got: #{spacer.class}" unless spacer.is_a?(String) || spacer.nil?
131
+ raise RuntimeError, 'Cannot pass spacer if formatted: is false.' if !formatted && !spacer.nil?
132
+ key = "#{File.expand_path(path)}-#{formatted}-#{spacer}"
133
+ return @@uncommitted_files_cache[key] if @@uncommitted_files_cache.has_key?(key)
134
+ renamed = []
135
+ modified = []
136
+ deleted = []
137
+ moved = []
138
+ new = []
139
+ run('git add .', path, verbose: verbose) if run_git_add
140
+ git_status = run('git status', path, verbose: verbose)
141
+ git_status = git_status.split("\n")
142
+ git_status.each do |line|
143
+ renamed << line.split('renamed:')[1].strip if line =~ /renamed:/i
144
+ modified << line.split('modified:')[1].strip if line =~ /modified:/i
145
+ deleted << line.split('deleted:')[1].strip if line =~ /deleted:/i
146
+ moved << line.split('moved:')[1].strip if line =~ /moved:/i
147
+ new << line.split('new file:')[1].strip if line =~ /new file:/i
148
+ end
149
+ if formatted
150
+ files = []
151
+ spacer = '' if spacer.nil?
152
+ new.each { |file| files << "#{spacer}\x1B[38;5;246m#{'New: '.rjust(10, ' ')}\x1B[38;5;48m#{file}\x1B[0m" } if new.any?
153
+ modified.each { |file| files << "#{spacer}\x1B[38;5;246m#{'Modified: '.rjust(10, ' ')}\x1B[38;5;34m#{file}\x1B[0m" } if modified.any?
154
+ renamed.each { |file| files << "#{spacer}\x1B[38;5;246m#{'Renamed: '.rjust(10, ' ')}\x1B[38;5;34m#{file}\x1B[0m" } if renamed.any?
155
+ deleted.each { |file| files << "#{spacer}\x1B[38;5;246m#{'Deleted: '.rjust(10, ' ')}\x1B[38;5;124m#{file}\x1B[0m" } if deleted.any?
156
+ moved.each { |file| files << "#{spacer}\x1B[38;5;246m#{'Moved: '.rjust(10, ' ')}\x1B[38;5;238m#{file}\x1B[0m" } if moved.any?
157
+ else
158
+ files = renamed + modified + deleted + moved + new
159
+ files.sort!
160
+ end
161
+ files.uniq!
162
+ @@uncommitted_files_cache[key] = files
163
+ @@uncommitted_files_cache[key]
164
+ end
165
+
166
+ # Checks if current branch exists.
167
+ # @return bool
168
+ def self.branch_exists(path, branch, run_git_fetch: false)
169
+ exists(path, branch, 'branch', run_git_fetch)
170
+ end
171
+
172
+ # Checks if tag exists.
173
+ # @return bool
174
+ def self.tag_exists(path, tag, run_git_fetch: false)
175
+ exists(path, tag, 'tag', run_git_fetch)
176
+ end
177
+
178
+ # Checks if commit exists.
179
+ # @return bool
180
+ def self.commit_exists(path, commit, run_git_fetch: false)
181
+ exists(path, commit, 'commit', run_git_fetch)
182
+ end
183
+
184
+ # Runs $ git add .
185
+ # @return void
186
+ def self.add(path, add = '.', verbose: true)
187
+ raise RuntimeError, "Path not found: #{path}" unless path.nil? || Blufin::Files::path_exists(path)
188
+ path = Blufin::Strings::strip_newline(`pwd`) if path.nil?
189
+ run("git add #{add}", path, verbose: verbose)
190
+ end
191
+
192
+ # Attempts to convert something like git@github.com:alb3rtuk/blufin-archetypes.git into -> blufin-archetypes.
193
+ # @return string
194
+ def self.extract_repo_name(string)
195
+ raise RuntimeError, "Expected String, instead got: #{string.class}" unless string.is_a?(String)
196
+ repo_name = Blufin::Strings::strip_newline(string).split('/')
197
+ repo_name[repo_name.length - 1].gsub(/\.git$/i, '')
198
+ end
199
+
200
+ private
201
+
202
+ # Convenience method to run commands. Throws error if anything fails.
203
+ # Returns only the output, not the error. Does not strip new lines! This must be done 1 level up.
204
+ # @return string
205
+ def self.run(cmd, path, text: nil, verbose: true)
206
+ res = Blufin::Terminal::execute(cmd, path, capture: true, text: text, verbose: verbose)
207
+ Blufin::Terminal::error("Something went wrong: #{Blufin::Terminal::format_invalid(res[1])}") unless res[1].nil?
208
+ res[0]
209
+ end
210
+
211
+ # Common code for 'exists' method(s).
212
+ # @return bool
213
+ def self.exists(path, btc, type, run_git_fetch)
214
+ raise RuntimeError, "Path not found: #{path}" unless path.nil? || Blufin::Files::path_exists(path)
215
+ raise RuntimeError, "Invalid type: #{type}" unless HISTORY_TYPES.include?(type)
216
+ key = "#{File.expand_path(path)}|#{btc}|#{type}|#{run_git_fetch}"
217
+ return @@btc_exists_cache[key] if @@btc_exists_cache.has_key?(key)
218
+ exists = false
219
+ Blufin::Terminal::execute_proc("Checking #{type} exists: #{Blufin::Terminal::format_highlight(btc)} \x1B[38;5;246m\xe2\x86\x92 \x1B[38;5;240m#{File.expand_path(path)}\x1B[0m", Proc.new {
220
+ run('git fetch -p', path, verbose: false) if run_git_fetch
221
+ cmd = %w(branch tag).include?(type) ? "git #{type} | grep #{btc}" : "git log | grep \"commit #{btc}\""
222
+ res = Blufin::Terminal::execute(cmd, path, capture: true, verbose: false)
223
+ res.each do |line|
224
+ next if line.nil? || line.strip == ''
225
+ case type
226
+ when 'branch'
227
+ line = line.gsub(/^\*\s?/, '')
228
+ when 'commit'
229
+ line = line.gsub(/^commit\s?/, '')
230
+ when 'tag'
231
+ else
232
+ raise RuntimeError, "Unrecognized type: #{type}"
233
+ end
234
+ line = Blufin::Strings::strip_newline(line)
235
+ if line =~ /^#{btc}$/
236
+ exists = true
237
+ break
238
+ end
239
+ end
240
+ exists
241
+ })
242
+ @@btc_exists_cache[key] = exists
243
+ @@btc_exists_cache[key]
244
+ end
245
+
246
+ # Convenience method to resolve type and branch/tag/commit name.
247
+ # @return Array
248
+ def self.resolve_type_btc(branch, tag, commit)
249
+ set = 0
250
+ set =+1 unless branch.nil?
251
+ set =+1 unless tag.nil?
252
+ set =+1 unless commit.nil?
253
+ raise RuntimeError, "Must set atleast one of: #{HISTORY_TYPES.join(', ')}" if set == 0
254
+ raise RuntimeError, "Can only set one of: #{HISTORY_TYPES.join(', ')}" if set > 1
255
+ type = 'branch' unless branch.nil?
256
+ type = 'tag' unless tag.nil?
257
+ type = 'commit' unless commit.nil?
258
+ case type
259
+ when 'branch'
260
+ btc = branch
261
+ when 'tag'
262
+ btc = tag
263
+ when 'commit'
264
+ btc = commit
265
+ else
266
+ raise RuntimeError, "Unrecognized type: #{type}"
267
+ end
268
+ raise RuntimeError, "#{type.capitalize} cannot be nil." if btc.nil? || btc.strip == ''
269
+ [type, btc]
270
+ end
271
+
272
+ end
273
+
274
+ end
@@ -16,7 +16,10 @@ module Blufin
16
16
  PROJECT_ROOT = 'ProjectRoot'
17
17
  TYPE = 'Type'
18
18
  REPOSITORY = 'Repository'
19
+ UPSTREAM = 'Upstream'
20
+ DOWNSTREAM = 'Downstream'
19
21
  LOCAL = 'Local'
22
+ REMOTE = 'Remote'
20
23
  FILE = 'File'
21
24
  DEPLOYMENT = 'Deployment'
22
25
  DEPLOYMENT_BUCKET = 'Bucket'
@@ -29,6 +32,8 @@ module Blufin
29
32
  CRON = 'Cron'
30
33
  WORKER = 'Worker'
31
34
  LAMBDA = 'Lambda'
35
+ UI = 'UI'
36
+ ROUTES_FILE = 'RoutesFile'
32
37
  TITLE = 'Title'
33
38
  ALIAS = 'Alias'
34
39
  DOMAIN = 'Domain'
@@ -40,25 +45,40 @@ module Blufin
40
45
  TYPE_ALEXA = 'alexa'
41
46
  TYPE_API = 'api'
42
47
  TYPE_LAMBDA = 'lambda'
48
+ TYPE_MVN_LIB = 'mvn-lib'
49
+ TYPE_NPM_LIB = 'npm-lib'
43
50
  TYPE_UI = 'ui'
51
+ TYPE_MOBILE = 'mobile'
44
52
  VALID_TYPES = [
45
53
  TYPE_ALEXA,
46
54
  TYPE_API,
47
55
  TYPE_LAMBDA,
56
+ TYPE_MVN_LIB,
57
+ TYPE_NPM_LIB,
48
58
  TYPE_UI,
59
+ TYPE_MOBILE,
49
60
  ]
50
61
 
51
- @@projects = nil
52
- @@project_names = []
53
- @@scripts = nil
54
- @@apis = nil
55
- @@lambdas = nil
62
+ @@projects = nil
63
+ @@projects_arr = []
64
+ @@projects_cache = {}
65
+ @@project_names = []
66
+ @@project_ids = []
67
+ @@scripts = nil
68
+ @@apis = nil
69
+ @@api_data = {}
70
+ @@lambdas = nil
71
+ @@libs = {}
72
+ @@uis = nil
73
+ @@dependant_projects_cache = {}
74
+ @@dependant_repos_cache = {}
75
+ @@project_path_cache = {}
56
76
 
57
77
  # Takes a Hash that needs to have a 'Projects' key.
58
78
  # This can come from both .awx.yml['Profiles'] or .blufin.yml (root).
59
79
  # @return void
60
- def self.init(projects)
61
- raise RuntimeError, 'Cannot run Blufin::Projects::init() more than once.' unless @@projects.nil? && @@scripts.nil?
80
+ def initialize(projects)
81
+ raise RuntimeError, 'Cannot run Blufin::Projects.new() more than once.' if !@@projects.nil? || !@@scripts.nil?
62
82
  raise RuntimeError, "Need either a Local or S3Bucket key, found neither: #{projects.keys}" unless projects.has_key?(LOCAL) || projects.has_key?('S3Bucket')
63
83
  @@projects = {}
64
84
  @@scripts = {}
@@ -77,20 +97,40 @@ module Blufin
77
97
  raise RuntimeError, 'Reading projects.yml from S3 is not yet implemented!'
78
98
 
79
99
  end
100
+
80
101
  end
81
102
 
82
- # Gets Project(s).
103
+ # Gets Project(s) -- as a nested hash with -> [PROJECT][PROJECT_ID] as the keys.
83
104
  # @return Hash
84
105
  def self.get_projects
85
106
  @@projects
86
107
  end
87
108
 
109
+ # Gets Project(s) -- but in a single array.
110
+ # @return Array
111
+ def self.get_projects_as_array(type: nil)
112
+ projects_arr = []
113
+ if type.nil?
114
+ validate_type(project_type, project_id)
115
+ projects_arr = @@projects_arr
116
+ else
117
+ @@projects_arr.each { |project| projects_arr << project if project[TYPE] == type }
118
+ end
119
+ projects_arr
120
+ end
121
+
88
122
  # Gets Project Name(s).
89
123
  # @return Array
90
124
  def self.get_project_names
91
125
  @@project_names
92
126
  end
93
127
 
128
+ # Gets Project ID(s).
129
+ # @return Array
130
+ def self.get_project_ids
131
+ @@project_ids
132
+ end
133
+
94
134
  # Gets Script(s).
95
135
  # @return Hash
96
136
  def self.get_scripts
@@ -109,25 +149,147 @@ module Blufin
109
149
  @@lambdas
110
150
  end
111
151
 
112
- # Maps root-level property to enum.
152
+ # Attempts to get a project's data by ID, displays error if not exists.
113
153
  # @return string
114
- def self.script_key_mapper(script_type)
115
- if [RUN_SCRIPTS, RUN].include?(script_type)
116
- return SCRIPT_RUN
117
- elsif [TEST_SCRIPTS, TEST].include?(script_type)
118
- return TEST_SCRIPTS
119
- elsif [BUILD_SCRIPTS, BUILD].include?(script_type)
120
- return BUILD_SCRIPTS
154
+ def self.get_project_by_id(project_id, runtime_error = false)
155
+ raise RuntimeError, "Expected String, instead got: #{project_id.class}" unless project_id.is_a?(String)
156
+ return @@projects_cache[project_id] if @@projects_cache.has_key?(project_id)
157
+ project_data = nil
158
+ if @@projects_arr.is_a?(Array)
159
+ @@projects_arr.each do |project|
160
+ raise RuntimeError, 'Missing Project ID.' unless project.has_key?(PROJECT_ID)
161
+ if project[PROJECT_ID].strip.downcase == project_id.strip.downcase
162
+ project_data = project
163
+ break
164
+ end
165
+ end
166
+ end
167
+ raise RuntimeError, "Unrecognized Project ID: #{project_id}" if project_data.nil? && runtime_error
168
+ Blufin::Terminal::error("Unrecognized Project ID: #{Blufin::Terminal::format_invalid(project_id)} . Available Projects IDs are:", get_project_ids) if project_data.nil? && !runtime_error
169
+ @@projects_cache[project_id] = project_data
170
+ @@projects_cache[project_id]
171
+ end
172
+
173
+ # Gets an array of project(s) from current path.
174
+ # @return Array
175
+ def self.get_projects_by_path
176
+ projects = {}
177
+ current_path = Blufin::Strings::strip_newline(`pwd`)
178
+ get_projects_as_array.each do |project|
179
+ if project.has_key?(REPOSITORY)
180
+ if project[REPOSITORY].has_key?(LOCAL)
181
+ project_path = File.expand_path(project[REPOSITORY][LOCAL])
182
+ if current_path =~ /^#{project_path}/
183
+ raise RuntimeError, 'Missing Project ID.' unless project.has_key?(PROJECT_ID)
184
+ projects[project[PROJECT_ID]] = project
185
+ end
186
+ end
187
+ end
188
+ end
189
+ projects
190
+ end
191
+
192
+ # Gets the path to a project. By default, gets the root (IE: doesn't take into account 'Repository.ProjectRoot' key).
193
+ # If you want the full path to the inner project, to_inner_project must be set to TRUE.
194
+ # If local, simply returns path in projects.yml.
195
+ # If not local (IE: on EC2), returns a standardized /tmp path.
196
+ # @return string
197
+ def self.get_project_path(project_id, to_inner_project = false, is_ec2: false, project: nil)
198
+ key = "#{project_id}|#{to_inner_project}|#{is_ec2}"
199
+ return @@project_path_cache[key] if @@project_path_cache.has_key?(key)
200
+ project = project.nil? ? get_project_by_id(project_id, true) : project
201
+ repo_data = project[REPOSITORY]
202
+ inner_path = repo_data.has_key?(PROJECT_ROOT) ? Blufin::Strings::remove_surrounding_slashes(repo_data[PROJECT_ROOT]) : ''
203
+ project_path = nil
204
+ if repo_data.has_key?(LOCAL) && !is_ec2
205
+ root_path = Blufin::Strings::remove_surrounding_slashes(File.expand_path(repo_data[LOCAL]))
206
+ project_path = "/#{root_path}" unless to_inner_project
207
+ project_path = "/#{root_path}#{inner_path.length > 0 ? '/' : ''}#{inner_path}" if to_inner_project
121
208
  else
122
- raise RuntimeError, "Unhandled script type: #{script_type}"
209
+ rs = repo_data[REMOTE].split('/')
210
+ tmp_path = "/tmp/repo-#{rs[rs.length - 1].gsub(/\.git$/i, '')}"
211
+ project_path = tmp_path unless to_inner_project
212
+ project_path = "#{tmp_path}#{inner_path.length > 0 ? '/' : ''}#{inner_path}" if to_inner_project
213
+ end
214
+ raise RuntimeError, "Project Path should never be nil or an empty string: #{key}" if project_path.nil? || project_path.strip == ''
215
+ @@project_path_cache[key] = project_path
216
+ @@project_path_cache[key]
217
+ end
218
+
219
+ # Gets a hash of dependant projects (with the Project IDs as keys).
220
+ # processed_projects Array prevents cyclic-dependency stack overflow.
221
+ # @return Hash
222
+ def self.get_dependant_projects(project_id, streams: [UPSTREAM, DOWNSTREAM], processed_projects: [])
223
+ valid_streams = [UPSTREAM, DOWNSTREAM]
224
+ raise RuntimeError, "Expected Array, instead got: #{streams.class}" unless streams.is_a?(Array)
225
+ streams.each { |s| raise RuntimeError, "Invalid stream: #{s}" unless valid_streams.include?(s) }
226
+ key = "#{project_id}-#{streams.join('-')}"
227
+ return @@dependant_projects_cache[key] if @@dependant_projects_cache.has_key?(key)
228
+ dependant_projects = {}
229
+ project = get_project_by_id(project_id, true)
230
+ streams.each do |stream|
231
+ if project.has_key?(stream)
232
+ project[stream].each do |dependant_project_id|
233
+ unless processed_projects.include?(dependant_project_id)
234
+ processed_projects << dependant_project_id
235
+ dependant_projects[dependant_project_id] = get_project_by_id(dependant_project_id, true) unless dependant_projects.has_key?(dependant_project_id)
236
+ dependant_projects_inner = Blufin::Projects::get_dependant_projects(dependant_project_id, streams: streams, processed_projects: processed_projects)
237
+ # Add nested dependant projects (if any).
238
+ dependant_projects_inner.each { |k, v| dependant_projects[k] = v unless dependant_projects.has_key?(k) } if dependant_projects_inner.any?
239
+ end
240
+ end
241
+ end
123
242
  end
243
+ # Don't include project itself in dependant projects.
244
+ dependant_projects.delete(project_id) if dependant_projects.has_key?(project_id)
245
+ @@dependant_projects_cache[key] = dependant_projects
246
+ @@dependant_projects_cache[key]
247
+ end
248
+
249
+ # Same as above, but only gets the repositories.
250
+ # Sometimes multiple dependant_project IDs may have the same repository.
251
+ # @return Hash
252
+ def self.get_dependant_repos(project_id, streams: [UPSTREAM, DOWNSTREAM])
253
+ key = "#{project_id}-#{streams.join('-')}"
254
+ return @@dependant_repos_cache[key] if @@dependant_repos_cache.has_key?(key)
255
+ dependant_repos = {}
256
+ get_dependant_projects(project_id, streams: streams).each do |k, v|
257
+ next if k == project_id
258
+ repo = v[REPOSITORY][REMOTE]
259
+ dependant_repos[repo] = {
260
+ :projects => []
261
+ } unless dependant_repos.include?(repo)
262
+ dependant_repos[repo][:path] = v[REPOSITORY][LOCAL] if v[REPOSITORY].has_key?(LOCAL)
263
+ dependant_repos[repo][:projects] << k unless dependant_repos[repo][:projects].include?(k)
264
+ end
265
+ @@dependant_repos_cache[key] = dependant_repos
266
+ @@dependant_repos_cache[key]
267
+ end
268
+
269
+ # Gets repo-name from project_id.
270
+ # @return string
271
+ def self.get_project_repo_name(project_id)
272
+ project = get_project_by_id(project_id, true)
273
+ Blufin::Git::extract_repo_name(project[REPOSITORY][REMOTE])
274
+ end
275
+
276
+ # Shows a prompt and returns project Hash once selected.
277
+ # If only one project exists, prompt not displayed.
278
+ # @return Hash
279
+ def self.show_project_prompt(array_of_projects)
280
+ raise RuntimeError, "Expected Array, instead got: #{array_of_projects.class}" unless array_of_projects.is_a?(Array)
281
+ Blufin::Terminal::error('No projects found.') unless array_of_projects.any?
282
+ return array_of_projects[0] if array_of_projects.length == 1
283
+ projects = []
284
+ array_of_projects.each { |project| projects << { :text => project[PROJECT_ID], :value => project } }
285
+ Blufin::Terminal::prompt_select('Select project:', projects)
124
286
  end
125
287
 
126
288
  private
127
289
 
128
290
  # Validate the Project YML.
129
291
  # @return void
130
- def self.process_source_file(source_file)
292
+ def process_source_file(source_file)
131
293
  # Skip empty file.
132
294
  return if Blufin::Files::is_empty(source_file)
133
295
  # Otherwise, validate file.
@@ -170,8 +332,31 @@ module Blufin
170
332
  end
171
333
  end
172
334
 
335
+ # Buffer/validate project(s) stuff.
336
+ file_parsed['Projects'].each do |project|
337
+ project_id = project[PROJECT_ID]
338
+ project_type = project[TYPE]
339
+ if project_type == TYPE_MVN_LIB || TYPE_NPM_LIB
340
+ Blufin::Terminal::error("#{Blufin::Terminal::format_highlight(project_id)} \xe2\x80\x94 Duplicate Library Project.", "A library with this ID (#{Blufin::Terminal::format_invalid(project_id)}) has already been registered.") if @@libs.keys.include?(project_id)
341
+ @@libs[project_id] = project
342
+ elsif project_type == TYPE_API
343
+ [ALIAS, PROJECT_NAME, PROJECT_NAME_PASCAL_CASE, TITLE].each do |x|
344
+ @@api_data[x] = [] unless @@api_data.has_key?(x) && @@api_data[x].is_a?(Array)
345
+ property_value = project[API][x].strip.downcase
346
+ Blufin::Terminal::error("#{Blufin::Terminal::format_highlight(project_id)} \xe2\x80\x94 Duplicate #{x} API Property.", "An API Property with this value (#{Blufin::Terminal::format_invalid(property_value)}) has already been registered.") if @@api_data[x].include?(property_value)
347
+ @@api_data[x] << property_value
348
+ end
349
+ end
350
+ end
351
+
173
352
  used_ports = {}
174
353
 
354
+ # Run through once quickly to populate critical objects (required for validation).
355
+ file_parsed['Projects'].each do |project|
356
+ @@project_names << project[PROJECT]
357
+ @@project_ids << project[PROJECT_ID]
358
+ end
359
+
175
360
  # Loop (and validate) projects.
176
361
  file_parsed['Projects'].each do |project|
177
362
  # Validate keys are in specific order.
@@ -180,25 +365,26 @@ module Blufin
180
365
  PROJECT => true,
181
366
  TYPE => true,
182
367
  REPOSITORY => true,
368
+ UPSTREAM => false,
369
+ DOWNSTREAM => false,
183
370
  RUN => false,
184
371
  TEST => false,
185
372
  BUILD => false,
186
373
  API => false,
187
374
  LAMBDA => false,
188
- DEPLOYMENT => false
375
+ UI => false,
376
+ DEPLOYMENT => false,
189
377
  }
190
378
  Blufin::Validate::assert_valid_keys(expected, project.keys, source_file)
191
379
  project_id = project[PROJECT_ID]
192
380
  project_name = project[PROJECT]
193
381
  project_type = project[TYPE]
194
- @@project_names << project_name
195
- # Validate Type.
196
- 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)
382
+ validate_type(project_type, project_id)
197
383
  # Validate Script(s).
198
384
  [RUN, TEST, BUILD].each do |script_type|
199
385
  if project.has_key?(script_type)
200
386
  # Validate the LAMBDA functions don't need build scripts.
201
- Blufin::Terminal::error("#{project_id} \xe2\x80\x94 Project type: #{Blufin::Terminal::format_highlight(TYPE_LAMBDA)} does not require #{Blufin::Terminal::format_invalid(script_type)} script(s).", 'This type of project does not support this.', true) if [BUILD].include?(script_type) && project_type == TYPE_LAMBDA
387
+ Blufin::Terminal::error("#{Blufin::Terminal::format_highlight(project_id)} \xe2\x80\x94 Project type: #{Blufin::Terminal::format_highlight(TYPE_LAMBDA)} does not require #{Blufin::Terminal::format_invalid(script_type)} script(s).", 'This type of project does not support this.', true) if [BUILD].include?(script_type) && project_type == TYPE_LAMBDA
202
388
  if project[script_type].is_a?(Hash)
203
389
  script_key = script_key_mapper(script_type)
204
390
  valid_scripts = []
@@ -207,12 +393,21 @@ module Blufin
207
393
  script_name = script['Script']
208
394
  unless valid_scripts.include?(script_name)
209
395
  error = valid_scripts.any? ? 'Valid values are:' : "There currently are no #{script_key} script(s) defined."
210
- 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)
396
+ Blufin::Terminal::error("#{Blufin::Terminal::format_highlight(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)
211
397
  end
212
398
  end
213
399
  end
214
400
  end
215
401
 
402
+ # Validate Repository property.
403
+ if project.has_key?(REPOSITORY)
404
+ if project[REPOSITORY].has_key?(LOCAL)
405
+ repo_path = project[REPOSITORY][LOCAL]
406
+ Blufin::Terminal::error("#{Blufin::Terminal::format_highlight(project_id)} \xe2\x80\x94 Repository path not found: #{Blufin::Terminal::format_invalid(repo_path)}") unless Blufin::Files::path_exists(repo_path)
407
+
408
+ end
409
+ end
410
+
216
411
  # Validate Deployment property.
217
412
  if project.has_key?(DEPLOYMENT)
218
413
  expected = {
@@ -225,7 +420,7 @@ module Blufin
225
420
  # Validate API property.
226
421
  if project_type == TYPE_API
227
422
  # Make sure we have the API property.
228
- 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)
423
+ Blufin::Terminal::error("#{Blufin::Terminal::format_highlight(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)
229
424
  # Validate keys are in specific order.
230
425
  expected = {
231
426
  TITLE => true,
@@ -248,16 +443,16 @@ module Blufin
248
443
  used_ports[project[API][PORTS][CRON]] = project_id
249
444
  used_ports[project[API][PORTS][WORKER]] = project_id
250
445
  @@apis = {} if @@apis.nil?
251
- @@apis[project[PROJECT_ID]] = project[API]
446
+ @@apis[project[PROJECT_ID]] = project
252
447
  else
253
448
  # Make sure we DON'T have the API key.
254
- 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)
449
+ Blufin::Terminal::error("#{Blufin::Terminal::format_highlight(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)
255
450
  end
256
451
 
257
452
  # Validate Lambda property.
258
453
  if project_type == TYPE_LAMBDA
259
- # Make sure we have the Lambda
260
- Blufin::Terminal::error("#{project_id} \xe2\x80\x94 Missing property: #{Blufin::Terminal::format_highlight(LAMBDA)}", "This property is required for project(s) with type: #{LAMBDA}", true) unless project.has_key?(LAMBDA)
454
+ # Make sure we have the Lambda property.
455
+ Blufin::Terminal::error("#{Blufin::Terminal::format_highlight(project_id)} \xe2\x80\x94 Missing property: #{Blufin::Terminal::format_highlight(LAMBDA)}", "This property is required for project(s) with type: #{TYPE_LAMBDA}", true) unless project.has_key?(LAMBDA)
261
456
  # Validate keys are in specific order.
262
457
  expected = {
263
458
  TITLE => true,
@@ -274,12 +469,49 @@ module Blufin
274
469
  @@lambdas[project[PROJECT_ID]] = project
275
470
  else
276
471
  # Make sure we DON'T have the Lambda key.
277
- Blufin::Terminal::error("#{project_id} \xe2\x80\x94 Property not supported: #{Blufin::Terminal::format_invalid(LAMBDA)}", "This property is only allowed for project(s) with type: #{LAMBDA}", true) if project.has_key?(LAMBDA)
472
+ Blufin::Terminal::error("#{Blufin::Terminal::format_highlight(project_id)} \xe2\x80\x94 Property not supported: #{Blufin::Terminal::format_invalid(LAMBDA)}", "This property is only allowed for project(s) with type: #{TYPE_LAMBDA}", true) if project.has_key?(LAMBDA)
473
+ end
474
+
475
+ # Validate UI property.
476
+ if project_type == TYPE_UI
477
+ # Make sure we have the UI property.
478
+ Blufin::Terminal::error("#{Blufin::Terminal::format_highlight(project_id)} \xe2\x80\x94 Missing property: #{Blufin::Terminal::format_highlight(UI)}", "This property is required for project(s) with type: #{TYPE_UI}", true) unless project.has_key?(UI)
479
+ # Validate keys are in specific order.
480
+ expected = {
481
+ ROUTES_FILE => true
482
+ }
483
+ Blufin::Validate::assert_valid_keys(expected, project[UI].keys, source_file)
484
+ # Validate RoutesFile exists.
485
+ routes_file = "#{Blufin::Projects::get_project_path(project_id, true, project: project)}/#{project[UI][ROUTES_FILE]}"
486
+ Blufin::Terminal::error("Cannot find #{Blufin::Terminal::format_highlight(ROUTES_FILE)}: #{Blufin::Terminal::format_directory(routes_file)}") unless Blufin::Files::file_exists(routes_file)
487
+ @@uis = {} if @@uis.nil?
488
+ Blufin::Terminal::error("Duplicate UI project: #{Blufin::Terminal::format_invalid(project[PROJECT_ID])}") if @@uis.has_key?(project[PROJECT_ID])
489
+ @@uis[project[PROJECT_ID]] = project
490
+ else
491
+ # Make sure we DON'T have the UI key.
492
+ Blufin::Terminal::error("#{Blufin::Terminal::format_highlight(project_id)} \xe2\x80\x94 Property not supported: #{Blufin::Terminal::format_invalid(UI)}", "This property is only allowed for project(s) with type: #{TYPE_UI}", true) if project.has_key?(UI)
493
+ end
494
+
495
+ # Validate upstream/downstream Libs(s).
496
+ [UPSTREAM, DOWNSTREAM].each do |stream|
497
+ if project.has_key?(stream)
498
+ project[stream].each do |library|
499
+ case stream
500
+ when UPSTREAM
501
+ Blufin::Terminal::error("#{Blufin::Terminal::format_highlight(project_id)} \xe2\x80\x94 Unrecognized #{Blufin::Terminal::format_action(UPSTREAM)} library: #{Blufin::Terminal::format_invalid(library)}") unless @@libs.keys.include?(library)
502
+ when DOWNSTREAM
503
+ Blufin::Terminal::error("#{Blufin::Terminal::format_highlight(project_id)} \xe2\x80\x94 Unrecognized #{Blufin::Terminal::format_action(DOWNSTREAM)} library: #{Blufin::Terminal::format_invalid(library)}") unless @@project_ids.include?(library)
504
+ else
505
+ raise RuntimeError, "Unrecognized stream: #{stream}"
506
+ end
507
+ end
508
+ end
278
509
  end
279
510
 
280
511
  @@projects[project_name] = {} unless @@projects.has_key?(project_name)
281
512
  Blufin::Terminal::error("Duplicate project ID: #{Blufin::Terminal::format_invalid(project_id)}") if @@projects[project_name].has_key?(project_id)
282
513
  @@projects[project_name][project_id] = project
514
+ @@projects_arr << project
283
515
 
284
516
  end
285
517
  @@project_names.uniq!
@@ -287,12 +519,33 @@ module Blufin
287
519
  end
288
520
  end
289
521
 
522
+ # Validate project type.
523
+ # @return void
524
+ def validate_type(project_type, project_id = nil)
525
+ project_id = project_id.nil? ? nil : "#{Blufin::Terminal::format_highlight(project_id)} \xe2\x80\x94 "
526
+ Blufin::Terminal::error("#{project_id}Invalid Project Type: #{Blufin::Terminal::format_invalid(project_type)}. Valid types are:", VALID_TYPES, true) unless VALID_TYPES.include?(project_type)
527
+ end
528
+
529
+ # Maps root-level property to enum.
530
+ # @return string
531
+ def script_key_mapper(script_type)
532
+ if [RUN_SCRIPTS, RUN].include?(script_type)
533
+ return SCRIPT_RUN
534
+ elsif [TEST_SCRIPTS, TEST].include?(script_type)
535
+ return TEST_SCRIPTS
536
+ elsif [BUILD_SCRIPTS, BUILD].include?(script_type)
537
+ return BUILD_SCRIPTS
538
+ else
539
+ raise RuntimeError, "Unhandled script type: #{script_type}"
540
+ end
541
+ end
542
+
290
543
  # Validate the ports. Make sure none of the ports conflict with other projects.
291
544
  # @return void
292
- def self.validate_ports(ports, project_id, used_ports)
545
+ def validate_ports(ports, project_id, used_ports)
293
546
  ports.each do |port|
294
547
  if used_ports.has_key?(port)
295
- Blufin::Terminal::error("#{project_id} \xe2\x80\x94 Duplicate port detected: #{Blufin::Terminal::format_invalid(port)} ", ["The conflicting project is: #{Blufin::Terminal::format_highlight(used_ports[port])}"])
548
+ Blufin::Terminal::error("#{Blufin::Terminal::format_highlight(project_id)} \xe2\x80\x94 Duplicate port detected: #{Blufin::Terminal::format_invalid(port)} ", ["The conflicting project is: #{Blufin::Terminal::format_highlight(used_ports[port])}"])
296
549
  end
297
550
 
298
551
  end