semaph 0.4.0 → 0.9.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.
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