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
@@ -10,6 +10,7 @@ module Semaph
10
10
  end
11
11
 
12
12
  def execute(_whatever)
13
+ @project_collection.reload
13
14
  @project_collection.all.each do |project|
14
15
  puts project.name
15
16
  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
@@ -17,7 +17,14 @@ module Semaph
17
17
  end
18
18
 
19
19
  def execute(name)
20
- ::Semaph::Shells::Organisation::OrganisationShell.new(@organisations[name]).push
20
+ organisation = @organisations[name]
21
+
22
+ unless organisation
23
+ puts "There is no organisation called #{name}"
24
+ return
25
+ end
26
+
27
+ ::Semaph::Shells::Organisation::OrganisationShell.new(organisation).push
21
28
  end
22
29
  end
23
30
  end
@@ -1,3 +1,4 @@
1
+ require "semaph/commands/reload_command"
1
2
  require "semaph/shells/organisations/organisations_list_command"
2
3
  require "semaph/shells/organisations/organisations_select_command"
3
4
  require "shell_shock/context"
@@ -11,8 +12,11 @@ module Semaph
11
12
  def initialize(organisations)
12
13
  @organisations = organisations
13
14
  @prompt = "🏗 > "
14
- add_command OrganisationsListCommand.new(organisations), "list-organisations"
15
- add_command OrganisationsSelectCommand.new(organisations), "select-organisation"
15
+ organisations_list_command = OrganisationsListCommand.new(organisations)
16
+ add_command organisations_list_command, "list-organisations", "ls"
17
+ add_command OrganisationsSelectCommand.new(organisations), "select-organisation", "cd"
18
+ add_command ::Semaph::Commands::ReloadCommand.new, "reload" if ENV["SEMAPH_RELOAD"]
19
+ organisations_list_command.execute("")
16
20
  end
17
21
  end
18
22
  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
@@ -0,0 +1,44 @@
1
+ require "fileutils"
2
+
3
+ module Semaph
4
+ module Shells
5
+ module Pipeline
6
+ class JobLogCommand
7
+ attr_reader :usage, :help
8
+
9
+ def initialize(job_collection)
10
+ @job_collection = job_collection
11
+ @usage = "<job index>"
12
+ @help = "retrieve log for job"
13
+ end
14
+
15
+ def execute(index_string)
16
+ base = "tmp/logs/pipeline/#{@job_collection.pipeline.id}"
17
+ with_job(index_string) do |job|
18
+ unless job.finished?
19
+ puts "This job has not finished yet"
20
+ return
21
+ end
22
+
23
+ system("less #{job.write_log(base)}")
24
+ end
25
+ end
26
+
27
+ private
28
+
29
+ def with_job(index_string)
30
+ index = index_string.to_i - 1
31
+
32
+ job = @job_collection.all[index]
33
+
34
+ unless job
35
+ puts "There is no job at position #{index}"
36
+ return
37
+ end
38
+
39
+ yield job
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,28 @@
1
+ module Semaph
2
+ module Shells
3
+ module Pipeline
4
+ class JobLogGrepCommand
5
+ attr_reader :usage, :help, :job_collection
6
+
7
+ def initialize(job_collection, scope)
8
+ @job_collection = job_collection
9
+ @scope = scope
10
+ @usage = "<expression>"
11
+ @help = "retrieve logs for #{scope} jobs and grep for text"
12
+ end
13
+
14
+ def execute(expression)
15
+ base = "tmp/logs/pipeline/#{job_collection.pipeline.id}"
16
+ @job_collection.send(@scope).each do |job|
17
+ unless job.finished?
18
+ puts "skipping incomplete job #{job.id}"
19
+ next
20
+ end
21
+ job.write_log(base)
22
+ end
23
+ system("ag #{expression} #{base} | less")
24
+ end
25
+ end
26
+ end
27
+ end
28
+ 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
@@ -0,0 +1,28 @@
1
+ module Semaph
2
+ module Shells
3
+ module Pipeline
4
+ class JobStopCommand
5
+ attr_reader :usage, :help
6
+
7
+ def initialize(job_collection)
8
+ @job_collection = job_collection
9
+ @usage = "<job index>"
10
+ @help = "stop 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
+ job.stop
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -1,3 +1,5 @@
1
+ require "semaph/formatting"
2
+
1
3
  module Semaph
2
4
  module Shells
3
5
  module Pipeline
@@ -9,24 +11,12 @@ module Semaph
9
11
  @help = "list jobs"
10
12
  end
11
13
 
12
- def execute(_whatever)
13
- @job_collection.all.each do |job|
14
- puts description(job)
14
+ def execute(_whatever = nil)
15
+ @job_collection.reload
16
+ @job_collection.all.each_with_index do |job, index|
17
+ puts [::Semaph::Formatting.index(index + 1), job.description].join(" ")
15
18
  end
