semaph 0.1.0 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
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