semaph 0.4.0 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +39 -0
  3. data/.semaph +9 -0
  4. data/.semaphore/semaphore.yml +5 -1
  5. data/Gemfile.lock +21 -1
  6. data/README.md +12 -8
  7. data/Rakefile +4 -1
  8. data/lib/semaph.rb +43 -8
  9. data/lib/semaph/{api.rb → client.rb} +24 -3
  10. data/lib/semaph/commands.rb +63 -0
  11. data/lib/semaph/commands/rerun_workflow_command.rb +0 -2
  12. data/lib/semaph/commands/stop_workflow_command.rb +0 -2
  13. data/lib/semaph/formatting.rb +19 -1
  14. data/lib/semaph/model/job.rb +21 -2
  15. data/lib/semaph/model/job_collection.rb +19 -3
  16. data/lib/semaph/model/pipeline.rb +29 -3
  17. data/lib/semaph/model/promotion.rb +31 -0
  18. data/lib/semaph/model/promotion_collection.rb +21 -0
  19. data/lib/semaph/model/workflow.rb +17 -3
  20. data/lib/semaph/shells/organisation/organisation_shell.rb +4 -4
  21. data/lib/semaph/shells/organisation/projects_select_command.rb +6 -0
  22. data/lib/semaph/shells/organisations/organisations_list_command.rb +2 -2
  23. data/lib/semaph/shells/organisations/organisations_select_command.rb +11 -2
  24. data/lib/semaph/shells/organisations/organisations_shell.rb +2 -2
  25. data/lib/semaph/shells/pipeline/job_debug_command.rb +29 -0
  26. data/lib/semaph/shells/pipeline/job_log_command.rb +17 -15
  27. data/lib/semaph/shells/pipeline/job_log_grep_command.rb +5 -11
  28. data/lib/semaph/shells/pipeline/job_show_command.rb +28 -0
  29. data/lib/semaph/shells/pipeline/job_stop_command.rb +28 -0
  30. data/lib/semaph/shells/pipeline/jobs_list_command.rb +4 -2
  31. data/lib/semaph/shells/pipeline/jobs_poll_command.rb +58 -22
  32. data/lib/semaph/shells/pipeline/pipeline_shell.rb +28 -68
  33. data/lib/semaph/shells/pipeline/promote_command.rb +21 -0
  34. data/lib/semaph/shells/pipeline/promotions_list_command.rb +21 -0
  35. data/lib/semaph/shells/project/project_shell.rb +4 -2
  36. data/lib/semaph/shells/project/save_command.rb +26 -0
  37. data/lib/semaph/shells/project/workflows_list_command.rb +11 -17
  38. data/lib/semaph/shells/workflow/pipelines_list_command.rb +3 -1
  39. data/lib/semaph/shells/workflow/workflow_shell.rb +6 -66
  40. data/lib/semaph/version.rb +1 -1
  41. data/semaph.gemspec +1 -0
  42. metadata +30 -6
@@ -1,9 +1,10 @@
1
+ require "date"
1
2
  require "semaph/model/job"
2
3
 
3
4
  module Semaph
4
5
  module Model
5
6
  class JobCollection
6
- attr_reader :all, :pipeline
7
+ attr_reader :all, :pipeline, :stopping_at, :running_at, :queuing_at, :pending_at, :done_at, :created_at
7
8
 
8
9
  def initialize(pipeline)
9
10
  @pipeline = pipeline
@@ -12,7 +13,7 @@ module Semaph
12
13
  def reload
13
14
  workflow = @pipeline.workflow
14
15
  project = workflow.project
15
- @all = build_jobs(project.client.pipeline(@pipeline.id))
16
+ @all = parse_content(project.client.pipeline(@pipeline.id))
16
17
  end
17
18
 
18
19
  def incomplete
@@ -25,7 +26,8 @@ module Semaph
25
26
 
26
27
  private
27
28
 
28
- def build_jobs(content)
29
+ def parse_content(content)
30
+ parse_pipeline(content["pipeline"])
29
31
  result = []
30
32
  blocks = content.delete("blocks")
31
33
  blocks.each do |block|
@@ -35,6 +37,15 @@ module Semaph
35
37
  result
36
38
  end
37
39
 
40
+ def parse_pipeline(pipeline_content)
41
+ with_time(pipeline_content["stopping_at"]) { |time| @stopping_at = time }
42
+ with_time(pipeline_content["running_at"]) { |time| @running_at = time }
43
+ with_time(pipeline_content["queuing_at"]) { |time| @queuing_at = time }
44
+ with_time(pipeline_content["pending_at"]) { |time| @pending_at = time }
45
+ with_time(pipeline_content["done_at"]) { |time| @done_at = time }
46
+ with_time(pipeline_content["created_at"]) { |time| @created_at = time }
47
+ end
48
+
38
49
  def append_jobs(result, block, jobs)
