semaph 0.1.0 → 0.6.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 (46) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +3 -0
  3. data/.rubocop.yml +18 -0
  4. data/.semaphore/semaphore.yml +23 -0
  5. data/.tool-versions +1 -0
  6. data/Gemfile +1 -1
  7. data/Gemfile.lock +55 -0
  8. data/README.md +85 -11
  9. data/Rakefile +4 -1
  10. data/exe/semaph +5 -0
  11. data/lib/semaph.rb +17 -1
  12. data/lib/semaph/api.rb +94 -0
  13. data/lib/semaph/commands.rb +63 -0
  14. data/lib/semaph/commands/reload_command.rb +17 -0
  15. data/lib/semaph/commands/rerun_workflow_command.rb +16 -0
  16. data/lib/semaph/commands/stop_workflow_command.rb +16 -0
  17. data/lib/semaph/commands/visit_url_command.rb +16 -0
  18. data/lib/semaph/formatting.rb +9 -0
  19. data/lib/semaph/model/job.rb +101 -0
  20. data/lib/semaph/model/job_collection.rb +47 -0
  21. data/lib/semaph/model/pipeline.rb +48 -0
  22. data/lib/semaph/model/pipeline_collection.rb +20 -0
  23. data/lib/semaph/model/project.rb +34 -0
  24. data/lib/semaph/model/project_collection.rb +19 -0
  25. data/lib/semaph/model/workflow.rb +48 -0
  26. data/lib/semaph/model/workflow_collection.rb +19 -0
  27. data/lib/semaph/shells/organisation/organisation_shell.rb +33 -0
  28. data/lib/semaph/shells/organisation/projects_list_command.rb +21 -0
  29. data/lib/semaph/shells/organisation/projects_select_command.rb +26 -0
  30. data/lib/semaph/shells/organisations/organisations_list_command.rb +20 -0
  31. data/lib/semaph/shells/organisations/organisations_select_command.rb +25 -0
  32. data/lib/semaph/shells/organisations/organisations_shell.rb +24 -0
  33. data/lib/semaph/shells/pipeline/job_log_command.rb +44 -0
  34. data/lib/semaph/shells/pipeline/job_log_grep_command.rb +28 -0
  35. data/lib/semaph/shells/pipeline/jobs_list_command.rb +21 -0
  36. data/lib/semaph/shells/pipeline/jobs_poll_command.rb +62 -0
  37. data/lib/semaph/shells/pipeline/pipeline_shell.rb +45 -0
  38. data/lib/semaph/shells/project/project_shell.rb +61 -0
  39. data/lib/semaph/shells/project/workflows_list_command.rb +24 -0
  40. data/lib/semaph/shells/project/workflows_select_command.rb +31 -0
  41. data/lib/semaph/shells/workflow/pipelines_list_command.rb +21 -0
  42. data/lib/semaph/shells/workflow/pipelines_select_command.rb +30 -0
  43. data/lib/semaph/shells/workflow/workflow_shell.rb +35 -0
  44. data/lib/semaph/version.rb +1 -1
  45. data/semaph.gemspec +12 -7
  46. metadata +115 -7
