semaph 0.2.0 → 0.7.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 (48) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -1
  3. data/.rubocop.yml +39 -0
  4. data/.semaphore/semaphore.yml +5 -1
  5. data/Gemfile.lock +21 -1
  6. data/README.md +85 -11
  7. data/Rakefile +4 -1
  8. data/lib/semaph/client.rb +115 -0
  9. data/lib/semaph/commands.rb +63 -0
  10. data/lib/semaph/commands/reload_command.rb +5 -4
  11. data/lib/semaph/commands/rerun_workflow_command.rb +16 -0
  12. data/lib/semaph/commands/stop_workflow_command.rb +16 -0
  13. data/lib/semaph/formatting.rb +15 -1
  14. data/lib/semaph/model/job.rb +70 -1
  15. data/lib/semaph/model/job_collection.rb +18 -5
  16. data/lib/semaph/model/pipeline.rb +54 -2
  17. data/lib/semaph/model/pipeline_collection.rb +0 -1
  18. data/lib/semaph/model/project.rb +1 -1
  19. data/lib/semaph/model/project_collection.rb +0 -1
  20. data/lib/semaph/model/promotion.rb +31 -0
  21. data/lib/semaph/model/promotion_collection.rb +21 -0
  22. data/lib/semaph/model/workflow.rb +28 -4
  23. data/lib/semaph/model/workflow_collection.rb +0 -1
  24. data/lib/semaph/shells/organisation/organisation_shell.rb +15 -11
  25. data/lib/semaph/shells/organisation/projects_list_command.rb +1 -0
  26. data/lib/semaph/shells/organisation/projects_select_command.rb +6 -0
  27. data/lib/semaph/shells/organisations/organisations_select_command.rb +8 -1
  28. data/lib/semaph/shells/organisations/organisations_shell.rb +6 -2
  29. data/lib/semaph/shells/pipeline/job_debug_command.rb +29 -0
  30. data/lib/semaph/shells/pipeline/job_log_command.rb +44 -0
  31. data/lib/semaph/shells/pipeline/job_log_grep_command.rb +28 -0
  32. data/lib/semaph/shells/pipeline/job_show_command.rb +28 -0
  33. data/lib/semaph/shells/pipeline/job_stop_command.rb +28 -0
  34. data/lib/semaph/shells/pipeline/jobs_list_command.rb +6 -16
  35. data/lib/semaph/shells/pipeline/jobs_poll_command.rb +55 -0
  36. data/lib/semaph/shells/pipeline/pipeline_shell.rb +42 -7
  37. data/lib/semaph/shells/pipeline/promote_command.rb +21 -0
  38. data/lib/semaph/shells/pipeline/promotions_list_command.rb +21 -0
  39. data/lib/semaph/shells/project/project_shell.rb +31 -18
  40. data/lib/semaph/shells/project/workflows_list_command.rb +4 -16
  41. data/lib/semaph/shells/project/workflows_select_command.rb +10 -3
  42. data/lib/semaph/shells/workflow/pipelines_list_command.rb +4 -1
  43. data/lib/semaph/shells/workflow/pipelines_select_command.rb +9 -3
  44. data/lib/semaph/shells/workflow/workflow_shell.rb +13 -42
  45. data/lib/semaph/version.rb +1 -1
  46. data/semaph.gemspec +1 -0
  47. metadata +33 -6
  48. data/lib/semaph/api.rb +0 -61
@@ -0,0 +1,16 @@
1
+ module Semaph
2
+ module Commands
3
+ class RerunWorkflowCommand
4
+ attr_reader :help
5
+
6
+ def initialize(workflow)
7
+ @workflow = workflow
8
+ @help = "rerun workflow"
9
+ end
10
+
11
+ def execute(_whatever)
12
+ ::Semaph::Shells::Workflow::WorkflowShell.new(@workflow.rerun).push
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,16 @@
1
+ module Semaph
2
+ module Commands
3
+ class StopWorkflowCommand
4
+ attr_reader :help
5
+
6
+ def initialize(workflow)
7
+ @workflow = workflow
8
+ @help = "stop workflow"
9
+ end
10
+
11
+ def execute(_whatever)
12
+ @workflow.stop
13
+ end
14
+ end
15
+ end
16
+ end
@@ -1,9 +1,23 @@
1
+ require "rainbow"
2
+
1
3
  module Semaph
2
4
  module Formatting
3
- TIME_FORMAT = "%Y-%m-%d %H:%M:%S".freeze
5
+ TIME_FORMAT = "%m-%d %H:%M".freeze
4
6
 