39
50
  if jobs.count.positive?
40
51
  jobs.each { |job| result << Job.new(@pipeline, block, job) }
@@ -42,6 +53,11 @@ module Semaph
42
53
  result << Job.new(@pipeline, block, {})
43
54
  end
44
55
  end
56
+
57
+ def with_time(string)
58
+ time = DateTime.parse(string).to_time
59
+ yield time unless time.to_i.zero?
60
+ end
45
61
  end
46
62
  end
47
63
  end
@@ -1,15 +1,18 @@
1
+ require "semaph/formatting"
1
2
  require "semaph/model/job_collection"
3
+ require "semaph/model/promotion_collection"
2
4
 
3
5
  module Semaph
4
6
  module Model
5
7
  class Pipeline
6
- attr_reader :workflow, :raw, :id, :yaml, :state, :result
8
+ attr_reader :workflow, :raw, :id, :name, :yaml, :state, :result
7
9
 
8
10
  def initialize(workflow, raw)
9
11
  @workflow = workflow
10
12
  @raw = raw
11
13
  @id = raw["ppl_id"]
12
14
  @yaml = raw["yaml_file_name"]
15
+ @name = raw["name"]
13
16
  @state = raw["state"]
14
17
  @result = raw["result"]
15
18
  %w[created done pending queuing running stopping].each do |name|
@@ -17,16 +20,33 @@ module Semaph
17
20
  end
18
21
  end
19
22
 
23
+ def promote(name)
24
+ workflow.project.client.promote(id, name)
25
+ end
26
+
20
27
  def job_collection
21
28
  @job_collection ||= JobCollection.new(self)
22
29
  end
23
30
 
31
+ def promotion_collection
32
+ @promotion_collection ||= PromotionCollection.new(self)
33
+ end
34
+
24
35
  def description
25
- "#{icon} #{yaml}"
36
+ [
37
+ icon,
38
+ time,
39
+ name,
40
+ "(#{yaml})",
41
+ ].compact.join(" ")
42
+ end
43
+
44
+ def done?
45
+ @state == "DONE"
26
46
  end
27
47
 
28
48
  def icon
29
- return "🔵" unless @state == "DONE"
49
+ return "🔵" unless done?
30
50
 
31
51
  return "⛔" if @result == "STOPPED"
32
52
 
@@ -37,6 +57,12 @@ module Semaph
37
57
 
38
58
  private
39
59
 
60
+ def time
61
+ return ::Semaph::Formatting.hours_minutes_seconds(@done_at.to_i - @created_at.to_i) if done?
62
+
63
+ ::Semaph::Formatting.hours_minutes_seconds(Time.now.to_i - @created_at.to_i)
64
+ end
65
+
40
66
  def extract_time(name)
41
67
  key = "#{name}_at"
42
68
  return if raw[key]["seconds"].zero?
@@ -0,0 +1,31 @@
1
+ require "semaph/formatting"
2
+
3
+ module Semaph
4
+ module Model
5
+ class Promotion
6
+ attr_reader :pipeline, :raw
7
+
8
+ def initialize(pipeline, raw)
9
+ @pipeline = pipeline
10
+ @raw = raw
11
+ @name = raw["name"]
12
+ @status = raw["status"]
13
+ @triggered_at = Time.at(raw["triggered_at"]["seconds"])
14
+ end
15
+
16
+ def description
17
+ [
18
+ status_icon,
19
+ Semaph::Formatting.time(@triggered_at),
20
+ @name,
21
+ ].join(" ")
22
+ end
23
+
24
+ def status_icon
25
+ return "🟢" if @status == "passed"
26
+
27
+ "🔴"
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,21 @@
1
+ require "semaph/model/promotion"
2
+
3
+ module Semaph
4
+ module Model
5
+ class PromotionCollection
6
+ attr_reader :all, :pipeline
7
+
8
+ def initialize(pipeline)
9
+ @pipeline = pipeline
10
+ end
11
+
12
+ def reload
13
+ workflow = @pipeline.workflow
14
+ project = workflow.project
15
+ @all = project.client.promotions(@pipeline.id).map do |promotion_response|
16
+ Promotion.new(@pipeline, promotion_response)
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -1,19 +1,25 @@
1
+ require "semaph/formatting"
1
2
  require "semaph/model/pipeline_collection"
2
3
 
3
4
  module Semaph
4
5
  module Model
5
6
  class Workflow
6
- attr_reader :project, :raw, :id, :sha, :branch, :branch_id, :created_at
7
+ attr_reader :project, :raw, :id, :sha, :commit, :branch, :branch_id, :created_at
7
8
 
8
9
  def initialize(project, raw)