@@ -0,0 +1,21 @@
1
+ module Semaph
2
+ module Shells
3
+ module Organisation
4
+ class ProjectsListCommand
5
+ attr_reader :usage, :help
6
+
7
+ def initialize(project_collection)
8
+ @project_collection = project_collection
9
+ @help = "list available projects"
10
+ end
11
+
12
+ def execute(_whatever)
13
+ @project_collection.reload
14
+ @project_collection.all.each do |project|
15
+ puts project.name
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,26 @@
1
+ require "semaph/shells/project/project_shell"
2
+
3
+ module Semaph
4
+ module Shells
5
+ module Organisation
6
+ class ProjectsSelectCommand
7
+ attr_reader :usage, :help
8
+
9
+ def initialize(project_collection)
10
+ @project_collection = project_collection
11
+ @usage = "<project>"
12
+ @help = "choose project"
13
+ end
14
+
15
+ def completion(text)
16
+ @project_collection.all.map(&:name).grep(/^#{text}/).sort
17
+ end
18
+
19
+ def execute(name)
20
+ selected_project = @project_collection.all.find { |project| project.name == name }
21
+ ::Semaph::Shells::Project::ProjectShell.new(selected_project).push
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,20 @@
1
+ module Semaph
2
+ module Shells
3
+ module Organisations
4
+ class OrganisationsListCommand
5
+ attr_reader :usage, :help
6
+
7
+ def initialize(organisations)
8
+ @organisations = organisations
9
+ @help = "list available organisations"
10
+ end
11
+
12
+ def execute(_whatever)
13
+ @organisations.each_key do |name|
14
+ puts "#{name} (#{@organisations[name]['host']})"
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,25 @@
1
+ require "semaph/shells/organisation/organisation_shell"
2
+
3
+ module Semaph
4
+ module Shells
5
+ module Organisations
6
+ class OrganisationsSelectCommand
7
+ attr_reader :usage, :help
8
+
9
+ def initialize(organisations)
10
+ @organisations = organisations
11
+ @usage = "<organisation>"
12
+ @help = "choose organisation"
13
+ end
14
+
15
+ def completion(text)
16
+ @organisations.keys.grep(/^#{text}/).sort
17
+ end
18
+
19
+ def execute(name)
20
+ ::Semaph::Shells::Organisation::OrganisationShell.new(@organisations[name]).push
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,24 @@
1
+ require "semaph/commands/reload_command"
2
+ require "semaph/shells/organisations/organisations_list_command"
3
+ require "semaph/shells/organisations/organisations_select_command"
4
+ require "shell_shock/context"
5
+
6
+ module Semaph
7
+ module Shells
8
+ module Organisations
9
+ class OrganisationsShell
10
+ include ShellShock::Context
11
+
12
+ def initialize(organisations)
13
+ @organisations = organisations
14
+ @prompt = "🏗 > "
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("")
20
+ end
21
+ end
22
+ end
23
+ end
24
+ 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,21 @@
1
+ module Semaph
2
+ module Shells
3
+ module Pipeline
4
+ class JobsListCommand
5
+ attr_reader :usage, :help
6
+
7
+ def initialize(job_collection)
8
+ @job_collection = job_collection
9
+ @help = "list jobs"
10
+ end
11
+
12
+ def execute(_whatever)
13
+ @job_collection.reload
14
+ @job_collection.all.each_with_index do |job, index|
15
+ puts "#{index + 1} #{job.description}"
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,62 @@
1
+ module Semaph
2
+ module Shells
3
+ module Pipeline
4
+ class JobsPollCommand
5
+ attr_reader :usage, :help, :job_collection
6
+
7
+ def initialize(job_collection)
8
+ @job_collection = job_collection
9
+ @help = "poll jobs"
10
+ @can_notify = !`which terminal-notifier`.chomp.empty?
11
+ end
12
+
13
+ def execute(_whatever)
14
+ while job_collection.incomplete.count.positive?
15
+ report_incomplete(job_collection.incomplete)
16
+ if job_collection.failed.count.positive?
17
+ report_failures(job_collection.failed)
18
+ return
19
+ end
20
+ sleep 20
21
+ job_collection.reload
22
+ end
23
+ report_final
24
+ end
25
+
26
+ private
27
+
28
+ def report_final
29
+ job_collection.all.each_with_index do |job, index|
30
+ puts "#{index + 1} #{job.description}"
31
+ end
32
+ failed_job_count = job_collection.failed.count
33
+ notify(
34
+ "Workflow completed",
35
+ "#{job_collection.pipeline.workflow.description} completed with #{failed_job_count} failed jobs",
36
+ failed_job_count.positive?,
37
+ )
38
+ end
39
+
40
+ def report_incomplete(incomplete_jobs)
41
+ puts "polling #{job_collection.pipeline.workflow.description}"
42
+ puts "#{incomplete_jobs.count} incomplete jobs remaining:"
43
+ incomplete_jobs.each { |job| puts job.description }
44
+ end
45
+
46
+ def report_failures(failed_jobs)
47
+ puts "Some jobs have failed:"
48
+ failed_jobs.each { |job| puts job.description }
49
+ notify("Job Failures", "#{failed_jobs.count} jobs have failed", true)
50
+ end
51
+
52
+ def notify(title, message, failed)
53
+ return unless @can_notify
54
+
55
+ sound = failed ? "basso" : "blow"
56
+
57
+ `terminal-notifier -group semaph -message "#{message}" -title "#{title}" -sound #{sound}`
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,45 @@
1
+ require "semaph/commands"
2
+ require "semaph/shells/pipeline/jobs_list_command"
3
+ require "semaph/shells/pipeline/job_log_command"
4
+ require "semaph/shells/pipeline/job_log_grep_command"
5
+ require "semaph/shells/pipeline/jobs_poll_command"
6
+ require "shell_shock/context"
7
+
8
+ module Semaph
9
+ module Shells
10
+ module Pipeline
11
+ class PipelineShell
12
+ attr_reader :pipeline
13
+
14
+ include ShellShock::Context
15
+
16
+ def initialize(pipeline)
17
+ @pipeline = pipeline
18
+ workflow = pipeline.workflow
19
+ project = workflow.project
20
+ @prompt = "🏗 #{project.client.name} #{project.name} #{workflow.id} #{pipeline.yaml} > "
21
+ add_commands
22
+ @jobs_list_command.execute("")
23
+ end
24
+
25
+ private
26
+
27
+ def add_commands
28
+ add_command ::Semaph::Commands::ReloadCommand.new, "reload" if ENV["SEMAPH_RELOAD"]
29
+ workflow = pipeline.workflow
30
+ ::Semaph::Commands.workflow_commands(self, workflow)
31
+ add_job_collection_commands(pipeline.job_collection)
32
+ end
33
+
34
+ def add_job_collection_commands(job_collection)
35
+ @jobs_list_command = JobsListCommand.new(job_collection)
36
+ add_command @jobs_list_command, "list-jobs", "ls"
37
+ add_command JobsPollCommand.new(job_collection), "poll-jobs"
38
+ add_command JobLogCommand.new(job_collection), "job-log"
39
+ add_command JobLogGrepCommand.new(job_collection, :all), "grep-all-logs"
40
+ add_command JobLogGrepCommand.new(job_collection, :failed), "grep-failed-logs"
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,61 @@
1
+ require "semaph/commands/reload_command"
2
+ require "semaph/commands/visit_url_command"
3
+ require "semaph/shells/project/workflows_list_command"
4
+ require "semaph/shells/project/workflows_select_command"
5
+ require "shell_shock/context"
6
+
7
+ module Semaph
8
+ module Shells
9
+ module Project
10
+ class ProjectShell
11
+ attr_reader :project
12
+
13
+ include ShellShock::Context
14
+
15
+ def initialize(project)
16
+ @project = project
17
+ @prompt = "🏗 #{project.client.name} #{project.name} > "
18
+ add_commands
19
+ @workflows_list_command.execute("")
20
+ end
21
+
22
+ private
23
+
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
38
+ return unless project.github_url
39
+
40
+ add_command(
41
+ ::Semaph::Commands::VisitUrlCommand.new(
42
+ project.github_url,
43
+ "browse to github project",
44
+ ),
45
+ "open-github",
46
+ )
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
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,24 @@
1
+ module Semaph
2
+ module Shells
3
+ module Project
4
+ class WorkflowsListCommand
5
+ attr_reader :usage, :help
6
+
7
+ def initialize(workflow_collection)
8
+ @workflow_collection = workflow_collection
9
+ @usage = "<branch>"
10
+ @help = "list available workflows"
11
+ end
12
+
13
+ def execute(branch)
14
+ @workflow_collection.reload
15
+ @workflow_collection.all.each_with_index do |workflow, index|
16
+ next unless workflow.branch.include?(branch)
17
+
18
+ puts "#{index + 1} #{workflow.description}"
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,31 @@
1
+ require "semaph/shells/workflow/workflow_shell"
2
+ require "semaph/shells/pipeline/pipeline_shell"
3
+
4
+ module Semaph
5
+ module Shells
6
+ module Project
7
+ class WorkflowsSelectCommand
8
+ attr_reader :usage, :help
9
+
10
+ def initialize(workflow_collection)
11
+ @workflow_collection = workflow_collection
12
+ @usage = "<workflow index>"
13
+ @help = "choose workflow by index"
14
+ end
15
+
16
+ def execute(index_string)
17
+ index = index_string.to_i - 1
18
+
19
+ workflow = @workflow_collection.all[index]
20
+
21
+ unless workflow
22
+ puts "There is no workflow at position #{index}"
23
+ return
24
+ end
25
+
26
+ ::Semaph::Shells::Workflow::WorkflowShell.new(workflow).push
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end