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