9
10
  @project = project
10
11
  @raw = raw
11
12
  @id = raw["wf_id"]
12
- @sha = raw["commit_sha"]
13
13
  @created_at = Time.at(raw["created_at"]["seconds"].to_i)
14
14
  @branch = raw["branch_name"]
15
15
  @branch_id = raw["branch_id"]
16
- # @summary = `git log -n 1 --format="%h %an %ci %s" #{sha}`
16
+ extract_git_details
17
+ end
18
+
19
+ def extract_git_details
20
+ @sha = raw["commit_sha"]
21
+ @commit = @sha.slice(0..10)
22
+ @commit = `git log -n 1 --format="%h %an %s" #{sha}`.chomp if `git cat-file -t #{sha} 2>&1`.chomp == "commit"
17
23
  end
18
24
 
19
25
  def pipeline_collection
@@ -26,6 +32,14 @@ module Semaph
26
32
  Workflow.new(project, workflow_response["workflow"])
27
33
  end
28
34
 
35
+ def created
36
+ Semaph::Formatting.time(created_at)
37
+ end
38
+
39
+ def description
40
+ [created, branch, commit].join(" ")
41
+ end
42
+
29
43
  def stop
30
44
  project.client.stop_workflow(@id)
31
45
  end
@@ -1,4 +1,4 @@
1
- require "semaph/api"
1
+ require "semaph/client"
2
2
  require "semaph/commands/reload_command"
3
3
  require "semaph/model/project_collection"
4
4
  require "semaph/shells/organisation/projects_list_command"
@@ -12,7 +12,7 @@ module Semaph
12
12
  include ShellShock::Context
13
13
 
14
14
  def initialize(organisation)
15
- @client = ::Semaph::Api.new(organisation["auth"]["token"], organisation["host"])
15
+ @client = ::Semaph::Client.new(organisation["auth"]["token"], organisation["host"])
16
16
  @prompt = "🏗 #{@client.name} > "
17
17
  add_commands
18
18
  @project_list_command.execute("")
@@ -23,8 +23,8 @@ module Semaph
23
23
  def add_commands
24
24
  project_collection = ::Semaph::Model::ProjectCollection.new(@client)
25
25
  @project_list_command = ProjectsListCommand.new(project_collection)
26
- add_command @project_list_command, "list-projects"
27
- add_command ProjectsSelectCommand.new(project_collection), "select-project"
26
+ add_command @project_list_command, "list-projects", "ls"
27
+ add_command ProjectsSelectCommand.new(project_collection), "select-project", "cd"
28
28
  add_command ::Semaph::Commands::ReloadCommand.new, "reload" if ENV["SEMAPH_RELOAD"]
29
29
  end
30
30
  end
@@ -18,6 +18,12 @@ module Semaph
18
18
 
19
19
  def execute(name)
20
20
  selected_project = @project_collection.all.find { |project| project.name == name }
21
+
22
+ unless selected_project
23
+ puts "There is no project called #{name}"
24
+ return
25
+ end
26
+
21
27
  ::Semaph::Shells::Project::ProjectShell.new(selected_project).push
22
28
  end
23
29
  end
@@ -10,8 +10,8 @@ module Semaph
10
10
  end
11
11
 
12
12
  def execute(_whatever)
13
- @organisations.each_key do |name|
14
- puts "#{name} (#{@organisations[name]['host']})"
13
+ @organisations.each do |organisation|
14
+ puts organisation["host"].split(".").first
15
15
  end
16
16
  end
17
17
  end
@@ -13,11 +13,20 @@ module Semaph
13
13
  end
14
14
 
15
15
  def completion(text)
