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.
- checksums.yaml +4 -4
- data/.gitignore +3 -0
- data/.rubocop.yml +18 -0
- data/.semaphore/semaphore.yml +23 -0
- data/.tool-versions +1 -0
- data/Gemfile +1 -1
- data/Gemfile.lock +55 -0
- data/README.md +85 -11
- data/Rakefile +4 -1
- data/exe/semaph +5 -0
- data/lib/semaph.rb +17 -1
- data/lib/semaph/api.rb +94 -0
- data/lib/semaph/commands.rb +63 -0
- data/lib/semaph/commands/reload_command.rb +17 -0
- data/lib/semaph/commands/rerun_workflow_command.rb +16 -0
- data/lib/semaph/commands/stop_workflow_command.rb +16 -0
- data/lib/semaph/commands/visit_url_command.rb +16 -0
- data/lib/semaph/formatting.rb +9 -0
- data/lib/semaph/model/job.rb +101 -0
- data/lib/semaph/model/job_collection.rb +47 -0
- data/lib/semaph/model/pipeline.rb +48 -0
- data/lib/semaph/model/pipeline_collection.rb +20 -0
- data/lib/semaph/model/project.rb +34 -0
- data/lib/semaph/model/project_collection.rb +19 -0
- data/lib/semaph/model/workflow.rb +48 -0
- data/lib/semaph/model/workflow_collection.rb +19 -0
- data/lib/semaph/shells/organisation/organisation_shell.rb +33 -0
- data/lib/semaph/shells/organisation/projects_list_command.rb +21 -0
- data/lib/semaph/shells/organisation/projects_select_command.rb +26 -0
- data/lib/semaph/shells/organisations/organisations_list_command.rb +20 -0
- data/lib/semaph/shells/organisations/organisations_select_command.rb +25 -0
- data/lib/semaph/shells/organisations/organisations_shell.rb +24 -0
- data/lib/semaph/shells/pipeline/job_log_command.rb +44 -0
- data/lib/semaph/shells/pipeline/job_log_grep_command.rb +28 -0
- data/lib/semaph/shells/pipeline/jobs_list_command.rb +21 -0
- data/lib/semaph/shells/pipeline/jobs_poll_command.rb +62 -0
- data/lib/semaph/shells/pipeline/pipeline_shell.rb +45 -0
- data/lib/semaph/shells/project/project_shell.rb +61 -0
- data/lib/semaph/shells/project/workflows_list_command.rb +24 -0
- data/lib/semaph/shells/project/workflows_select_command.rb +31 -0
- data/lib/semaph/shells/workflow/pipelines_list_command.rb +21 -0
- data/lib/semaph/shells/workflow/pipelines_select_command.rb +30 -0
- data/lib/semaph/shells/workflow/workflow_shell.rb +35 -0
- data/lib/semaph/version.rb +1 -1
- data/semaph.gemspec +12 -7
- metadata +115 -7
@@ -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,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
|