16
19
  end
17
-
18
- private
19
-
20
- def description(job)
21
- [
22
- job.block_name,
23
- job.block_state,
24
- job.block_result,
25
- job.name,
26
- job.status,
27
- job.result,
28
- ].join(" ")
29
- end
30
20
  end
31
21
  end
32
22
  end
@@ -0,0 +1,55 @@
1
+ require "semaph/formatting"
2
+
3
+ module Semaph
4
+ module Shells
5
+ module Pipeline
6
+ class JobsPollCommand
7
+ attr_reader :usage, :help, :job_collection
8
+
9
+ def initialize(job_collection, list_command)
10
+ @job_collection = job_collection
11
+ @list_command = list_command
12
+ @help = "poll jobs"
13
+ @can_notify = !`which terminal-notifier`.chomp.empty?
14
+ end
15
+
16
+ def execute(_whatever = nil)
17
+ report_and_reload(15) while job_collection.incomplete.count.positive? && job_collection.failed.count.zero?
18
+ report_final
19
+ end
20
+
21
+ private
22
+
23
+ def report_and_reload(period)
24
+ report_incomplete(job_collection.incomplete)
25
+ sleep period
26
+ job_collection.reload
27
+ end
28
+
29
+ def report_final
30
+ @list_command.execute
31
+ failed_job_count = job_collection.failed.count
32
+ notify(
33
+ "Workflow completed",
34
+ "#{job_collection.pipeline.workflow.description} completed with #{failed_job_count} failed jobs",
35
+ failed_job_count.positive?,
36
+ )
37
+ end
38
+
39
+ def report_incomplete(incomplete_jobs)
40
+ puts "polling #{job_collection.pipeline.workflow.description}"
41
+ puts "#{incomplete_jobs.count} incomplete jobs remaining:"
42
+ incomplete_jobs.each { |job| puts job.description }
43
+ end
44
+
45
+ def notify(title, message, failed)
46
+ return unless @can_notify
47
+
48
+ sound = failed ? "basso" : "blow"
49
+
50
+ `terminal-notifier -group semaph -message "#{message}" -title "#{title}" -sound #{sound}`
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -1,23 +1,58 @@
1
+ require "semaph/commands"
2
+ require "semaph/shells/pipeline/job_debug_command"
3
+ require "semaph/shells/pipeline/job_log_command"
4
+ require "semaph/shells/pipeline/job_log_grep_command"
5
+ require "semaph/shells/pipeline/job_show_command"
6
+ require "semaph/shells/pipeline/job_stop_command"
1
7
  require "semaph/shells/pipeline/jobs_list_command"
8
+ require "semaph/shells/pipeline/jobs_poll_command"
9
+ require "semaph/shells/pipeline/promote_command"
10
+ require "semaph/shells/pipeline/promotions_list_command"
2
11
  require "shell_shock/context"
3
12
 
4
13
  module Semaph
5
14
  module Shells
6
15
  module Pipeline
7
16
  class PipelineShell
17
+ attr_reader :pipeline
18
+
8
19
  include ShellShock::Context
9
20
 
10
21
  def initialize(pipeline)
11
22
  @pipeline = pipeline
12
23
  workflow = pipeline.workflow
13
24
  project = workflow.project
14
- @prompt = "🏗 #{project.client.host} #{project.name} #{workflow.id} #{pipeline.yaml} > "
15
- job_collection = pipeline.job_collection
16
- add_command(
17
- ::Semaph::Commands::ReloadCommand.new(job_collection, "reload jobs"),
18
- "reload-jobs",
19
- )
20
- add_command JobsListCommand.new(job_collection), "list-jobs"
25
+ @prompt = "🏗 #{project.client.name} #{project.name} #{workflow.id} #{pipeline.yaml} > "
26
+ add_command ::Semaph::Commands::ReloadCommand.new, "reload" if ENV["SEMAPH_RELOAD"]
27
+ add_commands
28
+ jobs_list_command.execute
29
+ end
30
+
31
+ private
32
+
33
+ def add_commands
34
+ ::Semaph::Commands.workflow_commands(self, pipeline.workflow)
35
+ add_job_collection_commands(pipeline.job_collection)
36
+ add_command PromoteCommand.new(pipeline), "promote"
37
+ add_command PromotionsListCommand.new(pipeline.promotion_collection), "list-promotions"
38
+ end
39
+
40
+ def add_job_collection_commands(job_collection)
41
+ add_command JobsPollCommand.new(job_collection, jobs_list_command), "poll"
42
+ add_command JobLogCommand.new(job_collection), "log"
43
+ add_command JobDebugCommand.new(job_collection), "debug"
44
+ add_command JobShowCommand.new(job_collection), "show"
45
+ add_command JobStopCommand.new(job_collection), "stop"
46
+ add_command JobLogGrepCommand.new(job_collection, :all), "grep-all-logs"
47
+ add_command JobLogGrepCommand.new(job_collection, :failed), "grep-failed-logs"
48
+ end
49
+
50
+ def jobs_list_command
51
+ return @jobs_list_command if @jobs_list_command
52
+
53
+ @jobs_list_command = JobsListCommand.new(pipeline.job_collection)
54
+ add_command @jobs_list_command, "list-jobs", "ls"
55
+ @jobs_list_command
21
56
  end