16
- @organisations.keys.grep(/^#{text}/).sort
16
+ @organisations.map { |org| org["host"].split(".").first }.grep(/^#{text}/).sort
17
17
  end
18
18
 
19
19
  def execute(name)
20
- ::Semaph::Shells::Organisation::OrganisationShell.new(@organisations[name]).push
20
+ organisation = @organisations.find do |org|
21
+ org["host"].split(".").first == name
22
+ end
23
+
24
+ unless organisation
25
+ puts "There is no organisation called #{name}"
26
+ return
27
+ end
28
+
29
+ ::Semaph::Shells::Organisation::OrganisationShell.new(organisation).push
21
30
  end
22
31
  end
23
32
  end
@@ -13,8 +13,8 @@ module Semaph
13
13
  @organisations = organisations
14
14
  @prompt = "🏗 > "
15
15
  organisations_list_command = OrganisationsListCommand.new(organisations)
16
- add_command organisations_list_command, "list-organisations"
17
- add_command OrganisationsSelectCommand.new(organisations), "select-organisation"
16
+ add_command organisations_list_command, "list-organisations", "ls"
17
+ add_command OrganisationsSelectCommand.new(organisations), "select-organisation", "cd"
18
18
  add_command ::Semaph::Commands::ReloadCommand.new, "reload" if ENV["SEMAPH_RELOAD"]
19
19
  organisations_list_command.execute("")
20
20
  end
@@ -0,0 +1,29 @@
1
+ module Semaph
2
+ module Shells
3
+ module Pipeline
4
+ class JobDebugCommand
5
+ attr_reader :usage, :help
6
+
7
+ def initialize(job_collection)
8
+ @job_collection = job_collection
9
+ @usage = "<job index>"
10
+ @help = "debug job"
11
+ end
12
+
13
+ def execute(index_string)
14
+ index = index_string.to_i - 1
15
+
16
+ job = @job_collection.all[index]
17
+
18
+ unless job
19
+ puts "There is no job at position #{index}"
20
+ return
21
+ end
22
+
23
+ puts "Debugging #{job.description}"
24
+ system("sem debug job #{job.id}")
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -4,7 +4,7 @@ module Semaph
4
4
  module Shells
5
5
  module Pipeline
6
6
  class JobLogCommand
7
- attr_reader :usage, :help
7
+ attr_reader :usage, :help, :job_collection
8
8
 
9
9
  def initialize(job_collection)
10
10
  @job_collection = job_collection
@@ -13,28 +13,30 @@ module Semaph
13
13
  end
14
14
 
15
15
  def execute(index_string)
16
+ with_job(index_string) { |job| system(command(job)) }
17
+ end
18
+
19
+ private
20
+
21
+ def command(job)
22
+ base = "tmp/logs/pipeline/#{job_collection.pipeline.id}"
23
+
24
+ return "less #{job.write_log(base)}" if job.finished?
25
+
26
+ "open https://#{job_collection.pipeline.workflow.project.client.host}/jobs/#{job.id}"
27
+ end
28
+
29
+ def with_job(index_string)
16
30
  index = index_string.to_i - 1
17
31
 
18
- job = @job_collection.all[index]
32
+ job = job_collection.all[index]
19
33
 
20
34
  unless job
21
35
  puts "There is no job at position #{index}"
22
36
  return
23
37
  end
24
38
 
25
- unless job.finished?
26
- puts "This job has not finished yet"
27
- return
28
- end
29
-
30
- base = "tmp/logs/pipeline/#{job.pipeline.id}"
31
- FileUtils.mkdir_p(base)
32
- filename = "#{base}/#{job.id}.log"
33
- unless File.exist?(filename)
34
- puts "retrieving log for job #{job.id}"
35
- File.open(filename, "w") { |file| file.puts job.log }
36
- end
37
- system("less #{filename}")
39
+ yield job
38
40
  end
39
41
  end
40
42
  end
@@ -1,30 +1,24 @@
1
- require "fileutils"
2
-
3
1
  module Semaph
4
2
  module Shells
5
3
  module Pipeline
6
4
  class JobLogGrepCommand
7
5
  attr_reader :usage, :help, :job_collection
8
6
 
9
- def initialize(job_collection)
7
+ def initialize(job_collection, scope)
10
8
  @job_collection = job_collection
9
+ @scope = scope
11
10
  @usage = "<expression>"
12
- @help = "retrieve all logs and grep for text"
11
+ @help = "retrieve logs for #{scope} jobs and grep for text"
13
12
  end
14
13
 
15
14
  def execute(expression)
16
15
  base = "tmp/logs/pipeline/#{job_collection.pipeline.id}"
17
- FileUtils.mkdir_p(base)
18
- @job_collection.all.each do |job|
16
+ @job_collection.send(@scope).each do |job|
19
17
  unless job.finished?
20
18
  puts "skipping incomplete job #{job.id}"
21
19
  next
22
20
  end
23
- filename = "#{base}/#{job.id}.log"
24
- unless File.exist?(filename)
25
- puts "retrieving log for job #{job.id}"
26
- File.open(filename, "w") { |file| file.puts job.log }
27
- end
21
+ job.write_log(base)
28
22
  end
29
23
  system("ag #{expression} #{base} | less")
30
24
  end
@@ -0,0 +1,28 @@
1
+ module Semaph
2
+ module Shells
3
+ module Pipeline
4
+ class JobShowCommand
5
+ attr_reader :usage, :help
6
+
7
+ def initialize(job_collection)
8
+ @job_collection = job_collection
9
+ @usage = "<job index>"
10
+ @help = "show job"
11
+ end
12
+
13
+ def execute(index_string)
14
+ index = index_string.to_i - 1
15
+
16
+ job = @job_collection.all[index]
17
+
18
+ unless job
19
+ puts "There is no job at position #{index}"
20
+ return
21
+ end
22
+
23
+ puts job.show
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end