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,17 @@
1
+ module Semaph
2
+ module Commands
3
+ class ReloadCommand
4
+ attr_reader :help
5
+
6
+ def initialize
7
+ @help = "reload all code"
8
+ end
9
+
10
+ def execute(_whatever)
11
+ Dir["lib/**/*.rb"].each do |path|
12
+ load path
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -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
@@ -0,0 +1,16 @@
1
+ module Semaph
2
+ module Commands
3
+ class VisitUrlCommand
4
+ attr_reader :help
5
+
6
+ def initialize(url, help)
7
+ @url = url
8
+ @help = help
9
+ end
10
+
11
+ def execute(_whatever)
12
+ system("open #{@url}")
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,9 @@
1
+ module Semaph
2
+ module Formatting
3
+ TIME_FORMAT = "%m-%d %H:%M".freeze
4
+
5
+ def self.time(time)
6
+ time.strftime(TIME_FORMAT)
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,101 @@
1
+ require "fileutils"
2
+
3
+ module Semaph
4
+ module Model
5
+ class Job
6
+ attr_reader(
7
+ :pipeline,
8
+ :id,
9
+ :name,
10
+ :status,
11
+ :result,
12
+ :block_name,
13
+ :block_state,
14
+ :block_result,
15
+ )
16
+
17
+ def initialize(pipeline, raw_block, raw_job)
18
+ @pipeline = pipeline
19
+ @raw_block = raw_block
20
+ @raw_job = raw_job
21
+ assign_from_job(raw_job)
22
+ assign_from_block(raw_block)
23
+ end
24
+
25
+ def write_log(base)
26
+ FileUtils.mkdir_p(base)
27
+ filename = "#{base}/#{id}.log"
28
+ return filename if File.exist?(filename)
29
+
30
+ puts "retrieving log for job #{id}"
31
+ File.open(filename, "w") do |file|
32
+ file.puts pipeline.workflow.project.client.job_log(id)
33
+ end
34
+
35
+ filename
36
+ end
37
+
38
+ def description
39
+ [
40
+ block_icon,
41
+ @block_name,
42
+ job_icon,
43
+ @name,
44
+ ].compact.join(" ")
45
+ end
46
+
47
+ def finished?
48
+ @status == "FINISHED"
49
+ end
50
+
51
+ def failed?
52
+ @result == "FAILED"
53
+ end
54
+
55
+ # block_state can be waiting/running/done
56
+ # block_result can be passed/failed/canceled/stopped
57
+ def block_icon
58
+ return "🟠" if @block_state == "waiting"
59
+
60
+ return "🔵" if @block_state == "running"
61
+
62
+ return "⚪" if @block_result == "canceled"
63
+
64
+ return "⛔" if @block_result == "stopped"
65
+
66
+ return "🟢" if @block_result == "passed"
67
+
68
+ "🔴"
69
+ end
70
+
71
+ # status can be FINISHED/RUNNING
72
+ # result can be PASSED/FAILED/STOPPED
73
+ def job_icon
74
+ return nil unless @status
75
+
76
+ return "🔵" unless @status == "FINISHED"
77
+
78
+ return "⛔" if @result == "STOPPED"
79
+
80
+ return "🟢" if @result == "PASSED"
81
+
82
+ "🔴"
83
+ end
84
+
85
+ private
86
+
87
+ def assign_from_job(raw)
88
+ @id = raw["job_id"]
89
+ @status = raw["status"]
90
+ @name = raw["name"]
91
+ @result = raw["result"]
92
+ end
93
+
94
+ def assign_from_block(raw)
95
+ @block_name = raw["name"]
96
+ @block_state = raw["state"]
97
+ @block_result = raw["result"]
98
+ end
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,47 @@
1
+ require "semaph/model/job"
2
+
3
+ module Semaph
4
+ module Model
5
+ class JobCollection
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 = build_jobs(project.client.pipeline(@pipeline.id))
16
+ end
17
+
18
+ def incomplete
19
+ @all.reject(&:finished?)
20
+ end
21
+
22
+ def failed
23
+ @all.select(&:failed?)
24
+ end
25
+
26
+ private
27
+
28
+ def build_jobs(content)
29
+ result = []
30
+ blocks = content.delete("blocks")
31
+ blocks.each do |block|
32
+ jobs = block.delete("jobs").sort_by { |job| job["index"] }
33
+ append_jobs(result, block, jobs)
34
+ end
35
+ result
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
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,48 @@
1
+ require "semaph/model/job_collection"
2
+
3
+ module Semaph
4
+ module Model
5
+ class Pipeline
6
+ attr_reader :workflow, :raw, :id, :yaml, :state, :result
7
+
8
+ def initialize(workflow, raw)
9
+ @workflow = workflow
10
+ @raw = raw
11
+ @id = raw["ppl_id"]
12
+ @yaml = raw["yaml_file_name"]
13
+ @state = raw["state"]
14
+ @result = raw["result"]
15
+ %w[created done pending queuing running stopping].each do |name|
16
+ extract_time(name)
17
+ end
18
+ end
19
+
20
+ def job_collection
21
+ @job_collection ||= JobCollection.new(self)
22
+ end
23
+
24
+ def description
25
+ "#{icon} #{yaml}"
26
+ end
27
+
28
+ def icon
29
+ return "🔵" unless @state == "DONE"
30
+
31
+ return "⛔" if @result == "STOPPED"
32
+
33
+ return "🟢" if @result == "PASSED"
34
+
35
+ "🔴"
36
+ end
37
+
38
+ private
39
+
40
+ def extract_time(name)
41
+ key = "#{name}_at"
42
+ return if raw[key]["seconds"].zero?
43
+
44
+ instance_variable_set("@#{key}", Time.at(raw[key]["seconds"]))
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,20 @@
1
+ require "semaph/model/pipeline"
2
+
3
+ module Semaph
4
+ module Model
5
+ class PipelineCollection
6
+ attr_reader :all
7
+
8
+ def initialize(workflow)
9
+ @workflow = workflow
10
+ end
11
+
12
+ def reload
13
+ project = @workflow.project
14
+ @all = project.client.pipelines({ wf_id: @workflow.id }).map do |content|
15
+ Pipeline.new @workflow, content
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,34 @@
1
+ require "semaph/model/workflow_collection"
2
+
3
+ module Semaph
4
+ module Model
5
+ class Project
6
+ GITHUB_REGGEXP = %r{git@github.com:(.*)/(.*).git}.freeze
7
+
8
+ attr_reader :client, :raw, :id, :name
9
+
10
+ def initialize(client, raw)
11
+ @client = client
12
+ @raw = raw
13
+ @id = raw["metadata"]["id"]
14
+ @name = raw["metadata"]["name"]
15
+ repo = raw["spec"]["repository"]["url"]
16
+ match = GITHUB_REGGEXP.match(repo)
17
+ return unless match
18
+
19
+ @repo_owner = match[1]
20
+ @repo_name = match[2]
21
+ end
22
+
23
+ def github_url
24
+ return nil unless @repo_owner && @repo_name
25
+
26
+ "https://github.com/#{@repo_owner}/#{@repo_name}"
27
+ end
28
+
29
+ def workflow_collection
30
+ @workflow_collection ||= WorkflowCollection.new(self)
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,19 @@
1
+ require "semaph/model/project"
2
+
3
+ module Semaph
4
+ module Model
5
+ class ProjectCollection
6
+ attr_reader :all
7
+
8
+ def initialize(client)
9
+ @client = client
10
+ end
11
+
12
+ def reload
13
+ @all = @client.projects.map do |content|
14
+ Project.new @client, content
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,48 @@
1
+ require "semaph/formatting"
2
+ require "semaph/model/pipeline_collection"
3
+
4
+ module Semaph
5
+ module Model
6
+ class Workflow
7
+ attr_reader :project, :raw, :id, :sha, :commit, :branch, :branch_id, :created_at
8
+
9
+ def initialize(project, raw)
10
+ @project = project
11
+ @raw = raw
12
+ @id = raw["wf_id"]
13
+ @created_at = Time.at(raw["created_at"]["seconds"].to_i)
14
+ @branch = raw["branch_name"]
15
+ @branch_id = raw["branch_id"]
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"
23
+ end
24
+
25
+ def pipeline_collection
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)
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,19 @@
1
+ require "semaph/model/workflow"
2
+
3
+ module Semaph
4
+ module Model
5
+ class WorkflowCollection
6
+ attr_reader :all
7
+
8
+ def initialize(project)
9
+ @project = project
10
+ end
11
+
12
+ def reload
13
+ @all = @project.client.workflows(@project.id).map do |content|
14
+ Workflow.new @project, content
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,33 @@
1
+ require "semaph/api"
2
+ require "semaph/commands/reload_command"
3
+ require "semaph/model/project_collection"
4
+ require "semaph/shells/organisation/projects_list_command"
5
+ require "semaph/shells/organisation/projects_select_command"
6
+ require "shell_shock/context"
7
+
8
+ module Semaph
9
+ module Shells
10
+ module Organisation
11
+ class OrganisationShell
12
+ include ShellShock::Context
13
+
14
+ def initialize(organisation)
15
+ @client = ::Semaph::Api.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"]
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end