22
57
  end
23
58
  end
@@ -0,0 +1,21 @@
1
+ module Semaph
2
+ module Shells
3
+ module Pipeline
4
+ class PromoteCommand
5
+ attr_reader :usage, :help
6
+
7
+ def initialize(pipeline)
8
+ @pipeline = pipeline
9
+ @usage = "<promotion name>"
10
+ @help = "promote pipeline"
11
+ end
12
+
13
+ def execute(name)
14
+ @pipeline.promote(name.chomp.strip)
15
+ rescue ::Semaph::Client::RequestException => e
16
+ puts e.message
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,21 @@
1
+ module Semaph
2
+ module Shells
3
+ module Pipeline
4
+ class PromotionsListCommand
5
+ attr_reader :usage, :help
6
+
7
+ def initialize(promotion_collection)
8
+ @promotion_collection = promotion_collection
9
+ @help = "list promotions"
10
+ end
11
+
12
+ def execute(_whatever)
13
+ @promotion_collection.reload
14
+ @promotion_collection.all.each_with_index do |promotion, index|
15
+ puts "#{index + 1} #{promotion.description}"
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -1,5 +1,5 @@
1
- require "semaph/commands/visit_url_command"
2
1
  require "semaph/commands/reload_command"
2
+ require "semaph/commands/visit_url_command"
3
3
  require "semaph/shells/project/workflows_list_command"
4
4
  require "semaph/shells/project/workflows_select_command"
5
5
  require "shell_shock/context"
@@ -8,30 +8,33 @@ module Semaph
8
8
  module Shells
9
9
  module Project
10
10
  class ProjectShell
11
+ attr_reader :project
12
+
11
13
  include ShellShock::Context
12
14
 
13
15
  def initialize(project)
14
- @prompt = "🏗 #{project.client.host} #{project.name} > "
15
- workflow_collection = project.workflow_collection
16
- add_github_command(project)
17
- add_command(
18
- ::Semaph::Commands::ReloadCommand.new(workflow_collection, "reload workflows"),
19
- "reload-workflows",
20
- )
21
- add_command(
22
- ::Semaph::Commands::VisitUrlCommand.new(
23
- "https://#{project.client.host}/projects/#{project.name}",
24
- "browse to project",
25
- ),
26
- "open-project",
27
- )
28
- add_command WorkflowsListCommand.new(workflow_collection), "list-workflows"
29
- add_command WorkflowsSelectCommand.new(workflow_collection), "select-workflow"
16
+ @project = project
17
+ @prompt = "🏗 #{project.client.name} #{project.name} > "
18
+ add_commands
19
+ @workflows_list_command.execute("")
30
20
  end
31
21
 
32
22
  private
33
23
 
34
- def add_github_command(project)
24
+ def workflow_collection
25
+ project.workflow_collection
26
+ end
27
+
28
+ def add_commands
29
+ add_github_command
30
+ add_open_project_command
31
+ @workflows_list_command = WorkflowsListCommand.new(workflow_collection)
32
+ add_command @workflows_list_command, "list-workflows", "ls"
33
+ add_command WorkflowsSelectCommand.new(workflow_collection), "select-workflow", "cd"
34
+ add_command ::Semaph::Commands::ReloadCommand.new, "reload" if ENV["SEMAPH_RELOAD"]
35
+ end
36
+
37
+ def add_github_command
35
38
  return unless project.github_url
36
39
 
37
40
  add_command(
@@ -42,6 +45,16 @@ module Semaph
42
45
  "open-github",
43
46
  )
44
47
  end
48
+
49
+ def add_open_project_command
50
+ add_command(
51
+ ::Semaph::Commands::VisitUrlCommand.new(
52
+ "https://#{project.client.host}/projects/#{project.name}",
53
+ "browse to project",
54
+ ),
55
+ "open-project",
56
+ )
57
+ end
45
58
  end
46
59
  end
47
60
  end