5
7
  def self.time(time)
6
8
  time.strftime(TIME_FORMAT)
7
9
  end
10
+
11
+ def self.hours_minutes_seconds(total_seconds)
12
+ seconds = total_seconds % 60
13
+ minutes = (total_seconds / 60) % 60
14
+ hours = total_seconds / (60 * 60)
15
+
16
+ format("%02<hours>d:%02<minutes>d:%02<seconds>d", hours: hours, minutes: minutes, seconds: seconds)
17
+ end
18
+
19
+ def self.index(number)
20
+ Rainbow(number.to_s.rjust(2)).yellow
21
+ end
8
22
  end
9
23
  end
@@ -1,3 +1,5 @@
1
+ require "fileutils"
2
+
1
3
  module Semaph
2
4
  module Model
3
5
  class Job
@@ -20,6 +22,74 @@ module Semaph
20
22
  assign_from_block(raw_block)
21
23
  end
22
24
 
25
+ def stop
26
+ pipeline.workflow.project.client.stop_job(id)
27
+ end
28
+
29
+ def show
30
+ pp pipeline.workflow.project.client.job(id)
31
+ end
32
+
33
+ def write_log(base)
34
+ FileUtils.mkdir_p(base)
35
+ filename = "#{base}/#{id}.log"
36
+ return filename if File.exist?(filename)
37
+
38
+ puts "retrieving log for job #{id}"
39
+ File.open(filename, "w") do |file|
40
+ file.puts pipeline.workflow.project.client.job_log(id)
41
+ end
42
+
43
+ filename
44
+ end
45
+
46
+ def description
47
+ [
48
+ block_icon,
49
+ @block_name,
50
+ job_icon,
51
+ @name,
52
+ ].compact.join(" ")
53
+ end
54
+
55
+ def finished?
56
+ @status == "FINISHED"
57
+ end
58
+
59
+ def failed?
60
+ @result == "FAILED"
61
+ end
62
+
63
+ # block_state can be waiting/running/done
64
+ # block_result can be passed/failed/canceled/stopped
65
+ def block_icon
66
+ return "🟠" if @block_state == "waiting"
67
+
68
+ return "🔵" if @block_state == "running"
69
+
70
+ return "⚪" if @block_result == "canceled"
71
+
72
+ return "⛔" if @block_result == "stopped"
73
+
74
+ return "🟢" if @block_result == "passed"
75
+
76
+ "🔴"
77
+ end
78
+
79
+ # status can be FINISHED/RUNNING
80
+ # result can be PASSED/FAILED/STOPPED
81
+ def job_icon
82
+ return nil unless @status
83
+
84
+ return "🔵" unless @status == "FINISHED"
85
+
86
+ return "⛔" if @result == "STOPPED"
87
+
88
+ return "🟢" if @result == "PASSED"
89
+
90
+ "🔴"
91
+ end
92
+
23
93
  private
24
94
 
25
95
  def assign_from_job(raw)
@@ -37,4 +107,3 @@ module Semaph
37
107
  end
38
108
  end
39
109
  end
40
-
@@ -3,11 +3,10 @@ require "semaph/model/job"
3
3
  module Semaph
4
4
  module Model
5
5
  class JobCollection
6
- attr_reader :all
6
+ attr_reader :all, :pipeline
7
7
 
8
8
  def initialize(pipeline)
9
9
  @pipeline = pipeline
10
- reload
11
10
  end
12
11
 
13
12
  def reload
@@ -16,6 +15,14 @@ module Semaph
16
15
  @all = build_jobs(project.client.pipeline(@pipeline.id))
17
16
  end
18
17
 
18
+ def incomplete
19
+ @all.reject(&:finished?)
20
+ end
21
+
22
+ def failed
23
+ @all.select(&:failed?)
24
+ end
25
+
19
26
  private
20
27
 
21
28
  def build_jobs(content)
@@ -23,12 +30,18 @@ module Semaph
23
30
  blocks = content.delete("blocks")
24
31
  blocks.each do |block|
25
32
  jobs = block.delete("jobs").sort_by { |job| job["index"] }
26
- jobs.each do |job|
27
- result << Job.new(@pipeline, block, job)
28
- end
33
+ append_jobs(result, block, jobs)
29
34
  end
30
35
  result
31
36
  end
37
+
38
+ def append_jobs(result, block, jobs)
39
+ if jobs.count.positive?
40
+ jobs.each { |job| result << Job.new(@pipeline, block, job) }
41
+ else
42
+ result << Job.new(@pipeline, block, {})
43
+ end
44
+ end
32
45
  end
