semaph 0.4.0 → 0.9.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/.rubocop.yml +39 -0
- data/.semaph +9 -0
- data/.semaphore/semaphore.yml +5 -1
- data/Gemfile.lock +21 -1
- data/README.md +12 -8
- data/Rakefile +4 -1
- data/lib/semaph.rb +43 -8
- data/lib/semaph/{api.rb → client.rb} +24 -3
- data/lib/semaph/commands.rb +63 -0
- data/lib/semaph/commands/rerun_workflow_command.rb +0 -2
- data/lib/semaph/commands/stop_workflow_command.rb +0 -2
- data/lib/semaph/formatting.rb +19 -1
- data/lib/semaph/model/job.rb +21 -2
- data/lib/semaph/model/job_collection.rb +19 -3
- data/lib/semaph/model/pipeline.rb +29 -3
- data/lib/semaph/model/promotion.rb +31 -0
- data/lib/semaph/model/promotion_collection.rb +21 -0
- data/lib/semaph/model/workflow.rb +17 -3
- data/lib/semaph/shells/organisation/organisation_shell.rb +4 -4
- data/lib/semaph/shells/organisation/projects_select_command.rb +6 -0
- data/lib/semaph/shells/organisations/organisations_list_command.rb +2 -2
- data/lib/semaph/shells/organisations/organisations_select_command.rb +11 -2
- data/lib/semaph/shells/organisations/organisations_shell.rb +2 -2
- data/lib/semaph/shells/pipeline/job_debug_command.rb +29 -0
- data/lib/semaph/shells/pipeline/job_log_command.rb +17 -15
- data/lib/semaph/shells/pipeline/job_log_grep_command.rb +5 -11
- data/lib/semaph/shells/pipeline/job_show_command.rb +28 -0
- data/lib/semaph/shells/pipeline/job_stop_command.rb +28 -0
- data/lib/semaph/shells/pipeline/jobs_list_command.rb +4 -2
- data/lib/semaph/shells/pipeline/jobs_poll_command.rb +58 -22
- data/lib/semaph/shells/pipeline/pipeline_shell.rb +28 -68
- data/lib/semaph/shells/pipeline/promote_command.rb +21 -0
- data/lib/semaph/shells/pipeline/promotions_list_command.rb +21 -0
- data/lib/semaph/shells/project/project_shell.rb +4 -2
- data/lib/semaph/shells/project/save_command.rb +26 -0
- data/lib/semaph/shells/project/workflows_list_command.rb +11 -17
- data/lib/semaph/shells/workflow/pipelines_list_command.rb +3 -1
- data/lib/semaph/shells/workflow/workflow_shell.rb +6 -66
- data/lib/semaph/version.rb +1 -1
- data/semaph.gemspec +1 -0
- metadata +30 -6
@@ -1,9 +1,10 @@
|
|
1
|
+
require "date"
|
1
2
|
require "semaph/model/job"
|
2
3
|
|
3
4
|
module Semaph
|
4
5
|
module Model
|
5
6
|
class JobCollection
|
6
|
-
attr_reader :all, :pipeline
|
7
|
+
attr_reader :all, :pipeline, :stopping_at, :running_at, :queuing_at, :pending_at, :done_at, :created_at
|
7
8
|
|
8
9
|
def initialize(pipeline)
|
9
10
|
@pipeline = pipeline
|
@@ -12,7 +13,7 @@ module Semaph
|
|
12
13
|
def reload
|
13
14
|
workflow = @pipeline.workflow
|
14
15
|
project = workflow.project
|
15
|
-
@all =
|
16
|
+
@all = parse_content(project.client.pipeline(@pipeline.id))
|
16
17
|
end
|
17
18
|
|
18
19
|
def incomplete
|
@@ -25,7 +26,8 @@ module Semaph
|
|
25
26
|
|
26
27
|
private
|
27
28
|
|
28
|
-
def
|
29
|
+
def parse_content(content)
|
30
|
+
parse_pipeline(content["pipeline"])
|
29
31
|
result = []
|
30
32
|
blocks = content.delete("blocks")
|
31
33
|
blocks.each do |block|
|
@@ -35,6 +37,15 @@ module Semaph
|
|
35
37
|
result
|
36
38
|
end
|
37
39
|
|
40
|
+
def parse_pipeline(pipeline_content)
|
41
|
+
with_time(pipeline_content["stopping_at"]) { |time| @stopping_at = time }
|
42
|
+
with_time(pipeline_content["running_at"]) { |time| @running_at = time }
|
43
|
+
with_time(pipeline_content["queuing_at"]) { |time| @queuing_at = time }
|
44
|
+
with_time(pipeline_content["pending_at"]) { |time| @pending_at = time }
|
45
|
+
with_time(pipeline_content["done_at"]) { |time| @done_at = time }
|
46
|
+
with_time(pipeline_content["created_at"]) { |time| @created_at = time }
|
47
|
+
end
|
48
|
+
|
38
49
|
def append_jobs(result, block, jobs)
|
39
50
|
if jobs.count.positive?
|
40
51
|
jobs.each { |job| result << Job.new(@pipeline, block, job) }
|
@@ -42,6 +53,11 @@ module Semaph
|
|
42
53
|
result << Job.new(@pipeline, block, {})
|
43
54
|
end
|
44
55
|
end
|
56
|
+
|
57
|
+
def with_time(string)
|
58
|
+
time = DateTime.parse(string).to_time
|
59
|
+
yield time unless time.to_i.zero?
|
60
|
+
end
|
45
61
|
end
|
46
62
|
end
|
47
63
|
end
|
@@ -1,15 +1,18 @@
|
|
1
|
+
require "semaph/formatting"
|
1
2
|
require "semaph/model/job_collection"
|
3
|
+
require "semaph/model/promotion_collection"
|
2
4
|
|
3
5
|
module Semaph
|
4
6
|
module Model
|
5
7
|
class Pipeline
|
6
|
-
attr_reader :workflow, :raw, :id, :yaml, :state, :result
|
8
|
+
attr_reader :workflow, :raw, :id, :name, :yaml, :state, :result
|
7
9
|
|
8
10
|
def initialize(workflow, raw)
|
9
11
|
@workflow = workflow
|
10
12
|
@raw = raw
|
11
13
|
@id = raw["ppl_id"]
|
12
14
|
@yaml = raw["yaml_file_name"]
|
15
|
+
@name = raw["name"]
|
13
16
|
@state = raw["state"]
|
14
17
|
@result = raw["result"]
|
15
18
|
%w[created done pending queuing running stopping].each do |name|
|
@@ -17,16 +20,33 @@ module Semaph
|
|
17
20
|
end
|
18
21
|
end
|
19
22
|
|
23
|
+
def promote(name)
|
24
|
+
workflow.project.client.promote(id, name)
|
25
|
+
end
|
26
|
+
|
20
27
|
def job_collection
|
21
28
|
@job_collection ||= JobCollection.new(self)
|
22
29
|
end
|
23
30
|
|
31
|
+
def promotion_collection
|
32
|
+
@promotion_collection ||= PromotionCollection.new(self)
|
33
|
+
end
|
34
|
+
|
24
35
|
def description
|
25
|
-
|
36
|
+
[
|
37
|
+
icon,
|
38
|
+
time,
|
39
|
+
name,
|
40
|
+
"(#{yaml})",
|
41
|
+
].compact.join(" ")
|
42
|
+
end
|
43
|
+
|
44
|
+
def done?
|
45
|
+
@state == "DONE"
|
26
46
|
end
|
27
47
|
|
28
48
|
def icon
|
29
|
-
return "🔵" unless
|
49
|
+
return "🔵" unless done?
|
30
50
|
|
31
51
|
return "⛔" if @result == "STOPPED"
|
32
52
|
|
@@ -37,6 +57,12 @@ module Semaph
|
|
37
57
|
|
38
58
|
private
|
39
59
|
|
60
|
+
def time
|
61
|
+
return ::Semaph::Formatting.hours_minutes_seconds(@done_at.to_i - @created_at.to_i) if done?
|
62
|
+
|
63
|
+
::Semaph::Formatting.hours_minutes_seconds(Time.now.to_i - @created_at.to_i)
|
64
|
+
end
|
65
|
+
|
40
66
|
def extract_time(name)
|
41
67
|
key = "#{name}_at"
|
42
68
|
return if raw[key]["seconds"].zero?
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require "semaph/formatting"
|
2
|
+
|
3
|
+
module Semaph
|
4
|
+
module Model
|
5
|
+
class Promotion
|
6
|
+
attr_reader :pipeline, :raw
|
7
|
+
|
8
|
+
def initialize(pipeline, raw)
|
9
|
+
@pipeline = pipeline
|
10
|
+
@raw = raw
|
11
|
+
@name = raw["name"]
|
12
|
+
@status = raw["status"]
|
13
|
+
@triggered_at = Time.at(raw["triggered_at"]["seconds"])
|
14
|
+
end
|
15
|
+
|
16
|
+
def description
|
17
|
+
[
|
18
|
+
status_icon,
|
19
|
+
Semaph::Formatting.time(@triggered_at),
|
20
|
+
@name,
|
21
|
+
].join(" ")
|
22
|
+
end
|
23
|
+
|
24
|
+
def status_icon
|
25
|
+
return "🟢" if @status == "passed"
|
26
|
+
|
27
|
+
"🔴"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require "semaph/model/promotion"
|
2
|
+
|
3
|
+
module Semaph
|
4
|
+
module Model
|
5
|
+
class PromotionCollection
|
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 = project.client.promotions(@pipeline.id).map do |promotion_response|
|
16
|
+
Promotion.new(@pipeline, promotion_response)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -1,19 +1,25 @@
|
|
1
|
+
require "semaph/formatting"
|
1
2
|
require "semaph/model/pipeline_collection"
|
2
3
|
|
3
4
|
module Semaph
|
4
5
|
module Model
|
5
6
|
class Workflow
|
6
|
-
attr_reader :project, :raw, :id, :sha, :branch, :branch_id, :created_at
|
7
|
+
attr_reader :project, :raw, :id, :sha, :commit, :branch, :branch_id, :created_at
|
7
8
|
|
8
9
|
def initialize(project, raw)
|
9
10
|
@project = project
|
10
11
|
@raw = raw
|
11
12
|
@id = raw["wf_id"]
|
12
|
-
@sha = raw["commit_sha"]
|
13
13
|
@created_at = Time.at(raw["created_at"]["seconds"].to_i)
|
14
14
|
@branch = raw["branch_name"]
|
15
15
|
@branch_id = raw["branch_id"]
|
16
|
-
|
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"
|
17
23
|
end
|
18
24
|
|
19
25
|
def pipeline_collection
|
@@ -26,6 +32,14 @@ module Semaph
|
|
26
32
|
Workflow.new(project, workflow_response["workflow"])
|
27
33
|
end
|
28
34
|
|
35
|
+
def created
|
36
|
+
Semaph::Formatting.time(created_at)
|
37
|
+
end
|
38
|
+
|
39
|
+
def description
|
40
|
+
[created, branch, commit].join(" ")
|
41
|
+
end
|
42
|
+
|
29
43
|
def stop
|
30
44
|
project.client.stop_workflow(@id)
|
31
45
|
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
require "semaph/
|
1
|
+
require "semaph/client"
|
2
2
|
require "semaph/commands/reload_command"
|
3
3
|
require "semaph/model/project_collection"
|
4
4
|
require "semaph/shells/organisation/projects_list_command"
|
@@ -12,7 +12,7 @@ module Semaph
|
|
12
12
|
include ShellShock::Context
|
13
13
|
|
14
14
|
def initialize(organisation)
|
15
|
-
@client = ::Semaph::
|
15
|
+
@client = ::Semaph::Client.new(organisation["auth"]["token"], organisation["host"])
|
16
16
|
@prompt = "🏗 #{@client.name} > "
|
17
17
|
add_commands
|
18
18
|
@project_list_command.execute("")
|
@@ -23,8 +23,8 @@ module Semaph
|
|
23
23
|
def add_commands
|
24
24
|
project_collection = ::Semaph::Model::ProjectCollection.new(@client)
|
25
25
|
@project_list_command = ProjectsListCommand.new(project_collection)
|
26
|
-
add_command @project_list_command, "list-projects"
|
27
|
-
add_command ProjectsSelectCommand.new(project_collection), "select-project"
|
26
|
+
add_command @project_list_command, "list-projects", "ls"
|
27
|
+
add_command ProjectsSelectCommand.new(project_collection), "select-project", "cd"
|
28
28
|
add_command ::Semaph::Commands::ReloadCommand.new, "reload" if ENV["SEMAPH_RELOAD"]
|
29
29
|
end
|
30
30
|
end
|
@@ -18,6 +18,12 @@ module Semaph
|
|
18
18
|
|
19
19
|
def execute(name)
|
20
20
|
selected_project = @project_collection.all.find { |project| project.name == name }
|
21
|
+
|
22
|
+
unless selected_project
|
23
|
+
puts "There is no project called #{name}"
|
24
|
+
return
|
25
|
+
end
|
26
|
+
|
21
27
|
::Semaph::Shells::Project::ProjectShell.new(selected_project).push
|
22
28
|
end
|
23
29
|
end
|
@@ -13,11 +13,20 @@ module Semaph
|
|
13
13
|
end
|
14
14
|
|
15
15
|
def completion(text)
|
16
|
-
@organisations.
|
16
|
+
@organisations.map { |org| org["host"].split(".").first }.grep(/^#{text}/).sort
|
17
17
|
end
|
18
18
|
|
19
19
|
def execute(name)
|
20
|
-
|
20
|
+
organisation = @organisations.find do |org|
|
21
|
+
org["host"].split(".").first == name
|
22
|
+
end
|
23
|
+
|
24
|
+
unless organisation
|
25
|
+
puts "There is no organisation called #{name}"
|
26
|
+
return
|
27
|
+
end
|
28
|
+
|
29
|
+
::Semaph::Shells::Organisation::OrganisationShell.new(organisation).push
|
21
30
|
end
|
22
31
|
end
|
23
32
|
end
|
@@ -13,8 +13,8 @@ module Semaph
|
|
13
13
|
@organisations = organisations
|
14
14
|
@prompt = "🏗 > "
|
15
15
|
organisations_list_command = OrganisationsListCommand.new(organisations)
|
16
|
-
add_command organisations_list_command, "list-organisations"
|
17
|
-
add_command OrganisationsSelectCommand.new(organisations), "select-organisation"
|
16
|
+
add_command organisations_list_command, "list-organisations", "ls"
|
17
|
+
add_command OrganisationsSelectCommand.new(organisations), "select-organisation", "cd"
|
18
18
|
add_command ::Semaph::Commands::ReloadCommand.new, "reload" if ENV["SEMAPH_RELOAD"]
|
19
19
|
organisations_list_command.execute("")
|
20
20
|
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Semaph
|
2
|
+
module Shells
|
3
|
+
module Pipeline
|
4
|
+
class JobDebugCommand
|
5
|
+
attr_reader :usage, :help
|
6
|
+
|
7
|
+
def initialize(job_collection)
|
8
|
+
@job_collection = job_collection
|
9
|
+
@usage = "<job index>"
|
10
|
+
@help = "debug job"
|
11
|
+
end
|
12
|
+
|
13
|
+
def execute(index_string)
|
14
|
+
index = index_string.to_i - 1
|
15
|
+
|
16
|
+
job = @job_collection.all[index]
|
17
|
+
|
18
|
+
unless job
|
19
|
+
puts "There is no job at position #{index}"
|
20
|
+
return
|
21
|
+
end
|
22
|
+
|
23
|
+
puts "Debugging #{job.description}"
|
24
|
+
system("sem debug job #{job.id}")
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -4,7 +4,7 @@ module Semaph
|
|
4
4
|
module Shells
|
5
5
|
module Pipeline
|
6
6
|
class JobLogCommand
|
7
|
-
attr_reader :usage, :help
|
7
|
+
attr_reader :usage, :help, :job_collection
|
8
8
|
|
9
9
|
def initialize(job_collection)
|
10
10
|
@job_collection = job_collection
|
@@ -13,28 +13,30 @@ module Semaph
|
|
13
13
|
end
|
14
14
|
|
15
15
|
def execute(index_string)
|
16
|
+
with_job(index_string) { |job| system(command(job)) }
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def command(job)
|
22
|
+
base = "tmp/logs/pipeline/#{job_collection.pipeline.id}"
|
23
|
+
|
24
|
+
return "less #{job.write_log(base)}" if job.finished?
|
25
|
+
|
26
|
+
"open https://#{job_collection.pipeline.workflow.project.client.host}/jobs/#{job.id}"
|
27
|
+
end
|
28
|
+
|
29
|
+
def with_job(index_string)
|
16
30
|
index = index_string.to_i - 1
|
17
31
|
|
18
|
-
job =
|
32
|
+
job = job_collection.all[index]
|
19
33
|
|
20
34
|
unless job
|
21
35
|
puts "There is no job at position #{index}"
|
22
36
|
return
|
23
37
|
end
|
24
38
|
|
25
|
-
|
26
|
-
puts "This job has not finished yet"
|
27
|
-
return
|
28
|
-
end
|
29
|
-
|
30
|
-
base = "tmp/logs/pipeline/#{job.pipeline.id}"
|
31
|
-
FileUtils.mkdir_p(base)
|
32
|
-
filename = "#{base}/#{job.id}.log"
|
33
|
-
unless File.exist?(filename)
|
34
|
-
puts "retrieving log for job #{job.id}"
|
35
|
-
File.open(filename, "w") { |file| file.puts job.log }
|
36
|
-
end
|
37
|
-
system("less #{filename}")
|
39
|
+
yield job
|
38
40
|
end
|
39
41
|
end
|
40
42
|
end
|
@@ -1,30 +1,24 @@
|
|
1
|
-
require "fileutils"
|
2
|
-
|
3
1
|
module Semaph
|
4
2
|
module Shells
|
5
3
|
module Pipeline
|
6
4
|
class JobLogGrepCommand
|
7
5
|
attr_reader :usage, :help, :job_collection
|
8
6
|
|
9
|
-
def initialize(job_collection)
|
7
|
+
def initialize(job_collection, scope)
|
10
8
|
@job_collection = job_collection
|
9
|
+
@scope = scope
|
11
10
|
@usage = "<expression>"
|
12
|
-
@help = "retrieve
|
11
|
+
@help = "retrieve logs for #{scope} jobs and grep for text"
|
13
12
|
end
|
14
13
|
|
15
14
|
def execute(expression)
|
16
15
|
base = "tmp/logs/pipeline/#{job_collection.pipeline.id}"
|
17
|
-
|
18
|
-
@job_collection.all.each do |job|
|
16
|
+
@job_collection.send(@scope).each do |job|
|
19
17
|
unless job.finished?
|
20
18
|
puts "skipping incomplete job #{job.id}"
|
21
19
|
next
|
22
20
|
end
|
23
|
-
|
24
|
-
unless File.exist?(filename)
|
25
|
-
puts "retrieving log for job #{job.id}"
|
26
|
-
File.open(filename, "w") { |file| file.puts job.log }
|
27
|
-
end
|
21
|
+
job.write_log(base)
|
28
22
|
end
|
29
23
|
system("ag #{expression} #{base} | less")
|
30
24
|
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Semaph
|
2
|
+
module Shells
|
3
|
+
module Pipeline
|
4
|
+
class JobShowCommand
|
5
|
+
attr_reader :usage, :help
|
6
|
+
|
7
|
+
def initialize(job_collection)
|
8
|
+
@job_collection = job_collection
|
9
|
+
@usage = "<job index>"
|
10
|
+
@help = "show job"
|
11
|
+
end
|
12
|
+
|
13
|
+
def execute(index_string)
|
14
|
+
index = index_string.to_i - 1
|
15
|
+
|
16
|
+
job = @job_collection.all[index]
|
17
|
+
|
18
|
+
unless job
|
19
|
+
puts "There is no job at position #{index}"
|
20
|
+
return
|
21
|
+
end
|
22
|
+
|
23
|
+
puts job.show
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|