33
46
  end
34
47
  end
@@ -1,21 +1,73 @@
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"]
18
+ %w[created done pending queuing running stopping].each do |name|
19
+ extract_time(name)
20
+ end
21
+ end
22
+
23
+ def promote(name)
24
+ workflow.project.client.promote(id, name)
15
25
  end
16
26
 
17
27
  def job_collection
18
- JobCollection.new(self)
28
+ @job_collection ||= JobCollection.new(self)
29
+ end
30
+
31
+ def promotion_collection
32
+ @promotion_collection ||= PromotionCollection.new(self)
33
+ end
34
+
35
+ def description
36
+ [
37
+ icon,
38
+ time,
39
+ name,
40
+ "(#{yaml})",
41
+ ].compact.join(" ")
42
+ end
43
+
44
+ def done?
45
+ @state == "DONE"
46
+ end
47
+
48
+ def icon
49
+ return "🔵" unless done?
50
+
51
+ return "⛔" if @result == "STOPPED"
52
+
53
+ return "🟢" if @result == "PASSED"
54
+
55
+ "🔴"
56
+ end
57
+
58
+ private
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
+
66
+ def extract_time(name)
67
+ key = "#{name}_at"
68
+ return if raw[key]["seconds"].zero?
69
+
70
+ instance_variable_set("@#{key}", Time.at(raw[key]["seconds"]))
19
71
  end
20
72
  end
21
73
  end
@@ -7,7 +7,6 @@ module Semaph
7
7
 
8
8
  def initialize(workflow)
9
9
  @workflow = workflow
10
- reload
11
10
  end
12
11
 
13
12
  def reload
@@ -27,7 +27,7 @@ module Semaph
27
27
  end
28
28
 
29
29
  def workflow_collection
30
- WorkflowCollection.new(self)
30
+ @workflow_collection ||= WorkflowCollection.new(self)
31
31
  end
32
32
  end
33
33
  end
@@ -7,7 +7,6 @@ module Semaph
7
7
 
8
8
  def initialize(client)
9
9
  @client = client
10
- reload
11
10
  end
12
11
 
13
12
  def reload
@@ -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,23 +1,47 @@
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
20
- PipelineCollection.new(self)
26
+ @pipeline_collection ||= PipelineCollection.new(self)
27
+ end
28
+
29
+ def rerun
30
+ rerun_response = project.client.rerun_workflow(@id)
31
+ workflow_response = project.client.workflow(rerun_response["wf_id"])
32
+ Workflow.new(project, workflow_response["workflow"])
33
+ end
34
+
35
+ def description
36
+ [
37
+ Semaph::Formatting.time(created_at),
38
+ branch,
39
+ commit,
40
+ ].join(" ")
41
+ end
42
+
43
+ def stop
44
+ project.client.stop_workflow(@id)
21
45
  end
22
46
  end
23
47
  end
@@ -7,7 +7,6 @@ module Semaph
7
7
 
8
8
  def initialize(project)
9
9
  @project = project
10
- reload
11
10
  end
12
11
 
13
12
  def reload
@@ -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,16 +12,20 @@ module Semaph
12
12
  include ShellShock::Context
13
13
 
14
14
  def initialize(organisation)
15
- host = organisation["host"]
16
- @prompt = "🏗 #{host} > "
17
- client = ::Semaph::Api.new(organisation["auth"]["token"], host)
18
- project_collection = ::Semaph::Model::ProjectCollection.new(client)
19
- add_command ProjectsListCommand.new(project_collection), "list-projects"
20
- add_command ProjectsSelectCommand.new(project_collection), "select-project"
21
- add_command(
22
- ::Semaph::Commands::ReloadCommand.new(project_collection, "reload projects"),
23
- "reload-projects",
24
- )
15
+ @client = ::Semaph::Client.new(organisation["auth"]["token"], organisation["host"])
16
+ @prompt = "🏗 #{@client.name} > "
17
+ add_commands
18
+ @project_list_command.execute("")
19
+ end
20
+
21
+ private
22
+
23
+ def add_commands
24
+ project_collection = ::Semaph::Model::ProjectCollection.new(@client)
25
+ @project_list_command = ProjectsListCommand.new(project_collection)
26
+ add_command @project_list_command, "list-projects", "ls"
27
+ add_command ProjectsSelectCommand.new(project_collection), "select-project", "cd"
28
+ add_command ::Semaph::Commands::ReloadCommand.new, "reload" if ENV["SEMAPH_RELOAD"]
25
29
  end
26
30
  end
27
31
  end